Template
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f41c7dbeff | ||
|
|
628a664fda | ||
|
|
cb8fe89991 | ||
|
|
2c4158a808 | ||
|
|
39b8e86540 | ||
|
|
b40db025cc |
@@ -3,6 +3,29 @@
|
|||||||
"knowledge": {
|
"knowledge": {
|
||||||
"url": "http://localhost:3100/mcp",
|
"url": "http://localhost:3100/mcp",
|
||||||
"description": "Project knowledge base — vector + graph retrieval"
|
"description": "Project knowledge base — vector + graph retrieval"
|
||||||
|
},
|
||||||
|
"brain": {
|
||||||
|
"type": "http",
|
||||||
|
"url": "https://koala.tail30472e.ts.net:8443/mcp",
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Bearer ${BRAIN_MCP_TOKEN}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gitea": {
|
||||||
|
"_comment": "gitea-mcp authenticates by PASS-THROUGH: the bearer is a Gitea PAT it validates against Gitea and then acts as. GITEA_MCP_TOKEN -- which this entry used until 2026-09-08 -- 401s against it, so every harness synced from this file had a dead gitea server and no signal saying so. Probed 2026-09-08: GITEA_MCP_TOKEN 401, DMABE_GITEA_API_TOKEN 200, on both the public and the Serve URL.",
|
||||||
|
"type": "http",
|
||||||
|
"url": "https://koala.tail30472e.ts.net:8446/mcp",
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Bearer ${DMABE_GITEA_API_TOKEN}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"infra": {
|
||||||
|
"_comment": "The bearer is belt-and-braces: this endpoint answers 200 with or without it, since Serve already limits reach to the tailnet. Sending it anyway means the day the server starts enforcing is not the day every harness breaks.",
|
||||||
|
"type": "http",
|
||||||
|
"url": "https://koala.tail30472e.ts.net:8444/mcp",
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Bearer ${INFRA_MCP_TOKEN}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,6 +78,27 @@ These rules apply to every task across every project, regardless of harness.
|
|||||||
when a human reviewer outside the project is required. Document the reason in
|
when a human reviewer outside the project is required. Document the reason in
|
||||||
PROJECT.md.
|
PROJECT.md.
|
||||||
|
|
||||||
|
**Exception — a GitOps repo where a branch cannot be reconciled.** In a repo whose
|
||||||
|
deployment watches one branch (`mathias/infra`: Flux watches `main`), a branch is
|
||||||
|
invisible to the thing that applies it. A `risk/medium` change to `alerts.yaml` or
|
||||||
|
`litellm-config.yaml` can be linted on a branch but never *exercised* there, so a
|
||||||
|
PR would buy a review of a state that was never deployed.
|
||||||
|
|
||||||
|
There, `risk/medium`+ work stays on `main` and instead requires a **recorded
|
||||||
|
adversarial review pass before the push** — `skills:pr-gatekeeper` or equivalent,
|
||||||
|
reviewing the staged diff. **The reviewer's verdict goes in the issue, not a PR
|
||||||
|
thread.** No verdict recorded means the lane was not used, and saying "the gate was
|
||||||
|
green" is not the same claim.
|
||||||
|
|
||||||
|
Be honest about what this is: a recorded *independent* pass, not four-eyes in the
|
||||||
|
sense `assessor-loop` §4.1 means for a regulated organisation, because the reviewer
|
||||||
|
is an agent. It is more than the repo had and less than the model it mirrors; do not
|
||||||
|
describe it as the stronger property. (infra#470, decided 2026-09-29.)
|
||||||
|
|
||||||
|
This lane depends on the `risk/` label being right. A label that triggers a heavier
|
||||||
|
lane is load-bearing, not cosmetic — if work that belongs in it is unlabelled, the
|
||||||
|
lane silently does not apply.
|
||||||
|
|
||||||
6. **Close the loop — every substantive task ends with the same ritual.** Shipping
|
6. **Close the loop — every substantive task ends with the same ritual.** Shipping
|
||||||
the code is not the end of the task; capturing it is. Run this unprompted:
|
the code is not the end of the task; capturing it is. Run this unprompted:
|
||||||
- **Tag + bump SemVer** on the change (annotated tag; minor for a feature or
|
- **Tag + bump SemVer** on the change (annotated tag; minor for a feature or
|
||||||
@@ -106,6 +127,43 @@ These rules apply to every task across every project, regardless of harness.
|
|||||||
revisit Y". Those are exactly the items that need a locator. If it is worth
|
revisit Y". Those are exactly the items that need a locator. If it is worth
|
||||||
saying in a summary, it is worth writing where it can be found again without you.
|
saying in a summary, it is worth writing where it can be found again without you.
|
||||||
|
|
||||||
|
7. **Finish what's closable — only involve me for a genuine call.** If a
|
||||||
|
tracker issue, PR, or checklist item is demonstrably resolved — the fix
|
||||||
|
landed, the state changed, you verified it live — close/merge/check it off
|
||||||
|
in the same turn. Don't leave it open "for the record" or park it waiting
|
||||||
|
on me to confirm something that already happened; that's bookkeeping, not
|
||||||
|
a decision. Reserve asking me (`AskUserQuestion`, a `DECISION NEEDED`
|
||||||
|
comment, or otherwise pausing) for when it's genuinely mine to call:
|
||||||
|
irreversible or prod/host-impacting actions, a real judgment call with no
|
||||||
|
clearly-better option, ambiguous intent, or anything secret-writes/
|
||||||
|
destructive-git already gate. Before claiming something is closable,
|
||||||
|
verify it live (re-check the issue, re-run the command) rather than
|
||||||
|
trusting your own earlier summary (brain: `filed-issue-not-same-as-fixed`).
|
||||||
|
|
||||||
|
8. **Control code needs an adversary, not a checklist.** Code whose job is to
|
||||||
|
refuse, deny, gate or contain is not adequately verified by a green gate.
|
||||||
|
Rule 5 makes CI the quality gate and that holds for ordinary work — but a
|
||||||
|
passing suite only asserts what its author already thought of, and for a
|
||||||
|
control the author is precisely the person whose imagination just failed.
|
||||||
|
Measured on `dispatch#75`: five review rounds, five real escapes verified by
|
||||||
|
execution, and **CI was green on every one of them** while `git rm
|
||||||
|
././.dispatch-allow` deleted the file the control existed to protect. So
|
||||||
|
route this work through a review with authority to block, instructed to
|
||||||
|
verify by execution rather than by reading the implementation. Expect to be
|
||||||
|
blocked more than once; that is the control working, not the reviewer being
|
||||||
|
difficult. (brain: `a-green-gate-is-not-evidence-for-control-code`; the
|
||||||
|
practices live in the `pr-gatekeeper` agent.)
|
||||||
|
|
||||||
|
9. **A test's name must not claim more than its assertions prove.** Name a test
|
||||||
|
after the mechanism it observes, not the goal it serves.
|
||||||
|
`DoesNotInheritEnv` is honest; `IsContained` is a claim the test cannot
|
||||||
|
support. This is not pedantry about naming — when name and assertion
|
||||||
|
diverge, the name wins in the reader's head and the gap becomes invisible,
|
||||||
|
which is what lets the defect survive review. Found three times in one PR,
|
||||||
|
twice in tests written to fix the previous instance of it; each passed while
|
||||||
|
the thing it was named for was exploitable. If you cannot name the mechanism,
|
||||||
|
you do not yet know what the test observes.
|
||||||
|
|
||||||
## CAD sprint patterns
|
## CAD sprint patterns
|
||||||
|
|
||||||
Two CAD sprint archetypes (defs: brain `wiki/homelab/decisions/continuous-agentic-development-cad-concept-2026-06-16`):
|
Two CAD sprint archetypes (defs: brain `wiki/homelab/decisions/continuous-agentic-development-cad-concept-2026-06-16`):
|
||||||
@@ -246,10 +304,16 @@ entries that age well are about *why*, *how to avoid*, and *what to do when*.
|
|||||||
|---------|-------|-------|
|
|---------|-------|-------|
|
||||||
| **Claude Code, Claude Desktop** | `brain_query` (BM25), `brain_answer` (LLM-synth + sources) MCP tools | `brain_write` MCP tool |
|
| **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 |
|
| **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":"..."}` |
|
| **Anything HTTP-only (curl, scripts)** | Tailnet only: `POST https://koala.tail30472e.ts.net:8443/query` with `{"query":"..."}`, sending `BRAIN_MCP_TOKEN` (not yet enforced on REST, hyperguild#149). The public `brain-mcp.d-ma.be` serves `/mcp` only (infra#517). | `POST .../write` with `{"content":"...","wing":"...","hall":"..."}` |
|
||||||
| **Browser / human inspection** | `https://git.d-ma.be/mathias/hyperguild` → `knowledge/` and `wiki/` markdown files |
|
| **Browser / human inspection** | `https://git.d-ma.be/mathias/hyperguild` → `knowledge/` and `wiki/` markdown files |
|
||||||
|
|
||||||
- **Scoping**: defaults to `public` collection; client projects filter to `{client}` + `public`.
|
- **Scoping — the brain holds no confidential client content.** Nothing on the read path
|
||||||
|
filters by client: every token holder can read every wing, and `wing` on `brain_query`
|
||||||
|
is an optional argument, not an access control. Client confidentiality is enforced only
|
||||||
|
at write time (`capture`'s I1 gate refuses internal/confidential). So never `brain_write`
|
||||||
|
client material; generalise it first or keep it in the client's own repo. (hyperguild#144,
|
||||||
|
decided 2026-10-01. This line previously claimed client projects were filtered; they
|
||||||
|
were not.)
|
||||||
- **Routing**: brain_answer's LLM uses berget.ai as primary, iguana ollama as
|
- **Routing**: brain_answer's LLM uses berget.ai as primary, iguana ollama as
|
||||||
fallback. Both are configurable in the `supervisor/ingestion-deployment.yaml`
|
fallback. Both are configurable in the `supervisor/ingestion-deployment.yaml`
|
||||||
on the koala k3s cluster; don't hardcode local-only model names into the
|
on the koala k3s cluster; don't hardcode local-only model names into the
|
||||||
@@ -319,8 +383,20 @@ Workflow:
|
|||||||
and missing-untracked adapters). A drift fails the check with a
|
and missing-untracked adapters). A drift fails the check with a
|
||||||
message telling you to stage the regenerated files.
|
message telling you to stage the regenerated files.
|
||||||
|
|
||||||
Behavior rules in this file and per-project rules in `PROJECT.md` apply
|
Behavior rules in this file and per-project rules in `PROJECT.md` apply on every
|
||||||
unconditionally on every host, every harness.
|
host and every harness.
|
||||||
|
|
||||||
|
**Where a project rule contradicts a rule here, the project rule governs — and it
|
||||||
|
must state its reason.** Both halves matter: the project file wins because it knows
|
||||||
|
something this file cannot (infra's Flux constraint is real and not visible from
|
||||||
|
here), and it must say why because a bare override is indistinguishable from drift.
|
||||||
|
|
||||||
|
This replaces the previous wording, which said rules here and in `PROJECT.md` both
|
||||||
|
apply *"unconditionally"*. That was not a tiebreak but a contradiction, and it went
|
||||||
|
unnoticed until rule 5 and infra's "never create feature branches" had to be obeyed
|
||||||
|
at the same time (infra#470). A standard can hold two individually sensible clauses
|
||||||
|
that are jointly unsatisfiable, and survive for as long as everyone reads it
|
||||||
|
charitably — the same failure the Inception Sprint Oath's G6 had.
|
||||||
|
|
||||||
## Engineering Skills
|
## Engineering Skills
|
||||||
|
|
||||||
|
|||||||
+17
-2
@@ -34,8 +34,16 @@ jobs:
|
|||||||
|
|
||||||
check:
|
check:
|
||||||
name: Lint / Test / Vet
|
name: Lint / Test / Vet
|
||||||
|
# Deliberately NOT gated on the guard (#12). build and deploy are, because
|
||||||
|
# they genuinely cannot work here -- placeholder module path, no image name,
|
||||||
|
# no namespace. But linting and testing the template is meaningful, and
|
||||||
|
# gating it meant this repo had no working gate at all: #8 sat here with CI
|
||||||
|
# green throughout, and was found by a child rather than by us.
|
||||||
|
#
|
||||||
|
# `task check` includes scripts/tests/generated-child_test.sh, which covers
|
||||||
|
# the entrypoint that ./... cannot see -- ungating without that would run a
|
||||||
|
# gate that still skips the package every child is built around.
|
||||||
needs: guard
|
needs: guard
|
||||||
if: needs.guard.outputs.is_template != 'true'
|
|
||||||
runs-on: self-hosted
|
runs-on: self-hosted
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -48,7 +56,14 @@ jobs:
|
|||||||
- name: Install toolchain
|
- name: Install toolchain
|
||||||
run: |
|
run: |
|
||||||
go version
|
go version
|
||||||
go install github.com/a-h/templ/cmd/templ@latest
|
# Pin the GENERATOR to the same version as the templ LIBRARY in
|
||||||
|
# go.mod. Installing @latest let them drift: the generator emitted
|
||||||
|
# calls (templruntime.WriteString) that the pinned library did not
|
||||||
|
# have, which is template-go-web#8. Nothing caught it because the
|
||||||
|
# guard skipped `check` in this repo entirely (#12).
|
||||||
|
templ_version="$(go list -m -f '{{.Version}}' github.com/a-h/templ)"
|
||||||
|
echo "templ library is $templ_version — installing the matching generator"
|
||||||
|
go install "github.com/a-h/templ/cmd/templ@$templ_version"
|
||||||
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh \
|
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh \
|
||||||
| sh -s -- -b "$(go env GOPATH)/bin" v2.11.4
|
| sh -s -- -b "$(go env GOPATH)/bin" v2.11.4
|
||||||
|
|
||||||
|
|||||||
+4
-1
@@ -27,4 +27,7 @@ go.work.sum
|
|||||||
|
|
||||||
# Project-specific
|
# Project-specific
|
||||||
bin/
|
bin/
|
||||||
*.templ.go
|
# templ writes <name>_templ.go -- this was '*.templ.go' (a dot, not an
|
||||||
|
# underscore) and therefore matched nothing, so generated files were only ever
|
||||||
|
# kept out of git by luck. template-go-web#8.
|
||||||
|
*_templ.go
|
||||||
|
|||||||
@@ -73,6 +73,27 @@ These rules apply to every task across every project, regardless of harness.
|
|||||||
when a human reviewer outside the project is required. Document the reason in
|
when a human reviewer outside the project is required. Document the reason in
|
||||||
PROJECT.md.
|
PROJECT.md.
|
||||||
|
|
||||||
|
**Exception — a GitOps repo where a branch cannot be reconciled.** In a repo whose
|
||||||
|
deployment watches one branch (`mathias/infra`: Flux watches `main`), a branch is
|
||||||
|
invisible to the thing that applies it. A `risk/medium` change to `alerts.yaml` or
|
||||||
|
`litellm-config.yaml` can be linted on a branch but never *exercised* there, so a
|
||||||
|
PR would buy a review of a state that was never deployed.
|
||||||
|
|
||||||
|
There, `risk/medium`+ work stays on `main` and instead requires a **recorded
|
||||||
|
adversarial review pass before the push** — `skills:pr-gatekeeper` or equivalent,
|
||||||
|
reviewing the staged diff. **The reviewer's verdict goes in the issue, not a PR
|
||||||
|
thread.** No verdict recorded means the lane was not used, and saying "the gate was
|
||||||
|
green" is not the same claim.
|
||||||
|
|
||||||
|
Be honest about what this is: a recorded *independent* pass, not four-eyes in the
|
||||||
|
sense `assessor-loop` §4.1 means for a regulated organisation, because the reviewer
|
||||||
|
is an agent. It is more than the repo had and less than the model it mirrors; do not
|
||||||
|
describe it as the stronger property. (infra#470, decided 2026-09-29.)
|
||||||
|
|
||||||
|
This lane depends on the `risk/` label being right. A label that triggers a heavier
|
||||||
|
lane is load-bearing, not cosmetic — if work that belongs in it is unlabelled, the
|
||||||
|
lane silently does not apply.
|
||||||
|
|
||||||
6. **Close the loop — every substantive task ends with the same ritual.** Shipping
|
6. **Close the loop — every substantive task ends with the same ritual.** Shipping
|
||||||
the code is not the end of the task; capturing it is. Run this unprompted:
|
the code is not the end of the task; capturing it is. Run this unprompted:
|
||||||
- **Tag + bump SemVer** on the change (annotated tag; minor for a feature or
|
- **Tag + bump SemVer** on the change (annotated tag; minor for a feature or
|
||||||
@@ -101,6 +122,43 @@ These rules apply to every task across every project, regardless of harness.
|
|||||||
revisit Y". Those are exactly the items that need a locator. If it is worth
|
revisit Y". Those are exactly the items that need a locator. If it is worth
|
||||||
saying in a summary, it is worth writing where it can be found again without you.
|
saying in a summary, it is worth writing where it can be found again without you.
|
||||||
|
|
||||||
|
7. **Finish what's closable — only involve me for a genuine call.** If a
|
||||||
|
tracker issue, PR, or checklist item is demonstrably resolved — the fix
|
||||||
|
landed, the state changed, you verified it live — close/merge/check it off
|
||||||
|
in the same turn. Don't leave it open "for the record" or park it waiting
|
||||||
|
on me to confirm something that already happened; that's bookkeeping, not
|
||||||
|
a decision. Reserve asking me (`AskUserQuestion`, a `DECISION NEEDED`
|
||||||
|
comment, or otherwise pausing) for when it's genuinely mine to call:
|
||||||
|
irreversible or prod/host-impacting actions, a real judgment call with no
|
||||||
|
clearly-better option, ambiguous intent, or anything secret-writes/
|
||||||
|
destructive-git already gate. Before claiming something is closable,
|
||||||
|
verify it live (re-check the issue, re-run the command) rather than
|
||||||
|
trusting your own earlier summary (brain: `filed-issue-not-same-as-fixed`).
|
||||||
|
|
||||||
|
8. **Control code needs an adversary, not a checklist.** Code whose job is to
|
||||||
|
refuse, deny, gate or contain is not adequately verified by a green gate.
|
||||||
|
Rule 5 makes CI the quality gate and that holds for ordinary work — but a
|
||||||
|
passing suite only asserts what its author already thought of, and for a
|
||||||
|
control the author is precisely the person whose imagination just failed.
|
||||||
|
Measured on `dispatch#75`: five review rounds, five real escapes verified by
|
||||||
|
execution, and **CI was green on every one of them** while `git rm
|
||||||
|
././.dispatch-allow` deleted the file the control existed to protect. So
|
||||||
|
route this work through a review with authority to block, instructed to
|
||||||
|
verify by execution rather than by reading the implementation. Expect to be
|
||||||
|
blocked more than once; that is the control working, not the reviewer being
|
||||||
|
difficult. (brain: `a-green-gate-is-not-evidence-for-control-code`; the
|
||||||
|
practices live in the `pr-gatekeeper` agent.)
|
||||||
|
|
||||||
|
9. **A test's name must not claim more than its assertions prove.** Name a test
|
||||||
|
after the mechanism it observes, not the goal it serves.
|
||||||
|
`DoesNotInheritEnv` is honest; `IsContained` is a claim the test cannot
|
||||||
|
support. This is not pedantry about naming — when name and assertion
|
||||||
|
diverge, the name wins in the reader's head and the gap becomes invisible,
|
||||||
|
which is what lets the defect survive review. Found three times in one PR,
|
||||||
|
twice in tests written to fix the previous instance of it; each passed while
|
||||||
|
the thing it was named for was exploitable. If you cannot name the mechanism,
|
||||||
|
you do not yet know what the test observes.
|
||||||
|
|
||||||
## CAD sprint patterns
|
## CAD sprint patterns
|
||||||
|
|
||||||
Two CAD sprint archetypes (defs: brain `wiki/homelab/decisions/continuous-agentic-development-cad-concept-2026-06-16`):
|
Two CAD sprint archetypes (defs: brain `wiki/homelab/decisions/continuous-agentic-development-cad-concept-2026-06-16`):
|
||||||
@@ -241,10 +299,16 @@ entries that age well are about *why*, *how to avoid*, and *what to do when*.
|
|||||||
|---------|-------|-------|
|
|---------|-------|-------|
|
||||||
| **Claude Code, Claude Desktop** | `brain_query` (BM25), `brain_answer` (LLM-synth + sources) MCP tools | `brain_write` MCP tool |
|
| **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 |
|
| **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":"..."}` |
|
| **Anything HTTP-only (curl, scripts)** | Tailnet only: `POST https://koala.tail30472e.ts.net:8443/query` with `{"query":"..."}`, sending `BRAIN_MCP_TOKEN` (not yet enforced on REST, hyperguild#149). The public `brain-mcp.d-ma.be` serves `/mcp` only (infra#517). | `POST .../write` with `{"content":"...","wing":"...","hall":"..."}` |
|
||||||
| **Browser / human inspection** | `https://git.d-ma.be/mathias/hyperguild` → `knowledge/` and `wiki/` markdown files |
|
| **Browser / human inspection** | `https://git.d-ma.be/mathias/hyperguild` → `knowledge/` and `wiki/` markdown files |
|
||||||
|
|
||||||
- **Scoping**: defaults to `public` collection; client projects filter to `{client}` + `public`.
|
- **Scoping — the brain holds no confidential client content.** Nothing on the read path
|
||||||
|
filters by client: every token holder can read every wing, and `wing` on `brain_query`
|
||||||
|
is an optional argument, not an access control. Client confidentiality is enforced only
|
||||||
|
at write time (`capture`'s I1 gate refuses internal/confidential). So never `brain_write`
|
||||||
|
client material; generalise it first or keep it in the client's own repo. (hyperguild#144,
|
||||||
|
decided 2026-10-01. This line previously claimed client projects were filtered; they
|
||||||
|
were not.)
|
||||||
- **Routing**: brain_answer's LLM uses berget.ai as primary, iguana ollama as
|
- **Routing**: brain_answer's LLM uses berget.ai as primary, iguana ollama as
|
||||||
fallback. Both are configurable in the `supervisor/ingestion-deployment.yaml`
|
fallback. Both are configurable in the `supervisor/ingestion-deployment.yaml`
|
||||||
on the koala k3s cluster; don't hardcode local-only model names into the
|
on the koala k3s cluster; don't hardcode local-only model names into the
|
||||||
@@ -314,8 +378,20 @@ Workflow:
|
|||||||
and missing-untracked adapters). A drift fails the check with a
|
and missing-untracked adapters). A drift fails the check with a
|
||||||
message telling you to stage the regenerated files.
|
message telling you to stage the regenerated files.
|
||||||
|
|
||||||
Behavior rules in this file and per-project rules in `PROJECT.md` apply
|
Behavior rules in this file and per-project rules in `PROJECT.md` apply on every
|
||||||
unconditionally on every host, every harness.
|
host and every harness.
|
||||||
|
|
||||||
|
**Where a project rule contradicts a rule here, the project rule governs — and it
|
||||||
|
must state its reason.** Both halves matter: the project file wins because it knows
|
||||||
|
something this file cannot (infra's Flux constraint is real and not visible from
|
||||||
|
here), and it must say why because a bare override is indistinguishable from drift.
|
||||||
|
|
||||||
|
This replaces the previous wording, which said rules here and in `PROJECT.md` both
|
||||||
|
apply *"unconditionally"*. That was not a tiebreak but a contradiction, and it went
|
||||||
|
unnoticed until rule 5 and infra's "never create feature branches" had to be obeyed
|
||||||
|
at the same time (infra#470). A standard can hold two individually sensible clauses
|
||||||
|
that are jointly unsatisfiable, and survive for as long as everyone reads it
|
||||||
|
charitably — the same failure the Inception Sprint Oath's G6 had.
|
||||||
|
|
||||||
## Engineering Skills
|
## Engineering Skills
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ tasks:
|
|||||||
# The template guard decides whether CI runs at all in every child repo,
|
# The template guard decides whether CI runs at all in every child repo,
|
||||||
# so its regression test belongs in the same gate as the Go tests.
|
# so its regression test belongs in the same gate as the Go tests.
|
||||||
- bash scripts/tests/is-template_test.sh
|
- bash scripts/tests/is-template_test.sh
|
||||||
|
# The three Go commands above cannot see cmd/__PROJECT_NAME__ -- Go's
|
||||||
|
# ./... expansion skips directories whose names start with an underscore.
|
||||||
|
# This gates the substituted result, which is what a child actually runs.
|
||||||
|
- bash scripts/tests/generated-child_test.sh
|
||||||
|
|
||||||
context:sync:
|
context:sync:
|
||||||
desc: Regenerate all harness-specific context files
|
desc: Regenerate all harness-specific context files
|
||||||
|
|||||||
@@ -2,6 +2,4 @@ module __MODULE_PATH__
|
|||||||
|
|
||||||
go 1.26
|
go 1.26
|
||||||
|
|
||||||
require (
|
require github.com/a-h/templ v0.3.1020
|
||||||
github.com/a-h/templ v0.2.778
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
github.com/a-h/templ v0.3.1020 h1:ypAT/L5ySWEnZ6Zft/5yfoWXYYkhFNvEFOeeqecg4tw=
|
||||||
|
github.com/a-h/templ v0.3.1020/go.mod h1:A2DlK61v+K+NRoGnhmYbNYVmtYHcFO5/AisMvBdDxTM=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
@@ -58,7 +58,7 @@ if [ "$FORCE" = false ]; then
|
|||||||
while IFS= read -r _match; do
|
while IFS= read -r _match; do
|
||||||
_lineno="${_match%%:*}"
|
_lineno="${_match%%:*}"
|
||||||
_content="${_match#*:}"
|
_content="${_match#*:}"
|
||||||
_token=$(printf '%s' "$_content" | grep -o '{{[^}]*}}' | head -1)
|
_token=$(head -1 < <(grep -o '{{[^}]*}}' <<<"$_content"))
|
||||||
echo " $PROJECT_FILE:$_lineno: unfilled placeholder $_token" >&2
|
echo " $PROJECT_FILE:$_lineno: unfilled placeholder $_token" >&2
|
||||||
done <<< "$_placeholders"
|
done <<< "$_placeholders"
|
||||||
echo "" >&2
|
echo "" >&2
|
||||||
|
|||||||
Executable
+114
@@ -0,0 +1,114 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Does a repo generated from this template pass its own gate?
|
||||||
|
#
|
||||||
|
# WHY THIS EXISTS
|
||||||
|
#
|
||||||
|
# `golangci-lint run ./...` and friends cannot see this template's entrypoint.
|
||||||
|
# Go skips directories whose names begin with "_" or "." when expanding package
|
||||||
|
# patterns, and the entrypoint lives in cmd/__PROJECT_NAME__:
|
||||||
|
#
|
||||||
|
# $ go list ./...
|
||||||
|
# __MODULE_PATH__/internal/web <- no cmd package
|
||||||
|
#
|
||||||
|
# After substitution the directory has a real name, becomes visible, and any
|
||||||
|
# defect in it appears — in the child, not here. That is how template-go-agent
|
||||||
|
# shipped a broken entrypoint for three months while its own CI stayed green
|
||||||
|
# (template-go-agent#1, #2).
|
||||||
|
#
|
||||||
|
# Linting the directory by name is not a fix: `golangci-lint run
|
||||||
|
# ./cmd/__PROJECT_NAME__` works, but reports formatter findings that do not
|
||||||
|
# exist in a generated repo, because gofumpt's import grouping depends on the
|
||||||
|
# module path and this one is a placeholder.
|
||||||
|
#
|
||||||
|
# So this substitutes the tracked tree into a throwaway child and gates the
|
||||||
|
# RESULT, which is the only artifact anyone actually runs.
|
||||||
|
#
|
||||||
|
# Usage: ./scripts/tests/generated-child_test.sh
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
|
||||||
|
pass=0; fail=0
|
||||||
|
ok() { printf ' ok %s\n' "$1"; pass=$((pass+1)); }
|
||||||
|
bad() { printf ' FAIL %s\n %s\n' "$1" "${2:-}"; fail=$((fail+1)); }
|
||||||
|
|
||||||
|
echo "generated-child tests"
|
||||||
|
|
||||||
|
# A missing tool is a FAILURE, not a skip. An `exit 0` with zero assertions is
|
||||||
|
# indistinguishable from a real pass to anything reading the exit code -- which
|
||||||
|
# is the whole failure mode this harness exists to close.
|
||||||
|
for tool in golangci-lint templ; do
|
||||||
|
if ! command -v "$tool" >/dev/null 2>&1; then
|
||||||
|
bad "$tool is available" "not on PATH — the generated child cannot be gated"
|
||||||
|
echo; echo "$pass passed, $fail failed"; exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
tmp="$(mktemp -d)"; trap 'rm -rf "$tmp"' EXIT
|
||||||
|
|
||||||
|
# Tracked files from the WORKING TREE, not `git archive HEAD`: this runs inside
|
||||||
|
# `task check`, so it must gate what is about to be committed rather than the
|
||||||
|
# previous commit. `git ls-files` keeps the file set honest while taking
|
||||||
|
# current content.
|
||||||
|
( cd "$ROOT" && git ls-files -z ) | while IFS= read -r -d '' f; do
|
||||||
|
mkdir -p "$tmp/$(dirname "$f")"
|
||||||
|
cp "$ROOT/$f" "$tmp/$f"
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ! -d "$tmp/cmd/__PROJECT_NAME__" ]; then
|
||||||
|
bad "the template still has a placeholder entrypoint directory" \
|
||||||
|
"cmd/__PROJECT_NAME__ not found — has the layout changed?"
|
||||||
|
echo; echo "$pass passed, $fail failed"; exit 1
|
||||||
|
fi
|
||||||
|
mv "$tmp/cmd/__PROJECT_NAME__" "$tmp/cmd/childproj"
|
||||||
|
find "$tmp" -type f \( -name '*.go' -o -name '*.templ' -o -name '*.mod' -o -name '*.yml' -o -name '*.md' -o -name 'Dockerfile' \) \
|
||||||
|
-exec sed -i \
|
||||||
|
-e 's|__MODULE_PATH__|git.d-ma.be/mathias/childproj|g' \
|
||||||
|
-e 's|__PROJECT_NAME__|childproj|g' {} +
|
||||||
|
|
||||||
|
# Generated files are gitignored, so the child arrives without them — exactly as
|
||||||
|
# a freshly scaffolded repo does. Generating here also proves the pinned
|
||||||
|
# generator and the pinned library agree, which is template-go-web#8.
|
||||||
|
out="$(cd "$tmp" && templ generate 2>&1)"; rc=$?
|
||||||
|
if [ "$rc" -eq 0 ]; then
|
||||||
|
ok "templ generate succeeds in the generated child"
|
||||||
|
else
|
||||||
|
bad "templ generate succeeds in the generated child" "exit $rc: $(printf '%s' "$out" | tail -2)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The whole point: the entrypoint must be visible to Go tooling in the child.
|
||||||
|
# If this regresses, everything below it passes for the wrong reason.
|
||||||
|
if ( cd "$tmp" && go list ./... 2>/dev/null ) | grep -q '/cmd/childproj$'; then
|
||||||
|
ok "the generated child's entrypoint is visible to go list"
|
||||||
|
else
|
||||||
|
bad "the generated child's entrypoint is visible to go list" \
|
||||||
|
"cmd package missing from ./... — the child would be unchecked too"
|
||||||
|
fi
|
||||||
|
|
||||||
|
out="$(cd "$tmp" && go build ./... 2>&1)"
|
||||||
|
[ -z "$out" ] && ok "the generated child builds" \
|
||||||
|
|| bad "the generated child builds" "$(printf '%s' "$out" | head -3)"
|
||||||
|
|
||||||
|
out="$(cd "$tmp" && go vet ./... 2>&1)"
|
||||||
|
[ -z "$out" ] && ok "the generated child passes go vet" \
|
||||||
|
|| bad "the generated child passes go vet" "$(printf '%s' "$out" | head -3)"
|
||||||
|
|
||||||
|
# Graded on the EXIT CODE, never on grepping the output. golangci-lint exits
|
||||||
|
# 0 clean / 1 issues found / >1 the linter itself failed. Matching its
|
||||||
|
# human-readable summary instead scores a CRASHED linter as a pass -- bad
|
||||||
|
# config, unwritable TMPDIR, OOM, or simply a future change to its output
|
||||||
|
# format all produce non-matching text. Since this harness is the only gate
|
||||||
|
# covering the entrypoint, that would leave cmd/__PROJECT_NAME__ unchecked with
|
||||||
|
# CI green, which is precisely the defect class this file exists to end.
|
||||||
|
out="$(cd "$tmp" && golangci-lint run ./... 2>&1)"; rc=$?
|
||||||
|
case "$rc" in
|
||||||
|
0) ok "the generated child passes golangci-lint" ;;
|
||||||
|
1) bad "the generated child passes golangci-lint" \
|
||||||
|
"$(printf '%s' "$out" | grep -E '\.go:[0-9]+' | head -3)" ;;
|
||||||
|
*) bad "golangci-lint ran successfully" \
|
||||||
|
"linter exited $rc — the child is UNCHECKED, not clean: $(printf '%s' "$out" | tail -2)" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "$pass passed, $fail failed"
|
||||||
|
[ "$fail" -eq 0 ]
|
||||||
Reference in New Issue
Block a user