diff --git a/INDEX.md b/INDEX.md index d644e55..80fee3f 100644 --- a/INDEX.md +++ b/INDEX.md @@ -14,7 +14,7 @@ follow the cross-pointers in their index or take the next matching seeded domain | [databases](wiki/databases/index.md) | **seeded** | Choosing a datastore/database type for a workload (relational vs document vs vector vs graph), designing schemas/tables/keys, choosing or evaluating indexes, writing or optimizing queries, choosing transaction/isolation behavior, surveying live data to derive a rule, verifying additive migrations | | [backend](wiki/backend/index.md) | **seeded** | Server-side application code — language-agnostic (`common/`: API contracts, call-site enumeration before a contract change, idempotency, JWT, timeouts/retries, caching, jobs, transactions in app code, shared state/pools, errors, consuming LLM APIs (completion validation, context budgeting), authoring agent-facing artifacts (binding instruction text, agent tool-surface granularity/parity), MAPE-aligned point-prediction calibration, consuming external-API responses, externally-owned defaults, object-storage references, sync-vs-async integration choice, WebSocket/SSE connection lifecycle) plus stack subtrees: `java/` (JPA, Spring proxies, JVM threads/memory), `node/` (event loop, promises, runtime validation, shutdown), `python/` (GIL/asyncio, pydantic, WSGI/ASGI workers, language traps, packaging data files with `importlib.resources`) | | [frontend](wiki/frontend/index.md) | **seeded** | Web UI code: state placement, rendering performance, in-UI data fetching (races, infinite scroll), auth token handling, forms, XSS-safe output, accessibility, agent-facing tool surfaces (WebMCP) | -| [infrastructure](wiki/infrastructure/index.md) | **seeded** | CI/CD pipelines, secrets in build/deploy, container image builds, rollout/rollback strategy, observability (logs/metrics/alerting), per-environment/path-valued config, multi-agent orchestration (worker liveness signals, shared run state, tmux pane delivery, completion gates, worktree-isolated workers, autonomous ask-vs-rule decisions, session context/token budgeting) | +| [infrastructure](wiki/infrastructure/index.md) | **seeded** | CI/CD pipelines, secrets in build/deploy, container image builds, rollout/rollback strategy, observability (logs/metrics/alerting), per-environment/path-valued config, multi-agent orchestration (worker liveness signals, shared run state, tmux pane delivery, completion gates, worktree-isolated workers, autonomous ask-vs-rule decisions, session context/token budgeting, a pre-built code knowledge graph as a freshness-gated orientation layer for planning) | | [testing](wiki/testing/index.md) | **seeded** | Writing or structuring automated tests: level choice, test-before-code ordering, cases/assertions, cross-layer effect scoping, test data, mock decisions, flaky tests (release-process quality → qa) | | [qa](wiki/qa/index.md) | **seeded** | Release-quality process: release gates, regression scoping, bug reports, severity/priority triage, evidence for completion claims, acting on code-review feedback, adversarial review of high-risk diffs, exploratory testing (guarded-path coverage, override matrices), scope-purity gates, sourcing deliverable documents from generated artifacts, verifying the quantitative claims in a document before publishing it, automated verification of document deliverables (spec/RFC gates) (writing automated test code → testing) | | [debugging](wiki/debugging/index.md) | **seeded** | Diagnosing a failure — finding what is wrong and why: reproducing, bisection, hypothesis testing, traces/logs, intermittent failures (fixing the diagnosed fault → its owning domain) | diff --git a/examples/tools.example.json b/examples/tools.example.json index f0e05ed..304c53a 100644 --- a/examples/tools.example.json +++ b/examples/tools.example.json @@ -26,9 +26,9 @@ }, "explore": { "kind": "cli", - "ref": "rtb-sourcecode", - "how": "find-references / read-definition for external repos and symbols", - "when": "step 1 — locating code, symbols, call sites (read-only)" + "ref": "graphify", + "how": "graphify explain \"\" --graph /graphify-out/graph.json | head -40; graphify path \"\" \"\" --graph /graphify-out/graph.json", + "when": "step 1 before opening source, and orchestrate Phase 2 leads — only after scripts/graph-freshness.sh printed fresh; a hit is a lead to confirm by search" }, "design": { "kind": "mcp", diff --git a/log.md b/log.md index 3f8265b..e2ee9ca 100644 --- a/log.md +++ b/log.md @@ -103,3 +103,4 @@ Append-only. Format: `## [YYYY-MM-DD] + +## Requirements +Example-Mapping table: one row per rule, with a concrete Given/When/Then +example. +| Rule | Concrete example | Open question | +|------|------------------|---------------| +| R1 `scripts/graph-freshness.sh [--graph ]` has a fixed exit contract: 0 fresh, 2 stale, 3 absent, 4 cannot-evaluate (graphify CLI missing, graph.json unreadable, root not a git repo). It never builds or updates a graph. | Given a repo with `graphify-out/graph.json` and no commits after its mtime, When run, Then rc=0 and stdout `fresh`. Given a repo with no `graphify-out/graph.json`, Then rc=3 and stdout `absent`. | | +| R2 Stale = at least one file changed in a commit after graph.json's mtime, counted via `git log --since=@ --name-only --format=''`; stdout prints `stale ` and stderr one hint line naming `graphify update .` (AST-only). | Given graph.json then one later commit touching 2 files, Then rc=2, stdout `stale 2`, stderr contains `graphify update`. | | +| R3 graph.json validity is checked by `jq -e '.nodes'` in the script itself, because `graphify explain/path` return rc 0 on a missing node AND on a JSON decode error (spike S1). | Given `graphify-out/graph.json` containing `{bad`, Then rc=4 and stderr names the file. | | +| R4 orchestrate Preflight runs the freshness check once per run. rc 0 → `explore` resolves to graphify for this run; rc 2 → one chooser (update now / continue without the graph), the update is `graphify update ` and only on an explicit yes; rc 3/4 → silent skip, behavior identical to today. | Given rc 2 and the user picks "continue without", Then no `graphify` command runs and Phase 2 uses grep only. | | +| R5 orchestrate Phase 2, when the graph is fresh: for each candidate task, run `graphify explain "" --graph /graphify-out/graph.json \| head -40` on the task's named symbols; the printed connections seed the task's affected files and shared surfaces, recorded as `graphify explain -> connections`. A graph hit is a lead: every affected file still gets a grep/file confirmation before it enters `graph.json`'s `files`. | Given `graphify explain ChatGateway` lists 20 connections in 3 files, Then those 3 files are grep-confirmed and the evidence line reads `graphify explain ChatGateway -> 20 connections; grep -rn ChatGateway src -> 7 hits`. | | +| R6 Brief `` explore row carries the concrete command with `--graph /graphify-out/graph.json` because worker worktrees carry no `graphify-out` (gitignored, spike S3) and the graph reflects the integration base, not the worker branch. | Given a dispatched brief, Then its Tools section contains `explore: graphify explain "" --graph /graphify-out/graph.json` and the sentence that the graph is a lead, not evidence. | | +| R7 wiki-plan A2: an `Affected files` bullet may cite graphify output only in the same bullet as a grep/file-backed `evidence:` token; `plan-gate.sh` and its fixtures are unchanged. | Given a bullet `- src/x.ts — evidence: graphify explain X -> 4 connections; grep -rn X src -> 3 hits`, Then gate-A `affected-files-evidenced` passes with today's parser (it looks for the `evidence:` token). | | +| R8 loop-implement step 1 and step 6: when `explore` resolves to graphify, run `explain`/`path` before opening source files, and the step-6 self-review names every graph-derived assumption; the graphify SKILL.md is never loaded (CLI only); `--budget` only on `query` (spike S4). | Given explore=graphify, Then the task report NOTES line lists `graph-derived: ` entries or `graph-derived: none`. | | +| R9 `references/tool-profile.md`, `scripts/resolve-tools.sh` (explore `when` text), and `examples/tools.example.json` document `explore: {kind: "cli", ref: "graphify", how: ...}`; with no config, explore still resolves to `default` (tests/resolve-tools.bats:20 unchanged). | Given no config, When `resolve-tools.sh --json`, Then `.explore.kind == "default"`. | | +| R10 New page `wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md` with a row in `wiki/infrastructure/index.md`, passes `node scripts/wiki-structure-checks.js wiki` with findings: 0, and cites the four research sources. | Given the page, When the checker runs, Then stdout ends `findings: 0`. | | +| R11 `tests/graph-freshness.bats` covers fresh, stale, absent, unreadable graph, no-git root, missing CLI, and the boundary "graph newer than HEAD with dirty worktree" (still fresh — uncommitted edits are not counted). | Given the suite, Then ≥7 cases, each with ≥1 assertion, ≥1 error case, ≥1 boundary case. | | +| R12 Zero-config invariant: with graphify absent, no changed SKILL.md text mandates an action, and the full bats suite result is unchanged from baseline. | Given `PATH` without graphify, When the suite runs, Then the same pass/fail set as baseline. | | + +## Ground truth +- Baseline: PATH=/opt/homebrew/bin:$PATH bats tests/ -> rc=0, HEAD 484dd9d, git status clean + +### Affected files +- scripts/graph-freshness.sh (new) — evidence: `ls scripts/graph-freshness.sh` -> 0 hits (does not exist) +- tests/graph-freshness.bats (new) — evidence: `ls tests/graph-freshness.bats` -> 0 hits (does not exist) +- wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md (new) — evidence: `grep -rli "code graph\|knowledge graph\|graphify" wiki/` -> 2 hits, both in databases/ (DB selection pages), none in agent-orchestration +- wiki/infrastructure/index.md — evidence: `grep -c agent-orchestration wiki/infrastructure/index.md` -> 11 hits (the section the new row joins) +- INDEX.md — evidence: `grep -c "multi-agent orchestration" INDEX.md` -> 1 hit (line 17 scope text gains "code-graph orientation") +- skills/orchestrate/SKILL.md — evidence: `grep -c explore skills/orchestrate/SKILL.md` -> 1 hit (line 71, tools_guidance); `grep -n "^## Preflight\|^## Phase 2" ` -> 2 hits (lines 155, 266) +- skills/orchestrate/templates/brief.md — evidence: `grep -c explore skills/orchestrate/templates/brief.md` -> 1 hit (tools_guidance comment) +- skills/wiki-plan/SKILL.md — evidence: `grep -c "evidence:" skills/wiki-plan/SKILL.md` -> 2 hits (A2 affected-files rule) +- skills/loop-implement/SKILL.md — evidence: `grep -c explore skills/loop-implement/SKILL.md` -> 3 hits (tool profile, step 1, sweep) +- references/tool-profile.md — evidence: `grep -c explore references/tool-profile.md` -> 1 hit (roles table row) +- scripts/resolve-tools.sh — evidence: `grep -c explore scripts/resolve-tools.sh` -> 2 hits (comment + default `when`) +- examples/tools.example.json — evidence: `grep -c explore examples/tools.example.json` -> 1 hit + +Not touched, on purpose: `templates/brief.md` at the repo root (legacy copy; `grep -rn "templates/brief.md"` outside plans/docs -> only skills/orchestrate/SKILL.md, which resolves to the skill's own templates dir), `templates/session-prompt.md` (cksum-pinned), `skills/wiki-plan/scripts/plan-gate.sh` (R7 keeps the parser). + +## Constraints +- tests/resolve-tools.bats:20 pins `.explore.kind == "default"` with no config — checked: `grep -n explore tests/resolve-tools.bats` -> 1 hit; the default stays `default`, only its `when` text changes. +- tests/send-prompt.bats:739 cksum-pins `templates/session-prompt.md` from `## Subagent usage protocol` on — checked: `grep -n cksum tests/send-prompt.bats` -> 3 hits; this feature does not edit session-prompt.md. +- tests/scripts.bats:163-170 pins brief.md literals `split proposal`, `files it would touch`, `split_of`, and zero Hangul bytes; tests/scripts.bats:186 pins zero Hangul bytes in skills/orchestrate/SKILL.md — checked: `grep -n hangul_bytes tests/scripts.bats` -> 4 hits; all new text is English. +- tests/session-prompt-paths.bats:136 forbids any bare `.orchestration/` path in brief.md other than `.orchestration/status` — checked: `sed -n 130,139p tests/session-prompt-paths.bats`; the new explore row uses `/graphify-out/graph.json`, never `.orchestration/`. +- scripts/wiki-structure-checks.js (run by tests/wiki-structure-checks.bats:19 on the live wiki) requires frontmatter keys id/domain/category/applies_to/confidence/sources/last_verified/related, id == `infrastructure-agent-orchestration-code-graph-as-orientation-layer`, an index row, and `related` ids that exist — checked: `sed -n 17,45p scripts/wiki-structure-checks.js`. +- CI runs on ubuntu and macos (.github/workflows/test.yml) — checked: `grep -n "brew install\|apt-get" .github/workflows/test.yml` -> 2 hits; the script uses `date -r +%s` (works on both, spike S2) and no `stat -f`/`stat -c`. +- User hook `exit-code-masking-guard` blocks piping a decision-carrying script — checked: memory note; bats cases use `run sh "$SCRIPT"` and read `$status`, never a pipe. + +## Spikes +- S1 graphify exit codes (graphifyy 0.4.23): `graphify explain "NoSuchNodeXYZ" --graph g.json` -> rc 0; `graphify path A NoSuch` -> rc 0 with "No node matching"; `graphify explain X --graph bad.json` -> Python JSONDecodeError traceback, rc 0. Consequence: the freshness script must validate graph.json with jq itself and must not rely on graphify's rc for anything (R3). +- S2 mtime portability: `date -r graph.json +%s` -> 1788137456 on macOS 25.1; GNU coreutils `date -r FILE` is the same flag. `git log --since=@1788137456 --name-only --format='' | sort -u | wc -l` -> 97 on linkly-calendar (stale). graph.json's top-level `graph` dict is empty and GRAPH_REPORT.md carries only a date, so mtime is the only freshness signal available. +- S3 graphify-out is gitignored in linkly-calendar (`git check-ignore -q graphify-out` -> ignored), so a git worktree never carries it; workers must point `--graph` at the main checkout (R6). +- S4 `graphify explain X --budget 100` printed the full 20-connection list — `--budget` is a `query` option only; explain/path output is bounded with `| head -N` instead (R5/R8). +- S5 Query quality on linkly-calendar (1306 nodes): a free-text `graphify query "" --budget 600` returned noise (Error, String, .encode()); `explain ChatGateway` returned 20 precise connections; `path ChatGateway processFanoutMessage` returned a 3-hop path; `graphify benchmark` -> 11.4x average token reduction (6.3x–22.2x). Consequence: skills prescribe symbol-anchored explain/path, never free-text query, for planning. + +## Research +`research` role resolved to default; answered by `mcp__brave-search__brave_web_search` (2 of 6 queries rate-limited, retried) plus WebFetch on three articles. +| Query | Source | Applied | +|-------|--------|---------| +| local code graph coding agent context layer orientation impact analysis when to use vs grep | https://www.developersdigest.tech/blog/codegraph-local-indexes-ai-coding-agents — "Use the graph to choose where to look. Use the file, test, and runtime to decide what is true"; measure tool calls/file reads before first edit, wrong-file edits, staleness incidents; "If those improve, keep the graph. If they do not, remove it." | R5/R7 lead-not-evidence rule; wiki page "Do this" + measurement rows | +| keeping code knowledge graph fresh incremental update git hook stale graph detection coding agent | https://aq-score.com/blog/codegraph-local-code-knowledge-graph-agent-ops-guide-2026 — "If the graph is stale, incomplete, or conflicts with the files, stop using it and investigate from the files"; rebuild from the active checkout | R1/R2/R4 freshness gate before any use; never auto-build | +| AI coding agent orchestrator use dependency graph to split work into parallel tasks avoid file conflicts | https://getautonoma.com/blog/parallel-ai-agent-prs — "Use a dependency graph to map each task to the files it will likely touch before assigning tasks to agents. Only run tasks in parallel if their expected file sets are disjoint" | R5 Phase 2 explain-derived affected files feed the existing conflict matrix | +| graphify knowledge graph codebase Claude Code skill agent workflow best practices | https://www.tiarebalbi.com/en/blog/code-graphs-coding-agents-delivery-shape — a remote/local MCP exposing ten graph tools "lands tens of thousands of tokens in context whether you query it or not"; https://github.com/Graphify-Labs/graphify — `query`/`path`/`explain`, `graphify update` AST-only, `hook install` post-commit/post-checkout | R8 CLI-only, never load the 1319-line graphify SKILL.md; wiki page "Instead of" row | +| GraphRAG codebase agent code graph multi-agent task decomposition impact analysis blast radius | https://eliteai.tools/agent-skills/code-graph — PLAN → LOCATE → UNDERSTAND → BLAST → TRACE → CHANGE → VERIFY; graph refreshed by watcher + session-start + post-commit hook | R8 explain/path before reading source; wiki page edge case on refresh layers | diff --git a/plans/graphify-explore-role/design.md b/plans/graphify-explore-role/design.md new file mode 100644 index 0000000..92ed563 --- /dev/null +++ b/plans/graphify-explore-role/design.md @@ -0,0 +1,35 @@ +# Design — graphify-explore-role + + + +## Decisions +| # | Decision | Choice | Wiki basis | Rejected alternative | Testability | +|---|----------|--------|------------|----------------------|-------------| +| D1 | Freshness signal | graph.json mtime (epoch) vs `git log --since=@ --name-only --format=''` in ``; N distinct paths > 0 means stale; uncommitted edits are not counted. Secondary basis: control-signals-vs-primary-artifacts (git log is the primary artifact of "what changed") | wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md | Parse the date in GRAPH_REPORT.md — day granularity only, and graph.json's own `graph` metadata is empty (spike S2) | tests/graph-freshness.bats `stale` (one later commit), `fresh` (no later commit), and the boundary case "uncommitted edits to a tracked file do not make the graph stale" | +| D2 | `graph-freshness.sh` exit contract | stdout exactly one token per outcome: `fresh` (0), `stale ` (2), `absent` (3), `cannot-evaluate ` (4, reasons: `usage`, `no-cli`, `bad-graph`, `not-git`); stderr carries the hint naming `graphify update ` only on 2; the script never runs `graphify` at all — it only checks `command -v "${GRAPHIFY_BIN:-graphify}"`. Secondary basis: wiki/platforms/processes/tool-diagnostics-without-a-failing-exit-code.md (the CLI's exit code carries no signal) | wiki/testing/quality/checks-that-cannot-pass.md | Branch on graphify's own exit code — it is 0 on a missing node, missing file, and JSON decode error (spike S1) | tests/graph-freshness.bats one case per token, plus `unreadable graph -> 4 bad-graph`, `no git -> 4 not-git`, `GRAPHIFY_BIN unresolved -> 4 no-cli`, and `GRAPHIFY_BIN=false -> fresh` proving the binary is never executed | +| D3 | Script dialect and portability | `#!/usr/bin/env bash`, `set -euo pipefail`, quoted expansions; mtime via the fallback chain `date -r "$f" +%s`, then `stat -c %Y "$f"`, then `stat -f %m "$f"`; graph validity via `jq -e '.nodes` of type array. Secondary basis: wiki/platforms/shells/portable-shell-scripts.md | wiki/platforms/tools/bsd-vs-gnu-cli.md | `stat -f` alone (macOS-only; CI also runs ubuntu) | tests/graph-freshness.bats runs on both CI runners (.github/workflows/test.yml) | +| D4 | Graph update is a human choice, never automatic | On rc 2, orchestrate Preflight asks one AskUserQuestion chooser: "graph stale (N files since build) — run `graphify update ` (AST-only, seconds) now, or continue without the graph?" (recommended: update). Only an explicit yes runs `graphify update `; a full `/graphify` build is never offered by the coordinator. rc 3/4 → no question, one Preflight line. Secondary basis: wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md edge case row 1 | wiki/infrastructure/agent-orchestration/autonomous-decision-rulings.md | Auto-run `graphify update` on stale — writes into the user's checkout outside `.orchestration/` (category (c) side effect leaving the run's state) | tests/orchestrate-graph-explore.bats doc-gate: Preflight text contains `graph-freshness.sh`, the chooser reference, `graphify update `, and the literal "never runs a full" sentence; negative control strips the span | +| D5 | Graph results are leads, never evidence | orchestrate Phase 2 and wiki-plan A2 accept a graph citation only in the same bullet as a grep/file-backed `evidence:` token, in the fixed form `graphify explain -> connections; -> hits`; plan-gate.sh's `evidence:` parser is unchanged. Secondary basis: wiki/qa/process/completion-claims.md (claim → required evidence → not evidence) | wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md | Let a graph line stand alone as evidence — a stale or convention-blind edge enters the plan as fact | tests/plan-gate.bats (existing, unchanged) still passes; tests/orchestrate-graph-explore.bats asserts the paired-evidence form in both SKILL.md files | +| D6 | Which graph commands planning uses | `graphify explain "" --graph ` piped through `head -40` for affected files and shared surfaces; `graphify path "" "" --graph ` for a dependency edge; `query` only with `--budget 800` and only for orientation. Free-text query is not used to derive file sets. Secondary basis: wiki/infrastructure/agent-orchestration/session-context-token-budget.md directive 4 (bound tool output) | wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md | `graphify query ""` — returned unrelated nodes on a 1,306-node graph (spike S5); `--budget` on explain is ignored (spike S4) | doc-gate: SKILL.md texts contain `head -40` and `--budget 800`, and never `explain ... --budget` | +| D7 | Worker graph path | Brief `` explore row: `explore: graphify — graphify explain "" --graph /graphify-out/graph.json` piped through `head -40` `(lead, not evidence; graph reflects the integration base)`; `` is the absolute main checkout path, a read-only consumption of a main-checkout path. Secondary basis: wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md edge case row 2 | wiki/infrastructure/agent-orchestration/worktree-isolated-workers.md | Copy graphify-out into each worktree — N copies of an 8 MB file, and stale the moment the integration branch moves | tests/session-prompt-paths.bats (no bare `.orchestration/` in brief.md) unchanged; tests/orchestrate-graph-explore.bats asserts the row text | +| D8 | CLI-only delivery | Skill text says to call `graphify` directly and to read `graphify --help`; the graphify SKILL.md is never loaded or invoked from orchestrate/loop-implement; no MCP server is configured. Secondary basis: wiki/infrastructure/agent-orchestration/session-context-token-budget.md | wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md | Invoke the `/graphify` skill for queries — 1,319 lines of pipeline instructions enter the coordinator context | doc-gate: SKILL.md texts contain "never load" and do not contain `Skill(graphify` or `/graphify query` | +| D9 | Where the explore role activates | orchestrate Preflight rc 0 sets `explore` to graphify for the run (recorded in every brief); loop-implement step 1 runs explain/path before opening source when explore is graphify; step 6 self-review adds `graph-derived: ` or `graph-derived: none` to the task report NOTES line | wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md | Activate via a hook on Read/Grep like graphify's own installer — fires on every worker read, cannot see freshness | doc-gate on loop-implement SKILL.md: contains `graph-derived:` and `explain` in step 1 and step 6 spans | +| D10 | Tool-profile documentation | `references/tool-profile.md` gains an `### explore — graphify (optional)` subsection with the config `{"explore": {"kind": "cli", "ref": "graphify", "how": "...", "when": "..."}}`; `examples/tools.example.json` gets that explore entry; `scripts/resolve-tools.sh` explore default `when` text points at the graphify option while `kind` stays `default`. Ingest candidate: none — repo documentation convention, and tests/resolve-tools.bats pins `explore.kind == default` with no config | [no-wiki] | Change the built-in default to graphify — breaks the zero-config invariant and resolve-tools.bats:20 | tests/resolve-tools.bats (existing) passes; new case: explore configured as `{"kind":"cli","ref":"graphify"}` resolves `ref == graphify` | +| D11 | Doc-gate tests for SKILL.md and brief.md edits | One new file `tests/orchestrate-graph-explore.bats`, each gate paired with a negative control that strips the asserted span from a fixture copy and shows the same check failing; section-scoped via awk on the `## Preflight`, `## Phase 2 — Decompose` headings; whitespace-normalized like tests/orchestrate-token-budget.bats. Secondary basis: wiki/testing/quality/tests-that-cannot-fail.md | wiki/testing/quality/checks-that-cannot-pass.md | File-wide grep only — passes vacuously when the word appears in another section | The negative-control cases themselves | +| D12 | Wiki page as the plan's grounding | The page was ingested during Phase B (wiki-ingest steps 1–7: page, index row, INDEX.md scope text, log.md entry) so every decision above can cite it; it is a deliverable of this feature and is listed in the report's CHANGED lines | wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md | Mark D4–D9 `[no-wiki]` and create the page as task 01 — the plan would ship six ungrounded decisions past gate-B | `node scripts/wiki-structure-checks.js wiki` -> `findings: 0` (tests/wiki-structure-checks.bats:19 runs it live); `node scripts/wiki-lint-prohibitions.js wiki` -> `violations: 0` | + +## Review +plan-reviewer (dev-loop:plan-reviewer, fresh context, read-only), call 1 of 3 — 2026-09-04: + +``` +VERDICT: PASS +FINDINGS: +- [R11/D1] The specific boundary case "graph newer than HEAD with dirty worktree — still fresh" (required by R11's ≥7-case bats coverage) is not named as its own test case in any Decision's Testability column; D1's Choice text ("uncommitted edits are not counted") fully determines the behavior, but the design doesn't spell out the test case itself. (advisory) +- [D2] Wiki basis `wiki/testing/quality/checks-that-cannot-pass.md` is real and topically about unwritten-target gates, but is a looser fit for D2 (exit-code contract) than for D11 (doc-gate negative controls), where the same page is also cited. Not a blocking grounding failure since the page exists and D2 also cites the more directly on-point `tool-diagnostics-without-a-failing-exit-code.md`. (advisory) +SUMMARY: All 12 Requirements (R1–R12) map to at least one Decision row; every Wiki basis citation (D1–D9, D11–D12) resolves to a real, topically relevant wiki page, and D10's [no-wiki] is correctly justified by the resolve-tools.bats pin. Every Decision's Rejected alternative is concretely justified (spikes S1–S5 or pinned tests), no Decision contradicts analysis.md's Constraints (verified against tests/resolve-tools.bats:20, tests/scripts.bats:163-186, tests/session-prompt-paths.bats:136, and a live `node scripts/wiki-structure-checks.js wiki` run showing findings: 0). Two minor advisory gaps only — no blocking findings. PASS. +``` + +Applied: advisory 1 — D1's Testability cell now names the dirty-worktree boundary case (it is case 10 of tasks/01). Advisory 2 — D2 keeps `checks-that-cannot-pass.md` for its directives 3–4 (distinct exit codes and messages per outcome), which is the part D2 applies. diff --git a/plans/graphify-explore-role/plan.md b/plans/graphify-explore-role/plan.md new file mode 100644 index 0000000..6d0a531 --- /dev/null +++ b/plans/graphify-explore-role/plan.md @@ -0,0 +1,29 @@ +# graphify-explore-role +Goal: dev-loop treats a locally built graphify code graph as an optional, freshness-gated `explore` role — a lead-not-evidence orientation layer for orchestrate Phase 2, wiki-plan A2, and loop-implement steps 1/6. Acceptance: `scripts/graph-freshness.sh` has the 0/2/3/4 contract with bats coverage; the three SKILL.md files and the brief template carry the gated, paired-evidence, CLI-only wording under doc-gates with negative controls; tool-profile docs show the graphify config; zero-config behavior is byte-for-byte unchanged when graphify is absent (full suite green, resolve-tools explore default intact); the grounding wiki page passes the structure and prohibition checkers. +Stack: bash 4+ (bats-core, jq, git — CI ubuntu + macos), graphifyy 0.4.23 CLI (optional at runtime, never required by tests), Node for the wiki checkers. +## Decisions +| # | Decision | Choice | Wiki basis | +|---|----------|--------|------------| +| D1 | Freshness signal | graph.json mtime vs `git log --since=@ --name-only --format=''`; distinct paths > 0 = stale; uncommitted edits not counted | wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md, wiki/infrastructure/agent-orchestration/control-signals-vs-primary-artifacts.md | +| D2 | Exit contract | stdout one token line: `fresh`/0, `stale `/2, `absent`/3, `cannot-evaluate `/4; hint on stderr only for 2; the script never executes graphify (checks `command -v "${GRAPHIFY_BIN:-graphify}"` only) | wiki/testing/quality/checks-that-cannot-pass.md, wiki/platforms/processes/tool-diagnostics-without-a-failing-exit-code.md | +| D3 | Dialect/portability | `#!/usr/bin/env bash`, `set -euo pipefail`; mtime `date -r "$f" +%s 2>/dev/null \|\| stat -c %Y "$f" 2>/dev/null \|\| stat -f %m "$f"`; validity `jq -e '.nodes \| type == "array"'` | wiki/platforms/shells/portable-shell-scripts.md, wiki/platforms/tools/bsd-vs-gnu-cli.md | +| D4 | Update is a human choice | rc 2 → one AskUserQuestion chooser in Preflight (recommended: run `graphify update `); never a full build; rc 3/4 → one Preflight line, nothing else | wiki/infrastructure/agent-orchestration/autonomous-decision-rulings.md, wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md | +| D5 | Lead, not evidence | graph citation only paired in one bullet: `graphify explain -> connections; -> hits`; plan-gate.sh unchanged | wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md, wiki/qa/process/completion-claims.md | +| D6 | Commands used | `explain "" --graph \| head -40`; `path "" "" --graph `; `query` only with `--budget 800` for orientation | wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md, wiki/infrastructure/agent-orchestration/session-context-token-budget.md | +| D7 | Worker graph path | brief explore row uses `--graph /graphify-out/graph.json` (read-only main-checkout consumption; graph = integration base) | wiki/infrastructure/agent-orchestration/worktree-isolated-workers.md, wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md | +| D8 | CLI-only delivery | call `graphify` directly; never load the graphify SKILL.md; no MCP | wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md, wiki/infrastructure/agent-orchestration/session-context-token-budget.md | +| D9 | Activation points | Preflight rc 0 sets explore=graphify for the run; loop-implement step 1 explain/path before source; step 6 adds `graph-derived:` line to NOTES | wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md | +| D10 | Tool-profile docs | `### explore — graphify` subsection in references/tool-profile.md; example config entry; resolve-tools default `when` text mentions it; default kind stays `default` | [no-wiki] | +| D11 | Doc-gate tests | tests/orchestrate-graph-explore.bats, section-scoped, whitespace-normalized, each gate + negative control | wiki/testing/quality/checks-that-cannot-pass.md, wiki/testing/quality/tests-that-cannot-fail.md | +| D12 | Grounding page | wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md ingested in Phase B (page + index row + INDEX.md scope + log.md); a deliverable of this feature, already on disk | wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md | +## Size verdict +size: medium +5 tasks; every task touches ≤3 files and names ≤4 wiki pages; no task breaks a step-4 bound. +## Task order +| Task | Depends on | Parallel-ok | +|------|------------|-------------| +| 01-graph-freshness-script | — | with 02 | +| 02-resolve-tools-explore-text | — | with 01 | +| 03-orchestrate-preflight-and-phase2 | 01 | — | +| 04-wiki-plan-and-loop-implement-text | 01, 03 (appends to the bats file 03 creates) | — | +| 05-tool-profile-docs-and-full-verify | 01, 02, 03, 04 | — | diff --git a/plans/graphify-explore-role/review-verdict.md b/plans/graphify-explore-role/review-verdict.md new file mode 100644 index 0000000..5a904ff --- /dev/null +++ b/plans/graphify-explore-role/review-verdict.md @@ -0,0 +1 @@ +VERDICT: PASS diff --git a/plans/graphify-explore-role/tasks/01-graph-freshness-script.md b/plans/graphify-explore-role/tasks/01-graph-freshness-script.md new file mode 100644 index 0000000..97ee6d5 --- /dev/null +++ b/plans/graphify-explore-role/tasks/01-graph-freshness-script.md @@ -0,0 +1,63 @@ +# Task 01: graph-freshness.sh with its bats suite +## Objective +`scripts/graph-freshness.sh [--graph ]` exists, prints exactly one stdout token line, exits 0/2/3/4 per D2, never executes graphify, and `tests/graph-freshness.bats` proves every outcome on a runner that has no graphify installed. +## Wiki pages (read these first, only these) +- wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md — use for: directive 1 (freshness from git log), edge cases "CLI exits 0", "uncommitted edits" +- wiki/testing/quality/checks-that-cannot-pass.md — use for: directives 3–4 (target-missing vs content-missing get distinct exit codes and messages) +- wiki/platforms/shells/portable-shell-scripts.md — use for: shebang, `set -euo pipefail`, quoting, verifying state with an independent command +- wiki/platforms/tools/bsd-vs-gnu-cli.md — use for: the `date`/`stat` fallback chain (macOS BSD vs Linux GNU) +## Inputs +- Decisions that bind you: D1 (mtime vs git log), D2 (exit contract, `GRAPHIFY_BIN` override, never execute graphify), D3 (dialect, fallback chain, jq validity) +- Style reference: tests/resolve-tools.bats (setup() with `BATS_TEST_TMPDIR`, `run bash "$SCRIPT"`, `[ "$status" -eq N ]`) +## Steps +1. Create `scripts/graph-freshness.sh` (mode 0755 like the sibling scripts, `#!/usr/bin/env bash`, `set -euo pipefail`) with this header comment, verbatim in intent: + ``` + # graph-freshness.sh — is the repo's graphify code graph fresh enough to use as a lead? + # usage: graph-freshness.sh [--graph ] + # stdout: exactly one line the caller branches on + # fresh exit 0 no commit touched a file after graph.json's mtime + # stale exit 2 N distinct paths changed by commits after the mtime + # absent exit 3 no graph.json (default /graphify-out/graph.json) + # cannot-evaluate exit 4 reason: usage | no-cli | bad-graph | not-git + # stderr: on exit 2 one hint line naming `graphify update `; on exit 4 the reason + # Never runs graphify. Only checks that `${GRAPHIFY_BIN:-graphify}` resolves via command -v, + # because the CLI exits 0 on a missing node, a missing file, and a JSON decode error, so its + # exit status carries no signal (wiki: code-graph-as-orientation-layer, edge case row 3). + # Uncommitted edits are not counted — freshness is relative to commits only. + # A commit in the same second as the mtime counts as stale (git --since is inclusive). + ``` +2. Implement in this order, each branch printing its token to stdout and exiting: + 1. Parse args: first positional is `root`; `--graph ` optional. Missing root, unknown flag, or `root` not a directory → stderr `usage: graph-freshness.sh [--graph ]`, stdout `cannot-evaluate usage`, exit 4. + 2. `graph="${graph_override:-$root/graphify-out/graph.json}"`; `[ -f "$graph" ]` else stdout `absent`, exit 3. + 3. `command -v "${GRAPHIFY_BIN:-graphify}" >/dev/null 2>&1` else stderr `graphify CLI not found (GRAPHIFY_BIN=${GRAPHIFY_BIN:-graphify})`, stdout `cannot-evaluate no-cli`, exit 4. + 4. `jq -e '.nodes | type == "array"' "$graph" >/dev/null 2>&1` else stderr `unreadable graph: $graph`, stdout `cannot-evaluate bad-graph`, exit 4. + 5. `git -C "$root" rev-parse --is-inside-work-tree >/dev/null 2>&1` else stderr `not a git repository: $root`, stdout `cannot-evaluate not-git`, exit 4. + 6. `epoch=$(date -r "$graph" +%s 2>/dev/null || stat -c %Y "$graph" 2>/dev/null || stat -f %m "$graph")`. + 7. `n=$(git -C "$root" log --since="@$epoch" --name-only --format='' | sed '/^$/d' | sort -u | wc -l | tr -d ' ')`. + 8. `[ "$n" -eq 0 ]` → stdout `fresh`, exit 0. Else stderr `hint: graphify update "$root" # AST-only, no LLM, rebuilds graphify-out/graph.json from the current checkout`, stdout `stale $n`, exit 2. +3. Create `tests/graph-freshness.bats` with `setup()` that sets `SCRIPT="${BATS_TEST_DIRNAME}/../scripts/graph-freshness.sh"`, `export GRAPHIFY_BIN=true` (the `true` binary resolves via `command -v` on both OSes, and the script must never execute it), and a helper `make_repo()` that: `git init -q "$1"`, sets `user.email`/`user.name` locally, writes `a.txt`, and commits with `GIT_AUTHOR_DATE='2020-01-01T00:00:00Z' GIT_COMMITTER_DATE='2020-01-01T00:00:00Z'` so the commit is far older than any graph file written afterwards. A helper `make_graph()` writes `{"nodes":[{"id":"a"}],"links":[]}` to `$1/graphify-out/graph.json` (mkdir -p first). +4. Write these cases (names are the `@test` strings; keep the `normal:`/`error:`/`boundary:`/`state:` prefixes the suite uses): + - `error: no argument -> exit 4, cannot-evaluate usage on stdout, usage on stderr` + - `error: root that is not a directory -> exit 4 cannot-evaluate usage` + - `normal: no graph.json -> exit 3, stdout absent` + - `normal: graph newer than every commit -> exit 0, stdout fresh, stderr empty` + - `normal: a commit after the graph touching two files -> exit 2, stdout "stale 2", stderr hint names graphify update` (make_repo, make_graph, then write `b.txt` and `c.txt`, `git add -A`, commit with the default (now) date) + - `error: graph.json is not JSON -> exit 4 cannot-evaluate bad-graph, stderr names the file` + - `error: graph.json without a nodes array -> exit 4 cannot-evaluate bad-graph` (`{"foo":1}`) + - `error: GRAPHIFY_BIN does not resolve -> exit 4 cannot-evaluate no-cli` (`GRAPHIFY_BIN=graphify-missing-xyz-$$`) + - `error: root is not a git repository -> exit 4 cannot-evaluate not-git` (plain dir + make_graph) + - `boundary: uncommitted edits to a tracked file do not make the graph stale` (make_repo, make_graph, `echo x >> a.txt`, expect fresh) + - `boundary: an empty nodes array is a readable graph -> fresh` (`{"nodes":[]}`) + - `boundary: --graph override is used when the default path is absent` (graph at `$BATS_TEST_TMPDIR/elsewhere.json`, root has no graphify-out; expect fresh) + - `state: stdout carries exactly one line on every outcome` (loop the fresh, stale, absent, bad-graph cases; `[ "$(printf '%s\n' "$output" | wc -l | tr -d ' ')" -eq 1 ]` on stdout only — capture stdout separately with `run bash -c '... 2>/dev/null'`) + - `state: the graphify binary is never executed` (`GRAPHIFY_BIN=false` — resolves, would exit 1 if run — expect `fresh` exit 0) +5. Run `PATH=/opt/homebrew/bin:$PATH bats tests/graph-freshness.bats` and `shellcheck scripts/graph-freshness.sh` if shellcheck is installed (skip silently if not). +## Deliverables +- scripts/graph-freshness.sh (new, executable) +- tests/graph-freshness.bats (new) +## Verify +- `PATH=/opt/homebrew/bin:$PATH bats tests/graph-freshness.bats` → all `ok`, 14 cases +- `bash scripts/graph-freshness.sh` (no args) → prints `cannot-evaluate usage`, rc 4 (run bare, read `$?` on the next line — never in a pipe) +- covers: R1, R2, R3, R11 +## Out of scope +- Any SKILL.md or brief text (tasks 03/04); tool-profile docs (02/05); running or offering `graphify update` (the script only prints the hint). diff --git a/plans/graphify-explore-role/tasks/02-resolve-tools-explore-text.md b/plans/graphify-explore-role/tasks/02-resolve-tools-explore-text.md new file mode 100644 index 0000000..7965271 --- /dev/null +++ b/plans/graphify-explore-role/tasks/02-resolve-tools-explore-text.md @@ -0,0 +1,37 @@ +# Task 02: resolve-tools explore default text names the graphify option +## Objective +`scripts/resolve-tools.sh`'s built-in `explore` default keeps `kind: default` but its `when` text points at the graphify cli option, and `tests/resolve-tools.bats` proves both the unchanged default and a `{"kind":"cli","ref":"graphify"}` config resolving. +## Wiki pages (read these first, only these) +- wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md — use for: directive 5 (CLI delivery) wording of the `when` text +- wiki/testing/quality/tests-that-cannot-fail.md — use for: the new case asserts the configured `ref`, not merely a non-empty object +## Inputs +- Decisions that bind you: D10 (default kind stays `default`; only the `when` text changes) +- Constraint from analysis.md: tests/resolve-tools.bats:20 pins `.explore.kind == "default"` with no config +## Steps +1. In `scripts/resolve-tools.sh`, inside the `DEFAULTS='{ ... }'` literal, replace the `explore` line's `when` value `locating code, symbols, call sites (step 1)` with `locating code, symbols, call sites (step 1); with a fresh graphify graph, configure {"kind":"cli","ref":"graphify"} — see references/tool-profile.md` (single-quoted heredoc-free JSON: the value must stay a valid JSON string; use no single quotes inside it). +2. Run `bash scripts/resolve-tools.sh --json | jq -e '.explore.kind == "default"'` to confirm the default is intact. +3. In `tests/resolve-tools.bats`, after the `design role is configurable` case, add: + ``` + @test "explore role is configurable as the graphify cli (lead-not-evidence orientation layer)" { + printf '{"explore":{"kind":"cli","ref":"graphify","how":"graphify explain \\"\\" --graph /graphify-out/graph.json | head -40"}}' > "$PROJ_CFG" + run bash "$RT" --role explore + [ "$status" -eq 0 ] + [ "$(printf '%s' "$output" | jq -r '.kind')" = "cli" ] + [ "$(printf '%s' "$output" | jq -r '.ref')" = "graphify" ] + [[ "$(printf '%s' "$output" | jq -r '.how')" == *'graphify explain'* ]] + } + @test "no config: explore default text points at the graphify option without changing kind" { + run bash "$RT" --role explore + [ "$status" -eq 0 ] + [ "$(printf '%s' "$output" | jq -r '.kind')" = "default" ] + [[ "$(printf '%s' "$output" | jq -r '.when')" == *'graphify'* ]] + } + ``` +## Deliverables +- scripts/resolve-tools.sh (modified: one `when` string) +- tests/resolve-tools.bats (modified: two cases appended) +## Verify +- `PATH=/opt/homebrew/bin:$PATH bats tests/resolve-tools.bats` → all `ok` (existing cases plus 2) +- covers: R9 (resolve-tools half; the docs half is task 05) +## Out of scope +- references/tool-profile.md and examples/tools.example.json (task 05); any SKILL.md text. diff --git a/plans/graphify-explore-role/tasks/03-orchestrate-preflight-and-phase2.md b/plans/graphify-explore-role/tasks/03-orchestrate-preflight-and-phase2.md new file mode 100644 index 0000000..44c77f1 --- /dev/null +++ b/plans/graphify-explore-role/tasks/03-orchestrate-preflight-and-phase2.md @@ -0,0 +1,69 @@ +# Task 03: orchestrate Preflight freshness gate, Phase 2 graph leads, brief explore row +## Objective +`skills/orchestrate/SKILL.md` runs `graph-freshness.sh` in Preflight with the consent-gated update chooser, uses `graphify explain`/`path` as leads in Phase 2 with the paired-evidence form, writes the explore row into ``; `skills/orchestrate/templates/brief.md` shows that row; `tests/orchestrate-graph-explore.bats` gates every span with a negative control. +## Wiki pages (read these first, only these) +- wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md — use for: directives 1–5 and the worktree edge case; the exact command forms +- wiki/infrastructure/agent-orchestration/autonomous-decision-rulings.md — use for: directive 1(c) — the update writes outside the run's state, so it is the human's choice +- wiki/infrastructure/agent-orchestration/worktree-isolated-workers.md — use for: directive 3 — reading a main-checkout path is allowed; the brief carries one absolute path +- wiki/testing/quality/checks-that-cannot-pass.md — use for: each doc-gate paired with a fixture that strips the span and fails +## Inputs +- scripts/graph-freshness.sh from task 01 (exit contract 0/2/3/4, stdout token) +- Decisions that bind you: D4, D5, D6, D7, D8, D9, D11 +- Style reference: tests/orchestrate-token-budget.bats (`normalize_ws`, `section_body` via awk on a `## ` heading, negative-control fixtures in `BATS_TEST_TMPDIR`) +- Constraints: skills/orchestrate/SKILL.md and brief.md must stay Hangul-free (tests/scripts.bats:170,186); brief.md must not gain a bare `.orchestration/` path (tests/session-prompt-paths.bats:136) +## Steps +1. `skills/orchestrate/SKILL.md`, section `## Preflight`: after the paragraph that ends `Never auto-install without consent.` and before `**Coordinator permissions (tmux substrate).**`, insert this paragraph (wrap at ~80 columns like the file): + ``` + **Code-graph freshness (optional `explore` lead).** Run + `sh ${CLAUDE_PLUGIN_ROOT}/scripts/graph-freshness.sh ` once, bare + (read its exit code on the next line, never in a pipe). It never runs + graphify itself. Branch on the code: **0** (`fresh`) — set `explore` to + graphify for this run and write the row from Phase 3 step 2 into every + brief; **2** (`stale `) — ask ONE chooser (§ Asking the user): + "graphify graph is stale ( files changed since it was built) — run + `graphify update ` now (AST-only, seconds, no LLM), or continue + without the graph?" with *update* as the recommended answer; run the + update only on an explicit yes and re-run the freshness check afterwards; + **3**/**4** (`absent` / `cannot-evaluate `) — print one line saying + the graph is not in use and continue exactly as before. The coordinator + never runs a full `/graphify` build and never loads the graphify skill + document: the CLI is the whole interface (`graphify --help`). Basis: + `wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md`. + ``` +2. Same file, section `## Phase 2 — Decompose`: after the sentence `A markdown table is not machine readable.` and before the ```json block, insert: + ``` + **Graph leads (only when Preflight set `explore` to graphify).** For each + candidate task, run `graphify explain "" --graph + /graphify-out/graph.json | head -40` on the symbols the task names, + and `graphify path "" "" --graph /graphify-out/graph.json` for + a suspected edge between two tasks. The printed connections seed the task's + affected files and its shared surfaces; the disjointness test in the + conflict matrix runs on the confirmed sets, never on the graph's guesses. + A graph hit is a lead, not evidence: every file it suggests is confirmed + by a search before it enters `files`, and the record pairs both in one + line — `graphify explain -> connections; grep -rn + src -> hits`. Ask symbol-anchored questions only; a free-text + `graphify query` is for orientation (`--budget 800`) and never derives a + file set. Any assumption taken from the graph and not confirmed is + recorded on the blackboard as `graph-derived: `. + ``` +3. Same file, `## Tool profile` section: replace `` `explore` (code search) `` with `` `explore` (code search; a fresh graphify graph when Preflight says so) ``. +4. Same file, Phase 3 step **2** bullet (`- **2** Per task: write `briefs/.md` ...`): append the sentence `When `explore` is graphify, the `` row is: `explore: graphify — graphify explain "" --graph /graphify-out/graph.json | head -40 (lead, not evidence; the graph reflects the integration base, and a worktree carries no graphify-out)` with `` the absolute main-checkout path.` +5. `skills/orchestrate/templates/brief.md` line with `{e.g. docs/specs to read, how to explore; ...}`: change the placeholder text to `{e.g. docs/specs to read, how to explore; DB read-only if any; resolved roles — knowledge/tacit/explore; when explore=graphify: "explore: graphify — graphify explain \"\" --graph /graphify-out/graph.json | head -40 (lead, not evidence; graph reflects the integration base)"}`. Keep the file free of any bare `.orchestration/` path and of Hangul. +6. Create `tests/orchestrate-graph-explore.bats` with `setup()` (REPO_ROOT, SKILL, BRIEF, `SCRIPT="${REPO_ROOT}/scripts/graph-freshness.sh"`), `normalize_ws`, and `section_body ` helpers copied from tests/orchestrate-token-budget.bats, and these cases, each `doc-gate:` paired with a `doc-gate can fail:` fixture that `grep -v`s the asserted phrase: + - Preflight section body contains `graph-freshness.sh`, `AskUserQuestion` or `§ Asking the user`, `graphify update `, `never runs a full`, `never loads the graphify skill`, and the wiki slug `wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md` + - Phase 2 section body contains `head -40`, `-> connections;`, `lead, not evidence`, `--budget 800`, `graph-derived:` + - Phase 2 body does NOT contain `explain` followed by `--budget` on one normalized line (`! grep -qE 'explain[^|]*--budget'`) + - the whole SKILL.md does NOT contain `Skill(graphify` nor `/graphify query` + - Phase 3 step 2 text contains `/graphify-out/graph.json` + - brief.md contains `explore: graphify` and `/graphify-out/graph.json`; negative control: a fixture without it + - `scripts/graph-freshness.sh` referenced by Preflight exists and is executable (`[ -x "$SCRIPT" ]`) +## Deliverables +- skills/orchestrate/SKILL.md (modified: Preflight, Tool profile, Phase 2, Phase 3 step 2) +- skills/orchestrate/templates/brief.md (modified: tools_guidance placeholder) +- tests/orchestrate-graph-explore.bats (new) +## Verify +- `PATH=/opt/homebrew/bin:$PATH bats tests/orchestrate-graph-explore.bats tests/scripts.bats tests/session-prompt-paths.bats tests/orchestrate-token-budget.bats tests/orchestrate-ask-gate.bats` → all `ok` +- covers: R4, R5, R6 +## Out of scope +- wiki-plan and loop-implement SKILL.md text (task 04); tool-profile docs (05); templates/session-prompt.md (cksum-pinned, untouched). diff --git a/plans/graphify-explore-role/tasks/04-wiki-plan-and-loop-implement-text.md b/plans/graphify-explore-role/tasks/04-wiki-plan-and-loop-implement-text.md new file mode 100644 index 0000000..9307a63 --- /dev/null +++ b/plans/graphify-explore-role/tasks/04-wiki-plan-and-loop-implement-text.md @@ -0,0 +1,32 @@ +# Task 04: wiki-plan A2 paired evidence and loop-implement step 1/6 graph leads +## Objective +`skills/wiki-plan/SKILL.md` A2 states the paired-evidence rule for graph citations; `skills/loop-implement/SKILL.md` step 1 runs explain/path before opening source when `explore` is graphify and step 6 names graph-derived assumptions; both spans are doc-gated in `tests/orchestrate-graph-explore.bats`. +## Wiki pages (read these first, only these) +- wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md — use for: directives 2, 3, 6 (paired evidence, symbol-anchored commands, `graph-derived:` in the report) +- wiki/qa/process/completion-claims.md — use for: the "claim → required evidence → not evidence" framing of the A2 rule +- wiki/infrastructure/agent-orchestration/session-context-token-budget.md — use for: directive 4 (bound tool output) behind `head -40` / `--budget 800` +- wiki/testing/quality/checks-that-cannot-pass.md — use for: negative controls +## Inputs +- tests/orchestrate-graph-explore.bats from task 03 (helpers `normalize_ws`, `section_body`; append to it) +- Decisions that bind you: D5, D6, D8, D9, D11 +- Constraint: skills/wiki-plan/scripts/plan-gate.sh and tests/plan-gate.bats are not edited (R7) +## Steps +1. `skills/wiki-plan/SKILL.md`, Phase A, the A2 bullet that reads `` - `### Affected files` — every bullet needs an `evidence:` token backed by a real search (` — evidence: -> hits`). ``: append to that bullet: `` A code-graph hit (`explore` = graphify) may be cited only in the same bullet as that search, as a lead: ` — evidence: graphify explain -> connections; -> hits` — the graph chooses where to look, the search decides what is true (`wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md`). `` +2. `skills/loop-implement/SKILL.md`, loop step `1. Analyze + load refs`: after `Consult `knowledge`/`tacit`/`explore` if configured;` insert `when `explore` is graphify, run `graphify explain "" --graph /graphify-out/graph.json | head -40` (and `graphify path "" ""` for a suspected edge) BEFORE opening source files — its hits are leads to confirm by search, never evidence;` keeping the code-block column alignment of the neighbouring lines. +3. Same file, step `6. Self-review + refactor`: append to that step's text `; when `explore` is graphify, list every assumption taken from the graph and not confirmed by a search as `graph-derived: ` (or `graph-derived: none`) on the task report's NOTES line`. +4. Same file, `## Tool profile (pluggable)` paragraph: after `` `explore` (code/symbol search) `` add ` — a fresh graphify graph is the recommended cli; call the binary directly and never load the graphify skill document (1,300+ lines), see `references/tool-profile.md``. +5. Append to `tests/orchestrate-graph-explore.bats` (add `WIKI_PLAN` and `LOOP` paths in `setup()`): + - `doc-gate: wiki-plan A2 pairs a graph citation with a search in one bullet` — the A2 span (from `**A2. Ground truth**` to `**A3.`) contains `graphify explain -> connections; -> hits` and `lead`; negative control strips `graphify explain` + - `doc-gate: loop-implement step 1 runs explain/path before opening source when explore is graphify` — the text between `1. Analyze` and `3. Write tests` contains `graphify explain`, `head -40`, `BEFORE opening source`; negative control + - `doc-gate: loop-implement step 6 names graph-derived assumptions` — the text between `6. Self-review` and `6.5` contains `graph-derived:`; negative control + - `doc-gate: loop-implement never loads the graphify skill document` — file contains `never load the graphify skill document` and does not contain `Skill(graphify` + - `doc-gate: plan-gate.sh is untouched by the A2 wording` — `git -C "$REPO_ROOT" diff --quiet HEAD -- skills/wiki-plan/scripts/plan-gate.sh` exits 0 (skip with `skip` if not a git checkout) +## Deliverables +- skills/wiki-plan/SKILL.md (modified: one A2 bullet) +- skills/loop-implement/SKILL.md (modified: tool-profile paragraph, step 1, step 6) +- tests/orchestrate-graph-explore.bats (modified: cases appended) +## Verify +- `PATH=/opt/homebrew/bin:$PATH bats tests/orchestrate-graph-explore.bats tests/plan-gate.bats` → all `ok` +- covers: R7, R8 +## Out of scope +- orchestrate SKILL.md / brief.md (task 03); tool-profile docs (05); plan-gate.sh (never). diff --git a/plans/graphify-explore-role/tasks/05-tool-profile-docs-and-full-verify.md b/plans/graphify-explore-role/tasks/05-tool-profile-docs-and-full-verify.md new file mode 100644 index 0000000..a728c00 --- /dev/null +++ b/plans/graphify-explore-role/tasks/05-tool-profile-docs-and-full-verify.md @@ -0,0 +1,59 @@ +# Task 05: tool-profile docs for the graphify explore config, then the full-suite and wiki verification +## Objective +`references/tool-profile.md` documents `explore` = graphify with the freshness gate and the paired-evidence rule; `examples/tools.example.json` shows the entry; the full bats suite, the wiki structure checker, and the prohibition checker are green with graphify absent from PATH. +## Wiki pages (read these first, only these) +- wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md — use for: the subsection's wording (directives 1, 2, 5, 7) +- wiki/qa/process/completion-claims.md — use for: the final report claims exactly what the fresh runs show +## Inputs +- Deliverables of tasks 01–04 (script, SKILL texts, bats files) on disk +- Decisions that bind you: D8, D10, D12 +- Constraint: examples/tools.example.json currently has an `explore` entry (`rtb-sourcecode`); replace it +## Steps +1. `references/tool-profile.md`: in the `## Roles` table, change the `explore` row's Purpose cell to `locating code, symbols, call sites (read-only); optionally a fresh graphify code graph as a lead` and its Used-by cell to `loop-implement step 1 (Analyze) + step 6; orchestrate Preflight (freshness) + Phase 2 (leads)`. +2. Same file, before `### `research` — external best-practice/pitfall search`, add: + ``` + ### `explore` — a graphify code graph as the orientation layer (optional) + + With graphify installed (`pipx install graphifyy`) and a graph built once + (`/graphify ` in any assistant, or `graphify update ` for the + AST-only refresh), configure: + + ```json + { + "explore": { + "kind": "cli", + "ref": "graphify", + "how": "graphify explain \"\" --graph /graphify-out/graph.json | head -40; graphify path \"\" \"\" --graph /graphify-out/graph.json", + "when": "step 1 before opening source, and orchestrate Phase 2 — only after scripts/graph-freshness.sh printed fresh" + } + } + ``` + + Rules the skills apply (basis: + `wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md`): + - **Freshness first.** `sh ${CLAUDE_PLUGIN_ROOT}/scripts/graph-freshness.sh ` + exits 0 `fresh`, 2 `stale `, 3 `absent`, 4 `cannot-evaluate `; + the graph is used only on 0. On 2 the human chooses whether to run + `graphify update `; the plugin never runs a build itself. + - **Lead, not evidence.** A graph hit enters a plan only paired with a + search: `graphify explain -> connections; grep -rn + src -> hits`. Unconfirmed assumptions are reported as `graph-derived:`. + - **CLI only, bounded output.** `explain`/`path` through `head -40`, `query` + with `--budget 800`; the graphify skill document is never loaded. + - **Worktrees.** Workers point `--graph` at the main checkout's + `graphify-out/graph.json` (gitignored, so no worktree carries it); the + graph reflects the integration base. + Unset, `explore` resolves to `default` and nothing above runs. + ``` +3. `examples/tools.example.json`: replace the `explore` object with `{"kind": "cli", "ref": "graphify", "how": "graphify explain \"\" --graph /graphify-out/graph.json | head -40; graphify path \"\" \"\" --graph /graphify-out/graph.json", "when": "step 1 before opening source, and orchestrate Phase 2 leads — only after scripts/graph-freshness.sh printed fresh; a hit is a lead to confirm by search"}` and validate with `jq -e . examples/tools.example.json`. +4. Run the three verifications below with graphify absent: prefix the suite with `GRAPHIFY_BIN=graphify-absent-xyz` (the suite's own tests set their own `GRAPHIFY_BIN`, so this only proves nothing else depends on the CLI) and a `PATH` that excludes `~/.local/bin`. +## Deliverables +- references/tool-profile.md (modified) +- examples/tools.example.json (modified) +## Verify +- `jq -e '.explore.ref == "graphify"' examples/tools.example.json` → `true` +- `PATH=/opt/homebrew/bin:/usr/bin:/bin bats tests/` → rc 0, `not ok` count 0, `ok` count ≥ 1007 + the new cases +- `node scripts/wiki-structure-checks.js wiki` → `findings: 0`; `node scripts/wiki-lint-prohibitions.js wiki` → `violations: 0` +- covers: R9 (docs half), R10, R12 +## Out of scope +- Any SKILL.md or script edit — if a verification fails here, the fix belongs to the task that owns that file (01–04), re-run through loop-implement 7b. diff --git a/references/tool-profile.md b/references/tool-profile.md index ad4ee8e..ea447cf 100644 --- a/references/tool-profile.md +++ b/references/tool-profile.md @@ -14,7 +14,7 @@ the generic built-in behavior. Nothing breaks when nothing is configured. | `knowledge` | domain facts, business policy, code/status values | loop-implement step 1 (Analyze); orchestrate Phase 0/2 | | `tacit` | past incidents, edge cases, coupling/danger zones | loop-implement step 1 (Analyze) + step 6 (Self-review) | | `verify` | running the project's tests / build / QA checks | loop-implement step 5 (Run); orchestrate Phase 5 (integration) | -| `explore` | locating code, symbols, call sites (read-only) | loop-implement step 1 (Analyze) | +| `explore` | locating code, symbols, call sites (read-only); optionally a fresh graphify code graph as a lead | loop-implement step 1 (Analyze) + step 6; orchestrate Preflight (freshness) + Phase 2 (leads) | | `design` | visual/UI spec for FE/UI tasks (e.g. a Figma link in the issue) | orchestrate Phase 0/2 + brief; loop-implement step 1 (Analyze) | | `research` | external best-practice/pitfall search for a feature's domain, and evidence for `[no-wiki]` decisions | wiki-plan Phase A4 (external research), Phase A3 (spike research), Phase B (`[no-wiki]` grounding) | @@ -38,6 +38,41 @@ tasks skip it even when configured. Example mapping to a Figma MCP: "when": "issue has a Figma link AND the change touches UI; skip for backend-only tasks" } } ``` +### `explore` — a graphify code graph as the orientation layer (optional) + +With graphify installed (`pipx install graphifyy`) and a graph built once +(`/graphify ` in any assistant, or `graphify update ` for the +AST-only refresh), configure: + +```json +{ + "explore": { + "kind": "cli", + "ref": "graphify", + "how": "graphify explain \"\" --graph /graphify-out/graph.json | head -40; graphify path \"\" \"\" --graph /graphify-out/graph.json", + "when": "step 1 before opening source, and orchestrate Phase 2 — only after scripts/graph-freshness.sh printed fresh" + } +} +``` + +Rules the skills apply (basis: +`wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md`): + +- **Freshness first.** `sh ${CLAUDE_PLUGIN_ROOT}/scripts/graph-freshness.sh ` + exits 0 `fresh`, 2 `stale `, 3 `absent`, 4 `cannot-evaluate `; + the graph is used only on 0. On 2 the human chooses whether to run + `graphify update `; the plugin never runs a build itself. +- **Lead, not evidence.** A graph hit enters a plan only paired with a search: + `graphify explain -> connections; grep -rn src -> + hits`. Unconfirmed assumptions are reported as `graph-derived:`. +- **CLI only, bounded output.** `explain`/`path` through `head -40`, `query` + with `--budget 800`; the graphify skill document is never loaded. +- **Worktrees.** Workers point `--graph` at the main checkout's + `graphify-out/graph.json` (gitignored, so no worktree carries it); the graph + reflects the integration base. + +Unset, `explore` resolves to `default` and nothing above runs. + ### `research` — external best-practice/pitfall search (optional, fixed interpretation order) `research` backs `wiki-plan`'s Phase A4 (external research: best practices and diff --git a/scripts/graph-freshness.sh b/scripts/graph-freshness.sh new file mode 100755 index 0000000..1cc60c7 --- /dev/null +++ b/scripts/graph-freshness.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +# graph-freshness.sh — is the repo's graphify code graph fresh enough to use as a lead? +# +# usage: graph-freshness.sh [--graph ] +# +# stdout: exactly one line the caller branches on +# fresh exit 0 no commit touched a file after graph.json's mtime +# stale exit 2 N distinct paths changed by commits after the mtime +# absent exit 3 no graph.json (default /graphify-out/graph.json) +# cannot-evaluate exit 4 reason: usage | no-cli | bad-graph | not-git +# stderr: on exit 2 one hint line naming `graphify update `; on exit 4 the reason. +# +# Never runs graphify. It only checks that `${GRAPHIFY_BIN:-graphify}` resolves via +# `command -v`, because the CLI exits 0 on a missing node, a missing file, and a +# JSON decode error, so its exit status carries no signal (wiki: infrastructure/ +# agent-orchestration/code-graph-as-orientation-layer, edge case row 3). +# Uncommitted edits are not counted — freshness is relative to commits only. +# A commit in the same second as the mtime counts as stale (git --since is inclusive). +set -euo pipefail + +usage_fail() { + printf 'usage: graph-freshness.sh [--graph ]\n' >&2 + printf 'cannot-evaluate usage\n' + exit 4 +} + +root="" +graph_override="" +while [ $# -gt 0 ]; do + case "$1" in + --graph) + [ $# -ge 2 ] || usage_fail + graph_override="$2"; shift 2 ;; + --*) usage_fail ;; + *) + [ -z "$root" ] || usage_fail + root="$1"; shift ;; + esac +done +[ -n "$root" ] && [ -d "$root" ] || usage_fail + +graph="${graph_override:-$root/graphify-out/graph.json}" +if [ ! -f "$graph" ]; then + printf 'absent\n' + exit 3 +fi + +bin="${GRAPHIFY_BIN:-graphify}" +if ! command -v "$bin" >/dev/null 2>&1; then + printf 'graphify CLI not found (GRAPHIFY_BIN=%s)\n' "$bin" >&2 + printf 'cannot-evaluate no-cli\n' + exit 4 +fi + +if ! jq -e '.nodes | type == "array"' "$graph" >/dev/null 2>&1; then + printf 'unreadable graph: %s\n' "$graph" >&2 + printf 'cannot-evaluate bad-graph\n' + exit 4 +fi + +if ! git -C "$root" rev-parse --is-inside-work-tree >/dev/null 2>&1; then + printf 'not a git repository: %s\n' "$root" >&2 + printf 'cannot-evaluate not-git\n' + exit 4 +fi + +# mtime as epoch: BSD/GNU `date -r FILE`, then GNU stat, then BSD stat. +epoch=$(date -r "$graph" +%s 2>/dev/null || stat -c %Y "$graph" 2>/dev/null || stat -f %m "$graph") + +# Paths under the graph's own output directory are not code changes: a repo that +# commits graphify-out/ would otherwise report the rebuild commit itself as stale. +root_abs=$(cd "$root" && pwd -P) +gdir_abs=$(cd "$(dirname "$graph")" && pwd -P) +gdir_rel="" +case "$gdir_abs/" in + "$root_abs"/*) gdir_rel="${gdir_abs#"$root_abs"/}/" ;; +esac + +# An unborn HEAD (git init, no commits yet) has nothing newer than the graph; +# `git log` would exit 128 there, outside the contract. +if ! git -C "$root" rev-parse -q --verify HEAD >/dev/null 2>&1; then + printf 'fresh\n' + exit 0 +fi + +# -m --first-parent: a merge commit lists what it landed on this branch (plain +# --name-only prints nothing for merges, under-counting merged changes). +n=$(git -C "$root" log --since="@$epoch" -m --first-parent --name-only --format='' \ + | sed '/^$/d' \ + | awk -v p="$gdir_rel" 'p == "" || index($0, p) != 1' \ + | sort -u | wc -l | tr -d ' ') + +if [ "$n" -eq 0 ]; then + printf 'fresh\n' + exit 0 +fi + +printf 'hint: graphify update "%s" # AST-only, no LLM; rebuilds graphify-out/graph.json from the current checkout\n' "$root" >&2 +printf 'stale %s\n' "$n" +exit 2 diff --git a/scripts/resolve-tools.sh b/scripts/resolve-tools.sh index 28ce74b..97f2e2c 100755 --- a/scripts/resolve-tools.sh +++ b/scripts/resolve-tools.sh @@ -39,7 +39,7 @@ DEFAULTS='{ "knowledge": {"kind":"default","when":"domain facts, policy, code/status values (step 1)"}, "tacit": {"kind":"default","when":"past incidents, edge cases, danger zones (step 1/6)"}, "verify": {"kind":"default","when":"running tests / build / QA checks (step 5)"}, - "explore": {"kind":"default","when":"locating code, symbols, call sites (step 1)"}, + "explore": {"kind":"default","when":"locating code, symbols, call sites (step 1); with a fresh graphify graph, configure {\"kind\":\"cli\",\"ref\":\"graphify\"} — see references/tool-profile.md"}, "design": {"kind":"default","when":"visual/UI spec for FE/UI tasks, e.g. a Figma link in the issue — read the referenced design before implementing (orchestrate Phase 0/2; loop-implement step 1)"}, "research": {"kind":"default","when":"external best-practice/insight search during plan phases A/B; resolution order owned by loop-implement SKILL (configured tool -> brave-search MCP -> built-in WebSearch -> ABANDON)"} }' diff --git a/skills/loop-implement/SKILL.md b/skills/loop-implement/SKILL.md index 7e41457..a094b4c 100644 --- a/skills/loop-implement/SKILL.md +++ b/skills/loop-implement/SKILL.md @@ -44,9 +44,12 @@ pass executes decisions instead of guessing them. See step 2 below. The *other* steps can use environment-specific tools through named **capability roles**: `knowledge` (domain facts / policy / code values), `tacit` (past incidents, edge cases, danger zones), `verify` (the project's test / build / QA -command), `explore` (code/symbol search), `design` (visual/UI spec for a UI -task, e.g. a Figma link), and `research` (external best-practice/pitfall search -for wiki-plan Phase A4/A3 and `[no-wiki]` grounding in Phase B — see below). +command), `explore` (code/symbol search — a fresh graphify graph is the +recommended cli; call the binary directly and never load the graphify skill +document (1,300+ lines), see `references/tool-profile.md`), `design` +(visual/UI spec for a UI task, e.g. a Figma link), and `research` (external +best-practice/pitfall search for wiki-plan Phase A4/A3 and `[no-wiki]` +grounding in Phase B — see below). (`plan` is intentionally absent — see above.) Resolve them once at the start: ``` @@ -107,7 +110,12 @@ starting the next, so a downstream task always builds on a verified upstream one task's Inputs (confirm each exists; a missing Input is a plan defect -> 7b, not a stand-in). List the test scenarios. Consult `knowledge`/`tacit`/`explore` if - configured; for a UI task read the ``/`design`. [TDD step 1 / PDCA Plan] + configured; when `explore` is graphify, run + `graphify explain "" --graph /graphify-out/graph.json | head -40` + (and `graphify path "" ""` for a suspected edge) + BEFORE opening source files — its hits are leads to + confirm by search, never evidence; for a UI task read + the ``/`design`. [TDD step 1 / PDCA Plan] 3. Write tests (Red) — failing test(s) BEFORE code, from the task's Verify/Objective. If test-first is impractical (exploratory UI), fix the acceptance criteria / verify command first. [TDD test-first] @@ -119,7 +127,11 @@ starting the next, so a downstream task always builds on a verified upstream one preserve failure output. Use the `verify` role if set. [PDCA Check / self-testing code] 6. Self-review + refactor — bugs, edge cases, resource leaks, input validation, unused code; re-check against the named pages' edge-case - rows and the `tacit` danger zones if configured. [TDD Refactor / self-review] + rows and the `tacit` danger zones if configured; when + `explore` is graphify, list every assumption taken from + the graph and not confirmed by a search as + `graph-derived: ` (or `graph-derived: none`) + on the task report's NOTES line. [TDD Refactor / self-review] 6.5 Floor + independent audit — REQUIRED: run the test-floor.sh mechanical pre-gate first; exit 3 is an immediate VERDICT: FAIL (loop to step 3) without spending the auditor call. diff --git a/skills/orchestrate/SKILL.md b/skills/orchestrate/SKILL.md index d3b0301..452f685 100644 --- a/skills/orchestrate/SKILL.md +++ b/skills/orchestrate/SKILL.md @@ -64,7 +64,8 @@ Resolve the pluggable tool profile once up front: `sh ${CLAUDE_PLUGIN_ROOT}/scripts/resolve-tools.sh --summary`. It maps capability roles — `intake` (issue-tracker work-list source), `knowledge` (domain/policy), `tacit` (incidents/danger zones), `verify` (test/build/QA -command), `explore` (code search), `design` (visual/UI spec, e.g. Figma) — to +command), `explore` (code search; a fresh graphify graph when Preflight says +so), `design` (visual/UI spec, e.g. Figma) — to whatever tools this installation has, or to generic defaults when unset (optional, layered per-user then per-repo; see `references/tool-profile.md`). Use `knowledge`/`tacit` yourself during Clarify/Decompose, and write each task's @@ -160,6 +161,22 @@ advisory only; this skill must hard-require them). For a missing tmux: with the user's consent, install it (macOS: `brew install tmux`; otherwise advise) before launching sessions. Never auto-install without consent. +**Code-graph freshness (optional `explore` lead).** Run +`sh ${CLAUDE_PLUGIN_ROOT}/scripts/graph-freshness.sh ` once, bare (read +its exit code on the next line, never in a pipe). It never runs graphify +itself. Branch on the code: **0** (`fresh`) — set `explore` to graphify for +this run and write the row from Phase 3 step 2 into every brief; **2** +(`stale `) — ask ONE chooser (§ Asking the user): "graphify graph is stale +( files changed since it was built) — run `graphify update ` now +(AST-only, seconds, no LLM), or continue without the graph?" with *update* as +the recommended answer; run the update only on an explicit yes and re-run the +freshness check afterwards; **3**/**4** (`absent` / `cannot-evaluate `) +— print one line saying the graph is not in use and continue exactly as +before. The coordinator never runs a full `/graphify` build and never loads +the graphify skill document: the CLI is the whole interface +(`graphify --help`). Basis: +`wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md`. + **Coordinator permissions (tmux substrate).** `launch-session.sh` starts each worker as `claude --permission-mode bypassPermissions` — the exact surface an auto-mode permission classifier hard-flags as privilege escalation. It cannot @@ -283,6 +300,21 @@ Write BOTH artifacts: `conflict-matrix.md` for humans and `.orchestration/graph.json` for the scheduler. A markdown table is not machine readable. +**Graph leads (only when Preflight set `explore` to graphify).** For each +candidate task, run `graphify explain "" --graph +/graphify-out/graph.json | head -40` on the symbols the task names, and +`graphify path "" "" --graph /graphify-out/graph.json` for a +suspected edge between two tasks. The printed connections seed the task's +affected files and its shared surfaces; the disjointness test in the conflict +matrix runs on the confirmed sets, never on the graph's guesses. A graph hit is +a lead, not evidence: every file it suggests is confirmed by a search before it +enters `files`, and the record pairs both in one line — +`graphify explain -> connections; grep -rn src -> hits`. +Ask symbol-anchored questions only; a free-text `graphify query` is for +orientation (`--budget 800`) and never derives a file set. Any assumption taken +from the graph and not confirmed is recorded on the blackboard as +`graph-derived: `. + ```json { "tasks": [ { "id": "t1", "deps": [], "files": ["src/auth/**"], "outputs": ["AuthToken"] }, @@ -358,7 +390,12 @@ answered. `` and `` — then launch session and watch until `plan_ready` (step 3 below). **Write the brief at dispatch time.** It only needs the signatures this task consumes, and by then those are - `approved`, so they're settled. + `approved`, so they're settled. When `explore` is graphify, the + `` row is: `explore: graphify — graphify explain + "" --graph /graphify-out/graph.json | head -40 (lead, + not evidence; the graph reflects the integration base, and a worktree + carries no graphify-out)` with `` the absolute main-checkout + path. - **3** Collect `plans/.md` when each session reaches `plan_ready`. 3. For each planned task, deliver §2 (implement) with `scripts/send-prompt.sh send lo- ""` (tmux, see Phase 4 for exit-code branch logic), or diff --git a/skills/orchestrate/templates/brief.md b/skills/orchestrate/templates/brief.md index 6c8adf3..2d56832 100644 --- a/skills/orchestrate/templates/brief.md +++ b/skills/orchestrate/templates/brief.md @@ -50,7 +50,7 @@ specific tags below as authority. - {e.g. docs/specs to read, how to explore; DB read-only if any; resolved roles — knowledge/tacit/plan} + {e.g. docs/specs to read, how to explore; DB read-only if any; resolved roles — knowledge/tacit/explore; when explore=graphify: "explore: graphify — graphify explain \"\" --graph /graphify-out/graph.json | head -40 (lead, not evidence; graph reflects the integration base)"} {local rules; surgical changes only on shared files} token hygiene: bound tool output (tail/head, ranged reads); delegate visual checks to a subagent — see wiki/infrastructure/agent-orchestration/session-context-token-budget.md diff --git a/skills/wiki-plan/SKILL.md b/skills/wiki-plan/SKILL.md index 7f2fd39..1c5baf3 100644 --- a/skills/wiki-plan/SKILL.md +++ b/skills/wiki-plan/SKILL.md @@ -83,7 +83,12 @@ token remains, so leaving a question unresolved is what blocks entry to Phase B record one command that can be copy-pasted and re-run as-is; gate-A re-runs this exact command. - `### Affected files` — every bullet needs an `evidence:` token backed by a - real search (` — evidence: -> hits`). + real search (` — evidence: -> hits`). A + code-graph hit (`explore` = graphify) may be cited only in the same bullet + as that search, as a lead: ` — evidence: graphify explain -> + connections; -> hits` — the graph chooses where to + look, the search decides what is true + (`wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md`). - `Constraints` — every pinned file, protected span, and CI requirement this feature touches, each with the command used to check it; if none apply, say so explicitly (an empty section fails the gate). diff --git a/tests/graph-freshness.bats b/tests/graph-freshness.bats new file mode 100644 index 0000000..b033bbf --- /dev/null +++ b/tests/graph-freshness.bats @@ -0,0 +1,222 @@ +#!/usr/bin/env bats +# Tests for scripts/graph-freshness.sh — is the repo's graphify code graph +# fresh enough to use as a planning lead? +# +# Contract under test (wiki/infrastructure/agent-orchestration/ +# code-graph-as-orientation-layer.md, directive 1 + edge case rows 3 and 6): +# stdout one token line exit +# fresh 0 no commit touched a file after graph.json's mtime +# stale 2 N distinct paths changed by later commits +# absent 3 no graph.json at /graphify-out/ (or --graph) +# cannot-evaluate 4 usage | no-cli | bad-graph | not-git +# +# graphify itself is never executed: the CLI exits 0 on a missing node, a +# missing file, and a JSON decode error, so its status carries no signal. The +# suite therefore runs with GRAPHIFY_BIN pointing at `true` (resolves via +# command -v on both CI runners) and at `false` (would fail if ever run), and +# never needs a graphify install or a chmod +x stub. + +setup() { + SCRIPT="${BATS_TEST_DIRNAME}/../scripts/graph-freshness.sh" + export GRAPHIFY_BIN=true +} + +# A git repo whose only commit is dated 2020 — far older than any graph file +# the test writes afterwards, so "graph newer than every commit" needs no sleep. +make_repo() { + git init -q "$1" + git -C "$1" config user.email t@example.com + git -C "$1" config user.name t + printf 'a\n' > "$1/a.txt" + # Like a real repo: the graph output dir is gitignored (spike S3), so + # `git add -A` in later cases never sweeps graph.json into a commit. + printf 'graphify-out/\n' > "$1/.gitignore" + git -C "$1" add -A + GIT_AUTHOR_DATE='2020-01-01T00:00:00Z' GIT_COMMITTER_DATE='2020-01-01T00:00:00Z' \ + git -C "$1" commit -q -m init +} + +make_graph() { + mkdir -p "$1/graphify-out" + printf '{"nodes":[{"id":"a"}],"links":[]}\n' > "$1/graphify-out/graph.json" +} + +# --- error: usage ------------------------------------------------------------ + +@test "error: no argument -> exit 4, cannot-evaluate usage on stdout, usage on stderr" { + run bash "$SCRIPT" + [ "$status" -eq 4 ] + [[ "$output" == *'cannot-evaluate usage'* ]] + [[ "$output" == *'usage: graph-freshness.sh [--graph ]'* ]] +} + +@test "error: root that is not a directory -> exit 4 cannot-evaluate usage" { + run bash "$SCRIPT" "$BATS_TEST_TMPDIR/does-not-exist" + [ "$status" -eq 4 ] + [[ "$output" == *'cannot-evaluate usage'* ]] +} + +@test "error: unknown flag -> exit 4 cannot-evaluate usage" { + root="$BATS_TEST_TMPDIR/r"; make_repo "$root"; make_graph "$root" + run bash "$SCRIPT" "$root" --bogus x + [ "$status" -eq 4 ] + [[ "$output" == *'cannot-evaluate usage'* ]] +} + +# --- normal ------------------------------------------------------------------ + +@test "normal: no graph.json -> exit 3, stdout absent" { + root="$BATS_TEST_TMPDIR/r"; make_repo "$root" + run bash "$SCRIPT" "$root" + [ "$status" -eq 3 ] + [ "$output" = "absent" ] +} + +@test "normal: graph newer than every commit -> exit 0, stdout fresh, stderr empty" { + root="$BATS_TEST_TMPDIR/r"; make_repo "$root"; make_graph "$root" + run bash "$SCRIPT" "$root" + [ "$status" -eq 0 ] + [ "$output" = "fresh" ] + err="$(bash "$SCRIPT" "$root" 2>&1 >/dev/null)" + [ -z "$err" ] +} + +@test "normal: a commit after the graph touching two files -> exit 2, stdout \"stale 2\", stderr hint names graphify update" { + root="$BATS_TEST_TMPDIR/r"; make_repo "$root"; make_graph "$root" + printf 'b\n' > "$root/b.txt"; printf 'c\n' > "$root/c.txt" + git -C "$root" add -A; git -C "$root" commit -q -m later + run bash "$SCRIPT" "$root" + [ "$status" -eq 2 ] + [[ "$output" == *'stale 2'* ]] + [[ "$output" == *'graphify update'* ]] + out="$(bash "$SCRIPT" "$root" 2>/dev/null || true)" + [ "$out" = "stale 2" ] +} + +# --- error: cannot-evaluate -------------------------------------------------- + +@test "error: graph.json is not JSON -> exit 4 cannot-evaluate bad-graph, stderr names the file" { + root="$BATS_TEST_TMPDIR/r"; make_repo "$root" + mkdir -p "$root/graphify-out"; printf '{bad\n' > "$root/graphify-out/graph.json" + run bash "$SCRIPT" "$root" + [ "$status" -eq 4 ] + [[ "$output" == *'cannot-evaluate bad-graph'* ]] + [[ "$output" == *'graphify-out/graph.json'* ]] +} + +@test "error: graph.json without a nodes array -> exit 4 cannot-evaluate bad-graph" { + root="$BATS_TEST_TMPDIR/r"; make_repo "$root" + mkdir -p "$root/graphify-out"; printf '{"foo":1}\n' > "$root/graphify-out/graph.json" + run bash "$SCRIPT" "$root" + [ "$status" -eq 4 ] + [[ "$output" == *'cannot-evaluate bad-graph'* ]] +} + +@test "error: GRAPHIFY_BIN does not resolve -> exit 4 cannot-evaluate no-cli" { + root="$BATS_TEST_TMPDIR/r"; make_repo "$root"; make_graph "$root" + GRAPHIFY_BIN="graphify-missing-xyz-$$" run bash "$SCRIPT" "$root" + [ "$status" -eq 4 ] + [[ "$output" == *'cannot-evaluate no-cli'* ]] + [[ "$output" == *"graphify-missing-xyz-$$"* ]] +} + +@test "error: root is not a git repository -> exit 4 cannot-evaluate not-git" { + root="$BATS_TEST_TMPDIR/plain"; mkdir -p "$root"; make_graph "$root" + # BATS_TEST_TMPDIR sits outside any repository, so `git -C "$root"` cannot + # walk up into one; if that ever changes this case fails loudly (not-git + # would read as fresh), never falsely passes. + run bash "$SCRIPT" "$root" + [ "$status" -eq 4 ] + [[ "$output" == *'cannot-evaluate not-git'* ]] +} + +# --- boundary ---------------------------------------------------------------- + +@test "boundary: uncommitted edits to a tracked file do not make the graph stale" { + root="$BATS_TEST_TMPDIR/r"; make_repo "$root"; make_graph "$root" + printf 'edited\n' >> "$root/a.txt" + run bash "$SCRIPT" "$root" + [ "$status" -eq 0 ] + [ "$output" = "fresh" ] +} + +@test "boundary: an empty nodes array is a readable graph -> fresh" { + root="$BATS_TEST_TMPDIR/r"; make_repo "$root" + mkdir -p "$root/graphify-out"; printf '{"nodes":[]}\n' > "$root/graphify-out/graph.json" + run bash "$SCRIPT" "$root" + [ "$status" -eq 0 ] + [ "$output" = "fresh" ] +} + +@test "boundary: --graph override is used when the default path is absent" { + root="$BATS_TEST_TMPDIR/r"; make_repo "$root" + printf '{"nodes":[{"id":"x"}]}\n' > "$BATS_TEST_TMPDIR/elsewhere.json" + run bash "$SCRIPT" "$root" --graph "$BATS_TEST_TMPDIR/elsewhere.json" + [ "$status" -eq 0 ] + [ "$output" = "fresh" ] +} + +@test "boundary: a commit touching only the graph output dir does not make the graph stale" { + root="$BATS_TEST_TMPDIR/r"; make_repo "$root"; make_graph "$root" + # A repo that tracks its graph: the rebuild commit itself must not read as stale. + git -C "$root" add -f graphify-out/graph.json; git -C "$root" commit -q -m "track graph" + run bash "$SCRIPT" "$root" + [ "$status" -eq 0 ] + [ "$output" = "fresh" ] +} + +@test "boundary: a repo with no commits yet (unborn HEAD) -> fresh, not a git error" { + root="$BATS_TEST_TMPDIR/unborn"; git init -q "$root"; make_graph "$root" + run bash "$SCRIPT" "$root" + [ "$status" -eq 0 ] + [ "$output" = "fresh" ] +} + +@test "boundary: an old branch merged after the graph was built is counted as stale" { + root="$BATS_TEST_TMPDIR/r"; make_repo "$root"; make_graph "$root" + # The topic commit itself predates the graph (2020) — only the merge commit is + # newer. Plain `git log --name-only` prints nothing for a merge commit, so + # without `-m --first-parent` this would read as fresh. + git -C "$root" checkout -q -b topic + printf 'b\n' > "$root/b.txt"; git -C "$root" add -A + GIT_AUTHOR_DATE='2020-01-02T00:00:00Z' GIT_COMMITTER_DATE='2020-01-02T00:00:00Z' \ + git -C "$root" commit -q -m topic + git -C "$root" checkout -q - + git -C "$root" merge -q --no-ff -m merge topic + out="$(bash "$SCRIPT" "$root" 2>/dev/null || true)" + [ "$out" = "stale 1" ] +} + +@test "boundary: stale count is distinct paths, not commits or lines" { + root="$BATS_TEST_TMPDIR/r"; make_repo "$root"; make_graph "$root" + printf 'b\n' > "$root/b.txt"; git -C "$root" add -A; git -C "$root" commit -q -m one + printf 'b2\n' > "$root/b.txt"; git -C "$root" add -A; git -C "$root" commit -q -m two + out="$(bash "$SCRIPT" "$root" 2>/dev/null || true)" + [ "$out" = "stale 1" ] +} + +# --- state ------------------------------------------------------------------- + +@test "state: stdout carries exactly one line on every outcome" { + fresh="$BATS_TEST_TMPDIR/fresh"; make_repo "$fresh"; make_graph "$fresh" + stale="$BATS_TEST_TMPDIR/stale"; make_repo "$stale"; make_graph "$stale" + printf 'b\n' > "$stale/b.txt"; git -C "$stale" add -A; git -C "$stale" commit -q -m later + absent="$BATS_TEST_TMPDIR/absent"; make_repo "$absent" + bad="$BATS_TEST_TMPDIR/bad"; make_repo "$bad" + mkdir -p "$bad/graphify-out"; printf 'nope\n' > "$bad/graphify-out/graph.json" + for r in "$fresh" "$stale" "$absent" "$bad"; do + out="$(bash "$SCRIPT" "$r" 2>/dev/null || true)" + [ "$(printf '%s\n' "$out" | wc -l | tr -d ' ')" -eq 1 ] + # Non-vacuous: the one line is a contract token, never empty text. + [[ "$out" =~ ^(fresh|stale\ [0-9]+|absent|cannot-evaluate\ [a-z-]+)$ ]] + done +} + +@test "state: the graphify binary is never executed" { + root="$BATS_TEST_TMPDIR/r"; make_repo "$root"; make_graph "$root" + # `false` resolves via command -v but exits 1 if run — a fresh verdict + # proves the script only resolved the name. + GRAPHIFY_BIN=false run bash "$SCRIPT" "$root" + [ "$status" -eq 0 ] + [ "$output" = "fresh" ] +} diff --git a/tests/orchestrate-graph-explore.bats b/tests/orchestrate-graph-explore.bats new file mode 100644 index 0000000..dd0ccd1 --- /dev/null +++ b/tests/orchestrate-graph-explore.bats @@ -0,0 +1,237 @@ +#!/usr/bin/env bats +# Doc-gates for the graphify `explore` role — a freshness-gated, lead-not-evidence +# orientation layer (wiki/infrastructure/agent-orchestration/ +# code-graph-as-orientation-layer.md). Each gate asserts the span that makes the +# coordinator/worker behave, scoped to the section that owns it, and is paired +# with a negative control that strips the span from a fixture copy and shows the +# same check failing (checks-that-cannot-pass, +# wiki/testing/quality/checks-that-cannot-pass.md). +# +# Covered files: +# skills/orchestrate/SKILL.md Preflight gate, Phase 2 leads, Phase 3 brief row +# skills/orchestrate/templates/brief.md tools_guidance explore row +# skills/wiki-plan/SKILL.md A2 paired-evidence rule (task 04) +# skills/loop-implement/SKILL.md step 1 / step 6 graph leads (task 04) + +setup() { + REPO_ROOT="${BATS_TEST_DIRNAME}/.." + SKILL="${REPO_ROOT}/skills/orchestrate/SKILL.md" + BRIEF="${REPO_ROOT}/skills/orchestrate/templates/brief.md" + WIKI_PLAN="${REPO_ROOT}/skills/wiki-plan/SKILL.md" + LOOP="${REPO_ROOT}/skills/loop-implement/SKILL.md" + SCRIPT="${REPO_ROOT}/scripts/graph-freshness.sh" +} + +WIKI_SLUG='wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md' + +# Collapse hard-wrapped prose to one line so substring checks survive wrapping +# (same technique as tests/orchestrate-token-budget.bats). +normalize_ws() { + printf '%s' "$1" | tr '\n' ' ' | tr -s ' ' +} + +# The body of one `## ` section of a file — a file-wide grep would pass +# vacuously when the phrase also appears elsewhere. +section_body() { # + awk -v h="$2" ' + index($0, h) == 1 { f=1; next } + /^## / { f=0 } + f' "$1" +} + +flat_section() { normalize_ws "$(section_body "$1" "$2")"; } + +# --- Preflight: freshness gate + consent-gated update (D4) -------------------- + +@test "doc-gate: Preflight runs graph-freshness.sh and gates the update on a chooser" { + flat="$(flat_section "$SKILL" '## Preflight')" + [[ "$flat" == *'graph-freshness.sh'* ]] + [[ "$flat" == *'Asking the user'* ]] + [[ "$flat" == *'graphify update '* ]] + [[ "$flat" == *'never runs a full'* ]] + [[ "$flat" == *'never loads the graphify skill'* ]] + [[ "$flat" == *"$WIKI_SLUG"* ]] +} + +@test "doc-gate can fail: a Preflight without the freshness paragraph does not match" { + fixture="${BATS_TEST_TMPDIR}/no-freshness.md" + grep -v 'graph-freshness.sh' "$SKILL" > "$fixture" + flat="$(flat_section "$fixture" '## Preflight')" + count="$(printf '%s' "$flat" | grep -cF 'graph-freshness.sh' || true)" + [ "$count" -eq 0 ] +} + +@test "doc-gate: Preflight branches on every exit code of the freshness contract" { + flat="$(flat_section "$SKILL" '## Preflight')" + [[ "$flat" == *'`fresh`'* ]] + [[ "$flat" == *'`stale `'* ]] + [[ "$flat" == *'`absent`'* ]] + [[ "$flat" == *'cannot-evaluate'* ]] +} + +@test "doc-gate can fail: a Preflight without the stale branch does not match" { + fixture="${BATS_TEST_TMPDIR}/no-stale.md" + grep -v 'stale ' "$SKILL" > "$fixture" + flat="$(flat_section "$fixture" '## Preflight')" + count="$(printf '%s' "$flat" | grep -cF '`stale `' || true)" + [ "$count" -eq 0 ] +} + +@test "state: the script Preflight names exists and is executable" { + [ -x "$SCRIPT" ] +} + +# --- Phase 2: graph leads, paired evidence, bounded output (D5, D6) ------------ + +@test "doc-gate: Phase 2 uses explain/path as leads with the paired-evidence form" { + flat="$(flat_section "$SKILL" '## Phase 2')" + [[ "$flat" == *'head -40'* ]] + [[ "$flat" == *'-> connections;'* ]] + [[ "$flat" == *'lead, not evidence'* ]] + [[ "$flat" == *'--budget 800'* ]] + [[ "$flat" == *'graph-derived:'* ]] + [[ "$flat" == *'graphify path'* ]] +} + +@test "doc-gate can fail: a Phase 2 without the graph-leads paragraph does not match" { + fixture="${BATS_TEST_TMPDIR}/no-leads.md" + grep -v 'connections;' "$SKILL" > "$fixture" + flat="$(flat_section "$fixture" '## Phase 2')" + count="$(printf '%s' "$flat" | grep -cF -- '-> connections;' || true)" + [ "$count" -eq 0 ] +} + +@test "doc-gate: --budget is never attached to explain (it is a query-only option)" { + flat="$(flat_section "$SKILL" '## Phase 2')" + # Within one backticked command span: `explain ... --budget` would be the defect. + count="$(printf '%s' "$flat" | grep -cE 'explain[^`]*--budget' || true)" + [ "$count" -eq 0 ] + # Non-vacuous: the section does talk about explain. + [[ "$flat" == *'graphify explain'* ]] +} + +@test "doc-gate: SKILL.md never invokes the graphify skill document" { + count="$(grep -cF 'Skill(graphify' "$SKILL" || true)" + [ "$count" -eq 0 ] + count="$(grep -cF '/graphify query' "$SKILL" || true)" + [ "$count" -eq 0 ] + # Non-vacuous: graphify is mentioned at all. + [ "$(grep -cF 'graphify' "$SKILL")" -ge 1 ] +} + +# --- Phase 3 brief row + template (D7) ---------------------------------------- + +@test "doc-gate: Phase 3 step 2 writes the explore row with the main-checkout graph path" { + flat="$(flat_section "$SKILL" '## Phase 3')" + [[ "$flat" == *'/graphify-out/graph.json'* ]] + [[ "$flat" == *'explore: graphify'* ]] + [[ "$flat" == *'integration base'* ]] +} + +@test "doc-gate can fail: a Phase 3 without the explore row does not match" { + fixture="${BATS_TEST_TMPDIR}/no-row.md" + grep -v 'explore: graphify' "$SKILL" > "$fixture" + flat="$(flat_section "$fixture" '## Phase 3')" + count="$(printf '%s' "$flat" | grep -cF 'explore: graphify' || true)" + [ "$count" -eq 0 ] +} + +@test "doc-gate: brief.md tools_guidance shows the explore row and keeps the path in the main checkout" { + line="$(grep -F '' "$BRIEF")" + [[ "$line" == *'explore: graphify'* ]] + [[ "$line" == *'/graphify-out/graph.json'* ]] + [[ "$line" == *'lead, not evidence'* ]] +} + +@test "doc-gate can fail: a brief.md without the explore row does not match" { + fixture="${BATS_TEST_TMPDIR}/brief-no-row.md" + sed 's/explore: graphify//' "$BRIEF" > "$fixture" + line="$(grep -F '' "$fixture")" + [[ "$line" != *'explore: graphify'* ]] +} + +# --- wiki-plan A2: paired evidence (D5) — task 04 -------------------------------- + +# The A2 span only: from the A2 heading to the A3 heading. +a2_span() { # + awk '/\*\*A2\. Ground truth\*\*/{f=1} /\*\*A3\./{f=0} f' "$1" +} + +@test "doc-gate: wiki-plan A2 pairs a graph citation with a search in one bullet" { + flat="$(normalize_ws "$(a2_span "$WIKI_PLAN")")" + [[ "$flat" == *'graphify explain -> connections; -> hits'* ]] + [[ "$flat" == *'as a lead'* ]] + [[ "$flat" == *"$WIKI_SLUG"* ]] +} + +@test "doc-gate can fail: an A2 without the graph citation rule does not match" { + fixture="${BATS_TEST_TMPDIR}/wiki-plan-no-rule.md" + grep -v 'graphify explain' "$WIKI_PLAN" > "$fixture" + flat="$(normalize_ws "$(a2_span "$fixture")")" + count="$(printf '%s' "$flat" | grep -cF 'graphify explain' || true)" + [ "$count" -eq 0 ] +} + +@test "state: plan-gate.sh is untouched by the A2 wording" { + git -C "$REPO_ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1 || skip "not a git checkout" + run git -C "$REPO_ROOT" diff --quiet HEAD -- skills/wiki-plan/scripts/plan-gate.sh + [ "$status" -eq 0 ] +} + +# --- loop-implement step 1 / step 6 (D6, D8, D9) — task 04 -------------------- + +# The text of one loop step: from `. ` at column 0 to the next step number. +loop_step() { # + awk -v a="$2" -v b="$3" ' + index($0, a) == 1 { f=1 } + index($0, b) == 1 { f=0 } + f' "$1" +} + +@test "doc-gate: loop-implement step 1 runs explain/path before opening source when explore is graphify" { + flat="$(normalize_ws "$(loop_step "$LOOP" '1. Analyze' '3. Write tests')")" + [[ "$flat" == *'graphify explain'* ]] + [[ "$flat" == *'head -40'* ]] + [[ "$flat" == *'graphify path'* ]] + [[ "$flat" == *'BEFORE opening source'* ]] + [[ "$flat" == *'never evidence'* ]] +} + +@test "doc-gate can fail: a step 1 without the graph lead does not match" { + fixture="${BATS_TEST_TMPDIR}/loop-no-step1.md" + grep -v 'BEFORE opening source' "$LOOP" > "$fixture" + flat="$(normalize_ws "$(loop_step "$fixture" '1. Analyze' '3. Write tests')")" + count="$(printf '%s' "$flat" | grep -cF 'BEFORE opening source' || true)" + [ "$count" -eq 0 ] +} + +@test "doc-gate: loop-implement step 6 names graph-derived assumptions on the NOTES line" { + flat="$(normalize_ws "$(loop_step "$LOOP" '6. Self-review' '6.5')")" + [[ "$flat" == *'graph-derived: '* ]] + [[ "$flat" == *'graph-derived: none'* ]] + [[ "$flat" == *'NOTES line'* ]] +} + +@test "doc-gate can fail: a step 6 without graph-derived does not match" { + fixture="${BATS_TEST_TMPDIR}/loop-no-step6.md" + grep -v 'graph-derived' "$LOOP" > "$fixture" + flat="$(normalize_ws "$(loop_step "$fixture" '6. Self-review' '6.5')")" + count="$(printf '%s' "$flat" | grep -cF 'graph-derived' || true)" + [ "$count" -eq 0 ] +} + +@test "doc-gate: loop-implement never loads the graphify skill document" { + flat="$(normalize_ws "$(cat "$LOOP")")" + [[ "$flat" == *'never load the graphify skill document'* ]] + count="$(grep -cF 'Skill(graphify' "$LOOP" || true)" + [ "$count" -eq 0 ] +} + +@test "doc-gate can fail: a loop-implement without the never-load sentence does not match" { + fixture="${BATS_TEST_TMPDIR}/loop-no-never.md" + # The sentence is hard-wrapped in the file; strip by its single-line fragment. + grep -v 'never load the graphify' "$LOOP" > "$fixture" + flat="$(normalize_ws "$(cat "$fixture")")" + count="$(printf '%s' "$flat" | grep -cF 'never load the graphify skill document' || true)" + [ "$count" -eq 0 ] +} diff --git a/tests/resolve-tools.bats b/tests/resolve-tools.bats index ec642a1..5556a37 100644 --- a/tests/resolve-tools.bats +++ b/tests/resolve-tools.bats @@ -124,3 +124,22 @@ setup() { [ "$status" -eq 0 ] [ "$(printf '%s' "$output" | jq -r '.kind')" = "default" ] } + +# --- explore: graphify code graph as the optional orientation layer ---------- +# (wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md) + +@test "explore role is configurable as the graphify cli (lead-not-evidence orientation layer)" { + printf '{"explore":{"kind":"cli","ref":"graphify","how":"graphify explain \\"\\" --graph /graphify-out/graph.json | head -40"}}' > "$PROJ_CFG" + run bash "$RT" --role explore + [ "$status" -eq 0 ] + [ "$(printf '%s' "$output" | jq -r '.kind')" = "cli" ] + [ "$(printf '%s' "$output" | jq -r '.ref')" = "graphify" ] + [[ "$(printf '%s' "$output" | jq -r '.how')" == *'graphify explain'* ]] +} + +@test "no config: explore default text points at the graphify option without changing kind" { + run bash "$RT" --role explore + [ "$status" -eq 0 ] + [ "$(printf '%s' "$output" | jq -r '.kind')" = "default" ] + [[ "$(printf '%s' "$output" | jq -r '.when')" == *'graphify'* ]] +} diff --git a/wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md b/wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md new file mode 100644 index 0000000..8aeb575 --- /dev/null +++ b/wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md @@ -0,0 +1,102 @@ +--- +id: infrastructure-agent-orchestration-code-graph-as-orientation-layer +domain: infrastructure +category: agent-orchestration +applies_to: [general] +confidence: field-tested +sources: + - https://www.developersdigest.tech/blog/codegraph-local-indexes-ai-coding-agents + - https://aq-score.com/blog/codegraph-local-code-knowledge-graph-agent-ops-guide-2026 + - https://getautonoma.com/blog/parallel-ai-agent-prs + - https://www.tiarebalbi.com/en/blog/code-graphs-coding-agents-delivery-shape + - https://github.com/Graphify-Labs/graphify +last_verified: 2026-09-04 +related: [infrastructure-agent-orchestration-worktree-isolated-workers, infrastructure-agent-orchestration-session-context-token-budget, infrastructure-agent-orchestration-control-signals-vs-primary-artifacts, qa-process-completion-claims] +--- + +# A Pre-Built Code Knowledge Graph as the Orientation Layer for Planning Agents + +## When this applies + +A repository carries a locally built code knowledge graph (graphify's +`graphify-out/graph.json`, a CodeGraph index, or similar) and an agent is about +to plan, decompose, or estimate the blast radius of a change; an orchestrator is +splitting one goal into parallel tasks and needs each task's file set before +dispatch; deciding whether a graph result can stand as evidence in a plan. + +## Do this + +1. **Gate every use on freshness first, from the primary artifact.** A graph is + the repository at one point in time; graphify's `graph.json` stores no commit + sha or build timestamp, so compare the graph file's mtime against + `git log --since=@ --name-only` — the git log is the primary + artifact of "what changed". Treat a graph older than the newest code commit as + stale, and treat an unreadable graph, a missing CLI, or a non-git root as + "cannot evaluate": in all of those cases plan from the files, exactly as if no + graph existed. + +2. **Use the graph to choose where to look; use files, tests, and runtime to + decide what is true.** A graph hit is a lead, never evidence. Every affected + file a graph suggests is confirmed by a search or a read of that file before it + enters a plan, a task's file set, or a conflict matrix, and the evidence line + records both: `graphify explain -> connections; grep -rn + src -> hits`. + +3. **Ask symbol-anchored questions, never free-text ones, when planning.** + +| Question | Command | Read the output as | +|----------|---------|--------------------| +| What touches this type / class / function? | `graphify explain "" --graph \| head -40` | The candidate affected files and the shared surfaces between tasks | +| Does A reach B, and through what? | `graphify path "" "" --graph ` | A dependency edge to record between the tasks that own A and B | +| Orientation in an unfamiliar area | `graphify query "" --budget 800 --graph ` | Community names to open next; measured on a 1,306-node graph a planning-sentence query returned unrelated nodes (`Error`, `String`) while `explain` on a named class returned 20 precise connections | + +4. **Map each candidate task to its files through the graph before assigning + parallel work**, then run tasks in parallel only when their confirmed file sets + are disjoint. The graph makes a change's blast radius visible before dispatch; + the disjointness test still runs on the confirmed sets, not the graph's guesses. + +5. **Deliver the graph to the agent as a CLI, with bounded output.** A graph + exposed as a ten-tool MCP server lands tens of thousands of tokens of tool + definitions in context whether or not it is queried; a CLI costs only the lines + it prints. Pipe `explain`/`path` through `head`, cap `query` with `--budget`, + and call the binary directly rather than loading the vendor's skill document + (graphify's is 1,300+ lines) into a coordinator session. + +6. **Name graph-derived assumptions in the completion report.** Any file set, + edge, or "nothing else calls this" claim that came from the graph and was not + independently confirmed is listed as `graph-derived: ` so the + reviewer can challenge it with a direct search. + +7. **Measure before keeping it.** Track file reads and tool calls before the + first edit, wrong-file edits, review comments about missed impact, and + staleness incidents across runs with and without the graph. Keep the graph + only when those improve; graphify's own benchmark reported an 11.4x average + per-query token reduction on a 65k-word corpus, which is the upper bound, not + the planning-time gain. + +## Edge cases + +| Case | Then | +|------|------| +| The graph is stale and rebuilding is cheap (graphify `update ` is AST-only, no LLM) | Offer the rebuild as an explicit choice to the human; it writes into the user's checkout outside the run's own state, so it is never run on the orchestrator's own initiative | +| Workers run in git worktrees | The graph output directory is usually gitignored, so no worktree carries it; pass `--graph /graphify-out/graph.json` — a read of a main-checkout path is the sanctioned way to consume shared read-only input — and remember the graph reflects the integration base, not the worker's branch | +| The CLI exits 0 on a missing node, a missing graph file, or a JSON decode error | Validate the graph file yourself (`jq -e '.nodes'`) and detect "no node matching" from stdout; the exit code carries no signal | +| Routes, handlers, or imports are generated by framework convention, decorators, or dynamic imports | The graph under-reports edges there; widen the search with grep on the convention (route table, decorator name) before trusting a "no callers" result | +| The graph and a file disagree | The file wins; record the disagreement as a staleness incident and drop the graph for the rest of the task | +| Uncommitted edits exist in the checkout | The mtime-vs-log check does not count them; a graph is "fresh" relative to commits only — re-run the check after the edits land if the plan depends on them | + +## Instead of + +| If you are about to | Do this instead | Why | +|---------------------|-----------------|-----| +| Cite `graphify explain X -> 12 connections` as the sole evidence for an affected file | Pair it with the grep or read that confirmed the file | The graph is a lead; a stale or convention-blind edge would enter the plan as fact | +| Run the graph tool's full build inside an orchestration run | Report `stale` and let the human choose the AST-only update | A full build may call an LLM on docs, spends the user's budget, and writes outside the run's state | +| Load the vendor's skill document to learn the CLI | Call `graphify --help` (40 lines) and the three commands above | The skill document is 1,300+ lines of pipeline instructions the coordinator never executes | + +## Sources + +- https://www.developersdigest.tech/blog/codegraph-local-indexes-ai-coding-agents — "Use the graph to choose where to look. Use the file, test, and runtime to decide what is true"; the measurement list in directive 7 and the decorator/convention limit +- https://aq-score.com/blog/codegraph-local-code-knowledge-graph-agent-ops-guide-2026 — "If the graph is stale, incomplete, or conflicts with the files, stop using it and investigate from the files"; rebuild from the active checkout; results are leads, not evidence +- https://getautonoma.com/blog/parallel-ai-agent-prs — map each task to the files it will touch via the dependency graph before assigning; parallelize only disjoint file sets +- https://www.tiarebalbi.com/en/blog/code-graphs-coding-agents-delivery-shape — a graph MCP exposing ten tools costs tens of thousands of context tokens whether queried or not +- https://github.com/Graphify-Labs/graphify — `explain`/`path`/`query --budget`, `update` (AST-only, no LLM), `hook install` post-commit/post-checkout; exit-0-on-error, empty `graph` metadata, and the query-vs-explain quality gap measured on graphifyy 0.4.23 with a 1,306-node graph, 2026-09-04 diff --git a/wiki/infrastructure/index.md b/wiki/infrastructure/index.md index f781a71..86c2ac3 100644 --- a/wiki/infrastructure/index.md +++ b/wiki/infrastructure/index.md @@ -22,6 +22,7 @@ Match your situation to a "load when" line; load only matching pages. | [worktree-isolated-workers](agent-orchestration/worktree-isolated-workers.md) | Authoring the brief/output contract for parallel workers each confined to its own git worktree; workers stall at the same phase with no task-level error; deciding where shared or produced artifacts live and which direction (read vs write) a worktree guardrail stops; a guardrail escalates on read-only access to another worktree; the isolation guard is a Bash-command hook while workers also edit files with native Edit/Write tools | | [autonomous-decision-rulings](agent-orchestration/autonomous-decision-rulings.md) | An unattended agent hits a decision its plan does not answer and must choose between stopping to ask and proceeding; a run stalls on questions no human needed to see; deciding which decision categories require a human; recording autonomous decisions for audit; resuming after interruption/compaction without re-dispatching completed work | | [session-context-token-budget](agent-orchestration/session-context-token-budget.md) | Planning or running long-lived coordinator/worker agent sessions and deciding when to compact or clear context; a run's cost is dominated by cache reads; screenshots or large file reads are entering a long-lived session; choosing slot counts / per-phase token budgets for an orchestrated run | +| [code-graph-as-orientation-layer](agent-orchestration/code-graph-as-orientation-layer.md) | A repository carries a locally built code knowledge graph (graphify `graphify-out/graph.json` or similar) and an agent is about to plan, decompose, or estimate the blast radius of a change; an orchestrator needs each parallel task's file set before dispatch; deciding whether a graph hit can stand as plan evidence; checking whether the graph is fresh enough to use | ## ci-cd