@mmerterden/multi-agent-pipeline 16.13.0 → 16.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -16,6 +16,17 @@ Internal file-layout changes that don't affect the slash-command surface are sti
16
16
 
17
17
  ## [Unreleased]
18
18
 
19
+ ## [16.14.0] - 2026-08-28
20
+
21
+ ### Added
22
+
23
+ - **Go joins the code graph, and `web` / `frontend` resolve to the node rules.** Phase 1 has detected Go since the stack table existed, while both rule layers rejected it: there was no `test-gap-rules/go.json` either, so `/multi-agent:test "test-gap"` was equally blind to a Go repo. Both files now exist. The alias is the other half of the same gap: the rule files are named after the toolchain (`node`) and the rest of the pipeline names the product surface (`web`), so a caller typing the word they were last shown got "unsupported". `backend` is deliberately NOT an alias - it could be node, python or go, and the error names all three rather than guessing one.
24
+ - **Analysis ranks its repo-evidence candidates with the graph (opt-in).** The reuse-first scan (Locked 11) caps its candidate set at 200 files and warns when a generic feature name blows past it. `head -200` truncates in grep's walk order, so the cap was dropping an arbitrary file, not the least relevant one. With `prefs.global.codeGraph.enabled`, a graph query over the feature name sorts the set first. It is a sort key, never a filter: a file grep found and the graph did not rank is still scanned, because a regex extractor's silence is not evidence of absence. The 13 buckets, the cap and the no-graph behaviour are unchanged.
25
+
26
+ ### Fixed
27
+
28
+ - **The code-graph ref did not say to build when no graph exists.** It said "build only when `baseCommit` no longer matches HEAD", which has no answer when there is nothing to compare. That is the case that matters: the knowledge base's prose files are written in Phase 7, the phase a run is least likely to reach, so a repo can carry a long task history and an empty knowledge directory. The graph must not inherit that dependency, and `--status` exiting 1 on a missing file now explicitly means build.
29
+
19
30
  ## [16.13.0] - 2026-08-28
20
31
 
21
32
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmerterden/multi-agent-pipeline",
3
- "version": "16.13.0",
3
+ "version": "16.14.0",
4
4
  "description": "8-phase AI development pipeline with full orchestration on Claude Code, Copilot CLI and Codex CLI. Analysis, planning, TDD, CLI-aware parallel review with consensus surfacing + Fable triage, default-FAIL evidence gates, secret + intent guards, per-phase cost ledger, persistent learnings memory, wiki generation, commit automation. Token-preserving uninstall.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -75,6 +75,27 @@ grep -rilE "$slugs" $WHITELIST --include="*.swift" --include="*.kt" --include="*
75
75
 
76
76
  If `wc -l < $CANDIDATES` exceeds 200, record `fetchWarnings += "feature-name too generic; candidate cap hit"` in `repoEvidence[<repo>]`.
77
77
 
78
+ **Ranking the candidates (opt-in, `prefs.global.codeGraph.enabled`).** `head -200`
79
+ truncates in grep's walk order, so the cap drops an arbitrary file rather than the
80
+ least relevant one - the failure the warning above names and cannot fix. When a
81
+ graph exists for the repo, rank the set first:
82
+
83
+ ```bash
84
+ GRAPH_PATH="$HOME/.claude/knowledge/$(basename "$REPO_ROOT")/code-graph.json"
85
+ node $HOME/.claude/scripts/graph-report.mjs --graph "$GRAPH_PATH" --status \
86
+ || node $HOME/.claude/scripts/graph-build.mjs --root "$REPO_ROOT" --stack "$STACK" --json
87
+ node $HOME/.claude/scripts/graph-query.mjs "$featureName" --graph "$GRAPH_PATH" --json
88
+ ```
89
+
90
+ Ranked paths go to the front of `$CANDIDATES`, grep-only hits keep their order
91
+ behind them, everything else is unchanged, and a repo with no graph, an
92
+ unsupported stack or a failing validator gets exactly today's order. It is a sort
93
+ key, never a filter: a file grep found and the graph did not rank is still
94
+ scanned, because a regex extractor's silence is not evidence of absence. Sorting
95
+ rather than detecting is where each tool measured better - the graph beats grep on
96
+ domain words, a feature name is domain words, and it loses on the exact symbols
97
+ the bucket patterns use (`docs/adr/0010-own-code-graph.md`).
98
+
78
99
  **13 buckets** (each scanned in parallel with a 30 s timeout per bucket; timeout marks the bucket as `partial`, never blocks the others):
79
100
 
80
101
  | Bucket | Detection (iOS Swift sketch) | Cross-platform variant |
@@ -29,7 +29,14 @@ report scripts default it from the CWD's basename, and a run happens in a
29
29
  worktree whose basename need not equal the repo's, so a defaulted path can point
30
30
  at a graph that was never built. Pass it.
31
31
 
32
- Build only when `baseCommit` no longer matches HEAD. A build is not usable until
32
+ Build when there is no graph at all, and when `baseCommit` no longer matches
33
+ HEAD. The missing case is the one that matters in practice: `architecture.md`
34
+ and its siblings are written in Phase 7, which is the phase a run is least
35
+ likely to reach, so a repo can have a long history of tasks and an empty
36
+ knowledge directory. The graph must not inherit that. `--status` exits 1 on a
37
+ missing file, and that exit means build, not skip.
38
+
39
+ A build is not usable until
33
40
  `validate-code-graph.mjs` exits 0: a graph whose edges point at missing nodes
34
41
  truncates traversals silently, so a non-zero exit means skip the injection and
35
42
  run Explore as if no graph existed.
@@ -12,7 +12,7 @@
12
12
  },
13
13
  "stack": {
14
14
  "type": "string",
15
- "enum": ["ios", "android", "node", "python"]
15
+ "enum": ["ios", "android", "node", "python", "go"]
16
16
  },
17
17
  "root": {
18
18
  "type": "string",
@@ -37,6 +37,27 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
37
37
 
38
38
  export const GRAPH_SCHEMA_VERSION = "1.0.0";
39
39
 
40
+ /**
41
+ * Stack words the pipeline uses elsewhere, mapped to the rule-file id.
42
+ *
43
+ * The rule files are named after the toolchain (`node`), but the rest of the
44
+ * pipeline names the product surface (`web`), and a caller reasonably types
45
+ * whichever one they were last shown. Aliasing is safe only where the mapping
46
+ * is one-to-one: `backend` is deliberately absent, because it could be node,
47
+ * python or go and picking one would be a guess dressed as support.
48
+ */
49
+ export const STACK_ALIASES = { web: "node", frontend: "node" };
50
+
51
+ /**
52
+ * Canonical rule-file id for a stack word.
53
+ *
54
+ * @param {string} stack
55
+ * @returns {string}
56
+ */
57
+ export function resolveStack(stack) {
58
+ return STACK_ALIASES[stack] || stack;
59
+ }
60
+
40
61
  /**
41
62
  * Load and merge the two rule files for a stack.
42
63
  *
@@ -48,9 +69,15 @@ export const GRAPH_SCHEMA_VERSION = "1.0.0";
48
69
  * @param {string} [baseDir] - directory holding the two rule dirs
49
70
  * @returns {object} merged rules
50
71
  */
51
- export function loadRules(stack, baseDir = __dirname) {
72
+ export function loadRules(requested, baseDir = __dirname) {
73
+ const stack = resolveStack(requested);
52
74
  const gapPath = join(baseDir, "test-gap-rules", `${stack}.json`);
53
75
  const graphPath = join(baseDir, "code-graph-rules", `${stack}.json`);
76
+ if (requested === "backend") {
77
+ throw new Error(
78
+ "'backend' names a role, not a toolchain: pass node, python or go so the right rules load",
79
+ );
80
+ }
54
81
  if (!existsSync(gapPath)) throw new Error(`no test-gap rules for stack '${stack}': ${gapPath}`);
55
82
  if (!existsSync(graphPath))
56
83
  throw new Error(`no code-graph rules for stack '${stack}': ${graphPath}`);
@@ -0,0 +1,124 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "stack": "go",
4
+ "description": "Go code-graph rules. Adds what the graph needs on top of test-gap-rules/go.json, which already owns sourceExtensions, excludePathGlobs and the test-path predicates. Definition patterns here are deliberately broader than the test-gap publicApiPatterns: the graph wants unexported declarations too, because Phase 1 narrows scope on the whole package, not just its exported surface.",
5
+ "comments": {
6
+ "line": [
7
+ "//"
8
+ ],
9
+ "block": [
10
+ [
11
+ "/*",
12
+ "*/"
13
+ ]
14
+ ],
15
+ "string": [
16
+ "`",
17
+ "\"",
18
+ "'"
19
+ ]
20
+ },
21
+ "importSpecifiersAreStrings": true,
22
+ "definitionPatterns": [
23
+ {
24
+ "id": "struct",
25
+ "kind": "struct",
26
+ "regex": "\\btype\\s+([A-Za-z_][A-Za-z0-9_]*)\\s+struct\\b"
27
+ },
28
+ {
29
+ "id": "interface",
30
+ "kind": "interface",
31
+ "regex": "\\btype\\s+([A-Za-z_][A-Za-z0-9_]*)\\s+interface\\b"
32
+ },
33
+ {
34
+ "id": "type",
35
+ "kind": "type",
36
+ "regex": "\\btype\\s+([A-Za-z_][A-Za-z0-9_]*)\\s+(?!struct\\b|interface\\b)[A-Za-z_*\\[]"
37
+ },
38
+ {
39
+ "id": "func",
40
+ "kind": "func",
41
+ "regex": "^func\\s+(?:\\([^)]*\\)\\s+)?([A-Za-z_][A-Za-z0-9_]*)\\s*[(\\[]"
42
+ }
43
+ ],
44
+ "importPatterns": [
45
+ {
46
+ "id": "specifier",
47
+ "regex": "^[ \\t]*(?:import[ \\t]+)?(?:[A-Za-z_.][A-Za-z0-9_]*[ \\t]+)?\"(?:[^\"]*/)?([A-Za-z_][A-Za-z0-9_.-]*)\"[ \\t]*$"
48
+ }
49
+ ],
50
+ "referenceKinds": [
51
+ "struct",
52
+ "interface",
53
+ "type"
54
+ ],
55
+ "referenceKindsNote": "func is deliberately absent, for the reason the Swift, Kotlin, Node and Python rules all record: a bare name matched across files is almost never a call to that exact declaration, and Go's short-name convention (New, Run, Get, Close, Error) makes it worse than most. Functions still reach the graph through their defines edge, so they stay findable by name.",
56
+ "ignoredIdentifiers": [
57
+ "package",
58
+ "import",
59
+ "func",
60
+ "type",
61
+ "struct",
62
+ "interface",
63
+ "map",
64
+ "chan",
65
+ "var",
66
+ "const",
67
+ "return",
68
+ "if",
69
+ "else",
70
+ "for",
71
+ "range",
72
+ "switch",
73
+ "case",
74
+ "default",
75
+ "select",
76
+ "go",
77
+ "defer",
78
+ "break",
79
+ "continue",
80
+ "fallthrough",
81
+ "goto",
82
+ "nil",
83
+ "true",
84
+ "false",
85
+ "iota",
86
+ "make",
87
+ "new",
88
+ "len",
89
+ "cap",
90
+ "append",
91
+ "copy",
92
+ "delete",
93
+ "panic",
94
+ "recover",
95
+ "print",
96
+ "println",
97
+ "error",
98
+ "string",
99
+ "bool",
100
+ "byte",
101
+ "rune",
102
+ "int",
103
+ "int8",
104
+ "int16",
105
+ "int32",
106
+ "int64",
107
+ "uint",
108
+ "uint8",
109
+ "uint16",
110
+ "uint32",
111
+ "uint64",
112
+ "float32",
113
+ "float64",
114
+ "complex64",
115
+ "complex128",
116
+ "uintptr",
117
+ "any",
118
+ "comparable",
119
+ "err",
120
+ "ctx",
121
+ "ok"
122
+ ],
123
+ "note": "A Go import path is a string literal, so `importSpecifiersAreStrings` applies exactly as it does for JavaScript: the import pass reads a text with comments stripped and strings intact, and the pattern captures the last path segment, which is the package name in the overwhelmingly common case where the two agree. The pattern is anchored to a whole line holding nothing but an optional `import`, an optional alias and the quoted path, because Go has no import keyword on the lines inside a parenthesized block. Without that anchor it matched EVERY double-quoted string in the file and turned each one into a module node. A named import (`sql \"database/sql\"`) and the handful of packages whose name differs from their directory both resolve to the directory name, which is what the engine matches against a file basename anyway. The three type patterns are ordered so `struct` and `interface` claim their shapes before the general `type` alias pattern, which excludes them by lookahead rather than by order alone - order and lookahead together, because a rule file that relies on order silently changes meaning when someone sorts it. `func` is anchored to column 0, which is where every Go function and method declaration lives; the engine's nesting rule then never has to fire for this stack.\n\nUNMEASURED: unlike ios, android, node and python, these rules were never run against a real Go codebase. There was no Go repo and no toolchain on the machine that wrote them. The smoke exercises them on a synthetic fixture, which proves they parse and produce a schema-valid graph, not that they produce a USEFUL one. Treat the first real build as the measurement, and check the hub list before trusting it."
124
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "stack": "go",
4
+ "description": "Go test gap rules. Sources in *.go; tests are *_test.go beside the code they cover, which is why there is no test directory convention here.",
5
+ "sourceExtensions": [".go"],
6
+ "testPathSuffixes": ["_test.go"],
7
+ "testPathContains": [],
8
+ "excludePathGlobs": ["vendor/", "third_party/", "*.pb.go", "*_generated.go", "*_mock.go"],
9
+ "publicApiPatterns": [
10
+ {
11
+ "id": "exported_func",
12
+ "regex": "^func\\s+([A-Z][A-Za-z0-9_]*)\\s*\\("
13
+ },
14
+ {
15
+ "id": "exported_method",
16
+ "regex": "^func\\s+\\([^)]*\\)\\s+([A-Z][A-Za-z0-9_]*)\\s*\\("
17
+ },
18
+ {
19
+ "id": "exported_type",
20
+ "regex": "^type\\s+([A-Z][A-Za-z0-9_]*)\\s+(?:struct|interface|func|map|\\[|chan|\\*|[A-Za-z])"
21
+ }
22
+ ],
23
+ "hint": "Cover exported identifiers with a table-driven Test<Name> in the same package's <file>_test.go. An unexported identifier is covered through the exported one that calls it."
24
+ }
@@ -15,7 +15,7 @@
15
15
 
16
16
  import { readFileSync } from "node:fs";
17
17
 
18
- const ALLOWED_STACK = new Set(["ios", "android", "node", "python"]);
18
+ const ALLOWED_STACK = new Set(["ios", "android", "node", "python", "go"]);
19
19
  const ALLOWED_NODE_KIND = new Set(["file", "symbol", "module"]);
20
20
  const ALLOWED_EDGE_KIND = new Set(["defines", "imports", "references"]);
21
21
  const MAX_REPORTED_DANGLING = 5;