@mmerterden/multi-agent-pipeline 16.15.0 → 16.16.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.
Files changed (37) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +5 -4
  3. package/README.tr.md +5 -4
  4. package/docs/adr/0011-dormant-ci.md +87 -0
  5. package/docs/adr/README.md +1 -0
  6. package/docs/architecture.md +2 -2
  7. package/docs/ecosystem.md +5 -5
  8. package/install/_common.mjs +0 -1
  9. package/install/_dev-only-files.mjs +1 -0
  10. package/install/claude.mjs +14 -4
  11. package/install/index.mjs +8 -1
  12. package/package.json +6 -5
  13. package/pipeline/commands/multi-agent/help/SKILL.md +2 -0
  14. package/pipeline/commands/multi-agent/steer/SKILL.md +109 -0
  15. package/pipeline/commands/multi-agent/sync/SKILL.md +5 -7
  16. package/pipeline/multi-agent-refs/cross-cli-contract.md +4 -4
  17. package/pipeline/multi-agent-refs/phases/operations.md +23 -0
  18. package/pipeline/multi-agent-refs/phases.md +38 -0
  19. package/pipeline/preferences-template.json +2 -10
  20. package/pipeline/schemas/agent-state.schema.json +35 -0
  21. package/pipeline/schemas/analysis-spec.schema.json +8 -2
  22. package/pipeline/schemas/conventions-output.schema.json +2 -13
  23. package/pipeline/scripts/build-references.mjs +22 -6
  24. package/pipeline/scripts/code-graph-rules/android.json +4 -21
  25. package/pipeline/scripts/code-graph-rules/go.json +4 -19
  26. package/pipeline/scripts/code-graph-rules/node.json +4 -21
  27. package/pipeline/scripts/feedback-send.mjs +3 -1
  28. package/pipeline/scripts/gate-linux.sh +62 -0
  29. package/pipeline/scripts/localize-commands.mjs +1 -2
  30. package/pipeline/scripts/pre-push-check.sh +6 -4
  31. package/pipeline/scripts/usage-report.mjs +16 -14
  32. package/pipeline/scripts/validate-analysis-doc.mjs +46 -27
  33. package/pipeline/scripts/validate-analysis.mjs +7 -1
  34. package/pipeline/scripts/validate-complaint-doc.mjs +24 -8
  35. package/pipeline/scripts/write-state.mjs +71 -12
  36. package/pipeline/skills/shared/core/multi-agent-steer/SKILL.md +111 -0
  37. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +4 -4
@@ -26,6 +26,44 @@ Short: 0-Init -> (1,2 skipped) --------------> 3-Dev -> 4-Review -> 5-Test -
26
26
  Full or Short is the Phase 0 Step 7.5 question, not a command name. Autopilot never asks and always runs Full.
27
27
  ```
28
28
 
29
+ ## Phase entry - pending steer (every phase, every mode)
30
+
31
+ Before a phase does any work, read `pendingSteer` from `agent-state.json`. It
32
+ is how a correction reaches a run that is already going: `/multi-agent:steer #N
33
+ "<instruction>"` queues one, and the next phase to start consumes it.
34
+
35
+ ```bash
36
+ STEER=$(node -e 'try{const s=require(process.argv[1]);const p=s.pendingSteer;if(p&&p.text&&!p.appliedAt)process.stdout.write(p.text)}catch{}' "$STATE_FILE")
37
+ ```
38
+
39
+ The `catch` is not decoration: a state file that does not exist yet (Phase 0
40
+ before its first write) makes `require` throw, and an unguarded read prints a
41
+ stack trace at every phase entry that reads like a failed phase.
42
+
43
+ Empty → carry on, nothing to do. Non-empty:
44
+
45
+ 1. Apply it to this phase's context before starting. It is the user's own
46
+ words; treat it as an instruction from them, arriving now.
47
+ 2. Say what changed, in one line, so the correction is visible in the run and
48
+ not only in the state file.
49
+ 3. Mark it consumed. The record stays as a trace; `appliedAt` is what stops it
50
+ being read again, and the next steer overwrites all four keys:
51
+ ```bash
52
+ printf '{"pendingSteer":{"appliedAt":"%s","appliedPhase":<N>}}' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
53
+ | node $HOME/.claude/scripts/write-state.mjs "$STATE_FILE"
54
+ ```
55
+
56
+ `<N>` is this phase's number, substituted literally and unquoted - it is a
57
+ JSON number. Leaving it as an unset shell variable produces
58
+ `"appliedPhase":}`, which is invalid JSON and exits 1.
59
+ 4. Record it on the tracker and in the log: `bash $HOME/.claude/scripts/phase-tracker.sh meta <N> steer "applied"`, plus a `Steer applied: <text>` line in `agent-log.md`.
60
+
61
+ Two limits, both deliberate. It is read at phase **entry** only, never mid-phase:
62
+ a phase that changes target halfway discards the work it already did, which is
63
+ the outcome steer exists to avoid. And an instruction that contradicts an
64
+ approved plan halts for the user rather than silently rewriting the plan -
65
+ steering corrects a run, it does not re-authorize one.
66
+
29
67
  ## Visual Phase Tracker
30
68
 
31
69
  Two channels run in parallel at every phase boundary. Both are required in their target CLIs - skipping either is the #1 source of "I see no progress" complaints.
@@ -48,12 +48,7 @@
48
48
  "autoDiff": false,
49
49
  "manualNote": false
50
50
  },
51
- "wikiScope": [
52
- "main",
53
- "ios",
54
- "screenshots",
55
- "index"
56
- ],
51
+ "wikiScope": ["main", "ios", "screenshots", "index"],
57
52
  "autopilotReportTimeoutSeconds": 1800,
58
53
  "promptLanguage": "en",
59
54
  "outputLanguage": "en",
@@ -185,10 +180,7 @@
185
180
  "localPath": "plugins/<my-plugin>/skills/workflow",
186
181
  "upstreamMarketplace": "<installed-marketplace-name>",
187
182
  "upstreamPlugin": "<upstream-plugin-name>",
188
- "upstreamSkills": [
189
- "<skill-a>",
190
- "<skill-b>"
191
- ],
183
+ "upstreamSkills": ["<skill-a>", "<skill-b>"],
192
184
  "derivedFromVersion": "0.0.0",
193
185
  "upstreamVersionSource": "marketplace.json",
194
186
  "upstreamLocalClone": "$HOME/<upstream-repo-working-copy>",
@@ -104,6 +104,41 @@
104
104
  "type": ["string", "null"],
105
105
  "description": "Set when a phase halts on a hard error (validator failed twice, no subagent returned, dispatch error past fallback, lock irrecoverable). Format '<phase>:<cause>'. Surfaced to the user and cleared on successful resume. See operations.md 'Halt visibility'."
106
106
  },
107
+ "rev": {
108
+ "type": "integer",
109
+ "minimum": 0,
110
+ "description": "Monotonic revision, bumped by write-state.mjs on every successful write. A reader keeps the rev it read and compares before writing back; a changed rev means the record moved underneath it. Optional, so a record written by an earlier version stays valid: treat a missing rev as 0. No schemaVersion bump - the field is additive and nothing needs migrating."
111
+ },
112
+ "pendingSteer": {
113
+ "type": ["object", "null"],
114
+ "additionalProperties": false,
115
+ "required": ["text", "at"],
116
+ "description": "A mid-run instruction from the user, waiting for the next phase boundary to consume it. Written by /multi-agent:steer; a phase consumes it at entry by setting appliedAt, which is what stops it being read again. The record is kept as a trace and overwritten wholesale by the next steer. Never applied mid-phase: changing the target of work already in flight throws that work away.",
117
+ "properties": {
118
+ "text": {
119
+ "type": "string",
120
+ "minLength": 1,
121
+ "maxLength": 4000,
122
+ "description": "The instruction, verbatim as the user typed it."
123
+ },
124
+ "at": {
125
+ "type": "string",
126
+ "format": "date-time",
127
+ "description": "When it was queued."
128
+ },
129
+ "appliedAt": {
130
+ "type": ["string", "null"],
131
+ "format": "date-time",
132
+ "description": "When a phase consumed it. Set alongside clearing the record, so the log keeps the trace."
133
+ },
134
+ "appliedPhase": {
135
+ "type": ["integer", "null"],
136
+ "minimum": 0,
137
+ "maximum": 7,
138
+ "description": "The phase that consumed it."
139
+ }
140
+ }
141
+ },
107
142
  "telemetry": {
108
143
  "type": "object",
109
144
  "additionalProperties": true,
@@ -104,7 +104,10 @@
104
104
  "platforms": {
105
105
  "type": "array",
106
106
  "minItems": 1,
107
- "items": { "type": "string", "enum": ["ios", "android", "web", "backend", "mobile", "frontend"] },
107
+ "items": {
108
+ "type": "string",
109
+ "enum": ["ios", "android", "web", "backend", "mobile", "frontend"]
110
+ },
108
111
  "description": "Platforms selected in Phase 0 step 3 (multi-select)."
109
112
  },
110
113
  "repos": {
@@ -114,7 +117,10 @@
114
117
  "additionalProperties": false,
115
118
  "required": ["platform", "name", "canPush"],
116
119
  "properties": {
117
- "platform": { "type": "string", "enum": ["ios", "android", "web", "backend", "mobile", "frontend"] },
120
+ "platform": {
121
+ "type": "string",
122
+ "enum": ["ios", "android", "web", "backend", "mobile", "frontend"]
123
+ },
118
124
  "name": { "type": "string", "minLength": 1 },
119
125
  "cloneUrl": { "type": ["string", "null"] },
120
126
  "canPush": { "type": "boolean" }
@@ -66,13 +66,7 @@
66
66
  "conventionBucket": {
67
67
  "type": "object",
68
68
  "additionalProperties": false,
69
- "required": [
70
- "pattern",
71
- "example",
72
- "confidence",
73
- "evidenceFiles",
74
- "alternativeCandidates"
75
- ],
69
+ "required": ["pattern", "example", "confidence", "evidenceFiles", "alternativeCandidates"],
76
70
  "properties": {
77
71
  "pattern": {
78
72
  "type": "string",
@@ -84,12 +78,7 @@
84
78
  },
85
79
  "confidence": {
86
80
  "type": "string",
87
- "enum": [
88
- "high",
89
- "medium",
90
- "low",
91
- "none"
92
- ],
81
+ "enum": ["high", "medium", "low", "none"],
93
82
  "description": "high: 5+ examples; medium: 3-4; low: 1-2 or mixed; none: no evidence."
94
83
  },
95
84
  "evidenceFiles": {
@@ -34,7 +34,7 @@ const NONE = "-";
34
34
 
35
35
  function argOf(argv, flag) {
36
36
  const i = argv.indexOf(flag);
37
- return i === -1 ? null : argv[i + 1] ?? null;
37
+ return i === -1 ? null : (argv[i + 1] ?? null);
38
38
  }
39
39
 
40
40
  function readState(path) {
@@ -102,15 +102,27 @@ function rowsFrom(spec, lang) {
102
102
  c.url,
103
103
  ref || NONE,
104
104
  c.embeddedApiTable
105
- ? lang === "tr" ? "API kontratı" : "API contract"
106
- : lang === "tr" ? "özellik spesifikasyonu" : "feature spec",
105
+ ? lang === "tr"
106
+ ? "API kontratı"
107
+ : "API contract"
108
+ : lang === "tr"
109
+ ? "özellik spesifikasyonu"
110
+ : "feature spec",
107
111
  OK[lang],
108
112
  NONE,
109
113
  );
110
114
  }
111
115
 
112
116
  for (const j of ev.jira ?? []) {
113
- push("Jira", j.id, j.url ?? NONE, NONE, lang === "tr" ? "ticket" : "ticket", OK[lang], j.summary);
117
+ push(
118
+ "Jira",
119
+ j.id,
120
+ j.url ?? NONE,
121
+ NONE,
122
+ lang === "tr" ? "ticket" : "ticket",
123
+ OK[lang],
124
+ j.summary,
125
+ );
114
126
  }
115
127
 
116
128
  for (const s of ev.swagger ?? []) {
@@ -266,13 +278,17 @@ function tableLocators(block) {
266
278
  const found = [];
267
279
  for (const line of block.split(/\r?\n/)) {
268
280
  if (!line.trim().startsWith("|")) continue;
269
- const cells = line.split("|").slice(1, -1).map((c) => c.trim());
281
+ const cells = line
282
+ .split("|")
283
+ .slice(1, -1)
284
+ .map((c) => c.trim());
270
285
  if (cells.length < 3) continue;
271
286
  if (/^:?-{3,}:?$/.test(cells[0])) continue;
272
287
  const locator = cells[2];
273
288
  const source = cells[1];
274
289
  if (!locator) continue;
275
- if (HEADER_CELLS.has(locator.toLowerCase()) && HEADER_CELLS.has(cells[0].toLowerCase())) continue;
290
+ if (HEADER_CELLS.has(locator.toLowerCase()) && HEADER_CELLS.has(cells[0].toLowerCase()))
291
+ continue;
276
292
  found.push({ locator, source, raw: line });
277
293
  }
278
294
  return found;
@@ -3,19 +3,9 @@
3
3
  "stack": "android",
4
4
  "description": "Kotlin + Java code-graph rules. Adds what the graph needs on top of test-gap-rules/android.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 internal types too, because Phase 1 narrows scope on the whole module, not just its public surface.",
5
5
  "comments": {
6
- "line": [
7
- "//"
8
- ],
9
- "block": [
10
- [
11
- "/*",
12
- "*/"
13
- ]
14
- ],
15
- "string": [
16
- "\"\"\"",
17
- "\""
18
- ]
6
+ "line": ["//"],
7
+ "block": [["/*", "*/"]],
8
+ "string": ["\"\"\"", "\""]
19
9
  },
20
10
  "definitionPatterns": [
21
11
  {
@@ -60,14 +50,7 @@
60
50
  "regex": "^\\s*import\\s+(?:static\\s+)?(?:[A-Za-z_][A-Za-z0-9_]*\\.)+([A-Z][A-Za-z0-9_]*)"
61
51
  }
62
52
  ],
63
- "referenceKinds": [
64
- "class",
65
- "interface",
66
- "object",
67
- "enum",
68
- "record",
69
- "typealias"
70
- ],
53
+ "referenceKinds": ["class", "interface", "object", "enum", "record", "typealias"],
71
54
  "referenceKindsNote": "fun is deliberately absent, for the reason Swift's rules record for func: a bare lowercase name matched across files is almost never a call to that exact declaration, and on a large app the generic ones (invoke, build, create, get) take the top god-node slots purely because each happened to be declared once. Composables are the tempting exception - they are capitalised and behave like types - but they are declared with fun, so including them means including every other fun with them.",
72
55
  "ignoredIdentifiers": [
73
56
  "this",
@@ -3,20 +3,9 @@
3
3
  "stack": "go",
4
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
5
  "comments": {
6
- "line": [
7
- "//"
8
- ],
9
- "block": [
10
- [
11
- "/*",
12
- "*/"
13
- ]
14
- ],
15
- "string": [
16
- "`",
17
- "\"",
18
- "'"
19
- ]
6
+ "line": ["//"],
7
+ "block": [["/*", "*/"]],
8
+ "string": ["`", "\"", "'"]
20
9
  },
21
10
  "importSpecifiersAreStrings": true,
22
11
  "definitionPatterns": [
@@ -47,11 +36,7 @@
47
36
  "regex": "^[ \\t]*(?:import[ \\t]+)?(?:[A-Za-z_.][A-Za-z0-9_]*[ \\t]+)?\"(?:[^\"]*/)?([A-Za-z_][A-Za-z0-9_.-]*)\"[ \\t]*$"
48
37
  }
49
38
  ],
50
- "referenceKinds": [
51
- "struct",
52
- "interface",
53
- "type"
54
- ],
39
+ "referenceKinds": ["struct", "interface", "type"],
55
40
  "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
41
  "ignoredIdentifiers": [
57
42
  "package",
@@ -3,20 +3,9 @@
3
3
  "stack": "node",
4
4
  "description": "TypeScript + JavaScript code-graph rules. Adds what the graph needs on top of test-gap-rules/node.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 module, not just its public surface.",
5
5
  "comments": {
6
- "line": [
7
- "//"
8
- ],
9
- "block": [
10
- [
11
- "/*",
12
- "*/"
13
- ]
14
- ],
15
- "string": [
16
- "`",
17
- "\"",
18
- "'"
19
- ]
6
+ "line": ["//"],
7
+ "block": [["/*", "*/"]],
8
+ "string": ["`", "\"", "'"]
20
9
  },
21
10
  "definitionPatterns": [
22
11
  {
@@ -56,13 +45,7 @@
56
45
  "regex": "(?:\\bfrom|\\brequire\\s*\\(|\\bimport)\\s*\\(?\\s*[\"'](?:[^\"']*[\\/:])?([A-Za-z_$][A-Za-z0-9_$-]*)(?:\\.[A-Za-z0-9]+)*[\"']"
57
46
  }
58
47
  ],
59
- "referenceKinds": [
60
- "class",
61
- "interface",
62
- "type",
63
- "enum",
64
- "const"
65
- ],
48
+ "referenceKinds": ["class", "interface", "type", "enum", "const"],
66
49
  "referenceKindsNote": "function is deliberately absent, for the reason Swift's rules record for func: a bare name matched across files is almost never a call to that exact declaration, and the generic ones (run, main, get, parse, handler) would take the top god-node slots purely because each happened to be declared once. Functions still reach the graph through their defines edge, so they stay findable by name. const is present but narrowed to an uppercase-initial declaration at column 0, which is what a component, a singleton or an exported table looks like; a lowercase const is a local and is not a declaration anyone references across files. Measured on this repo (126 sources): including function put `ok` at degree 52, `f` at 38, `tokens` at 15 and `arg` at 12 - local helper names that happen to be declared exactly once, so the ambiguity rule does not catch them either. Excluding it leaves a thin symbol layer, and that is the honest shape of this stack: a JavaScript module's exported unit is usually a function, so the useful graph here is the import graph between files. `graph-affected \"<file>.mjs\"` answers who imports a module, which is the question this stack actually gets asked.",
67
50
  "ignoredIdentifiers": [
68
51
  "this",
@@ -115,7 +115,9 @@ function endpointAllowed(endpoint) {
115
115
  async function main() {
116
116
  const text = String(arg("--text", "") ?? "").trim();
117
117
  if (!text) {
118
- process.stderr.write('usage: feedback-send.mjs --text "<message>" [--kind bug|idea|question]\n');
118
+ process.stderr.write(
119
+ 'usage: feedback-send.mjs --text "<message>" [--kind bug|idea|question]\n',
120
+ );
119
121
  process.exit(2);
120
122
  }
121
123
  if (text.length > TEXT_MAX) {
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env bash
2
+ # gate-linux.sh - run the ci-lite job on a real Ubuntu image, locally.
3
+ #
4
+ # The pre-push gate (pre-push-check.sh) is the primary one, but it runs on the
5
+ # maintainer's macOS workstation against an installed node_modules that has
6
+ # accumulated over months. Two things it cannot answer: does this tree work on
7
+ # Linux, and does `npm ci` from the lockfile into an empty tree still resolve.
8
+ # ci-lite.yml answers both, and has not executed since 2026-07-25.
9
+ #
10
+ # `act` runs that workflow against the same image GitHub would use, so the
11
+ # answer comes from the workflow definition rather than from a second script
12
+ # that would drift away from it.
13
+ #
14
+ # NOT part of `npm run gate` on purpose: act and Docker are not installed
15
+ # everywhere, and a required gate that cannot run is the failure ADR-0011
16
+ # exists to avoid. Run it before a release, or after touching install/,
17
+ # lib/credential-store.sh, or anything path-shaped.
18
+ #
19
+ # Needs network inside the container: the scorecard's dependency metrics ask
20
+ # the registry (advisories and signatures) and report an unreachable registry
21
+ # as a failure rather than a skip, on the grounds that a supply-chain check
22
+ # which goes green without reaching the registry is worse than no check.
23
+ #
24
+ # Exit codes: 0 = the job passed, 1 = the job failed, 2 = tooling missing
25
+
26
+ set -uo pipefail
27
+
28
+ REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
29
+ cd "$REPO_ROOT" || { echo "FAIL: can't cd to repo root" >&2; exit 2; }
30
+
31
+ if ! command -v act >/dev/null 2>&1; then
32
+ cat >&2 <<'MSG'
33
+ ✗ act is not installed - this gate needs it to run the Ubuntu job locally.
34
+
35
+ brew install act # macOS
36
+ # and a running Docker daemon (Docker Desktop, colima, orbstack)
37
+
38
+ Why this is not bundled: it pulls a multi-gigabyte runner image. The rest of
39
+ the gate chain (npm run gate) has no such dependency and stays the default.
40
+ MSG
41
+ exit 2
42
+ fi
43
+
44
+ if ! docker info >/dev/null 2>&1; then
45
+ echo "✗ act is installed but no Docker daemon is reachable. Start Docker and retry." >&2
46
+ exit 2
47
+ fi
48
+
49
+ echo "→ Running ci-lite on ubuntu-22.04 via act (this pulls an image on first run)"
50
+ echo ""
51
+
52
+ if act workflow_dispatch \
53
+ -W .github/workflows/ci-lite.yml \
54
+ -P ubuntu-latest=catthehacker/ubuntu:act-22.04; then
55
+ echo ""
56
+ echo "✓ ci-lite passed on Linux"
57
+ exit 0
58
+ fi
59
+
60
+ echo "" >&2
61
+ echo "✗ ci-lite failed on Linux. A pass on macOS does not cover this." >&2
62
+ exit 1
@@ -116,8 +116,7 @@ export function localizeCommands(dir, mode) {
116
116
  // `.split("/").pop()` never split a backslash path, so on Windows this was
117
117
  // always false - `restore` exited 0 doing nothing and /multi-agent:sync then
118
118
  // mirrored localized descriptions into the repo, the one thing it must prevent.
119
- const invokedDirectly =
120
- process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
119
+ const invokedDirectly = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
121
120
  if (invokedDirectly) {
122
121
  const args = process.argv.slice(2);
123
122
  const mode = args[0];
@@ -3,10 +3,12 @@
3
3
  #
4
4
  # Purpose:
5
5
  # The repo HAS workflows (test.yml, ci-lite.yml), but they have not executed
6
- # since 2026-07-25: the jobs complete in ~2 seconds with zero steps recorded,
7
- # which is a runner/quota block rather than a test failure. Until that is
8
- # resolved, this hook is not a second opinion - it is the only thing standing
9
- # between a broken commit and npm.
6
+ # since 2026-07-02: every attempt after that day - the last on 2026-07-25 -
7
+ # finished in about four seconds with zero steps recorded, which is a
8
+ # runner/quota block rather than a test failure. Both are now dormant by
9
+ # declaration too (ADR-0011: the push triggers are commented out). So this
10
+ # hook is not a second opinion - it is the only thing standing between a
11
+ # broken commit and npm.
10
12
  #
11
13
  # It therefore runs the CANONICAL chain, `npm test`, rather than a hand-picked
12
14
  # subset. The subset it used to run was missing eight of the eleven steps
@@ -69,8 +69,7 @@ function readJson(path) {
69
69
 
70
70
  function resolveGlobalPrefs() {
71
71
  const path =
72
- process.env.MULTI_AGENT_PREFS ||
73
- join(homedir(), ".claude", "multi-agent-preferences.json");
72
+ process.env.MULTI_AGENT_PREFS || join(homedir(), ".claude", "multi-agent-preferences.json");
74
73
  return readJson(path)?.global ?? {};
75
74
  }
76
75
 
@@ -130,9 +129,7 @@ function enabledPlugins(state) {
130
129
  ]) {
131
130
  for (const name of readEnabledPluginNames(s)) names.add(name.split("@")[0]);
132
131
  }
133
- return Array.from(names)
134
- .filter(Boolean)
135
- .slice(0, 20);
132
+ return Array.from(names).filter(Boolean).slice(0, 20);
136
133
  }
137
134
 
138
135
  function integrationTags(state) {
@@ -146,8 +143,7 @@ function integrationTags(state) {
146
143
  const tool = String(call?.tool || "");
147
144
  // Server segment may contain hyphens (mcp__multi-agent-toolkit__ios_tap), so the
148
145
  // class must accept them or "multi-agent-toolkit" collapses to "dev".
149
- const m =
150
- tool.match(/^mcp__([a-z0-9_-]+?)__/i) || tool.match(/^mcp__([a-z0-9_-]+)/i);
146
+ const m = tool.match(/^mcp__([a-z0-9_-]+?)__/i) || tool.match(/^mcp__([a-z0-9_-]+)/i);
151
147
  if (m) apps.add(m[1].replace(/_/g, "-").toLowerCase());
152
148
  }
153
149
  return Array.from(apps).slice(0, 20);
@@ -345,7 +341,8 @@ function trackerSummary(state) {
345
341
  const phs = [];
346
342
  const failed = [];
347
343
  const models = new Set();
348
- let startedAt = typeof tracker?.started_at === "string" && tracker.started_at ? tracker.started_at : null;
344
+ let startedAt =
345
+ typeof tracker?.started_at === "string" && tracker.started_at ? tracker.started_at : null;
349
346
  let endedAt = null;
350
347
  for (const p of list) {
351
348
  const id = p?.id != null && String(p.id) !== "" ? String(p.id) : "?";
@@ -510,9 +507,7 @@ function buildEvent(state) {
510
507
  ph: typeof state.currentPhase === "number" ? state.currentPhase : null,
511
508
  st: state.status || null,
512
509
  hr: state.haltReason || null,
513
- ri: Array.isArray(state.reviewIterations)
514
- ? state.reviewIterations.length
515
- : 0,
510
+ ri: Array.isArray(state.reviewIterations) ? state.reviewIterations.length : 0,
516
511
  pr: Boolean(state.pr && (state.pr.url || state.pr.number)),
517
512
  du: durationSec(state, spend),
518
513
  tk: spend.tk,
@@ -612,10 +607,17 @@ async function main() {
612
607
  }
613
608
 
614
609
  // Windows-safe entry-point check: compare file URLs, never string-split a path.
615
- const invokedDirectly =
616
- process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
610
+ const invokedDirectly = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
617
611
  if (invokedDirectly) {
618
612
  main().catch(() => process.exit(0));
619
613
  }
620
614
 
621
- export { endpointAllowed, integrationTags, enabledPlugins, readEnabledPluginNames, trackerSummary, buildEvent, resolveUser };
615
+ export {
616
+ endpointAllowed,
617
+ integrationTags,
618
+ enabledPlugins,
619
+ readEnabledPluginNames,
620
+ trackerSummary,
621
+ buildEvent,
622
+ resolveUser,
623
+ };