From 78fa0bafb40132de381e32ce2d64a9b3092f4bc0 Mon Sep 17 00:00:00 2001 From: TGPSKI Date: Sun, 9 Aug 2026 02:01:24 -0700 Subject: [PATCH] Generate the agent work-item issue template; resolve the primary instruction surface Closes #1 and #2. - `.github/ISSUE_TEMPLATE/agent-work-item.md` for this repository, Context line `SKILL.md`. - Phase 3 Step 7 generates the same template for target repos from the new `assets/templates/agent-work-item.md`. The Context line is the router when the run generates one, otherwise the scan's primary instruction surface. - `contextctl scan` reports `instruction_surfaces.primary_surface`, resolved by precedence: Context Router, root SKILL.md, root AGENTS.md, root harness alias. Covered by TestScanPrimarySurface; golden inventories re-pinned for the new field. - Phase 2 shows the file set before writes; Phase 4 verifies the template's Context line; the lifecycle triage table repoints a stale one. - Both golden examples carry the generated template. Compliance and drift fixes found while reviewing: - Skill `name` is now `directed-contexts`, matching its directory as the Agent Skills spec requires; `parent:` updated in every phase file. - Phase 3 steps carry explicit **Generate** markers (directed-workflows Inspect-Decide-Generate). - SKILL.md claimed Phase 4 generates route-case fixtures; Phase 3 Step 6 does, and Phase 4 generates nothing. - CONTRIBUTING dropped a warmth opener and a closing aphorism (words-are-cheap). --- .github/ISSUE_TEMPLATE/agent-work-item.md | 12 ++++ CHANGELOG.md | 11 +++- CONTRIBUTING.md | 9 ++- README.md | 2 +- SKILL.md | 11 ++-- assets/templates/AGENTS.md | 2 +- assets/templates/agent-work-item.md | 12 ++++ examples/abductive-triage/README.md | 6 +- .../.github/ISSUE_TEMPLATE/agent-work-item.md | 12 ++++ examples/abductive-triage/inventory.json | 1 + examples/security-context-spec/README.md | 2 +- .../.github/ISSUE_TEMPLATE/agent-work-item.md | 12 ++++ examples/security-context-spec/inventory.json | 1 + references/adoption-existing-agents.md | 2 +- references/lifecycle.md | 3 +- references/phase-01-coordinate-check.md | 7 ++- references/phase-02-boundary-model.md | 14 ++++- references/phase-03-generate.md | 62 +++++++++++++++---- references/phase-04-validate.md | 15 ++++- scripts/contextctl.go | 26 ++++++++ scripts/contextctl_test.go | 55 ++++++++++++++++ 21 files changed, 243 insertions(+), 34 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/agent-work-item.md create mode 100644 assets/templates/agent-work-item.md create mode 100644 examples/abductive-triage/expected/.github/ISSUE_TEMPLATE/agent-work-item.md create mode 100644 examples/security-context-spec/expected/.github/ISSUE_TEMPLATE/agent-work-item.md diff --git a/.github/ISSUE_TEMPLATE/agent-work-item.md b/.github/ISSUE_TEMPLATE/agent-work-item.md new file mode 100644 index 0000000..7654154 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/agent-work-item.md @@ -0,0 +1,12 @@ +--- +name: Agent work item +about: Minimal issue body for agent-consumable units of work +title: "" +labels: agent-work +--- + +## Context +- SKILL.md + +## Work +- [ ] diff --git a/CHANGELOG.md b/CHANGELOG.md index 314d895..dbfeac2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - `PATTERN.md`: the Directed Contexts pattern specification — vocabulary, invariants, context kinds, router contract, ownership matching, route cases, and instruction-file classification. -- `SKILL.md`: the `generate-directed-contexts` skill router with operating +- `SKILL.md`: the `directed-contexts` skill router with operating modes for bootstrap, adopt, recover, audit, and decline. - `references/`: phase modules for coordinate check, boundary model, generation, validation, adoption of existing instruction files, and @@ -18,8 +18,15 @@ format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - `scripts/contextctl.go`: stdlib-only Go tool with `scan` (deterministic JSON inventory), `check` (Markdown contract validation), `routes` (route-case evaluation), and `drift` (codebase/context-set divergence). + `scan` reports `instruction_surfaces.primary_surface` — the root file an + agent reads first, resolved by precedence: Context Router, root `SKILL.md`, + root `AGENTS.md`, root harness alias. - `assets/templates/`: skeletons for the root router, context index, domain - context, policy context, and the target-repo lifecycle skill. + context, policy context, the target-repo lifecycle skill, and the agent + work-item issue template. +- `.github/ISSUE_TEMPLATE/agent-work-item.md`, and Phase 3 Step 7 generating + the same template for target repos with the Context line resolved from the + scan. - Golden examples for [abductive-triage](https://github.com/TGPSKI/abductive-triage) (semantic ownership) and diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2264431..01e3145 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,5 @@ # Contributing -Thanks for your interest in Directed Contexts. - ## Ground rules - **The pattern is the product.** `PATTERN.md` is canonical. Changes to the @@ -44,6 +42,7 @@ A new golden example needs: ## Reporting issues -Use GitHub issues. For validation bugs, include the offending Markdown and -the `contextctl` output. For boundary-modeling discussion, a route case that -routes wrongly is worth a thousand words. +Use GitHub issues; `agent-work-item` is the template for agent-consumable +work. For validation bugs, include the offending Markdown and the `contextctl` +output. For boundary-modeling discussion, include a route case that routes +wrongly. diff --git a/README.md b/README.md index 9c9b544..a849503 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ candidates. | [SKILL.md](SKILL.md) | The generator skill router | | [references/](references/) | Phase modules: coordinate check, boundary model, generate, validate, adoption, lifecycle | | [scripts/contextctl.go](scripts/contextctl.go) | Stdlib-only Go tool: `scan`, `check`, `routes`, `drift` | -| [assets/templates/](assets/templates/) | Skeletons for the router, domain/policy contexts, index, and lifecycle skill | +| [assets/templates/](assets/templates/) | Skeletons for the router, domain/policy contexts, index, lifecycle skill, and issue template | | [examples/abductive-triage/](examples/abductive-triage/) | Golden example: semantic ownership in an all-Markdown repo | | [examples/security-context-spec/](examples/security-context-spec/) | Golden example: mixed spec/tooling/workflow ownership | diff --git a/SKILL.md b/SKILL.md index ae6372c..7754e4a 100644 --- a/SKILL.md +++ b/SKILL.md @@ -1,5 +1,5 @@ --- -name: generate-directed-contexts +name: directed-contexts description: "Generate bounded codebase ownership contexts that agents can load inline or spawn in isolation. Analyzes a repository, proposes ownership boundaries, and produces a root AGENTS.md router plus .subagents/ context modules with executable validation. Use when asked to create directed contexts, split a large AGENTS.md, set up subagent contexts, add an agent routing table, or audit an existing context set." license: GPL-3.0 compatibility: "Requires Go toolchain (1.22+) for the contextctl scan/check/routes/drift tool." @@ -69,7 +69,7 @@ Inspect the scan output's `instruction_surfaces` to select an operating mode. | Router and contexts exist | **Audit** — run `contextctl check` + `drift`, then `references/lifecycle.md` | | Native `.claude/agents/` or `.github/agents/` exist | Preserve them; classify personas vs. ownership contexts per `PATTERN.md`, then continue detected mode | | Nested `AGENTS.md` files exist | Decide retain-vs-consolidate per `references/adoption-existing-agents.md`, then continue | -| Repository too small for meaningful partitioning | **Decline** — recommend one concise `AGENTS.md`; do not manufacture contexts | +| Repository too small for meaningful partitioning | **Decline** — recommend one concise `AGENTS.md`; do not manufacture contexts. The Phase 3 Step 7 issue template still applies | **Too small** means any two of: fewer than ~30 source files, one package root, one entry point, one audience. Say so plainly and stop. @@ -91,8 +91,8 @@ one entry point, one audience. Say so plainly and stop. | ----- | ---------------------------------------- | -------------------------------------------------- | ------------------------ | | 1 | `references/phase-01-coordinate-check.md` | Verify repo coordinates before modeling anything | Nothing | | 2 | `references/phase-02-boundary-model.md` | Propose ownership boundaries with weighted evidence | Proposal table only | -| 3 | `references/phase-03-generate.md` | Generate contexts, index, router, lifecycle skill | The context set | -| 4 | `references/phase-04-validate.md` | Mechanical checks and route-case validation | Route-case fixtures | +| 3 | `references/phase-03-generate.md` | Generate contexts, index, router, lifecycle skill, route cases, issue template | The context set | +| 4 | `references/phase-04-validate.md` | Mechanical checks and route-case validation | Nothing | | — | `references/adoption-existing-agents.md` | Rules for existing AGENTS.md / nested files / personas | — | | — | `references/lifecycle.md` | Audit, drift, split/merge maintenance loop | — | @@ -108,7 +108,7 @@ Each phase inherits from its predecessors — never re-ask for these: | --------------------------- | -------------- | | Repo root and package roots | Phase 1 | | Source vs. generated/vendor classification | Phase 1 | -| Instruction-surface inventory | Phase 1 | +| Instruction-surface inventory, including the primary surface | Phase 1 | | Confirmed boundary table | Phase 2 | | Context names and owned paths | Phase 2 | | Generated file list | Phase 3 | @@ -122,6 +122,7 @@ Each phase inherits from its predecessors — never re-ask for these: | `assets/templates/domain-context.md` | Primary domain context skeleton | | `assets/templates/policy-context.md` | Policy overlay skeleton | | `assets/templates/maintain-directed-contexts/SKILL.md` | Lifecycle skill for the target repo | +| `assets/templates/agent-work-item.md` | Target-repo issue template | After generation, always run: diff --git a/assets/templates/AGENTS.md b/assets/templates/AGENTS.md index a904f2e..afae935 100644 --- a/assets/templates/AGENTS.md +++ b/assets/templates/AGENTS.md @@ -1,6 +1,6 @@ # {REPO_NAME} — Agent Instructions - {ONE_PARAGRAPH_REPO_DESCRIPTION} diff --git a/assets/templates/agent-work-item.md b/assets/templates/agent-work-item.md new file mode 100644 index 0000000..4ef6548 --- /dev/null +++ b/assets/templates/agent-work-item.md @@ -0,0 +1,12 @@ +--- +name: Agent work item +about: Minimal issue body for agent-consumable units of work +title: "" +labels: agent-work +--- + +## Context +- {PRIMARY_INSTRUCTION_SURFACE} + +## Work +- [ ] diff --git a/examples/abductive-triage/README.md b/examples/abductive-triage/README.md index 2c85516..8a53d93 100644 --- a/examples/abductive-triage/README.md +++ b/examples/abductive-triage/README.md @@ -24,7 +24,11 @@ skill, not by any domain. - `source.json` — pinned source commit metadata - `inventory.json` — deterministic `contextctl scan` snapshot at that commit -- `expected/` — the complete generated context set +- `expected/` — the complete generated context set, issue template included + +The issue template's Context line is `AGENTS.md`, not the source repo's +pre-generation primary surface (`SKILL.md`): the router this run generates +outranks it. See `references/phase-03-generate.md`, Step 7. ## Validate diff --git a/examples/abductive-triage/expected/.github/ISSUE_TEMPLATE/agent-work-item.md b/examples/abductive-triage/expected/.github/ISSUE_TEMPLATE/agent-work-item.md new file mode 100644 index 0000000..9b237d4 --- /dev/null +++ b/examples/abductive-triage/expected/.github/ISSUE_TEMPLATE/agent-work-item.md @@ -0,0 +1,12 @@ +--- +name: Agent work item +about: Minimal issue body for agent-consumable units of work +title: "" +labels: agent-work +--- + +## Context +- AGENTS.md + +## Work +- [ ] diff --git a/examples/abductive-triage/inventory.json b/examples/abductive-triage/inventory.json index a9bb5d1..fd6c844 100644 --- a/examples/abductive-triage/inventory.json +++ b/examples/abductive-triage/inventory.json @@ -7,6 +7,7 @@ "languages": [], "entry_points": [], "instruction_surfaces": { + "primary_surface": "SKILL.md", "root_agents_md": true, "has_routing_table": false, "nested_agents_md": [], diff --git a/examples/security-context-spec/README.md b/examples/security-context-spec/README.md index 4dd314d..35076b5 100644 --- a/examples/security-context-spec/README.md +++ b/examples/security-context-spec/README.md @@ -26,7 +26,7 @@ keep new files inside automatically owned. - `source.json` — pinned source commit metadata - `inventory.json` — deterministic `contextctl scan` snapshot at that commit -- `expected/` — the complete generated context set +- `expected/` — the complete generated context set, issue template included ## Validate diff --git a/examples/security-context-spec/expected/.github/ISSUE_TEMPLATE/agent-work-item.md b/examples/security-context-spec/expected/.github/ISSUE_TEMPLATE/agent-work-item.md new file mode 100644 index 0000000..9b237d4 --- /dev/null +++ b/examples/security-context-spec/expected/.github/ISSUE_TEMPLATE/agent-work-item.md @@ -0,0 +1,12 @@ +--- +name: Agent work item +about: Minimal issue body for agent-consumable units of work +title: "" +labels: agent-work +--- + +## Context +- AGENTS.md + +## Work +- [ ] diff --git a/examples/security-context-spec/inventory.json b/examples/security-context-spec/inventory.json index 0f1678e..e2540f9 100644 --- a/examples/security-context-spec/inventory.json +++ b/examples/security-context-spec/inventory.json @@ -25,6 +25,7 @@ "tools/validate" ], "instruction_surfaces": { + "primary_surface": "AGENTS.md", "root_agents_md": true, "has_routing_table": false, "nested_agents_md": [], diff --git a/references/adoption-existing-agents.md b/references/adoption-existing-agents.md index 3011069..d65551b 100644 --- a/references/adoption-existing-agents.md +++ b/references/adoption-existing-agents.md @@ -1,7 +1,7 @@ --- name: adoption-existing-agents description: "Rules for adopting a repository that already has instruction files: monolithic AGENTS.md, nested AGENTS.md hierarchies, CLAUDE.md aliases, native personas, and skills." -parent: generate-directed-contexts +parent: directed-contexts metadata: author: TGPSKI version: "0.1" diff --git a/references/lifecycle.md b/references/lifecycle.md index f972d31..127d909 100644 --- a/references/lifecycle.md +++ b/references/lifecycle.md @@ -1,7 +1,7 @@ --- name: lifecycle description: "Audit and maintenance loop for an existing context set: drift detection, split/merge decisions, review-date hygiene." -parent: generate-directed-contexts +parent: directed-contexts metadata: author: TGPSKI version: "0.1" @@ -40,6 +40,7 @@ go run scripts/contextctl.go drift --repo /path/to/target/repo | Stale review date | Re-read the guide against the code; fix or confirm, then re-date | | Verification command missing | Replace with a command that exists; never leave aspirational commands | | New entry point / trust surface | Check whether an overlay's activation table covers it | +| Issue-template Context line points at a moved or deleted file | Repoint it at the current primary instruction surface | | Context above split threshold | Step 3 | | Context below merge threshold | Step 4 | diff --git a/references/phase-01-coordinate-check.md b/references/phase-01-coordinate-check.md index 5585e9e..943e158 100644 --- a/references/phase-01-coordinate-check.md +++ b/references/phase-01-coordinate-check.md @@ -1,7 +1,7 @@ --- name: phase-01-coordinate-check description: "Verify the repository's coordinate system — root, package layout, source classification, instruction surfaces — before any boundary modeling." -parent: generate-directed-contexts +parent: directed-contexts metadata: author: TGPSKI version: "0.1" @@ -32,7 +32,9 @@ The scan establishes, without asking the user: `pyproject.toml`, `Cargo.toml`, `pom.xml`, `Makefile`, …); - languages and manifests; - source versus generated/vendor/build trees; -- existing `AGENTS.md`, `CLAUDE.md`, `CODEX.md`, skills, and native agents; +- existing `AGENTS.md`, `CLAUDE.md`, `CODEX.md`, skills, and native agents, + and which of them is the repository's primary instruction surface + (`instruction_surfaces.primary_surface`); - CODEOWNERS and ownership documentation; - entry points, binaries, APIs, and deployment units; - build, test, lint, and validation commands (from Makefile targets and @@ -95,6 +97,7 @@ A short coordinate summary the user confirms, carried forward to Phase 2: - Package roots: {list} - Source trees: {list} | Generated: {list} | Vendored: {list} - Instruction surfaces: {list, with router/context/skill/persona classification} +- Primary instruction surface: {path, or "none"} - Declared ownership: {CODEOWNERS summary or "none"} - Build/test commands: {list} - Unresolved questions: {none, or the questions asked above} diff --git a/references/phase-02-boundary-model.md b/references/phase-02-boundary-model.md index 4f090fb..c2421e1 100644 --- a/references/phase-02-boundary-model.md +++ b/references/phase-02-boundary-model.md @@ -1,7 +1,7 @@ --- name: phase-02-boundary-model description: "Construct candidate ownership domains from weighted evidence, resolve ambiguous boundaries with competing hypotheses, and produce a confirmed boundary table." -parent: generate-directed-contexts +parent: directed-contexts metadata: author: TGPSKI version: "0.1" @@ -91,6 +91,18 @@ Rules the table must satisfy: - Every source path from Phase 1 is either owned or deliberately left unowned with a stated reason. +Present the file set the table implies alongside it, so the user sees every +path Phase 3 will write before confirming: + +| File | What it is | +| --------------------------------------------- | --------------------------------------- | +| `AGENTS.md` | Router; replaces or absorbs any existing root file | +| `.subagents/README.md` | Context index | +| `.subagents/AGENTS-{DOMAIN}.md` | One per row of the table above | +| `.subagents/route-cases.json` | Route fixtures | +| `.agents/skills/maintain-directed-contexts/SKILL.md` | Lifecycle skill, if accepted | +| `.github/ISSUE_TEMPLATE/agent-work-item.md` | Issue template, unless the repo is not on GitHub | + ## Step 5: Confirm **Decide**: Present the table. Ask the user **only** about medium- or diff --git a/references/phase-03-generate.md b/references/phase-03-generate.md index 2076ca5..cbd0243 100644 --- a/references/phase-03-generate.md +++ b/references/phase-03-generate.md @@ -1,7 +1,7 @@ --- name: phase-03-generate -description: "Generate the directed context files, index, root router, optional lifecycle skill, and route-case fixtures from the confirmed boundary table." -parent: generate-directed-contexts +description: "Generate the directed context files, index, root router, optional lifecycle skill, route-case fixtures, and agent work-item issue template from the confirmed boundary table." +parent: directed-contexts metadata: author: TGPSKI version: "0.1" @@ -16,7 +16,7 @@ existing instructions (see `references/adoption-existing-agents.md`). **Carry forward from prior phases**: confirmed boundary table, context names, kinds, boundary definitions, owned path globs, adjacencies, build/test -commands, source classification. +commands, source classification, primary instruction surface. ## Generation order @@ -28,11 +28,12 @@ speculative files: 3. Root `AGENTS.md` routing table 4. Optional lifecycle skill (`.agents/skills/maintain-directed-contexts/`) 5. Route-case fixtures (`.subagents/route-cases.json`) +6. Agent work-item issue template (`.github/ISSUE_TEMPLATE/agent-work-item.md`) ## Step 1: Domain contexts -Use `assets/templates/domain-context.md` as the skeleton. Every primary -context contains, in order: +**Generate** from `assets/templates/domain-context.md` as the skeleton. Every +primary context contains, in order: 1. Frontmatter: `name`, `kind: domain`, `description` (the one-sentence boundary definition) @@ -63,8 +64,8 @@ Content rules: ## Step 2: Policy contexts -Use `assets/templates/policy-context.md`. Policy contexts replace owned paths -with: +**Generate** from `assets/templates/policy-context.md`. Policy contexts replace +owned paths with: - `## Policy surface` — what the overlay governs - `## Affected contexts` — links to the primary contexts it applies to @@ -77,13 +78,15 @@ semantic ownership. An overlay never claims exclusive paths. ## Step 3: Index -Use `assets/templates/subagents-README.md`. One row per context: name, kind, -one-sentence description, link. The index and the router must agree exactly. +**Generate** from `assets/templates/subagents-README.md`. One row per context: +name, kind, one-sentence description, link. The index and the router must agree +exactly. ## Step 4: Root router -Use `assets/templates/AGENTS.md`. The router keeps universal rules and a thin -routing surface — it never authors domain content itself. It must contain: +**Generate** from `assets/templates/AGENTS.md`. The router keeps universal rules +and a thin routing surface — it never authors domain content itself. It must +contain: 1. The six-step routing procedure (verbatim from `PATTERN.md`) 2. Universal rules (build hygiene, commit conventions — whatever genuinely @@ -113,7 +116,7 @@ placeholders (repo name, context list, drift thresholds). ## Step 6: Route-case fixtures -Write `.subagents/route-cases.json`: an array of route cases (format in +**Generate** `.subagents/route-cases.json`: an array of route cases (format in `PATTERN.md`). Cover at minimum: - one narrow single-context case per primary context; @@ -124,6 +127,40 @@ Write `.subagents/route-cases.json`: an array of route cases (format in - one same-file collision case (one writer, others review); - one inline-versus-spawn contrast pair. +## Step 7: Agent work-item issue template + +The template gives every issue opened against the repo an entry point: the +instruction file an agent reads before touching anything. + +**Inspect** the Phase 1 scan's `instruction_surfaces.primary_surface`. The +scanner resolves it by precedence: the root Context Router, then a root +`SKILL.md` (skill repositories), then a router-less root `AGENTS.md`, then the +first root harness alias. Resolve the Context line against the repository as +it will stand **after** this run — a router the run generates outranks +whatever the scan found. + +| Status | Context line | +| ---------------------------------------------------------- | ------------------ | +| This run generates the router (bootstrap, adopt, recover) | `AGENTS.md` | +| No router results (decline mode); `primary_surface` set | `primary_surface` | +| No router and no `primary_surface` | `AGENTS.md` | + +**Generate** `.github/ISSUE_TEMPLATE/agent-work-item.md` from +`assets/templates/agent-work-item.md`, substituting +`{PRIMARY_INSTRUCTION_SURFACE}`. The body stays a skeleton: one Context bullet, +one unchecked Work box. Do not pre-fill task text, and do not add sections the +repository has no process for. + +The template is generator output, not part of the validated contract in +`PATTERN.md` — `contextctl check` never reads `.github/`. + +| Status | Action | +| ----------------------------------------------- | --------------------------------------------------- | +| No `.github/ISSUE_TEMPLATE/` directory | Create it with this one template | +| Templates exist, none named `agent-work-item.md`| Add this one alongside them | +| `agent-work-item.md` exists | Show the diff; replace only on user confirmation | +| Repository is not on GitHub | Skip; note the skip in the PR checkpoint | + ## PR Checkpoint **Title**: `Add directed contexts: router + {N} domain contexts + {M} overlays` @@ -134,6 +171,7 @@ Write `.subagents/route-cases.json`: an array of route cases (format in - `.subagents/AGENTS-{...}.md` (every generated context) - `.subagents/route-cases.json` - `.agents/skills/maintain-directed-contexts/SKILL.md` (if accepted) +- `.github/ISSUE_TEMPLATE/agent-work-item.md` (unless skipped) No unresolved `{placeholder}` tokens may remain in any generated file. diff --git a/references/phase-04-validate.md b/references/phase-04-validate.md index 6c76424..a16a2c3 100644 --- a/references/phase-04-validate.md +++ b/references/phase-04-validate.md @@ -1,7 +1,7 @@ --- name: phase-04-validate description: "Run mechanical validation and route-case validation over a generated context set; fix findings before the PR checkpoint." -parent: generate-directed-contexts +parent: directed-contexts metadata: author: TGPSKI version: "0.1" @@ -45,6 +45,19 @@ go run scripts/contextctl.go check --repo /path/to/target/repo | Ownership overlap | Return to the Phase 2 table — the model is wrong, not the file | | Path doesn't exist | Codebase wins: fix the glob or drop the claim | +`check` does not read `.github/`. Verify the Phase 3 Step 7 template by hand: + +```bash +cat .github/ISSUE_TEMPLATE/agent-work-item.md +``` + +| Status | Action | +| ----------------------------------------------------- | --------------------------------------------------- | +| Context bullet is `AGENTS.md`, and the router exists | Continue | +| Context bullet names a file that does not exist | Fix it; a template pointing at nothing sends agents nowhere | +| Context bullet is not the router | Correct unless this set has no router — then it must be the Phase 1 `primary_surface` | +| File missing and the repo is on GitHub | Phase 3 skipped Step 7; generate it | + ## Step 2: Route validation ```bash diff --git a/scripts/contextctl.go b/scripts/contextctl.go index 0e1a491..3c3d64f 100644 --- a/scripts/contextctl.go +++ b/scripts/contextctl.go @@ -94,6 +94,7 @@ type PackageRoot struct { } type Instructions struct { + PrimarySurface string `json:"primary_surface"` RootAgentsMD bool `json:"root_agents_md"` HasRoutingTable bool `json:"has_routing_table"` NestedAgentsMD []string `json:"nested_agents_md"` @@ -301,6 +302,31 @@ func scanInstructionSurfaces(root string, inv *Inventory) { sort.Strings(ins.Subagents) sort.Strings(ins.NativeAgents) sort.Strings(ins.Skills) + ins.PrimarySurface = primaryInstructionSurface(ins) +} + +// primaryInstructionSurface names the root file an agent should read first, by +// precedence: the Context Router, then a root SKILL.md (skill repositories), +// then a router-less root AGENTS.md, then the first root harness alias. Empty +// when the repository has no root instruction surface. +func primaryInstructionSurface(ins *Instructions) string { + if ins.RootAgentsMD && ins.HasRoutingTable { + return "AGENTS.md" + } + for _, s := range ins.Skills { + if s == "SKILL.md" { + return "SKILL.md" + } + } + if ins.RootAgentsMD { + return "AGENTS.md" + } + for _, a := range ins.HarnessAliases { + if !strings.Contains(a, "/") { + return a + } + } + return "" } func scanCodeowners(root string, inv *Inventory) { diff --git a/scripts/contextctl_test.go b/scripts/contextctl_test.go index c0efcb3..3213e0f 100644 --- a/scripts/contextctl_test.go +++ b/scripts/contextctl_test.go @@ -365,6 +365,61 @@ func TestScanConflictingInstructions(t *testing.T) { if !reflect.DeepEqual(ins.NestedAgentsMD, []string{"pkg/AGENTS.md"}) { t.Fatalf("nested: %v", ins.NestedAgentsMD) } + if ins.PrimarySurface != "AGENTS.md" { + t.Fatalf("primary surface: %q", ins.PrimarySurface) + } +} + +func TestScanPrimarySurface(t *testing.T) { + cases := []struct { + name string + files map[string]string + want string + }{ + { + name: "no instruction surface", + files: map[string]string{"go.mod": "module example.com/none\n"}, + want: "", + }, + { + name: "harness alias only", + files: map[string]string{"CLAUDE.md": "# Rules\n", "docs/CLAUDE.md": "# Local\n"}, + want: "CLAUDE.md", + }, + { + name: "skill repository", + files: map[string]string{"SKILL.md": "# Skill\n", "AGENTS.md": "# Rules\n"}, + want: "SKILL.md", + }, + { + name: "nested skills do not count", + files: map[string]string{"skills/gen/SKILL.md": "# Skill\n", "AGENTS.md": "# Rules\n"}, + want: "AGENTS.md", + }, + { + name: "router wins over root skill", + files: map[string]string{ + "SKILL.md": "# Skill\n", + "AGENTS.md": "| You're working on… | Load this context | Owns |\n", + }, + want: "AGENTS.md", + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + root := t.TempDir() + for rel, body := range tc.files { + writeFile(t, root, rel, body) + } + inv, err := scanRepo(root) + if err != nil { + t.Fatal(err) + } + if got := inv.Instructions.PrimarySurface; got != tc.want { + t.Fatalf("primary surface: got %q, want %q", got, tc.want) + } + }) + } } func TestScanSymlinkEscape(t *testing.T) {