Compare commits
4 Commits
v0.2.5
...
dc907fb7e0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc907fb7e0 | ||
|
|
c4bd3396c4 | ||
|
|
11f86f5d99 | ||
|
|
f7076c9ac8 |
@@ -36,6 +36,18 @@ These rules apply to every task across every project, regardless of harness.
|
|||||||
4. **Goal-driven execution.** Define clear success criteria up front for every task.
|
4. **Goal-driven execution.** Define clear success criteria up front for every task.
|
||||||
Loop — implement, verify, refine — until those criteria are met. Don't claim
|
Loop — implement, verify, refine — until those criteria are met. Don't claim
|
||||||
completion without evidence (tests pass, command output, observed behavior).
|
completion without evidence (tests pass, command output, observed behavior).
|
||||||
|
5. **Trunk-Based Development — commit directly to main.** Every commit is one
|
||||||
|
logical change (one tool, one fix, one test) with passing tests. Main is always
|
||||||
|
deployable. Never create long-lived feature branches.
|
||||||
|
|
||||||
|
**Exception — parallel agents on same repo:** If another agent is known to be
|
||||||
|
actively working on the same repo simultaneously, create a short-lived branch
|
||||||
|
(`agent/<description>`), finish the task, and merge to main within the same
|
||||||
|
session. Do not leave agent branches open between sessions.
|
||||||
|
|
||||||
|
**Exception — external contributor or client four-eyes requirement:** Use
|
||||||
|
PR flow only when a human reviewer outside the project is required. Document
|
||||||
|
the reason in PROJECT.md.
|
||||||
|
|
||||||
## Default stack
|
## Default stack
|
||||||
|
|
||||||
@@ -49,6 +61,7 @@ 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.
|
||||||
|
|
||||||
@@ -58,9 +71,12 @@ Exploratory: Rust, Zig — I'll tell you when I want these.
|
|||||||
- **Errors**: `fmt.Errorf("operation: %w", err)` — never naked, never log-and-return
|
- **Errors**: `fmt.Errorf("operation: %w", err)` — never naked, never log-and-return
|
||||||
- **Naming**: stdlib conventions, no stuttering
|
- **Naming**: stdlib conventions, no stuttering
|
||||||
- **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:`), commit directly to main,
|
||||||
|
one logical change per commit, CI is the quality gate
|
||||||
|
- **Never**: long-lived feature branches, PRs for solo work, direct push without
|
||||||
|
passing `task check` locally first
|
||||||
- **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 are pre-approved; anything else needs justification in the commit message
|
- **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
|
||||||
|
|
||||||
## Infrastructure
|
## Infrastructure
|
||||||
|
|
||||||
@@ -100,18 +116,64 @@ See `~/dev/PROJECT_SUMMARY.md` for detailed descriptions of each project.
|
|||||||
- **koala-ai-stack** (`AGENTS/`) — local AI server infrastructure management
|
- **koala-ai-stack** (`AGENTS/`) — local AI server infrastructure management
|
||||||
- **klimatkollen** (`XT/`) — Swedish municipal climate data platform
|
- **klimatkollen** (`XT/`) — Swedish municipal climate data platform
|
||||||
|
|
||||||
## Knowledge base
|
## Knowledge base — actively use it
|
||||||
|
|
||||||
When available, agents can query the shared knowledge base:
|
A persistent brain (BM25 search + LLM-synthesised Q&A) survives across sessions,
|
||||||
|
hosts, and harnesses. It holds 100+ hard-won entries: infra incident postmortems,
|
||||||
|
Go pitfalls, framework gotchas, design principles, ADRs. **It is not optional
|
||||||
|
reference material — query it actively, not just when explicitly told.**
|
||||||
|
|
||||||
- **MCP**: `mcp://hyperguild.<TAILNET>.ts.net:3100/knowledge`
|
### When to query (treat as a reflex)
|
||||||
- **HTTP**: `http://hyperguild.<TAILNET>.ts.net:3100/api/v1/search`
|
|
||||||
|
|
||||||
<!-- TODO: replace <TAILNET> placeholder with the real Tailscale tailnet
|
- **Before** starting a non-trivial task — search for prior art with the symptom
|
||||||
name once hyperguild is deployed. Until then, agents that try to
|
AND the system component ("how did we solve X in Y?"). 5 seconds beats 5 hours.
|
||||||
reach the knowledge service on a host where it isn't running will
|
- **When debugging** — search for the error string, the stack frame, the affected
|
||||||
get DNS NXDOMAIN, which is the desired fail-loudly behavior. -->
|
service. Past you may have already paid this tax.
|
||||||
- **Scoping**: defaults to `public` collection; client projects filter to `{client}` + `public`
|
- **Before adopting** a pattern, library, framework, or model name — check if it
|
||||||
|
was tried and rejected, or what the integration footguns are.
|
||||||
|
- **When making architectural decisions** — search for the domain + "ADR" or
|
||||||
|
"decision" to find prior reasoning before re-deriving it.
|
||||||
|
- **When a recommendation feels novel** — challenge yourself: "has this been
|
||||||
|
documented?" The brain often has it.
|
||||||
|
|
||||||
|
### When to write
|
||||||
|
|
||||||
|
After you discover something that **future-you would forget** and that **isn't
|
||||||
|
recoverable from the code, git log, or PR description alone**:
|
||||||
|
|
||||||
|
- Bugs whose root cause is non-obvious and generalisable beyond this project.
|
||||||
|
- Framework / library / model-name quirks that bit you and would bite anyone.
|
||||||
|
- Design principles validated under fire (e.g. "every `_get` needs a `_list`").
|
||||||
|
- Postmortems for incidents: what broke, why, how diagnosed, what to do next time.
|
||||||
|
|
||||||
|
DON'T write project status, sprint progress, PR summaries, or "what I did this
|
||||||
|
session" — those rot fast and the originals are in git/gitea anyway. Brain
|
||||||
|
entries that age well are about *why*, *how to avoid*, and *what to do when*.
|
||||||
|
|
||||||
|
### How to access (per harness)
|
||||||
|
|
||||||
|
| Harness | Query | Write |
|
||||||
|
|---------|-------|-------|
|
||||||
|
| **Claude Code, Claude Desktop** | `brain_query` (BM25), `brain_answer` (LLM-synth + sources) MCP tools | `brain_write` MCP tool |
|
||||||
|
| **Crush, Pi, Antigravity, other MCP-capable** | same MCP server: `ingestion-brain` (via the `mcp__*_brain__*` namespace once authenticated) | same |
|
||||||
|
| **Anything HTTP-only (curl, scripts)** | `POST https://brain-mcp.d-ma.be/query` with `{"query":"..."}` (auth via `BRAIN_MCP_TOKEN`) | `POST .../write` with `{"content":"...","filename":"..."}` |
|
||||||
|
| **Browser / human inspection** | `https://gitea.d-ma.be/mathias/hyperguild` → `knowledge/` and `wiki/` markdown files |
|
||||||
|
|
||||||
|
- **Scoping**: defaults to `public` collection; client projects filter to `{client}` + `public`.
|
||||||
|
- **Routing**: brain_answer's LLM uses berget.ai as primary, iguana ollama as
|
||||||
|
fallback. Both are configurable in the `supervisor/ingestion-deployment.yaml`
|
||||||
|
on the koala k3s cluster; don't hardcode local-only model names into the
|
||||||
|
berget URL (see knowledge entry on namespace mismatches).
|
||||||
|
|
||||||
|
### Quick reflex checks
|
||||||
|
|
||||||
|
If you find yourself about to say any of these out loud, you owe yourself a brain query first:
|
||||||
|
|
||||||
|
- "I think the issue might be..."
|
||||||
|
- "Let me try X and see..."
|
||||||
|
- "I'll just write a script to..."
|
||||||
|
- "This is probably a new bug..."
|
||||||
|
- "Has anyone done this before?" — *yes, probably, go check.*
|
||||||
|
|
||||||
## Client work rules
|
## Client work rules
|
||||||
|
|
||||||
@@ -208,9 +270,11 @@ Key skills:
|
|||||||
|
|
||||||
### Git
|
### Git
|
||||||
- Conventional commits: `feat:`, `fix:`, `chore:`, `docs:`, `refactor:`
|
- Conventional commits: `feat:`, `fix:`, `chore:`, `docs:`, `refactor:`
|
||||||
- Branch naming: `feat/short-description`, `fix/short-description`
|
- **Trunk-Based Development:** commit directly to main. One logical change per commit.
|
||||||
- PRs: one concern per PR, description explains *why* not *what*
|
- Run `task check` locally before every push. CI is the quality gate, not branch protection.
|
||||||
- **Branch protection:** always work on a feature branch, open a PR, never push directly to main
|
- No feature branches, no PRs for solo/agent work.
|
||||||
|
- Exception: if a parallel agent session is active on this repo, use a short-lived
|
||||||
|
`agent/<description>` branch and merge within the same session.
|
||||||
|
|
||||||
### 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,98 +312,29 @@ 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. Always work on a feature branch and open a PR — never push directly to main
|
6. Commit directly to main. Run `task check` before every push. Never create
|
||||||
|
feature branches unless a parallel agent is simultaneously active on this repo.
|
||||||
7. For client projects: never send code or context to cloud APIs — use local models via LiteLLM
|
7. For client projects: never send code or context to cloud APIs — use local models via LiteLLM
|
||||||
|
|
||||||
## Current sprint — gitea-mcp v0.2 patch (2026-05-14)
|
## Current state — v0.2.5 (2026-05-17)
|
||||||
|
|
||||||
### Context
|
All v0.2 work is complete and deployed. No active sprint.
|
||||||
The main v0.2 batch (repo_create, repo_update, repo_mirror_push, repo_delete,
|
|
||||||
repo_tree, repo_topics_update, file_read dir-fix, issue_get, release_create,
|
|
||||||
create_project_from_template) was implemented and pushed directly to main.
|
|
||||||
|
|
||||||
This sprint fixes three remaining gaps found during code review on 2026-05-14.
|
### What shipped
|
||||||
These are blockers for `hyperguild new-project`.
|
|
||||||
|
|
||||||
### Issues to fix (all three in one PR: `fix/v02-patch`)
|
| Tag | PR | Tools / fixes |
|
||||||
|
|-----|----|---------------|
|
||||||
|
| v0.2.2 | #21 | repo_create, repo_update, repo_mirror_push |
|
||||||
|
| v0.2.3 | #22 | repo_tree, repo_topics_update, file_read dir fix |
|
||||||
|
| v0.2.4 | #23 | issue_get, release_create, repo_delete |
|
||||||
|
| v0.2.5 | #26 | repo_update archived+template, create_project_from_template template_name, pr_files_diff loop fix |
|
||||||
|
|
||||||
#### #12 — repo_update: add `archived` and `template` fields
|
Current main: `e31fd3f`. CI green. Deployed via Flux.
|
||||||
**File:** `internal/gitea/repos.go` → `UpdateRepoArgs` struct
|
|
||||||
**File:** `internal/tools/repo_update.go` → input schema + args struct
|
|
||||||
|
|
||||||
Add to `UpdateRepoArgs`:
|
### Next up
|
||||||
```go
|
|
||||||
Archived *bool
|
|
||||||
Template *bool
|
|
||||||
```
|
|
||||||
|
|
||||||
Add to tool input schema:
|
1. **`hyperguild new-project` v1** — primary next target.
|
||||||
```json
|
See brain node `adr-new-project-gitea-first-github-mirror` for full flow spec.
|
||||||
"archived": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Mark repo as archived (read-only). Requires confirm=<repo name>."
|
|
||||||
},
|
|
||||||
"template": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Toggle template repo flag."
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Add confirm-guard for `archived=true` (same pattern as `private=false`):
|
2. **Issue #19** — end-to-end mirror flow verification.
|
||||||
```go
|
`repo_mirror_push` is implemented but the full flow (create repo → add push mirror → verify sync to GitHub) has not been tested manually. Do this before relying on it in production.
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
New test cases to add in `repo_update_test.go`:
|
|
||||||
- `TestRepoUpdateTool_Archive` — happy path with confirm
|
|
||||||
- `TestRepoUpdateTool_ArchiveRequiresConfirm` — missing confirm returns error
|
|
||||||
- `TestRepoUpdateTool_SetTemplate` — no confirm needed
|
|
||||||
|
|
||||||
#### #24 — create_project_from_template: make template selectable
|
|
||||||
**File:** `internal/tools/create_project_from_template.go`
|
|
||||||
|
|
||||||
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"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The tool should use `args.TemplateName` if set, fall back to the hardcoded default.
|
|
||||||
Remove the hardcoded template name from `cmd/gitea-mcp/main.go` constructor call —
|
|
||||||
the tool resolves it internally.
|
|
||||||
|
|
||||||
New test case: `TestCreateProjectFromTemplate_AgentTemplate`
|
|
||||||
|
|
||||||
#### #25 — pr_files_diff: fix same diff returned for all files
|
|
||||||
**File:** `internal/tools/pr_files_diff.go`
|
|
||||||
|
|
||||||
There is a loop bug where all file entries in the response contain the same diff
|
|
||||||
(the first file's diff is reused for every subsequent file). Find the loop and
|
|
||||||
ensure each iteration reads and assigns the correct diff for its own file.
|
|
||||||
|
|
||||||
Reproduce: call `pr_files_diff` on any PR with 3+ files, verify each file has
|
|
||||||
a distinct diff.
|
|
||||||
|
|
||||||
### Definition of done
|
|
||||||
|
|
||||||
- [ ] `task check` passes
|
|
||||||
- [ ] `repo_update` accepts `archived` and `template` params
|
|
||||||
- [ ] `archived=true` requires `confirm=<repo name>`
|
|
||||||
- [ ] `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.
|
|
||||||
|
|||||||
@@ -37,9 +37,11 @@
|
|||||||
|
|
||||||
### Git
|
### Git
|
||||||
- Conventional commits: `feat:`, `fix:`, `chore:`, `docs:`, `refactor:`
|
- Conventional commits: `feat:`, `fix:`, `chore:`, `docs:`, `refactor:`
|
||||||
- Branch naming: `feat/short-description`, `fix/short-description`
|
- **Trunk-Based Development:** commit directly to main. One logical change per commit.
|
||||||
- PRs: one concern per PR, description explains *why* not *what*
|
- Run `task check` locally before every push. CI is the quality gate, not branch protection.
|
||||||
- **Branch protection:** always work on a feature branch, open a PR, never push directly to main
|
- No feature branches, no PRs for solo/agent work.
|
||||||
|
- Exception: if a parallel agent session is active on this repo, use a short-lived
|
||||||
|
`agent/<description>` branch and merge within the same session.
|
||||||
|
|
||||||
### 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
|
||||||
@@ -77,98 +79,29 @@ 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. Always work on a feature branch and open a PR — never push directly to main
|
6. Commit directly to main. Run `task check` before every push. Never create
|
||||||
|
feature branches unless a parallel agent is simultaneously active on this repo.
|
||||||
7. For client projects: never send code or context to cloud APIs — use local models via LiteLLM
|
7. For client projects: never send code or context to cloud APIs — use local models via LiteLLM
|
||||||
|
|
||||||
## Current sprint — gitea-mcp v0.2 patch (2026-05-14)
|
## Current state — v0.2.5 (2026-05-17)
|
||||||
|
|
||||||
### Context
|
All v0.2 work is complete and deployed. No active sprint.
|
||||||
The main v0.2 batch (repo_create, repo_update, repo_mirror_push, repo_delete,
|
|
||||||
repo_tree, repo_topics_update, file_read dir-fix, issue_get, release_create,
|
|
||||||
create_project_from_template) was implemented and pushed directly to main.
|
|
||||||
|
|
||||||
This sprint fixes three remaining gaps found during code review on 2026-05-14.
|
### What shipped
|
||||||
These are blockers for `hyperguild new-project`.
|
|
||||||
|
|
||||||
### Issues to fix (all three in one PR: `fix/v02-patch`)
|
| Tag | PR | Tools / fixes |
|
||||||
|
|-----|----|---------------|
|
||||||
|
| v0.2.2 | #21 | repo_create, repo_update, repo_mirror_push |
|
||||||
|
| v0.2.3 | #22 | repo_tree, repo_topics_update, file_read dir fix |
|
||||||
|
| v0.2.4 | #23 | issue_get, release_create, repo_delete |
|
||||||
|
| v0.2.5 | #26 | repo_update archived+template, create_project_from_template template_name, pr_files_diff loop fix |
|
||||||
|
|
||||||
#### #12 — repo_update: add `archived` and `template` fields
|
Current main: `e31fd3f`. CI green. Deployed via Flux.
|
||||||
**File:** `internal/gitea/repos.go` → `UpdateRepoArgs` struct
|
|
||||||
**File:** `internal/tools/repo_update.go` → input schema + args struct
|
|
||||||
|
|
||||||
Add to `UpdateRepoArgs`:
|
### Next up
|
||||||
```go
|
|
||||||
Archived *bool
|
|
||||||
Template *bool
|
|
||||||
```
|
|
||||||
|
|
||||||
Add to tool input schema:
|
1. **`hyperguild new-project` v1** — primary next target.
|
||||||
```json
|
See brain node `adr-new-project-gitea-first-github-mirror` for full flow spec.
|
||||||
"archived": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Mark repo as archived (read-only). Requires confirm=<repo name>."
|
|
||||||
},
|
|
||||||
"template": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Toggle template repo flag."
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Add confirm-guard for `archived=true` (same pattern as `private=false`):
|
2. **Issue #19** — end-to-end mirror flow verification.
|
||||||
```go
|
`repo_mirror_push` is implemented but the full flow (create repo → add push mirror → verify sync to GitHub) has not been tested manually. Do this before relying on it in production.
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
New test cases to add in `repo_update_test.go`:
|
|
||||||
- `TestRepoUpdateTool_Archive` — happy path with confirm
|
|
||||||
- `TestRepoUpdateTool_ArchiveRequiresConfirm` — missing confirm returns error
|
|
||||||
- `TestRepoUpdateTool_SetTemplate` — no confirm needed
|
|
||||||
|
|
||||||
#### #24 — create_project_from_template: make template selectable
|
|
||||||
**File:** `internal/tools/create_project_from_template.go`
|
|
||||||
|
|
||||||
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"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The tool should use `args.TemplateName` if set, fall back to the hardcoded default.
|
|
||||||
Remove the hardcoded template name from `cmd/gitea-mcp/main.go` constructor call —
|
|
||||||
the tool resolves it internally.
|
|
||||||
|
|
||||||
New test case: `TestCreateProjectFromTemplate_AgentTemplate`
|
|
||||||
|
|
||||||
#### #25 — pr_files_diff: fix same diff returned for all files
|
|
||||||
**File:** `internal/tools/pr_files_diff.go`
|
|
||||||
|
|
||||||
There is a loop bug where all file entries in the response contain the same diff
|
|
||||||
(the first file's diff is reused for every subsequent file). Find the loop and
|
|
||||||
ensure each iteration reads and assigns the correct diff for its own file.
|
|
||||||
|
|
||||||
Reproduce: call `pr_files_diff` on any PR with 3+ files, verify each file has
|
|
||||||
a distinct diff.
|
|
||||||
|
|
||||||
### Definition of done
|
|
||||||
|
|
||||||
- [ ] `task check` passes
|
|
||||||
- [ ] `repo_update` accepts `archived` and `template` params
|
|
||||||
- [ ] `archived=true` requires `confirm=<repo name>`
|
|
||||||
- [ ] `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.
|
|
||||||
|
|||||||
@@ -41,6 +41,18 @@ These rules apply to every task across every project, regardless of harness.
|
|||||||
4. **Goal-driven execution.** Define clear success criteria up front for every task.
|
4. **Goal-driven execution.** Define clear success criteria up front for every task.
|
||||||
Loop — implement, verify, refine — until those criteria are met. Don't claim
|
Loop — implement, verify, refine — until those criteria are met. Don't claim
|
||||||
completion without evidence (tests pass, command output, observed behavior).
|
completion without evidence (tests pass, command output, observed behavior).
|
||||||
|
5. **Trunk-Based Development — commit directly to main.** Every commit is one
|
||||||
|
logical change (one tool, one fix, one test) with passing tests. Main is always
|
||||||
|
deployable. Never create long-lived feature branches.
|
||||||
|
|
||||||
|
**Exception — parallel agents on same repo:** If another agent is known to be
|
||||||
|
actively working on the same repo simultaneously, create a short-lived branch
|
||||||
|
(`agent/<description>`), finish the task, and merge to main within the same
|
||||||
|
session. Do not leave agent branches open between sessions.
|
||||||
|
|
||||||
|
**Exception — external contributor or client four-eyes requirement:** Use
|
||||||
|
PR flow only when a human reviewer outside the project is required. Document
|
||||||
|
the reason in PROJECT.md.
|
||||||
|
|
||||||
## Default stack
|
## Default stack
|
||||||
|
|
||||||
@@ -54,6 +66,7 @@ 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.
|
||||||
|
|
||||||
@@ -63,9 +76,12 @@ Exploratory: Rust, Zig — I'll tell you when I want these.
|
|||||||
- **Errors**: `fmt.Errorf("operation: %w", err)` — never naked, never log-and-return
|
- **Errors**: `fmt.Errorf("operation: %w", err)` — never naked, never log-and-return
|
||||||
- **Naming**: stdlib conventions, no stuttering
|
- **Naming**: stdlib conventions, no stuttering
|
||||||
- **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:`), commit directly to main,
|
||||||
|
one logical change per commit, CI is the quality gate
|
||||||
|
- **Never**: long-lived feature branches, PRs for solo work, direct push without
|
||||||
|
passing `task check` locally first
|
||||||
- **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 are pre-approved; anything else needs justification in the commit message
|
- **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
|
||||||
|
|
||||||
## Infrastructure
|
## Infrastructure
|
||||||
|
|
||||||
@@ -105,18 +121,64 @@ See `~/dev/PROJECT_SUMMARY.md` for detailed descriptions of each project.
|
|||||||
- **koala-ai-stack** (`AGENTS/`) — local AI server infrastructure management
|
- **koala-ai-stack** (`AGENTS/`) — local AI server infrastructure management
|
||||||
- **klimatkollen** (`XT/`) — Swedish municipal climate data platform
|
- **klimatkollen** (`XT/`) — Swedish municipal climate data platform
|
||||||
|
|
||||||
## Knowledge base
|
## Knowledge base — actively use it
|
||||||
|
|
||||||
When available, agents can query the shared knowledge base:
|
A persistent brain (BM25 search + LLM-synthesised Q&A) survives across sessions,
|
||||||
|
hosts, and harnesses. It holds 100+ hard-won entries: infra incident postmortems,
|
||||||
|
Go pitfalls, framework gotchas, design principles, ADRs. **It is not optional
|
||||||
|
reference material — query it actively, not just when explicitly told.**
|
||||||
|
|
||||||
- **MCP**: `mcp://hyperguild.<TAILNET>.ts.net:3100/knowledge`
|
### When to query (treat as a reflex)
|
||||||
- **HTTP**: `http://hyperguild.<TAILNET>.ts.net:3100/api/v1/search`
|
|
||||||
|
|
||||||
<!-- TODO: replace <TAILNET> placeholder with the real Tailscale tailnet
|
- **Before** starting a non-trivial task — search for prior art with the symptom
|
||||||
name once hyperguild is deployed. Until then, agents that try to
|
AND the system component ("how did we solve X in Y?"). 5 seconds beats 5 hours.
|
||||||
reach the knowledge service on a host where it isn't running will
|
- **When debugging** — search for the error string, the stack frame, the affected
|
||||||
get DNS NXDOMAIN, which is the desired fail-loudly behavior. -->
|
service. Past you may have already paid this tax.
|
||||||
- **Scoping**: defaults to `public` collection; client projects filter to `{client}` + `public`
|
- **Before adopting** a pattern, library, framework, or model name — check if it
|
||||||
|
was tried and rejected, or what the integration footguns are.
|
||||||
|
- **When making architectural decisions** — search for the domain + "ADR" or
|
||||||
|
"decision" to find prior reasoning before re-deriving it.
|
||||||
|
- **When a recommendation feels novel** — challenge yourself: "has this been
|
||||||
|
documented?" The brain often has it.
|
||||||
|
|
||||||
|
### When to write
|
||||||
|
|
||||||
|
After you discover something that **future-you would forget** and that **isn't
|
||||||
|
recoverable from the code, git log, or PR description alone**:
|
||||||
|
|
||||||
|
- Bugs whose root cause is non-obvious and generalisable beyond this project.
|
||||||
|
- Framework / library / model-name quirks that bit you and would bite anyone.
|
||||||
|
- Design principles validated under fire (e.g. "every `_get` needs a `_list`").
|
||||||
|
- Postmortems for incidents: what broke, why, how diagnosed, what to do next time.
|
||||||
|
|
||||||
|
DON'T write project status, sprint progress, PR summaries, or "what I did this
|
||||||
|
session" — those rot fast and the originals are in git/gitea anyway. Brain
|
||||||
|
entries that age well are about *why*, *how to avoid*, and *what to do when*.
|
||||||
|
|
||||||
|
### How to access (per harness)
|
||||||
|
|
||||||
|
| Harness | Query | Write |
|
||||||
|
|---------|-------|-------|
|
||||||
|
| **Claude Code, Claude Desktop** | `brain_query` (BM25), `brain_answer` (LLM-synth + sources) MCP tools | `brain_write` MCP tool |
|
||||||
|
| **Crush, Pi, Antigravity, other MCP-capable** | same MCP server: `ingestion-brain` (via the `mcp__*_brain__*` namespace once authenticated) | same |
|
||||||
|
| **Anything HTTP-only (curl, scripts)** | `POST https://brain-mcp.d-ma.be/query` with `{"query":"..."}` (auth via `BRAIN_MCP_TOKEN`) | `POST .../write` with `{"content":"...","filename":"..."}` |
|
||||||
|
| **Browser / human inspection** | `https://gitea.d-ma.be/mathias/hyperguild` → `knowledge/` and `wiki/` markdown files |
|
||||||
|
|
||||||
|
- **Scoping**: defaults to `public` collection; client projects filter to `{client}` + `public`.
|
||||||
|
- **Routing**: brain_answer's LLM uses berget.ai as primary, iguana ollama as
|
||||||
|
fallback. Both are configurable in the `supervisor/ingestion-deployment.yaml`
|
||||||
|
on the koala k3s cluster; don't hardcode local-only model names into the
|
||||||
|
berget URL (see knowledge entry on namespace mismatches).
|
||||||
|
|
||||||
|
### Quick reflex checks
|
||||||
|
|
||||||
|
If you find yourself about to say any of these out loud, you owe yourself a brain query first:
|
||||||
|
|
||||||
|
- "I think the issue might be..."
|
||||||
|
- "Let me try X and see..."
|
||||||
|
- "I'll just write a script to..."
|
||||||
|
- "This is probably a new bug..."
|
||||||
|
- "Has anyone done this before?" — *yes, probably, go check.*
|
||||||
|
|
||||||
## Client work rules
|
## Client work rules
|
||||||
|
|
||||||
@@ -213,9 +275,11 @@ Key skills:
|
|||||||
|
|
||||||
### Git
|
### Git
|
||||||
- Conventional commits: `feat:`, `fix:`, `chore:`, `docs:`, `refactor:`
|
- Conventional commits: `feat:`, `fix:`, `chore:`, `docs:`, `refactor:`
|
||||||
- Branch naming: `feat/short-description`, `fix/short-description`
|
- **Trunk-Based Development:** commit directly to main. One logical change per commit.
|
||||||
- PRs: one concern per PR, description explains *why* not *what*
|
- Run `task check` locally before every push. CI is the quality gate, not branch protection.
|
||||||
- **Branch protection:** always work on a feature branch, open a PR, never push directly to main
|
- No feature branches, no PRs for solo/agent work.
|
||||||
|
- Exception: if a parallel agent session is active on this repo, use a short-lived
|
||||||
|
`agent/<description>` branch and merge within the same session.
|
||||||
|
|
||||||
### 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,100 +317,31 @@ 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. Always work on a feature branch and open a PR — never push directly to main
|
6. Commit directly to main. Run `task check` before every push. Never create
|
||||||
|
feature branches unless a parallel agent is simultaneously active on this repo.
|
||||||
7. For client projects: never send code or context to cloud APIs — use local models via LiteLLM
|
7. For client projects: never send code or context to cloud APIs — use local models via LiteLLM
|
||||||
|
|
||||||
## Current sprint — gitea-mcp v0.2 patch (2026-05-14)
|
## Current state — v0.2.5 (2026-05-17)
|
||||||
|
|
||||||
### Context
|
All v0.2 work is complete and deployed. No active sprint.
|
||||||
The main v0.2 batch (repo_create, repo_update, repo_mirror_push, repo_delete,
|
|
||||||
repo_tree, repo_topics_update, file_read dir-fix, issue_get, release_create,
|
|
||||||
create_project_from_template) was implemented and pushed directly to main.
|
|
||||||
|
|
||||||
This sprint fixes three remaining gaps found during code review on 2026-05-14.
|
### What shipped
|
||||||
These are blockers for `hyperguild new-project`.
|
|
||||||
|
|
||||||
### Issues to fix (all three in one PR: `fix/v02-patch`)
|
| Tag | PR | Tools / fixes |
|
||||||
|
|-----|----|---------------|
|
||||||
|
| v0.2.2 | #21 | repo_create, repo_update, repo_mirror_push |
|
||||||
|
| v0.2.3 | #22 | repo_tree, repo_topics_update, file_read dir fix |
|
||||||
|
| v0.2.4 | #23 | issue_get, release_create, repo_delete |
|
||||||
|
| v0.2.5 | #26 | repo_update archived+template, create_project_from_template template_name, pr_files_diff loop fix |
|
||||||
|
|
||||||
#### #12 — repo_update: add `archived` and `template` fields
|
Current main: `e31fd3f`. CI green. Deployed via Flux.
|
||||||
**File:** `internal/gitea/repos.go` → `UpdateRepoArgs` struct
|
|
||||||
**File:** `internal/tools/repo_update.go` → input schema + args struct
|
|
||||||
|
|
||||||
Add to `UpdateRepoArgs`:
|
### Next up
|
||||||
```go
|
|
||||||
Archived *bool
|
|
||||||
Template *bool
|
|
||||||
```
|
|
||||||
|
|
||||||
Add to tool input schema:
|
1. **`hyperguild new-project` v1** — primary next target.
|
||||||
```json
|
See brain node `adr-new-project-gitea-first-github-mirror` for full flow spec.
|
||||||
"archived": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Mark repo as archived (read-only). Requires confirm=<repo name>."
|
|
||||||
},
|
|
||||||
"template": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Toggle template repo flag."
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Add confirm-guard for `archived=true` (same pattern as `private=false`):
|
2. **Issue #19** — end-to-end mirror flow verification.
|
||||||
```go
|
`repo_mirror_push` is implemented but the full flow (create repo → add push mirror → verify sync to GitHub) has not been tested manually. Do this before relying on it in production.
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
New test cases to add in `repo_update_test.go`:
|
|
||||||
- `TestRepoUpdateTool_Archive` — happy path with confirm
|
|
||||||
- `TestRepoUpdateTool_ArchiveRequiresConfirm` — missing confirm returns error
|
|
||||||
- `TestRepoUpdateTool_SetTemplate` — no confirm needed
|
|
||||||
|
|
||||||
#### #24 — create_project_from_template: make template selectable
|
|
||||||
**File:** `internal/tools/create_project_from_template.go`
|
|
||||||
|
|
||||||
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"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The tool should use `args.TemplateName` if set, fall back to the hardcoded default.
|
|
||||||
Remove the hardcoded template name from `cmd/gitea-mcp/main.go` constructor call —
|
|
||||||
the tool resolves it internally.
|
|
||||||
|
|
||||||
New test case: `TestCreateProjectFromTemplate_AgentTemplate`
|
|
||||||
|
|
||||||
#### #25 — pr_files_diff: fix same diff returned for all files
|
|
||||||
**File:** `internal/tools/pr_files_diff.go`
|
|
||||||
|
|
||||||
There is a loop bug where all file entries in the response contain the same diff
|
|
||||||
(the first file's diff is reused for every subsequent file). Find the loop and
|
|
||||||
ensure each iteration reads and assigns the correct diff for its own file.
|
|
||||||
|
|
||||||
Reproduce: call `pr_files_diff` on any PR with 3+ files, verify each file has
|
|
||||||
a distinct diff.
|
|
||||||
|
|
||||||
### Definition of done
|
|
||||||
|
|
||||||
- [ ] `task check` passes
|
|
||||||
- [ ] `repo_update` accepts `archived` and `template` params
|
|
||||||
- [ ] `archived=true` requires `confirm=<repo name>`
|
|
||||||
- [ ] `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.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
195
.cursorrules
195
.cursorrules
@@ -39,6 +39,18 @@ These rules apply to every task across every project, regardless of harness.
|
|||||||
4. **Goal-driven execution.** Define clear success criteria up front for every task.
|
4. **Goal-driven execution.** Define clear success criteria up front for every task.
|
||||||
Loop — implement, verify, refine — until those criteria are met. Don't claim
|
Loop — implement, verify, refine — until those criteria are met. Don't claim
|
||||||
completion without evidence (tests pass, command output, observed behavior).
|
completion without evidence (tests pass, command output, observed behavior).
|
||||||
|
5. **Trunk-Based Development — commit directly to main.** Every commit is one
|
||||||
|
logical change (one tool, one fix, one test) with passing tests. Main is always
|
||||||
|
deployable. Never create long-lived feature branches.
|
||||||
|
|
||||||
|
**Exception — parallel agents on same repo:** If another agent is known to be
|
||||||
|
actively working on the same repo simultaneously, create a short-lived branch
|
||||||
|
(`agent/<description>`), finish the task, and merge to main within the same
|
||||||
|
session. Do not leave agent branches open between sessions.
|
||||||
|
|
||||||
|
**Exception — external contributor or client four-eyes requirement:** Use
|
||||||
|
PR flow only when a human reviewer outside the project is required. Document
|
||||||
|
the reason in PROJECT.md.
|
||||||
|
|
||||||
## Default stack
|
## Default stack
|
||||||
|
|
||||||
@@ -52,6 +64,7 @@ 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,9 +74,12 @@ Exploratory: Rust, Zig — I'll tell you when I want these.
|
|||||||
- **Errors**: `fmt.Errorf("operation: %w", err)` — never naked, never log-and-return
|
- **Errors**: `fmt.Errorf("operation: %w", err)` — never naked, never log-and-return
|
||||||
- **Naming**: stdlib conventions, no stuttering
|
- **Naming**: stdlib conventions, no stuttering
|
||||||
- **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:`), commit directly to main,
|
||||||
|
one logical change per commit, CI is the quality gate
|
||||||
|
- **Never**: long-lived feature branches, PRs for solo work, direct push without
|
||||||
|
passing `task check` locally first
|
||||||
- **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 are pre-approved; anything else needs justification in the commit message
|
- **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
|
||||||
|
|
||||||
## Infrastructure
|
## Infrastructure
|
||||||
|
|
||||||
@@ -103,18 +119,64 @@ See `~/dev/PROJECT_SUMMARY.md` for detailed descriptions of each project.
|
|||||||
- **koala-ai-stack** (`AGENTS/`) — local AI server infrastructure management
|
- **koala-ai-stack** (`AGENTS/`) — local AI server infrastructure management
|
||||||
- **klimatkollen** (`XT/`) — Swedish municipal climate data platform
|
- **klimatkollen** (`XT/`) — Swedish municipal climate data platform
|
||||||
|
|
||||||
## Knowledge base
|
## Knowledge base — actively use it
|
||||||
|
|
||||||
When available, agents can query the shared knowledge base:
|
A persistent brain (BM25 search + LLM-synthesised Q&A) survives across sessions,
|
||||||
|
hosts, and harnesses. It holds 100+ hard-won entries: infra incident postmortems,
|
||||||
|
Go pitfalls, framework gotchas, design principles, ADRs. **It is not optional
|
||||||
|
reference material — query it actively, not just when explicitly told.**
|
||||||
|
|
||||||
- **MCP**: `mcp://hyperguild.<TAILNET>.ts.net:3100/knowledge`
|
### When to query (treat as a reflex)
|
||||||
- **HTTP**: `http://hyperguild.<TAILNET>.ts.net:3100/api/v1/search`
|
|
||||||
|
|
||||||
<!-- TODO: replace <TAILNET> placeholder with the real Tailscale tailnet
|
- **Before** starting a non-trivial task — search for prior art with the symptom
|
||||||
name once hyperguild is deployed. Until then, agents that try to
|
AND the system component ("how did we solve X in Y?"). 5 seconds beats 5 hours.
|
||||||
reach the knowledge service on a host where it isn't running will
|
- **When debugging** — search for the error string, the stack frame, the affected
|
||||||
get DNS NXDOMAIN, which is the desired fail-loudly behavior. -->
|
service. Past you may have already paid this tax.
|
||||||
- **Scoping**: defaults to `public` collection; client projects filter to `{client}` + `public`
|
- **Before adopting** a pattern, library, framework, or model name — check if it
|
||||||
|
was tried and rejected, or what the integration footguns are.
|
||||||
|
- **When making architectural decisions** — search for the domain + "ADR" or
|
||||||
|
"decision" to find prior reasoning before re-deriving it.
|
||||||
|
- **When a recommendation feels novel** — challenge yourself: "has this been
|
||||||
|
documented?" The brain often has it.
|
||||||
|
|
||||||
|
### When to write
|
||||||
|
|
||||||
|
After you discover something that **future-you would forget** and that **isn't
|
||||||
|
recoverable from the code, git log, or PR description alone**:
|
||||||
|
|
||||||
|
- Bugs whose root cause is non-obvious and generalisable beyond this project.
|
||||||
|
- Framework / library / model-name quirks that bit you and would bite anyone.
|
||||||
|
- Design principles validated under fire (e.g. "every `_get` needs a `_list`").
|
||||||
|
- Postmortems for incidents: what broke, why, how diagnosed, what to do next time.
|
||||||
|
|
||||||
|
DON'T write project status, sprint progress, PR summaries, or "what I did this
|
||||||
|
session" — those rot fast and the originals are in git/gitea anyway. Brain
|
||||||
|
entries that age well are about *why*, *how to avoid*, and *what to do when*.
|
||||||
|
|
||||||
|
### How to access (per harness)
|
||||||
|
|
||||||
|
| Harness | Query | Write |
|
||||||
|
|---------|-------|-------|
|
||||||
|
| **Claude Code, Claude Desktop** | `brain_query` (BM25), `brain_answer` (LLM-synth + sources) MCP tools | `brain_write` MCP tool |
|
||||||
|
| **Crush, Pi, Antigravity, other MCP-capable** | same MCP server: `ingestion-brain` (via the `mcp__*_brain__*` namespace once authenticated) | same |
|
||||||
|
| **Anything HTTP-only (curl, scripts)** | `POST https://brain-mcp.d-ma.be/query` with `{"query":"..."}` (auth via `BRAIN_MCP_TOKEN`) | `POST .../write` with `{"content":"...","filename":"..."}` |
|
||||||
|
| **Browser / human inspection** | `https://gitea.d-ma.be/mathias/hyperguild` → `knowledge/` and `wiki/` markdown files |
|
||||||
|
|
||||||
|
- **Scoping**: defaults to `public` collection; client projects filter to `{client}` + `public`.
|
||||||
|
- **Routing**: brain_answer's LLM uses berget.ai as primary, iguana ollama as
|
||||||
|
fallback. Both are configurable in the `supervisor/ingestion-deployment.yaml`
|
||||||
|
on the koala k3s cluster; don't hardcode local-only model names into the
|
||||||
|
berget URL (see knowledge entry on namespace mismatches).
|
||||||
|
|
||||||
|
### Quick reflex checks
|
||||||
|
|
||||||
|
If you find yourself about to say any of these out loud, you owe yourself a brain query first:
|
||||||
|
|
||||||
|
- "I think the issue might be..."
|
||||||
|
- "Let me try X and see..."
|
||||||
|
- "I'll just write a script to..."
|
||||||
|
- "This is probably a new bug..."
|
||||||
|
- "Has anyone done this before?" — *yes, probably, go check.*
|
||||||
|
|
||||||
## Client work rules
|
## Client work rules
|
||||||
|
|
||||||
@@ -211,9 +273,11 @@ Key skills:
|
|||||||
|
|
||||||
### Git
|
### Git
|
||||||
- Conventional commits: `feat:`, `fix:`, `chore:`, `docs:`, `refactor:`
|
- Conventional commits: `feat:`, `fix:`, `chore:`, `docs:`, `refactor:`
|
||||||
- Branch naming: `feat/short-description`, `fix/short-description`
|
- **Trunk-Based Development:** commit directly to main. One logical change per commit.
|
||||||
- PRs: one concern per PR, description explains *why* not *what*
|
- Run `task check` locally before every push. CI is the quality gate, not branch protection.
|
||||||
- **Branch protection:** always work on a feature branch, open a PR, never push directly to main
|
- No feature branches, no PRs for solo/agent work.
|
||||||
|
- Exception: if a parallel agent session is active on this repo, use a short-lived
|
||||||
|
`agent/<description>` branch and merge within the same session.
|
||||||
|
|
||||||
### 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,98 +315,29 @@ 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. Always work on a feature branch and open a PR — never push directly to main
|
6. Commit directly to main. Run `task check` before every push. Never create
|
||||||
|
feature branches unless a parallel agent is simultaneously active on this repo.
|
||||||
7. For client projects: never send code or context to cloud APIs — use local models via LiteLLM
|
7. For client projects: never send code or context to cloud APIs — use local models via LiteLLM
|
||||||
|
|
||||||
## Current sprint — gitea-mcp v0.2 patch (2026-05-14)
|
## Current state — v0.2.5 (2026-05-17)
|
||||||
|
|
||||||
### Context
|
All v0.2 work is complete and deployed. No active sprint.
|
||||||
The main v0.2 batch (repo_create, repo_update, repo_mirror_push, repo_delete,
|
|
||||||
repo_tree, repo_topics_update, file_read dir-fix, issue_get, release_create,
|
|
||||||
create_project_from_template) was implemented and pushed directly to main.
|
|
||||||
|
|
||||||
This sprint fixes three remaining gaps found during code review on 2026-05-14.
|
### What shipped
|
||||||
These are blockers for `hyperguild new-project`.
|
|
||||||
|
|
||||||
### Issues to fix (all three in one PR: `fix/v02-patch`)
|
| Tag | PR | Tools / fixes |
|
||||||
|
|-----|----|---------------|
|
||||||
|
| v0.2.2 | #21 | repo_create, repo_update, repo_mirror_push |
|
||||||
|
| v0.2.3 | #22 | repo_tree, repo_topics_update, file_read dir fix |
|
||||||
|
| v0.2.4 | #23 | issue_get, release_create, repo_delete |
|
||||||
|
| v0.2.5 | #26 | repo_update archived+template, create_project_from_template template_name, pr_files_diff loop fix |
|
||||||
|
|
||||||
#### #12 — repo_update: add `archived` and `template` fields
|
Current main: `e31fd3f`. CI green. Deployed via Flux.
|
||||||
**File:** `internal/gitea/repos.go` → `UpdateRepoArgs` struct
|
|
||||||
**File:** `internal/tools/repo_update.go` → input schema + args struct
|
|
||||||
|
|
||||||
Add to `UpdateRepoArgs`:
|
### Next up
|
||||||
```go
|
|
||||||
Archived *bool
|
|
||||||
Template *bool
|
|
||||||
```
|
|
||||||
|
|
||||||
Add to tool input schema:
|
1. **`hyperguild new-project` v1** — primary next target.
|
||||||
```json
|
See brain node `adr-new-project-gitea-first-github-mirror` for full flow spec.
|
||||||
"archived": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Mark repo as archived (read-only). Requires confirm=<repo name>."
|
|
||||||
},
|
|
||||||
"template": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Toggle template repo flag."
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Add confirm-guard for `archived=true` (same pattern as `private=false`):
|
2. **Issue #19** — end-to-end mirror flow verification.
|
||||||
```go
|
`repo_mirror_push` is implemented but the full flow (create repo → add push mirror → verify sync to GitHub) has not been tested manually. Do this before relying on it in production.
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
New test cases to add in `repo_update_test.go`:
|
|
||||||
- `TestRepoUpdateTool_Archive` — happy path with confirm
|
|
||||||
- `TestRepoUpdateTool_ArchiveRequiresConfirm` — missing confirm returns error
|
|
||||||
- `TestRepoUpdateTool_SetTemplate` — no confirm needed
|
|
||||||
|
|
||||||
#### #24 — create_project_from_template: make template selectable
|
|
||||||
**File:** `internal/tools/create_project_from_template.go`
|
|
||||||
|
|
||||||
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"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The tool should use `args.TemplateName` if set, fall back to the hardcoded default.
|
|
||||||
Remove the hardcoded template name from `cmd/gitea-mcp/main.go` constructor call —
|
|
||||||
the tool resolves it internally.
|
|
||||||
|
|
||||||
New test case: `TestCreateProjectFromTemplate_AgentTemplate`
|
|
||||||
|
|
||||||
#### #25 — pr_files_diff: fix same diff returned for all files
|
|
||||||
**File:** `internal/tools/pr_files_diff.go`
|
|
||||||
|
|
||||||
There is a loop bug where all file entries in the response contain the same diff
|
|
||||||
(the first file's diff is reused for every subsequent file). Find the loop and
|
|
||||||
ensure each iteration reads and assigns the correct diff for its own file.
|
|
||||||
|
|
||||||
Reproduce: call `pr_files_diff` on any PR with 3+ files, verify each file has
|
|
||||||
a distinct diff.
|
|
||||||
|
|
||||||
### Definition of done
|
|
||||||
|
|
||||||
- [ ] `task check` passes
|
|
||||||
- [ ] `repo_update` accepts `archived` and `template` params
|
|
||||||
- [ ] `archived=true` requires `confirm=<repo name>`
|
|
||||||
- [ ] `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.
|
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
tags: ["v*"]
|
tags: ["v*"]
|
||||||
pull_request:
|
|
||||||
branches: [main]
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
IMAGE: gitea-mcp
|
IMAGE: gitea-mcp
|
||||||
@@ -43,7 +41,6 @@ jobs:
|
|||||||
name: Build & Import
|
name: Build & Import
|
||||||
needs: check
|
needs: check
|
||||||
runs-on: self-hosted
|
runs-on: self-hosted
|
||||||
if: github.event_name != 'pull_request'
|
|
||||||
outputs:
|
outputs:
|
||||||
image-tag: ${{ steps.meta.outputs.sha-tag }}
|
image-tag: ${{ steps.meta.outputs.sha-tag }}
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
5
.githooks/pre-push
Normal file
5
.githooks/pre-push
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
echo "→ Running task check before push..."
|
||||||
|
task check
|
||||||
|
echo "✓ pre-push check passed"
|
||||||
195
AGENTS.md
195
AGENTS.md
@@ -36,6 +36,18 @@ These rules apply to every task across every project, regardless of harness.
|
|||||||
4. **Goal-driven execution.** Define clear success criteria up front for every task.
|
4. **Goal-driven execution.** Define clear success criteria up front for every task.
|
||||||
Loop — implement, verify, refine — until those criteria are met. Don't claim
|
Loop — implement, verify, refine — until those criteria are met. Don't claim
|
||||||
completion without evidence (tests pass, command output, observed behavior).
|
completion without evidence (tests pass, command output, observed behavior).
|
||||||
|
5. **Trunk-Based Development — commit directly to main.** Every commit is one
|
||||||
|
logical change (one tool, one fix, one test) with passing tests. Main is always
|
||||||
|
deployable. Never create long-lived feature branches.
|
||||||
|
|
||||||
|
**Exception — parallel agents on same repo:** If another agent is known to be
|
||||||
|
actively working on the same repo simultaneously, create a short-lived branch
|
||||||
|
(`agent/<description>`), finish the task, and merge to main within the same
|
||||||
|
session. Do not leave agent branches open between sessions.
|
||||||
|
|
||||||
|
**Exception — external contributor or client four-eyes requirement:** Use
|
||||||
|
PR flow only when a human reviewer outside the project is required. Document
|
||||||
|
the reason in PROJECT.md.
|
||||||
|
|
||||||
## Default stack
|
## Default stack
|
||||||
|
|
||||||
@@ -49,6 +61,7 @@ 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.
|
||||||
|
|
||||||
@@ -58,9 +71,12 @@ Exploratory: Rust, Zig — I'll tell you when I want these.
|
|||||||
- **Errors**: `fmt.Errorf("operation: %w", err)` — never naked, never log-and-return
|
- **Errors**: `fmt.Errorf("operation: %w", err)` — never naked, never log-and-return
|
||||||
- **Naming**: stdlib conventions, no stuttering
|
- **Naming**: stdlib conventions, no stuttering
|
||||||
- **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:`), commit directly to main,
|
||||||
|
one logical change per commit, CI is the quality gate
|
||||||
|
- **Never**: long-lived feature branches, PRs for solo work, direct push without
|
||||||
|
passing `task check` locally first
|
||||||
- **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 are pre-approved; anything else needs justification in the commit message
|
- **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
|
||||||
|
|
||||||
## Infrastructure
|
## Infrastructure
|
||||||
|
|
||||||
@@ -100,18 +116,64 @@ See `~/dev/PROJECT_SUMMARY.md` for detailed descriptions of each project.
|
|||||||
- **koala-ai-stack** (`AGENTS/`) — local AI server infrastructure management
|
- **koala-ai-stack** (`AGENTS/`) — local AI server infrastructure management
|
||||||
- **klimatkollen** (`XT/`) — Swedish municipal climate data platform
|
- **klimatkollen** (`XT/`) — Swedish municipal climate data platform
|
||||||
|
|
||||||
## Knowledge base
|
## Knowledge base — actively use it
|
||||||
|
|
||||||
When available, agents can query the shared knowledge base:
|
A persistent brain (BM25 search + LLM-synthesised Q&A) survives across sessions,
|
||||||
|
hosts, and harnesses. It holds 100+ hard-won entries: infra incident postmortems,
|
||||||
|
Go pitfalls, framework gotchas, design principles, ADRs. **It is not optional
|
||||||
|
reference material — query it actively, not just when explicitly told.**
|
||||||
|
|
||||||
- **MCP**: `mcp://hyperguild.<TAILNET>.ts.net:3100/knowledge`
|
### When to query (treat as a reflex)
|
||||||
- **HTTP**: `http://hyperguild.<TAILNET>.ts.net:3100/api/v1/search`
|
|
||||||
|
|
||||||
<!-- TODO: replace <TAILNET> placeholder with the real Tailscale tailnet
|
- **Before** starting a non-trivial task — search for prior art with the symptom
|
||||||
name once hyperguild is deployed. Until then, agents that try to
|
AND the system component ("how did we solve X in Y?"). 5 seconds beats 5 hours.
|
||||||
reach the knowledge service on a host where it isn't running will
|
- **When debugging** — search for the error string, the stack frame, the affected
|
||||||
get DNS NXDOMAIN, which is the desired fail-loudly behavior. -->
|
service. Past you may have already paid this tax.
|
||||||
- **Scoping**: defaults to `public` collection; client projects filter to `{client}` + `public`
|
- **Before adopting** a pattern, library, framework, or model name — check if it
|
||||||
|
was tried and rejected, or what the integration footguns are.
|
||||||
|
- **When making architectural decisions** — search for the domain + "ADR" or
|
||||||
|
"decision" to find prior reasoning before re-deriving it.
|
||||||
|
- **When a recommendation feels novel** — challenge yourself: "has this been
|
||||||
|
documented?" The brain often has it.
|
||||||
|
|
||||||
|
### When to write
|
||||||
|
|
||||||
|
After you discover something that **future-you would forget** and that **isn't
|
||||||
|
recoverable from the code, git log, or PR description alone**:
|
||||||
|
|
||||||
|
- Bugs whose root cause is non-obvious and generalisable beyond this project.
|
||||||
|
- Framework / library / model-name quirks that bit you and would bite anyone.
|
||||||
|
- Design principles validated under fire (e.g. "every `_get` needs a `_list`").
|
||||||
|
- Postmortems for incidents: what broke, why, how diagnosed, what to do next time.
|
||||||
|
|
||||||
|
DON'T write project status, sprint progress, PR summaries, or "what I did this
|
||||||
|
session" — those rot fast and the originals are in git/gitea anyway. Brain
|
||||||
|
entries that age well are about *why*, *how to avoid*, and *what to do when*.
|
||||||
|
|
||||||
|
### How to access (per harness)
|
||||||
|
|
||||||
|
| Harness | Query | Write |
|
||||||
|
|---------|-------|-------|
|
||||||
|
| **Claude Code, Claude Desktop** | `brain_query` (BM25), `brain_answer` (LLM-synth + sources) MCP tools | `brain_write` MCP tool |
|
||||||
|
| **Crush, Pi, Antigravity, other MCP-capable** | same MCP server: `ingestion-brain` (via the `mcp__*_brain__*` namespace once authenticated) | same |
|
||||||
|
| **Anything HTTP-only (curl, scripts)** | `POST https://brain-mcp.d-ma.be/query` with `{"query":"..."}` (auth via `BRAIN_MCP_TOKEN`) | `POST .../write` with `{"content":"...","filename":"..."}` |
|
||||||
|
| **Browser / human inspection** | `https://gitea.d-ma.be/mathias/hyperguild` → `knowledge/` and `wiki/` markdown files |
|
||||||
|
|
||||||
|
- **Scoping**: defaults to `public` collection; client projects filter to `{client}` + `public`.
|
||||||
|
- **Routing**: brain_answer's LLM uses berget.ai as primary, iguana ollama as
|
||||||
|
fallback. Both are configurable in the `supervisor/ingestion-deployment.yaml`
|
||||||
|
on the koala k3s cluster; don't hardcode local-only model names into the
|
||||||
|
berget URL (see knowledge entry on namespace mismatches).
|
||||||
|
|
||||||
|
### Quick reflex checks
|
||||||
|
|
||||||
|
If you find yourself about to say any of these out loud, you owe yourself a brain query first:
|
||||||
|
|
||||||
|
- "I think the issue might be..."
|
||||||
|
- "Let me try X and see..."
|
||||||
|
- "I'll just write a script to..."
|
||||||
|
- "This is probably a new bug..."
|
||||||
|
- "Has anyone done this before?" — *yes, probably, go check.*
|
||||||
|
|
||||||
## Client work rules
|
## Client work rules
|
||||||
|
|
||||||
@@ -208,9 +270,11 @@ Key skills:
|
|||||||
|
|
||||||
### Git
|
### Git
|
||||||
- Conventional commits: `feat:`, `fix:`, `chore:`, `docs:`, `refactor:`
|
- Conventional commits: `feat:`, `fix:`, `chore:`, `docs:`, `refactor:`
|
||||||
- Branch naming: `feat/short-description`, `fix/short-description`
|
- **Trunk-Based Development:** commit directly to main. One logical change per commit.
|
||||||
- PRs: one concern per PR, description explains *why* not *what*
|
- Run `task check` locally before every push. CI is the quality gate, not branch protection.
|
||||||
- **Branch protection:** always work on a feature branch, open a PR, never push directly to main
|
- No feature branches, no PRs for solo/agent work.
|
||||||
|
- Exception: if a parallel agent session is active on this repo, use a short-lived
|
||||||
|
`agent/<description>` branch and merge within the same session.
|
||||||
|
|
||||||
### 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,98 +312,29 @@ 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. Always work on a feature branch and open a PR — never push directly to main
|
6. Commit directly to main. Run `task check` before every push. Never create
|
||||||
|
feature branches unless a parallel agent is simultaneously active on this repo.
|
||||||
7. For client projects: never send code or context to cloud APIs — use local models via LiteLLM
|
7. For client projects: never send code or context to cloud APIs — use local models via LiteLLM
|
||||||
|
|
||||||
## Current sprint — gitea-mcp v0.2 patch (2026-05-14)
|
## Current state — v0.2.5 (2026-05-17)
|
||||||
|
|
||||||
### Context
|
All v0.2 work is complete and deployed. No active sprint.
|
||||||
The main v0.2 batch (repo_create, repo_update, repo_mirror_push, repo_delete,
|
|
||||||
repo_tree, repo_topics_update, file_read dir-fix, issue_get, release_create,
|
|
||||||
create_project_from_template) was implemented and pushed directly to main.
|
|
||||||
|
|
||||||
This sprint fixes three remaining gaps found during code review on 2026-05-14.
|
### What shipped
|
||||||
These are blockers for `hyperguild new-project`.
|
|
||||||
|
|
||||||
### Issues to fix (all three in one PR: `fix/v02-patch`)
|
| Tag | PR | Tools / fixes |
|
||||||
|
|-----|----|---------------|
|
||||||
|
| v0.2.2 | #21 | repo_create, repo_update, repo_mirror_push |
|
||||||
|
| v0.2.3 | #22 | repo_tree, repo_topics_update, file_read dir fix |
|
||||||
|
| v0.2.4 | #23 | issue_get, release_create, repo_delete |
|
||||||
|
| v0.2.5 | #26 | repo_update archived+template, create_project_from_template template_name, pr_files_diff loop fix |
|
||||||
|
|
||||||
#### #12 — repo_update: add `archived` and `template` fields
|
Current main: `e31fd3f`. CI green. Deployed via Flux.
|
||||||
**File:** `internal/gitea/repos.go` → `UpdateRepoArgs` struct
|
|
||||||
**File:** `internal/tools/repo_update.go` → input schema + args struct
|
|
||||||
|
|
||||||
Add to `UpdateRepoArgs`:
|
### Next up
|
||||||
```go
|
|
||||||
Archived *bool
|
|
||||||
Template *bool
|
|
||||||
```
|
|
||||||
|
|
||||||
Add to tool input schema:
|
1. **`hyperguild new-project` v1** — primary next target.
|
||||||
```json
|
See brain node `adr-new-project-gitea-first-github-mirror` for full flow spec.
|
||||||
"archived": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Mark repo as archived (read-only). Requires confirm=<repo name>."
|
|
||||||
},
|
|
||||||
"template": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Toggle template repo flag."
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Add confirm-guard for `archived=true` (same pattern as `private=false`):
|
2. **Issue #19** — end-to-end mirror flow verification.
|
||||||
```go
|
`repo_mirror_push` is implemented but the full flow (create repo → add push mirror → verify sync to GitHub) has not been tested manually. Do this before relying on it in production.
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
New test cases to add in `repo_update_test.go`:
|
|
||||||
- `TestRepoUpdateTool_Archive` — happy path with confirm
|
|
||||||
- `TestRepoUpdateTool_ArchiveRequiresConfirm` — missing confirm returns error
|
|
||||||
- `TestRepoUpdateTool_SetTemplate` — no confirm needed
|
|
||||||
|
|
||||||
#### #24 — create_project_from_template: make template selectable
|
|
||||||
**File:** `internal/tools/create_project_from_template.go`
|
|
||||||
|
|
||||||
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"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The tool should use `args.TemplateName` if set, fall back to the hardcoded default.
|
|
||||||
Remove the hardcoded template name from `cmd/gitea-mcp/main.go` constructor call —
|
|
||||||
the tool resolves it internally.
|
|
||||||
|
|
||||||
New test case: `TestCreateProjectFromTemplate_AgentTemplate`
|
|
||||||
|
|
||||||
#### #25 — pr_files_diff: fix same diff returned for all files
|
|
||||||
**File:** `internal/tools/pr_files_diff.go`
|
|
||||||
|
|
||||||
There is a loop bug where all file entries in the response contain the same diff
|
|
||||||
(the first file's diff is reused for every subsequent file). Find the loop and
|
|
||||||
ensure each iteration reads and assigns the correct diff for its own file.
|
|
||||||
|
|
||||||
Reproduce: call `pr_files_diff` on any PR with 3+ files, verify each file has
|
|
||||||
a distinct diff.
|
|
||||||
|
|
||||||
### Definition of done
|
|
||||||
|
|
||||||
- [ ] `task check` passes
|
|
||||||
- [ ] `repo_update` accepts `archived` and `template` params
|
|
||||||
- [ ] `archived=true` requires `confirm=<repo name>`
|
|
||||||
- [ ] `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.
|
|
||||||
|
|||||||
111
CLAUDE.md
111
CLAUDE.md
@@ -37,9 +37,11 @@
|
|||||||
|
|
||||||
### Git
|
### Git
|
||||||
- Conventional commits: `feat:`, `fix:`, `chore:`, `docs:`, `refactor:`
|
- Conventional commits: `feat:`, `fix:`, `chore:`, `docs:`, `refactor:`
|
||||||
- Branch naming: `feat/short-description`, `fix/short-description`
|
- **Trunk-Based Development:** commit directly to main. One logical change per commit.
|
||||||
- PRs: one concern per PR, description explains *why* not *what*
|
- Run `task check` locally before every push. CI is the quality gate, not branch protection.
|
||||||
- **Branch protection:** always work on a feature branch, open a PR, never push directly to main
|
- No feature branches, no PRs for solo/agent work.
|
||||||
|
- Exception: if a parallel agent session is active on this repo, use a short-lived
|
||||||
|
`agent/<description>` branch and merge within the same session.
|
||||||
|
|
||||||
### 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
|
||||||
@@ -77,98 +79,29 @@ 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. Always work on a feature branch and open a PR — never push directly to main
|
6. Commit directly to main. Run `task check` before every push. Never create
|
||||||
|
feature branches unless a parallel agent is simultaneously active on this repo.
|
||||||
7. For client projects: never send code or context to cloud APIs — use local models via LiteLLM
|
7. For client projects: never send code or context to cloud APIs — use local models via LiteLLM
|
||||||
|
|
||||||
## Current sprint — gitea-mcp v0.2 patch (2026-05-14)
|
## Current state — v0.2.5 (2026-05-17)
|
||||||
|
|
||||||
### Context
|
All v0.2 work is complete and deployed. No active sprint.
|
||||||
The main v0.2 batch (repo_create, repo_update, repo_mirror_push, repo_delete,
|
|
||||||
repo_tree, repo_topics_update, file_read dir-fix, issue_get, release_create,
|
|
||||||
create_project_from_template) was implemented and pushed directly to main.
|
|
||||||
|
|
||||||
This sprint fixes three remaining gaps found during code review on 2026-05-14.
|
### What shipped
|
||||||
These are blockers for `hyperguild new-project`.
|
|
||||||
|
|
||||||
### Issues to fix (all three in one PR: `fix/v02-patch`)
|
| Tag | PR | Tools / fixes |
|
||||||
|
|-----|----|---------------|
|
||||||
|
| v0.2.2 | #21 | repo_create, repo_update, repo_mirror_push |
|
||||||
|
| v0.2.3 | #22 | repo_tree, repo_topics_update, file_read dir fix |
|
||||||
|
| v0.2.4 | #23 | issue_get, release_create, repo_delete |
|
||||||
|
| v0.2.5 | #26 | repo_update archived+template, create_project_from_template template_name, pr_files_diff loop fix |
|
||||||
|
|
||||||
#### #12 — repo_update: add `archived` and `template` fields
|
Current main: `e31fd3f`. CI green. Deployed via Flux.
|
||||||
**File:** `internal/gitea/repos.go` → `UpdateRepoArgs` struct
|
|
||||||
**File:** `internal/tools/repo_update.go` → input schema + args struct
|
|
||||||
|
|
||||||
Add to `UpdateRepoArgs`:
|
### Next up
|
||||||
```go
|
|
||||||
Archived *bool
|
|
||||||
Template *bool
|
|
||||||
```
|
|
||||||
|
|
||||||
Add to tool input schema:
|
1. **`hyperguild new-project` v1** — primary next target.
|
||||||
```json
|
See brain node `adr-new-project-gitea-first-github-mirror` for full flow spec.
|
||||||
"archived": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Mark repo as archived (read-only). Requires confirm=<repo name>."
|
|
||||||
},
|
|
||||||
"template": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Toggle template repo flag."
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Add confirm-guard for `archived=true` (same pattern as `private=false`):
|
2. **Issue #19** — end-to-end mirror flow verification.
|
||||||
```go
|
`repo_mirror_push` is implemented but the full flow (create repo → add push mirror → verify sync to GitHub) has not been tested manually. Do this before relying on it in production.
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
New test cases to add in `repo_update_test.go`:
|
|
||||||
- `TestRepoUpdateTool_Archive` — happy path with confirm
|
|
||||||
- `TestRepoUpdateTool_ArchiveRequiresConfirm` — missing confirm returns error
|
|
||||||
- `TestRepoUpdateTool_SetTemplate` — no confirm needed
|
|
||||||
|
|
||||||
#### #24 — create_project_from_template: make template selectable
|
|
||||||
**File:** `internal/tools/create_project_from_template.go`
|
|
||||||
|
|
||||||
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"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The tool should use `args.TemplateName` if set, fall back to the hardcoded default.
|
|
||||||
Remove the hardcoded template name from `cmd/gitea-mcp/main.go` constructor call —
|
|
||||||
the tool resolves it internally.
|
|
||||||
|
|
||||||
New test case: `TestCreateProjectFromTemplate_AgentTemplate`
|
|
||||||
|
|
||||||
#### #25 — pr_files_diff: fix same diff returned for all files
|
|
||||||
**File:** `internal/tools/pr_files_diff.go`
|
|
||||||
|
|
||||||
There is a loop bug where all file entries in the response contain the same diff
|
|
||||||
(the first file's diff is reused for every subsequent file). Find the loop and
|
|
||||||
ensure each iteration reads and assigns the correct diff for its own file.
|
|
||||||
|
|
||||||
Reproduce: call `pr_files_diff` on any PR with 3+ files, verify each file has
|
|
||||||
a distinct diff.
|
|
||||||
|
|
||||||
### Definition of done
|
|
||||||
|
|
||||||
- [ ] `task check` passes
|
|
||||||
- [ ] `repo_update` accepts `archived` and `template` params
|
|
||||||
- [ ] `archived=true` requires `confirm=<repo name>`
|
|
||||||
- [ ] `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.
|
|
||||||
|
|||||||
11
README.md
11
README.md
@@ -2,3 +2,14 @@
|
|||||||
|
|
||||||
Streamable HTTP MCP service exposing Gitea repo operations to Claude apps.
|
Streamable HTTP MCP service exposing Gitea repo operations to Claude apps.
|
||||||
See `~/dev/AI/infra/docs/superpowers/specs/2026-05-04-gitea-mcp-gitops-workflow-design.md`.
|
See `~/dev/AI/infra/docs/superpowers/specs/2026-05-04-gitea-mcp-gitops-workflow-design.md`.
|
||||||
|
|
||||||
|
## Quickstart
|
||||||
|
|
||||||
|
```bash
|
||||||
|
task setup:hooks # installs .githooks/pre-push — runs task check before every push
|
||||||
|
task check # context sync + lint + test + vet
|
||||||
|
task build # produces bin/gitea-mcp
|
||||||
|
```
|
||||||
|
|
||||||
|
This repo uses Trunk-Based Development. Commit directly to `main`. The pre-push
|
||||||
|
hook enforces the quality gate locally; CI re-runs `task check` on every push.
|
||||||
|
|||||||
@@ -47,6 +47,13 @@ tasks:
|
|||||||
cmds:
|
cmds:
|
||||||
- bash scripts/context-sync.sh
|
- bash scripts/context-sync.sh
|
||||||
|
|
||||||
|
setup:hooks:
|
||||||
|
desc: Install git hooks (.githooks/pre-push)
|
||||||
|
cmds:
|
||||||
|
- git config core.hooksPath .githooks
|
||||||
|
- chmod +x .githooks/pre-push
|
||||||
|
- echo "✓ git hooks installed (pre-push runs task check)"
|
||||||
|
|
||||||
context:sync:claude:
|
context:sync:claude:
|
||||||
cmds: [bash scripts/context-sync.sh claude]
|
cmds: [bash scripts/context-sync.sh claude]
|
||||||
context:sync:agents:
|
context:sync:agents:
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ func main() {
|
|||||||
reg.Register(tools.NewRepoTree(giteaClient, ownerAllow))
|
reg.Register(tools.NewRepoTree(giteaClient, ownerAllow))
|
||||||
reg.Register(tools.NewRepoTopicsUpdate(giteaClient, ownerAllow))
|
reg.Register(tools.NewRepoTopicsUpdate(giteaClient, ownerAllow))
|
||||||
reg.Register(tools.NewIssueGet(giteaClient, ownerAllow))
|
reg.Register(tools.NewIssueGet(giteaClient, ownerAllow))
|
||||||
|
reg.Register(tools.NewIssueClose(giteaClient, ownerAllow))
|
||||||
|
reg.Register(tools.NewIssueReopen(giteaClient, ownerAllow))
|
||||||
reg.Register(tools.NewReleaseCreate(giteaClient, ownerAllow))
|
reg.Register(tools.NewReleaseCreate(giteaClient, ownerAllow))
|
||||||
reg.Register(tools.NewRepoDelete(giteaClient, ownerAllow))
|
reg.Register(tools.NewRepoDelete(giteaClient, ownerAllow))
|
||||||
|
|
||||||
|
|||||||
@@ -72,6 +72,28 @@ func (c *Client) CreateIssue(ctx context.Context, owner, repo string, args Creat
|
|||||||
return &iss, nil
|
return &iss, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetIssueState flips an issue between "open" and "closed" via PATCH.
|
||||||
|
// Gitea uses the same endpoint for both transitions.
|
||||||
|
func (c *Client) SetIssueState(ctx context.Context, owner, repo string, number int, state string) (*Issue, error) {
|
||||||
|
p := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d", owner, repo, number)
|
||||||
|
payload, err := json.Marshal(map[string]string{"state": state})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
body, status, err := c.PatchJSON(ctx, p, payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := MapStatus(status, body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var iss Issue
|
||||||
|
if err := json.Unmarshal(body, &iss); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &iss, nil
|
||||||
|
}
|
||||||
|
|
||||||
type IssueComment struct {
|
type IssueComment struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Body string `json:"body"`
|
Body string `json:"body"`
|
||||||
|
|||||||
56
internal/tools/issue_close.go
Normal file
56
internal/tools/issue_close.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package tools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"gitea.d-ma.be/mathias/gitea-mcp/internal/allowlist"
|
||||||
|
"gitea.d-ma.be/mathias/gitea-mcp/internal/gitea"
|
||||||
|
"gitea.d-ma.be/mathias/gitea-mcp/internal/registry"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IssueClose struct {
|
||||||
|
c *gitea.Client
|
||||||
|
a *allowlist.Allowlist
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIssueClose(c *gitea.Client, a *allowlist.Allowlist) *IssueClose {
|
||||||
|
return &IssueClose{c: c, a: a}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *IssueClose) Descriptor() registry.ToolDescriptor {
|
||||||
|
return registry.ToolDescriptor{
|
||||||
|
Name: "issue_close",
|
||||||
|
Description: "Close an open issue. Reversible via issue_reopen.",
|
||||||
|
InputSchema: json.RawMessage(`{
|
||||||
|
"type":"object",
|
||||||
|
"properties":{
|
||||||
|
"owner":{"type":"string"},
|
||||||
|
"name":{"type":"string"},
|
||||||
|
"number":{"type":"integer","minimum":1}
|
||||||
|
},
|
||||||
|
"required":["owner","name","number"]
|
||||||
|
}`),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type issueCloseArgs struct {
|
||||||
|
Owner string `json:"owner"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Number int `json:"number"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *IssueClose) Call(ctx context.Context, raw json.RawMessage) (json.RawMessage, error) {
|
||||||
|
var args issueCloseArgs
|
||||||
|
if err := parseArgs(raw, &args); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := t.a.Check(args.Owner); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
iss, err := t.c.SetIssueState(ctx, args.Owner, args.Name, args.Number, "closed")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return textOK(iss)
|
||||||
|
}
|
||||||
52
internal/tools/issue_close_test.go
Normal file
52
internal/tools/issue_close_test.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package tools_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gitea.d-ma.be/mathias/gitea-mcp/internal/allowlist"
|
||||||
|
"gitea.d-ma.be/mathias/gitea-mcp/internal/gitea"
|
||||||
|
"gitea.d-ma.be/mathias/gitea-mcp/internal/tools"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIssueCloseTool(t *testing.T) {
|
||||||
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
assert.Equal(t, http.MethodPatch, r.Method)
|
||||||
|
assert.Equal(t, "/api/v1/repos/mathias/infra/issues/26", r.URL.Path)
|
||||||
|
b, _ := io.ReadAll(r.Body)
|
||||||
|
assert.JSONEq(t, `{"state":"closed"}`, string(b))
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_, _ = w.Write([]byte(`{"number":26,"title":"feat: ntfy via NPM","state":"closed","html_url":"http://gitea.example.com/mathias/infra/issues/26"}`))
|
||||||
|
}))
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
tool := tools.NewIssueClose(gitea.NewClient(srv.URL, "tok"), allowlist.New([]string{"mathias"}))
|
||||||
|
out, err := tool.Call(context.Background(), json.RawMessage(`{"owner":"mathias","name":"infra","number":26}`))
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Contains(t, string(out), `"number":26`)
|
||||||
|
assert.Contains(t, string(out), `"state":"closed"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIssueCloseTool_NotFound(t *testing.T) {
|
||||||
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
_, _ = w.Write([]byte(`{"message":"issue not found"}`))
|
||||||
|
}))
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
tool := tools.NewIssueClose(gitea.NewClient(srv.URL, "tok"), allowlist.New([]string{"mathias"}))
|
||||||
|
_, err := tool.Call(context.Background(), json.RawMessage(`{"owner":"mathias","name":"infra","number":999}`))
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIssueCloseAllowlistRejects(t *testing.T) {
|
||||||
|
tool := tools.NewIssueClose(gitea.NewClient("http://unused", ""), allowlist.New([]string{"mathias"}))
|
||||||
|
_, err := tool.Call(context.Background(), json.RawMessage(`{"owner":"evil","name":"x","number":1}`))
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
56
internal/tools/issue_reopen.go
Normal file
56
internal/tools/issue_reopen.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package tools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"gitea.d-ma.be/mathias/gitea-mcp/internal/allowlist"
|
||||||
|
"gitea.d-ma.be/mathias/gitea-mcp/internal/gitea"
|
||||||
|
"gitea.d-ma.be/mathias/gitea-mcp/internal/registry"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IssueReopen struct {
|
||||||
|
c *gitea.Client
|
||||||
|
a *allowlist.Allowlist
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIssueReopen(c *gitea.Client, a *allowlist.Allowlist) *IssueReopen {
|
||||||
|
return &IssueReopen{c: c, a: a}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *IssueReopen) Descriptor() registry.ToolDescriptor {
|
||||||
|
return registry.ToolDescriptor{
|
||||||
|
Name: "issue_reopen",
|
||||||
|
Description: "Reopen a closed issue. Reversible via issue_close.",
|
||||||
|
InputSchema: json.RawMessage(`{
|
||||||
|
"type":"object",
|
||||||
|
"properties":{
|
||||||
|
"owner":{"type":"string"},
|
||||||
|
"name":{"type":"string"},
|
||||||
|
"number":{"type":"integer","minimum":1}
|
||||||
|
},
|
||||||
|
"required":["owner","name","number"]
|
||||||
|
}`),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type issueReopenArgs struct {
|
||||||
|
Owner string `json:"owner"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Number int `json:"number"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *IssueReopen) Call(ctx context.Context, raw json.RawMessage) (json.RawMessage, error) {
|
||||||
|
var args issueReopenArgs
|
||||||
|
if err := parseArgs(raw, &args); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := t.a.Check(args.Owner); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
iss, err := t.c.SetIssueState(ctx, args.Owner, args.Name, args.Number, "open")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return textOK(iss)
|
||||||
|
}
|
||||||
40
internal/tools/issue_reopen_test.go
Normal file
40
internal/tools/issue_reopen_test.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package tools_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gitea.d-ma.be/mathias/gitea-mcp/internal/allowlist"
|
||||||
|
"gitea.d-ma.be/mathias/gitea-mcp/internal/gitea"
|
||||||
|
"gitea.d-ma.be/mathias/gitea-mcp/internal/tools"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIssueReopenTool(t *testing.T) {
|
||||||
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
assert.Equal(t, http.MethodPatch, r.Method)
|
||||||
|
assert.Equal(t, "/api/v1/repos/mathias/infra/issues/26", r.URL.Path)
|
||||||
|
b, _ := io.ReadAll(r.Body)
|
||||||
|
assert.JSONEq(t, `{"state":"open"}`, string(b))
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_, _ = w.Write([]byte(`{"number":26,"title":"feat: ntfy via NPM","state":"open","html_url":"http://gitea.example.com/mathias/infra/issues/26"}`))
|
||||||
|
}))
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
tool := tools.NewIssueReopen(gitea.NewClient(srv.URL, "tok"), allowlist.New([]string{"mathias"}))
|
||||||
|
out, err := tool.Call(context.Background(), json.RawMessage(`{"owner":"mathias","name":"infra","number":26}`))
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Contains(t, string(out), `"number":26`)
|
||||||
|
assert.Contains(t, string(out), `"state":"open"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIssueReopenAllowlistRejects(t *testing.T) {
|
||||||
|
tool := tools.NewIssueReopen(gitea.NewClient("http://unused", ""), allowlist.New([]string{"mathias"}))
|
||||||
|
_, err := tool.Call(context.Background(), json.RawMessage(`{"owner":"evil","name":"x","number":1}`))
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user