fix/v02-patch: pr_files_diff, template_name, repo_update #26

Merged
mathias merged 6 commits from fix/v02-patch into main 2026-05-16 22:03:29 +00:00
5 changed files with 384 additions and 233 deletions
Showing only changes of commit 3cccbfb8cb - Show all commits

View File

@@ -49,7 +49,6 @@ These rules apply to every task across every project, regardless of harness.
| Search | pgvector (vector), BM25 | Qdrant (when >1M vectors or hybrid retrieval) | — | | Search | pgvector (vector), BM25 | Qdrant (when >1M vectors or hybrid retrieval) | — |
| Logging | slog (structured) | — | — | | Logging | slog (structured) | — | — |
| Testing | Table-driven, testify | — | — | | Testing | Table-driven, testify | — | — |
| Agents (Go) | google.golang.org/adk + pkg/litellm adapter | — | — |
Exploratory: Rust, Zig — I'll tell you when I want these. Exploratory: Rust, Zig — I'll tell you when I want these.
@@ -61,7 +60,7 @@ Exploratory: Rust, Zig — I'll tell you when I want these.
- **Architecture**: prefer stdlib over frameworks, constructor injection, env-var config parsed into typed structs - **Architecture**: prefer stdlib over frameworks, constructor injection, env-var config parsed into typed structs
- **Git**: conventional commits (`feat:`, `fix:`, `chore:`), one concern per PR, PR describes *why* not *what* - **Git**: conventional commits (`feat:`, `fix:`, `chore:`), one concern per PR, PR describes *why* not *what*
- **Security**: no secrets in code, govulncheck before adding deps, SOPS for encrypted config - **Security**: no secrets in code, govulncheck before adding deps, SOPS for encrypted config
- **Dependencies**: prefer stdlib. testify, slog, templ, sqlc, google.golang.org/adk (agent projects only) are pre-approved; anything else needs justification in the commit message - **Dependencies**: prefer stdlib. testify, slog, templ, sqlc are pre-approved; anything else needs justification in the commit message
## Infrastructure ## Infrastructure
@@ -211,6 +210,7 @@ Key skills:
- Conventional commits: `feat:`, `fix:`, `chore:`, `docs:`, `refactor:` - Conventional commits: `feat:`, `fix:`, `chore:`, `docs:`, `refactor:`
- Branch naming: `feat/short-description`, `fix/short-description` - Branch naming: `feat/short-description`, `fix/short-description`
- PRs: one concern per PR, description explains *why* not *what* - PRs: one concern per PR, description explains *why* not *what*
- **Branch protection:** always work on a feature branch, open a PR, never push directly to main
### Security ### Security
- No secrets in code, ever — use env vars or SOPS-encrypted files - No secrets in code, ever — use env vars or SOPS-encrypted files
@@ -248,68 +248,98 @@ When acting as a coding agent on this project:
3. If unsure about a convention, check `DECISIONS.md` or ask 3. If unsure about a convention, check `DECISIONS.md` or ask
4. Never modify files outside the project root without explicit permission 4. Never modify files outside the project root without explicit permission
5. When adding a dependency, explain why in the commit message 5. When adding a dependency, explain why in the commit message
6. For client projects: never send code or context to cloud APIs — use local models via LiteLLM 6. Always work on a feature branch and open a PR — never push directly to main
7. For client projects: never send code or context to cloud APIs — use local models via LiteLLM
## Current sprint — gitea-mcp v0.2 (2026-05-14) ## Current sprint — gitea-mcp v0.2 patch (2026-05-14)
### Context ### Context
This sprint implements new MCP tools needed for `hyperguild new-project` The main v0.2 batch (repo_create, repo_update, repo_mirror_push, repo_delete,
the automated project creation flow triggered from claude.ai. See brain knowledge repo_tree, repo_topics_update, file_read dir-fix, issue_get, release_create,
nodes `adr-new-project-gitea-first-github-mirror` and `roadmap-github-ingestion-pipeline` create_project_from_template) was implemented and pushed directly to main.
for full background.
### Issues to implement (priority order) This sprint fixes three remaining gaps found during code review on 2026-05-14.
These are blockers for `hyperguild new-project`.
**Batch 1 — blockers (do first, one PR: `feat/repo-crud`)** ### Issues to fix (all three in one PR: `fix/v02-patch`)
| Issue | Tool | Gitea API | #### #12 — repo_update: add `archived` and `template` fields
|-------|------|-----------| **File:** `internal/gitea/repos.go``UpdateRepoArgs` struct
| #13 | `repo_create` | POST /api/v1/user/repos or /api/v1/orgs/{org}/repos | **File:** `internal/tools/repo_update.go` → input schema + args struct
| #16 | `repo_mirror_push` (add/list/delete) | POST/GET/DELETE /api/v1/repos/{owner}/{repo}/push_mirrors |
| #12 | `repo_update` | PATCH /api/v1/repos/{owner}/{repo} |
**Batch 2 — quality of life (second PR: `feat/repo-ux`)** Add to `UpdateRepoArgs`:
```go
Archived *bool
Template *bool
```
| Issue | Tool | Gitea API | Add to tool input schema:
|-------|------|-----------| ```json
| #15 | `file_read` dir-path fix | existing endpoint, detect array vs object response | "archived": {
| #14 | `repo_tree` | GET /api/v1/repos/{owner}/{repo}/git/trees/{sha}?recursive=true | "type": "boolean",
| #18 | `repo_topics_update` | PUT /api/v1/repos/{owner}/{repo}/topics | "description": "Mark repo as archived (read-only). Requires confirm=<repo name>."
},
"template": {
"type": "boolean",
"description": "Toggle template repo flag."
}
```
**Batch 3 — can wait** Add confirm-guard for `archived=true` (same pattern as `private=false`):
```go
if args.Archived != nil && *args.Archived {
if args.Confirm != args.Name {
return nil, fmt.Errorf("setting archived=true is irreversible: set confirm=%q to proceed", args.Name)
}
}
```
| Issue | Tool | Note | New test cases to add in `repo_update_test.go`:
|-------|------|------| - `TestRepoUpdateTool_Archive` — happy path with confirm
| #11 | `repo_delete` | HIGH risk — needs `confirm` param == repo name | - `TestRepoUpdateTool_ArchiveRequiresConfirm` — missing confirm returns error
| #17 | `release_create` | POST /api/v1/repos/{owner}/{repo}/releases | - `TestRepoUpdateTool_SetTemplate` — no confirm needed
### How to add a tool (pattern) #### #24 — create_project_from_template: make template selectable
**File:** `internal/tools/create_project_from_template.go`
Every tool = 4 files following `internal/tools/repo_get.go` exactly: Add optional `template_name` param to input schema:
```json
"template_name": {
"type": "string",
"enum": ["template-go-web", "template-go-agent"],
"description": "Template repo to generate from. Defaults to template-go-web.",
"default": "template-go-web"
}
```
1. `internal/gitea/<domain>.go` — API client method (use PostJSON/PatchJSON/DeleteJSON) The tool should use `args.TemplateName` if set, fall back to the hardcoded default.
2. `internal/tools/repo_<name>.go` — tool handler with Descriptor() + Call() Remove the hardcoded template name from `cmd/gitea-mcp/main.go` constructor call
3. `internal/tools/repo_<name>_test.go` — table-driven tests with httptest.NewServer the tool resolves it internally.
4. Registration in main — find where `NewRepoGet` is registered, add new tool same place
Key rules: New test case: `TestCreateProjectFromTemplate_AgentTemplate`
- Always call `t.a.Check(args.Owner)` before any API call (allowlist guard)
- Use `textOK(result)` for success output
- For `repo_mirror_push`: NEVER log or return `remote_password` in any output
- For `repo_update` with `private: false` and `repo_delete`: require `confirm` param == repo name
### Token permissions needed #### #25 — pr_files_diff: fix same diff returned for all files
**File:** `internal/tools/pr_files_diff.go`
New tools require these additional Gitea token scopes: There is a loop bug where all file entries in the response contain the same diff
- `write:repository` — repo_create, repo_update, repo_mirror_push, repo_topics_update, release_create (the first file's diff is reused for every subsequent file). Find the loop and
- `delete_repo` — repo_delete ensure each iteration reads and assigns the correct diff for its own file.
Check current token: `curl -H "Authorization: token $GITEA_TOKEN" https://gitea.d-ma.be/api/v1/user` Reproduce: call `pr_files_diff` on any PR with 3+ files, verify each file has
If scopes are missing, update token in Gitea settings before running tests. a distinct diff.
### Definition of done ### Definition of done
- `task check` passes (all tools, all batches) - [ ] `task check` passes
- Each new tool manually callable via `claude mcp call` - [ ] `repo_update` accepts `archived` and `template` params
- PR #1 (batch 1) merged before starting batch 2 - [ ] `archived=true` requires `confirm=<repo name>`
- Issue #19 (mirror flow e2e test) verified manually after batch 1 is deployed - [ ] `create_project_from_template` accepts `template_name` param, defaults to `template-go-web`
- [ ] `pr_files_diff` returns distinct diff per file
- [ ] All new test cases pass
- [ ] PR `fix/v02-patch` merged to main via PR (not direct push)
### After this sprint
Next: `hyperguild new-project` v1 implementation.
See brain node `adr-new-project-gitea-first-github-mirror` for the full flow spec.
Also: verify end-to-end mirror flow (issue #19) once `repo_mirror_push` is confirmed working.

View File

@@ -54,7 +54,6 @@ These rules apply to every task across every project, regardless of harness.
| Search | pgvector (vector), BM25 | Qdrant (when >1M vectors or hybrid retrieval) | — | | Search | pgvector (vector), BM25 | Qdrant (when >1M vectors or hybrid retrieval) | — |
| Logging | slog (structured) | — | — | | Logging | slog (structured) | — | — |
| Testing | Table-driven, testify | — | — | | Testing | Table-driven, testify | — | — |
| Agents (Go) | google.golang.org/adk + pkg/litellm adapter | — | — |
Exploratory: Rust, Zig — I'll tell you when I want these. Exploratory: Rust, Zig — I'll tell you when I want these.
@@ -66,7 +65,7 @@ Exploratory: Rust, Zig — I'll tell you when I want these.
- **Architecture**: prefer stdlib over frameworks, constructor injection, env-var config parsed into typed structs - **Architecture**: prefer stdlib over frameworks, constructor injection, env-var config parsed into typed structs
- **Git**: conventional commits (`feat:`, `fix:`, `chore:`), one concern per PR, PR describes *why* not *what* - **Git**: conventional commits (`feat:`, `fix:`, `chore:`), one concern per PR, PR describes *why* not *what*
- **Security**: no secrets in code, govulncheck before adding deps, SOPS for encrypted config - **Security**: no secrets in code, govulncheck before adding deps, SOPS for encrypted config
- **Dependencies**: prefer stdlib. testify, slog, templ, sqlc, google.golang.org/adk (agent projects only) are pre-approved; anything else needs justification in the commit message - **Dependencies**: prefer stdlib. testify, slog, templ, sqlc are pre-approved; anything else needs justification in the commit message
## Infrastructure ## Infrastructure
@@ -216,6 +215,7 @@ Key skills:
- Conventional commits: `feat:`, `fix:`, `chore:`, `docs:`, `refactor:` - Conventional commits: `feat:`, `fix:`, `chore:`, `docs:`, `refactor:`
- Branch naming: `feat/short-description`, `fix/short-description` - Branch naming: `feat/short-description`, `fix/short-description`
- PRs: one concern per PR, description explains *why* not *what* - PRs: one concern per PR, description explains *why* not *what*
- **Branch protection:** always work on a feature branch, open a PR, never push directly to main
### Security ### Security
- No secrets in code, ever — use env vars or SOPS-encrypted files - No secrets in code, ever — use env vars or SOPS-encrypted files
@@ -253,70 +253,100 @@ When acting as a coding agent on this project:
3. If unsure about a convention, check `DECISIONS.md` or ask 3. If unsure about a convention, check `DECISIONS.md` or ask
4. Never modify files outside the project root without explicit permission 4. Never modify files outside the project root without explicit permission
5. When adding a dependency, explain why in the commit message 5. When adding a dependency, explain why in the commit message
6. For client projects: never send code or context to cloud APIs — use local models via LiteLLM 6. Always work on a feature branch and open a PR — never push directly to main
7. For client projects: never send code or context to cloud APIs — use local models via LiteLLM
## Current sprint — gitea-mcp v0.2 (2026-05-14) ## Current sprint — gitea-mcp v0.2 patch (2026-05-14)
### Context ### Context
This sprint implements new MCP tools needed for `hyperguild new-project` — The main v0.2 batch (repo_create, repo_update, repo_mirror_push, repo_delete,
the automated project creation flow triggered from claude.ai. See brain knowledge repo_tree, repo_topics_update, file_read dir-fix, issue_get, release_create,
nodes `adr-new-project-gitea-first-github-mirror` and `roadmap-github-ingestion-pipeline` create_project_from_template) was implemented and pushed directly to main.
for full background.
### Issues to implement (priority order) This sprint fixes three remaining gaps found during code review on 2026-05-14.
These are blockers for `hyperguild new-project`.
**Batch 1 — blockers (do first, one PR: `feat/repo-crud`)** ### Issues to fix (all three in one PR: `fix/v02-patch`)
| Issue | Tool | Gitea API | #### #12 — repo_update: add `archived` and `template` fields
|-------|------|-----------| **File:** `internal/gitea/repos.go` → `UpdateRepoArgs` struct
| #13 | `repo_create` | POST /api/v1/user/repos or /api/v1/orgs/{org}/repos | **File:** `internal/tools/repo_update.go` → input schema + args struct
| #16 | `repo_mirror_push` (add/list/delete) | POST/GET/DELETE /api/v1/repos/{owner}/{repo}/push_mirrors |
| #12 | `repo_update` | PATCH /api/v1/repos/{owner}/{repo} |
**Batch 2 — quality of life (second PR: `feat/repo-ux`)** Add to `UpdateRepoArgs`:
```go
Archived *bool
Template *bool
```
| Issue | Tool | Gitea API | Add to tool input schema:
|-------|------|-----------| ```json
| #15 | `file_read` dir-path fix | existing endpoint, detect array vs object response | "archived": {
| #14 | `repo_tree` | GET /api/v1/repos/{owner}/{repo}/git/trees/{sha}?recursive=true | "type": "boolean",
| #18 | `repo_topics_update` | PUT /api/v1/repos/{owner}/{repo}/topics | "description": "Mark repo as archived (read-only). Requires confirm=<repo name>."
},
"template": {
"type": "boolean",
"description": "Toggle template repo flag."
}
```
**Batch 3 — can wait** Add confirm-guard for `archived=true` (same pattern as `private=false`):
```go
if args.Archived != nil && *args.Archived {
if args.Confirm != args.Name {
return nil, fmt.Errorf("setting archived=true is irreversible: set confirm=%q to proceed", args.Name)
}
}
```
| Issue | Tool | Note | New test cases to add in `repo_update_test.go`:
|-------|------|------| - `TestRepoUpdateTool_Archive` — happy path with confirm
| #11 | `repo_delete` | HIGH risk — needs `confirm` param == repo name | - `TestRepoUpdateTool_ArchiveRequiresConfirm` — missing confirm returns error
| #17 | `release_create` | POST /api/v1/repos/{owner}/{repo}/releases | - `TestRepoUpdateTool_SetTemplate` — no confirm needed
### How to add a tool (pattern) #### #24 — create_project_from_template: make template selectable
**File:** `internal/tools/create_project_from_template.go`
Every tool = 4 files following `internal/tools/repo_get.go` exactly: Add optional `template_name` param to input schema:
```json
"template_name": {
"type": "string",
"enum": ["template-go-web", "template-go-agent"],
"description": "Template repo to generate from. Defaults to template-go-web.",
"default": "template-go-web"
}
```
1. `internal/gitea/<domain>.go` — API client method (use PostJSON/PatchJSON/DeleteJSON) The tool should use `args.TemplateName` if set, fall back to the hardcoded default.
2. `internal/tools/repo_<name>.go` — tool handler with Descriptor() + Call() Remove the hardcoded template name from `cmd/gitea-mcp/main.go` constructor call
3. `internal/tools/repo_<name>_test.go` — table-driven tests with httptest.NewServer the tool resolves it internally.
4. Registration in main — find where `NewRepoGet` is registered, add new tool same place
Key rules: New test case: `TestCreateProjectFromTemplate_AgentTemplate`
- Always call `t.a.Check(args.Owner)` before any API call (allowlist guard)
- Use `textOK(result)` for success output
- For `repo_mirror_push`: NEVER log or return `remote_password` in any output
- For `repo_update` with `private: false` and `repo_delete`: require `confirm` param == repo name
### Token permissions needed #### #25 — pr_files_diff: fix same diff returned for all files
**File:** `internal/tools/pr_files_diff.go`
New tools require these additional Gitea token scopes: There is a loop bug where all file entries in the response contain the same diff
- `write:repository` — repo_create, repo_update, repo_mirror_push, repo_topics_update, release_create (the first file's diff is reused for every subsequent file). Find the loop and
- `delete_repo` — repo_delete ensure each iteration reads and assigns the correct diff for its own file.
Check current token: `curl -H "Authorization: token $GITEA_TOKEN" https://gitea.d-ma.be/api/v1/user` Reproduce: call `pr_files_diff` on any PR with 3+ files, verify each file has
If scopes are missing, update token in Gitea settings before running tests. a distinct diff.
### Definition of done ### Definition of done
- `task check` passes (all tools, all batches) - [ ] `task check` passes
- Each new tool manually callable via `claude mcp call` - [ ] `repo_update` accepts `archived` and `template` params
- PR #1 (batch 1) merged before starting batch 2 - [ ] `archived=true` requires `confirm=<repo name>`
- Issue #19 (mirror flow e2e test) verified manually after batch 1 is deployed - [ ] `create_project_from_template` accepts `template_name` param, defaults to `template-go-web`
- [ ] `pr_files_diff` returns distinct diff per file
- [ ] All new test cases pass
- [ ] PR `fix/v02-patch` merged to main via PR (not direct push)
### After this sprint
Next: `hyperguild new-project` v1 implementation.
See brain node `adr-new-project-gitea-first-github-mirror` for the full flow spec.
Also: verify end-to-end mirror flow (issue #19) once `repo_mirror_push` is confirmed working.
--- ---

View File

@@ -52,7 +52,6 @@ These rules apply to every task across every project, regardless of harness.
| Search | pgvector (vector), BM25 | Qdrant (when >1M vectors or hybrid retrieval) | — | | Search | pgvector (vector), BM25 | Qdrant (when >1M vectors or hybrid retrieval) | — |
| Logging | slog (structured) | — | — | | Logging | slog (structured) | — | — |
| Testing | Table-driven, testify | — | — | | Testing | Table-driven, testify | — | — |
| Agents (Go) | google.golang.org/adk + pkg/litellm adapter | — | — |
Exploratory: Rust, Zig — I'll tell you when I want these. Exploratory: Rust, Zig — I'll tell you when I want these.
@@ -64,7 +63,7 @@ Exploratory: Rust, Zig — I'll tell you when I want these.
- **Architecture**: prefer stdlib over frameworks, constructor injection, env-var config parsed into typed structs - **Architecture**: prefer stdlib over frameworks, constructor injection, env-var config parsed into typed structs
- **Git**: conventional commits (`feat:`, `fix:`, `chore:`), one concern per PR, PR describes *why* not *what* - **Git**: conventional commits (`feat:`, `fix:`, `chore:`), one concern per PR, PR describes *why* not *what*
- **Security**: no secrets in code, govulncheck before adding deps, SOPS for encrypted config - **Security**: no secrets in code, govulncheck before adding deps, SOPS for encrypted config
- **Dependencies**: prefer stdlib. testify, slog, templ, sqlc, google.golang.org/adk (agent projects only) are pre-approved; anything else needs justification in the commit message - **Dependencies**: prefer stdlib. testify, slog, templ, sqlc are pre-approved; anything else needs justification in the commit message
## Infrastructure ## Infrastructure
@@ -214,6 +213,7 @@ Key skills:
- Conventional commits: `feat:`, `fix:`, `chore:`, `docs:`, `refactor:` - Conventional commits: `feat:`, `fix:`, `chore:`, `docs:`, `refactor:`
- Branch naming: `feat/short-description`, `fix/short-description` - Branch naming: `feat/short-description`, `fix/short-description`
- PRs: one concern per PR, description explains *why* not *what* - PRs: one concern per PR, description explains *why* not *what*
- **Branch protection:** always work on a feature branch, open a PR, never push directly to main
### Security ### Security
- No secrets in code, ever — use env vars or SOPS-encrypted files - No secrets in code, ever — use env vars or SOPS-encrypted files
@@ -251,68 +251,98 @@ When acting as a coding agent on this project:
3. If unsure about a convention, check `DECISIONS.md` or ask 3. If unsure about a convention, check `DECISIONS.md` or ask
4. Never modify files outside the project root without explicit permission 4. Never modify files outside the project root without explicit permission
5. When adding a dependency, explain why in the commit message 5. When adding a dependency, explain why in the commit message
6. For client projects: never send code or context to cloud APIs — use local models via LiteLLM 6. Always work on a feature branch and open a PR — never push directly to main
7. For client projects: never send code or context to cloud APIs — use local models via LiteLLM
## Current sprint — gitea-mcp v0.2 (2026-05-14) ## Current sprint — gitea-mcp v0.2 patch (2026-05-14)
### Context ### Context
This sprint implements new MCP tools needed for `hyperguild new-project` — The main v0.2 batch (repo_create, repo_update, repo_mirror_push, repo_delete,
the automated project creation flow triggered from claude.ai. See brain knowledge repo_tree, repo_topics_update, file_read dir-fix, issue_get, release_create,
nodes `adr-new-project-gitea-first-github-mirror` and `roadmap-github-ingestion-pipeline` create_project_from_template) was implemented and pushed directly to main.
for full background.
### Issues to implement (priority order) This sprint fixes three remaining gaps found during code review on 2026-05-14.
These are blockers for `hyperguild new-project`.
**Batch 1 — blockers (do first, one PR: `feat/repo-crud`)** ### Issues to fix (all three in one PR: `fix/v02-patch`)
| Issue | Tool | Gitea API | #### #12 — repo_update: add `archived` and `template` fields
|-------|------|-----------| **File:** `internal/gitea/repos.go` → `UpdateRepoArgs` struct
| #13 | `repo_create` | POST /api/v1/user/repos or /api/v1/orgs/{org}/repos | **File:** `internal/tools/repo_update.go` → input schema + args struct
| #16 | `repo_mirror_push` (add/list/delete) | POST/GET/DELETE /api/v1/repos/{owner}/{repo}/push_mirrors |
| #12 | `repo_update` | PATCH /api/v1/repos/{owner}/{repo} |
**Batch 2 — quality of life (second PR: `feat/repo-ux`)** Add to `UpdateRepoArgs`:
```go
Archived *bool
Template *bool
```
| Issue | Tool | Gitea API | Add to tool input schema:
|-------|------|-----------| ```json
| #15 | `file_read` dir-path fix | existing endpoint, detect array vs object response | "archived": {
| #14 | `repo_tree` | GET /api/v1/repos/{owner}/{repo}/git/trees/{sha}?recursive=true | "type": "boolean",
| #18 | `repo_topics_update` | PUT /api/v1/repos/{owner}/{repo}/topics | "description": "Mark repo as archived (read-only). Requires confirm=<repo name>."
},
"template": {
"type": "boolean",
"description": "Toggle template repo flag."
}
```
**Batch 3 — can wait** Add confirm-guard for `archived=true` (same pattern as `private=false`):
```go
if args.Archived != nil && *args.Archived {
if args.Confirm != args.Name {
return nil, fmt.Errorf("setting archived=true is irreversible: set confirm=%q to proceed", args.Name)
}
}
```
| Issue | Tool | Note | New test cases to add in `repo_update_test.go`:
|-------|------|------| - `TestRepoUpdateTool_Archive` — happy path with confirm
| #11 | `repo_delete` | HIGH risk — needs `confirm` param == repo name | - `TestRepoUpdateTool_ArchiveRequiresConfirm` — missing confirm returns error
| #17 | `release_create` | POST /api/v1/repos/{owner}/{repo}/releases | - `TestRepoUpdateTool_SetTemplate` — no confirm needed
### How to add a tool (pattern) #### #24 — create_project_from_template: make template selectable
**File:** `internal/tools/create_project_from_template.go`
Every tool = 4 files following `internal/tools/repo_get.go` exactly: Add optional `template_name` param to input schema:
```json
"template_name": {
"type": "string",
"enum": ["template-go-web", "template-go-agent"],
"description": "Template repo to generate from. Defaults to template-go-web.",
"default": "template-go-web"
}
```
1. `internal/gitea/<domain>.go` — API client method (use PostJSON/PatchJSON/DeleteJSON) The tool should use `args.TemplateName` if set, fall back to the hardcoded default.
2. `internal/tools/repo_<name>.go` — tool handler with Descriptor() + Call() Remove the hardcoded template name from `cmd/gitea-mcp/main.go` constructor call
3. `internal/tools/repo_<name>_test.go` — table-driven tests with httptest.NewServer the tool resolves it internally.
4. Registration in main — find where `NewRepoGet` is registered, add new tool same place
Key rules: New test case: `TestCreateProjectFromTemplate_AgentTemplate`
- Always call `t.a.Check(args.Owner)` before any API call (allowlist guard)
- Use `textOK(result)` for success output
- For `repo_mirror_push`: NEVER log or return `remote_password` in any output
- For `repo_update` with `private: false` and `repo_delete`: require `confirm` param == repo name
### Token permissions needed #### #25 — pr_files_diff: fix same diff returned for all files
**File:** `internal/tools/pr_files_diff.go`
New tools require these additional Gitea token scopes: There is a loop bug where all file entries in the response contain the same diff
- `write:repository` — repo_create, repo_update, repo_mirror_push, repo_topics_update, release_create (the first file's diff is reused for every subsequent file). Find the loop and
- `delete_repo` — repo_delete ensure each iteration reads and assigns the correct diff for its own file.
Check current token: `curl -H "Authorization: token $GITEA_TOKEN" https://gitea.d-ma.be/api/v1/user` Reproduce: call `pr_files_diff` on any PR with 3+ files, verify each file has
If scopes are missing, update token in Gitea settings before running tests. a distinct diff.
### Definition of done ### Definition of done
- `task check` passes (all tools, all batches) - [ ] `task check` passes
- Each new tool manually callable via `claude mcp call` - [ ] `repo_update` accepts `archived` and `template` params
- PR #1 (batch 1) merged before starting batch 2 - [ ] `archived=true` requires `confirm=<repo name>`
- Issue #19 (mirror flow e2e test) verified manually after batch 1 is deployed - [ ] `create_project_from_template` accepts `template_name` param, defaults to `template-go-web`
- [ ] `pr_files_diff` returns distinct diff per file
- [ ] All new test cases pass
- [ ] PR `fix/v02-patch` merged to main via PR (not direct push)
### After this sprint
Next: `hyperguild new-project` v1 implementation.
See brain node `adr-new-project-gitea-first-github-mirror` for the full flow spec.
Also: verify end-to-end mirror flow (issue #19) once `repo_mirror_push` is confirmed working.

124
AGENTS.md
View File

@@ -49,7 +49,6 @@ These rules apply to every task across every project, regardless of harness.
| Search | pgvector (vector), BM25 | Qdrant (when >1M vectors or hybrid retrieval) | — | | Search | pgvector (vector), BM25 | Qdrant (when >1M vectors or hybrid retrieval) | — |
| Logging | slog (structured) | — | — | | Logging | slog (structured) | — | — |
| Testing | Table-driven, testify | — | — | | Testing | Table-driven, testify | — | — |
| Agents (Go) | google.golang.org/adk + pkg/litellm adapter | — | — |
Exploratory: Rust, Zig — I'll tell you when I want these. Exploratory: Rust, Zig — I'll tell you when I want these.
@@ -61,7 +60,7 @@ Exploratory: Rust, Zig — I'll tell you when I want these.
- **Architecture**: prefer stdlib over frameworks, constructor injection, env-var config parsed into typed structs - **Architecture**: prefer stdlib over frameworks, constructor injection, env-var config parsed into typed structs
- **Git**: conventional commits (`feat:`, `fix:`, `chore:`), one concern per PR, PR describes *why* not *what* - **Git**: conventional commits (`feat:`, `fix:`, `chore:`), one concern per PR, PR describes *why* not *what*
- **Security**: no secrets in code, govulncheck before adding deps, SOPS for encrypted config - **Security**: no secrets in code, govulncheck before adding deps, SOPS for encrypted config
- **Dependencies**: prefer stdlib. testify, slog, templ, sqlc, google.golang.org/adk (agent projects only) are pre-approved; anything else needs justification in the commit message - **Dependencies**: prefer stdlib. testify, slog, templ, sqlc are pre-approved; anything else needs justification in the commit message
## Infrastructure ## Infrastructure
@@ -211,6 +210,7 @@ Key skills:
- Conventional commits: `feat:`, `fix:`, `chore:`, `docs:`, `refactor:` - Conventional commits: `feat:`, `fix:`, `chore:`, `docs:`, `refactor:`
- Branch naming: `feat/short-description`, `fix/short-description` - Branch naming: `feat/short-description`, `fix/short-description`
- PRs: one concern per PR, description explains *why* not *what* - PRs: one concern per PR, description explains *why* not *what*
- **Branch protection:** always work on a feature branch, open a PR, never push directly to main
### Security ### Security
- No secrets in code, ever — use env vars or SOPS-encrypted files - No secrets in code, ever — use env vars or SOPS-encrypted files
@@ -248,68 +248,98 @@ When acting as a coding agent on this project:
3. If unsure about a convention, check `DECISIONS.md` or ask 3. If unsure about a convention, check `DECISIONS.md` or ask
4. Never modify files outside the project root without explicit permission 4. Never modify files outside the project root without explicit permission
5. When adding a dependency, explain why in the commit message 5. When adding a dependency, explain why in the commit message
6. For client projects: never send code or context to cloud APIs — use local models via LiteLLM 6. Always work on a feature branch and open a PR — never push directly to main
7. For client projects: never send code or context to cloud APIs — use local models via LiteLLM
## Current sprint — gitea-mcp v0.2 (2026-05-14) ## Current sprint — gitea-mcp v0.2 patch (2026-05-14)
### Context ### Context
This sprint implements new MCP tools needed for `hyperguild new-project` The main v0.2 batch (repo_create, repo_update, repo_mirror_push, repo_delete,
the automated project creation flow triggered from claude.ai. See brain knowledge repo_tree, repo_topics_update, file_read dir-fix, issue_get, release_create,
nodes `adr-new-project-gitea-first-github-mirror` and `roadmap-github-ingestion-pipeline` create_project_from_template) was implemented and pushed directly to main.
for full background.
### Issues to implement (priority order) This sprint fixes three remaining gaps found during code review on 2026-05-14.
These are blockers for `hyperguild new-project`.
**Batch 1 — blockers (do first, one PR: `feat/repo-crud`)** ### Issues to fix (all three in one PR: `fix/v02-patch`)
| Issue | Tool | Gitea API | #### #12 — repo_update: add `archived` and `template` fields
|-------|------|-----------| **File:** `internal/gitea/repos.go``UpdateRepoArgs` struct
| #13 | `repo_create` | POST /api/v1/user/repos or /api/v1/orgs/{org}/repos | **File:** `internal/tools/repo_update.go` → input schema + args struct
| #16 | `repo_mirror_push` (add/list/delete) | POST/GET/DELETE /api/v1/repos/{owner}/{repo}/push_mirrors |
| #12 | `repo_update` | PATCH /api/v1/repos/{owner}/{repo} |
**Batch 2 — quality of life (second PR: `feat/repo-ux`)** Add to `UpdateRepoArgs`:
```go
Archived *bool
Template *bool
```
| Issue | Tool | Gitea API | Add to tool input schema:
|-------|------|-----------| ```json
| #15 | `file_read` dir-path fix | existing endpoint, detect array vs object response | "archived": {
| #14 | `repo_tree` | GET /api/v1/repos/{owner}/{repo}/git/trees/{sha}?recursive=true | "type": "boolean",
| #18 | `repo_topics_update` | PUT /api/v1/repos/{owner}/{repo}/topics | "description": "Mark repo as archived (read-only). Requires confirm=<repo name>."
},
"template": {
"type": "boolean",
"description": "Toggle template repo flag."
}
```
**Batch 3 — can wait** Add confirm-guard for `archived=true` (same pattern as `private=false`):
```go
if args.Archived != nil && *args.Archived {
if args.Confirm != args.Name {
return nil, fmt.Errorf("setting archived=true is irreversible: set confirm=%q to proceed", args.Name)
}
}
```
| Issue | Tool | Note | New test cases to add in `repo_update_test.go`:
|-------|------|------| - `TestRepoUpdateTool_Archive` — happy path with confirm
| #11 | `repo_delete` | HIGH risk — needs `confirm` param == repo name | - `TestRepoUpdateTool_ArchiveRequiresConfirm` — missing confirm returns error
| #17 | `release_create` | POST /api/v1/repos/{owner}/{repo}/releases | - `TestRepoUpdateTool_SetTemplate` — no confirm needed
### How to add a tool (pattern) #### #24 — create_project_from_template: make template selectable
**File:** `internal/tools/create_project_from_template.go`
Every tool = 4 files following `internal/tools/repo_get.go` exactly: Add optional `template_name` param to input schema:
```json
"template_name": {
"type": "string",
"enum": ["template-go-web", "template-go-agent"],
"description": "Template repo to generate from. Defaults to template-go-web.",
"default": "template-go-web"
}
```
1. `internal/gitea/<domain>.go` — API client method (use PostJSON/PatchJSON/DeleteJSON) The tool should use `args.TemplateName` if set, fall back to the hardcoded default.
2. `internal/tools/repo_<name>.go` — tool handler with Descriptor() + Call() Remove the hardcoded template name from `cmd/gitea-mcp/main.go` constructor call
3. `internal/tools/repo_<name>_test.go` — table-driven tests with httptest.NewServer the tool resolves it internally.
4. Registration in main — find where `NewRepoGet` is registered, add new tool same place
Key rules: New test case: `TestCreateProjectFromTemplate_AgentTemplate`
- Always call `t.a.Check(args.Owner)` before any API call (allowlist guard)
- Use `textOK(result)` for success output
- For `repo_mirror_push`: NEVER log or return `remote_password` in any output
- For `repo_update` with `private: false` and `repo_delete`: require `confirm` param == repo name
### Token permissions needed #### #25 — pr_files_diff: fix same diff returned for all files
**File:** `internal/tools/pr_files_diff.go`
New tools require these additional Gitea token scopes: There is a loop bug where all file entries in the response contain the same diff
- `write:repository` — repo_create, repo_update, repo_mirror_push, repo_topics_update, release_create (the first file's diff is reused for every subsequent file). Find the loop and
- `delete_repo` — repo_delete ensure each iteration reads and assigns the correct diff for its own file.
Check current token: `curl -H "Authorization: token $GITEA_TOKEN" https://gitea.d-ma.be/api/v1/user` Reproduce: call `pr_files_diff` on any PR with 3+ files, verify each file has
If scopes are missing, update token in Gitea settings before running tests. a distinct diff.
### Definition of done ### Definition of done
- `task check` passes (all tools, all batches) - [ ] `task check` passes
- Each new tool manually callable via `claude mcp call` - [ ] `repo_update` accepts `archived` and `template` params
- PR #1 (batch 1) merged before starting batch 2 - [ ] `archived=true` requires `confirm=<repo name>`
- Issue #19 (mirror flow e2e test) verified manually after batch 1 is deployed - [ ] `create_project_from_template` accepts `template_name` param, defaults to `template-go-web`
- [ ] `pr_files_diff` returns distinct diff per file
- [ ] All new test cases pass
- [ ] PR `fix/v02-patch` merged to main via PR (not direct push)
### After this sprint
Next: `hyperguild new-project` v1 implementation.
See brain node `adr-new-project-gitea-first-github-mirror` for the full flow spec.
Also: verify end-to-end mirror flow (issue #19) once `repo_mirror_push` is confirmed working.

121
CLAUDE.md
View File

@@ -39,6 +39,7 @@
- Conventional commits: `feat:`, `fix:`, `chore:`, `docs:`, `refactor:` - Conventional commits: `feat:`, `fix:`, `chore:`, `docs:`, `refactor:`
- Branch naming: `feat/short-description`, `fix/short-description` - Branch naming: `feat/short-description`, `fix/short-description`
- PRs: one concern per PR, description explains *why* not *what* - PRs: one concern per PR, description explains *why* not *what*
- **Branch protection:** always work on a feature branch, open a PR, never push directly to main
### Security ### Security
- No secrets in code, ever — use env vars or SOPS-encrypted files - No secrets in code, ever — use env vars or SOPS-encrypted files
@@ -76,68 +77,98 @@ When acting as a coding agent on this project:
3. If unsure about a convention, check `DECISIONS.md` or ask 3. If unsure about a convention, check `DECISIONS.md` or ask
4. Never modify files outside the project root without explicit permission 4. Never modify files outside the project root without explicit permission
5. When adding a dependency, explain why in the commit message 5. When adding a dependency, explain why in the commit message
6. For client projects: never send code or context to cloud APIs — use local models via LiteLLM 6. Always work on a feature branch and open a PR — never push directly to main
7. For client projects: never send code or context to cloud APIs — use local models via LiteLLM
## Current sprint — gitea-mcp v0.2 (2026-05-14) ## Current sprint — gitea-mcp v0.2 patch (2026-05-14)
### Context ### Context
This sprint implements new MCP tools needed for `hyperguild new-project` The main v0.2 batch (repo_create, repo_update, repo_mirror_push, repo_delete,
the automated project creation flow triggered from claude.ai. See brain knowledge repo_tree, repo_topics_update, file_read dir-fix, issue_get, release_create,
nodes `adr-new-project-gitea-first-github-mirror` and `roadmap-github-ingestion-pipeline` create_project_from_template) was implemented and pushed directly to main.
for full background.
### Issues to implement (priority order) This sprint fixes three remaining gaps found during code review on 2026-05-14.
These are blockers for `hyperguild new-project`.
**Batch 1 — blockers (do first, one PR: `feat/repo-crud`)** ### Issues to fix (all three in one PR: `fix/v02-patch`)
| Issue | Tool | Gitea API | #### #12 — repo_update: add `archived` and `template` fields
|-------|------|-----------| **File:** `internal/gitea/repos.go``UpdateRepoArgs` struct
| #13 | `repo_create` | POST /api/v1/user/repos or /api/v1/orgs/{org}/repos | **File:** `internal/tools/repo_update.go` → input schema + args struct
| #16 | `repo_mirror_push` (add/list/delete) | POST/GET/DELETE /api/v1/repos/{owner}/{repo}/push_mirrors |
| #12 | `repo_update` | PATCH /api/v1/repos/{owner}/{repo} |
**Batch 2 — quality of life (second PR: `feat/repo-ux`)** Add to `UpdateRepoArgs`:
```go
Archived *bool
Template *bool
```
| Issue | Tool | Gitea API | Add to tool input schema:
|-------|------|-----------| ```json
| #15 | `file_read` dir-path fix | existing endpoint, detect array vs object response | "archived": {
| #14 | `repo_tree` | GET /api/v1/repos/{owner}/{repo}/git/trees/{sha}?recursive=true | "type": "boolean",
| #18 | `repo_topics_update` | PUT /api/v1/repos/{owner}/{repo}/topics | "description": "Mark repo as archived (read-only). Requires confirm=<repo name>."
},
"template": {
"type": "boolean",
"description": "Toggle template repo flag."
}
```
**Batch 3 — can wait** Add confirm-guard for `archived=true` (same pattern as `private=false`):
```go
if args.Archived != nil && *args.Archived {
if args.Confirm != args.Name {
return nil, fmt.Errorf("setting archived=true is irreversible: set confirm=%q to proceed", args.Name)
}
}
```
| Issue | Tool | Note | New test cases to add in `repo_update_test.go`:
|-------|------|------| - `TestRepoUpdateTool_Archive` — happy path with confirm
| #11 | `repo_delete` | HIGH risk — needs `confirm` param == repo name | - `TestRepoUpdateTool_ArchiveRequiresConfirm` — missing confirm returns error
| #17 | `release_create` | POST /api/v1/repos/{owner}/{repo}/releases | - `TestRepoUpdateTool_SetTemplate` — no confirm needed
### How to add a tool (pattern) #### #24 — create_project_from_template: make template selectable
**File:** `internal/tools/create_project_from_template.go`
Every tool = 4 files following `internal/tools/repo_get.go` exactly: Add optional `template_name` param to input schema:
```json
"template_name": {
"type": "string",
"enum": ["template-go-web", "template-go-agent"],
"description": "Template repo to generate from. Defaults to template-go-web.",
"default": "template-go-web"
}
```
1. `internal/gitea/<domain>.go` — API client method (use PostJSON/PatchJSON/DeleteJSON) The tool should use `args.TemplateName` if set, fall back to the hardcoded default.
2. `internal/tools/repo_<name>.go` — tool handler with Descriptor() + Call() Remove the hardcoded template name from `cmd/gitea-mcp/main.go` constructor call
3. `internal/tools/repo_<name>_test.go` — table-driven tests with httptest.NewServer the tool resolves it internally.
4. Registration in main — find where `NewRepoGet` is registered, add new tool same place
Key rules: New test case: `TestCreateProjectFromTemplate_AgentTemplate`
- Always call `t.a.Check(args.Owner)` before any API call (allowlist guard)
- Use `textOK(result)` for success output
- For `repo_mirror_push`: NEVER log or return `remote_password` in any output
- For `repo_update` with `private: false` and `repo_delete`: require `confirm` param == repo name
### Token permissions needed #### #25 — pr_files_diff: fix same diff returned for all files
**File:** `internal/tools/pr_files_diff.go`
New tools require these additional Gitea token scopes: There is a loop bug where all file entries in the response contain the same diff
- `write:repository` — repo_create, repo_update, repo_mirror_push, repo_topics_update, release_create (the first file's diff is reused for every subsequent file). Find the loop and
- `delete_repo` — repo_delete ensure each iteration reads and assigns the correct diff for its own file.
Check current token: `curl -H "Authorization: token $GITEA_TOKEN" https://gitea.d-ma.be/api/v1/user` Reproduce: call `pr_files_diff` on any PR with 3+ files, verify each file has
If scopes are missing, update token in Gitea settings before running tests. a distinct diff.
### Definition of done ### Definition of done
- `task check` passes (all tools, all batches) - [ ] `task check` passes
- Each new tool manually callable via `claude mcp call` - [ ] `repo_update` accepts `archived` and `template` params
- PR #1 (batch 1) merged before starting batch 2 - [ ] `archived=true` requires `confirm=<repo name>`
- Issue #19 (mirror flow e2e test) verified manually after batch 1 is deployed - [ ] `create_project_from_template` accepts `template_name` param, defaults to `template-go-web`
- [ ] `pr_files_diff` returns distinct diff per file
- [ ] All new test cases pass
- [ ] PR `fix/v02-patch` merged to main via PR (not direct push)
### After this sprint
Next: `hyperguild new-project` v1 implementation.
See brain node `adr-new-project-gitea-first-github-mirror` for the full flow spec.
Also: verify end-to-end mirror flow (issue #19) once `repo_mirror_push` is confirmed working.