@mmerterden/multi-agent-pipeline 20.36.0 → 20.37.1

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 (222) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/docs/facts.json +2 -2
  3. package/docs/features.md +1 -1
  4. package/docs/recovery-guide.md +1 -1
  5. package/install/catalog-history.json +1 -1
  6. package/manifest.json +225 -148
  7. package/package.json +1 -1
  8. package/pipeline/commands/multi-agent/SKILL.md +33 -154
  9. package/pipeline/commands/multi-agent/analysis/SKILL.md +14 -68
  10. package/pipeline/commands/multi-agent/analysis-jira/SKILL.md +5 -8
  11. package/pipeline/commands/multi-agent/autopilot-on/SKILL.md +38 -109
  12. package/pipeline/commands/multi-agent/channels/SKILL.md +38 -167
  13. package/pipeline/commands/multi-agent/complaint-analysis/SKILL.md +37 -144
  14. package/pipeline/commands/multi-agent/design-check/SKILL.md +27 -131
  15. package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +22 -48
  16. package/pipeline/commands/multi-agent/ios-coding-standard/SKILL.md +37 -180
  17. package/pipeline/commands/multi-agent/jira/SKILL.md +16 -46
  18. package/pipeline/commands/multi-agent/manual-test/SKILL.md +12 -7
  19. package/pipeline/commands/multi-agent/refactor/SKILL.md +23 -184
  20. package/pipeline/commands/multi-agent/resume/SKILL.md +30 -136
  21. package/pipeline/commands/multi-agent/review/SKILL.md +24 -199
  22. package/pipeline/commands/multi-agent/setup/SKILL.md +25 -181
  23. package/pipeline/commands/multi-agent/stack/SKILL.md +9 -53
  24. package/pipeline/commands/multi-agent/status/SKILL.md +13 -2
  25. package/pipeline/commands/multi-agent/store-ready/SKILL.md +27 -226
  26. package/pipeline/commands/multi-agent/sync/SKILL.md +35 -164
  27. package/pipeline/commands/multi-agent/test-gen/SKILL.md +2 -4
  28. package/pipeline/commands/multi-agent/update/SKILL.md +18 -60
  29. package/pipeline/contract/CHANGELOG.md +9 -0
  30. package/pipeline/contract/fixtures/error-invalid-request.json +1 -1
  31. package/pipeline/contract/fixtures/error-unauthorized.json +1 -1
  32. package/pipeline/contract/fixtures/error-unsigned.json +1 -1
  33. package/pipeline/contract/fixtures/runs-awaiting-question.json +1 -1
  34. package/pipeline/contract/fixtures/runs-empty.json +1 -1
  35. package/pipeline/contract/fixtures/runs-failed.json +1 -1
  36. package/pipeline/contract/fixtures/runs-pr-opened-redacted.json +1 -1
  37. package/pipeline/contract/fixtures/runs-pr-opened.json +1 -1
  38. package/pipeline/contract/fixtures/runs-running.json +1 -1
  39. package/pipeline/contract/manifest.json +5 -5
  40. package/pipeline/contract/types/index.d.ts +3 -2
  41. package/pipeline/multi-agent-refs/audit-guide.md +20 -0
  42. package/pipeline/multi-agent-refs/autopilot-on/details.md +148 -0
  43. package/pipeline/multi-agent-refs/channels/generator.md +14 -0
  44. package/pipeline/multi-agent-refs/channels/preview.md +13 -0
  45. package/pipeline/multi-agent-refs/channels/reference.md +46 -0
  46. package/pipeline/multi-agent-refs/complaint-analysis/phases.md +159 -0
  47. package/pipeline/multi-agent-refs/design-check/export.md +1 -0
  48. package/pipeline/multi-agent-refs/design-check/init-inventory.md +1 -1
  49. package/pipeline/multi-agent-refs/design-check/scope-and-ledger.md +64 -0
  50. package/pipeline/multi-agent-refs/features/analysis-command-notes.md +53 -0
  51. package/pipeline/multi-agent-refs/features/commit-multi-repo.md +63 -0
  52. package/pipeline/multi-agent-refs/features/dev-multi-repo.md +51 -0
  53. package/pipeline/multi-agent-refs/features/test-gap.md +40 -0
  54. package/pipeline/multi-agent-refs/features/worktree-finalize.md +1 -1
  55. package/pipeline/multi-agent-refs/garbage-collect/phases.md +60 -0
  56. package/pipeline/multi-agent-refs/ios-coding-standard/plan.md +71 -0
  57. package/pipeline/multi-agent-refs/ios-coding-standard/resolve-and-scan.md +135 -0
  58. package/pipeline/multi-agent-refs/jira/picker-detail.md +71 -0
  59. package/pipeline/multi-agent-refs/orchestrator/copilot-pipeline.md +88 -0
  60. package/pipeline/multi-agent-refs/orchestrator/dispatcher.md +96 -1
  61. package/pipeline/multi-agent-refs/orchestrator/phase-3-user-test.md +12 -9
  62. package/pipeline/multi-agent-refs/phases/phase-1-plan.md +4 -4
  63. package/pipeline/multi-agent-refs/phases/phase-2-dev.md +8 -47
  64. package/pipeline/multi-agent-refs/phases/phase-3-review.md +21 -65
  65. package/pipeline/multi-agent-refs/phases/phase-4-commit.md +9 -59
  66. package/pipeline/multi-agent-refs/phases/phase-5-report.md +0 -31
  67. package/pipeline/multi-agent-refs/refactor/sweep.md +190 -0
  68. package/pipeline/multi-agent-refs/resume/details.md +115 -2
  69. package/pipeline/multi-agent-refs/review/inputs.md +33 -1
  70. package/pipeline/multi-agent-refs/review/module-guides.md +9 -1
  71. package/pipeline/multi-agent-refs/review/panel.md +75 -0
  72. package/pipeline/multi-agent-refs/review/valven.md +5 -1
  73. package/pipeline/multi-agent-refs/rules.md +1 -1
  74. package/pipeline/multi-agent-refs/setup/hooks-and-plugins.md +4 -0
  75. package/pipeline/multi-agent-refs/setup/token-save-flow.md +13 -0
  76. package/pipeline/multi-agent-refs/stack/background.md +72 -0
  77. package/pipeline/multi-agent-refs/store-ready/gates.md +139 -0
  78. package/pipeline/multi-agent-refs/store-ready/report.md +40 -2
  79. package/pipeline/multi-agent-refs/sync/office.md +10 -0
  80. package/pipeline/multi-agent-refs/sync/overview.md +61 -0
  81. package/pipeline/multi-agent-refs/sync/repo.md +91 -0
  82. package/pipeline/multi-agent-refs/update/details.md +83 -0
  83. package/pipeline/office/index.html +6 -3
  84. package/pipeline/office/toolkit.json +1 -1
  85. package/pipeline/rules/figma-pipeline.md +6 -3
  86. package/pipeline/schemas/prefs.schema.json +1 -1
  87. package/pipeline/schemas/runs-index.schema.json +2 -1
  88. package/pipeline/scripts/_memory-policy.mjs +27 -5
  89. package/pipeline/scripts/checkpoint-fold.sh +59 -0
  90. package/pipeline/scripts/contract-server.mjs +1 -1
  91. package/pipeline/scripts/gate-ledger.mjs +1 -1
  92. package/pipeline/scripts/office-control.mjs +41 -15
  93. package/pipeline/scripts/offload-ref.sh +1 -1
  94. package/pipeline/scripts/rework-rulings.mjs +5 -3
  95. package/pipeline/scripts/runs-index.mjs +34 -7
  96. package/pipeline/scripts/scope-check-gate.mjs +15 -2
  97. package/pipeline/scripts/status-overview.mjs +401 -0
  98. package/pipeline/scripts/user-test-handoff.sh +203 -0
  99. package/pipeline/scripts/validate-planning.mjs +5 -3
  100. package/pipeline/scripts/validate-triage.mjs +2 -1
  101. package/pipeline/scripts/write-state.mjs +13 -4
  102. package/pipeline/skills/.skill-manifest.json +64 -64
  103. package/pipeline/skills/shared/core/apple-archive-compliance/SKILL.md +7 -70
  104. package/pipeline/skills/shared/core/apple-archive-compliance/references/examples.md +67 -0
  105. package/pipeline/skills/shared/core/apple-archive-compliance/references/version-history.md +8 -0
  106. package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +10 -72
  107. package/pipeline/skills/shared/core/google-play-compliance/references/examples.md +69 -0
  108. package/pipeline/skills/shared/core/google-play-compliance/references/notes.md +7 -0
  109. package/pipeline/skills/shared/core/google-play-compliance/references/version-history.md +5 -0
  110. package/pipeline/skills/shared/core/multi-agent/SKILL.md +39 -262
  111. package/pipeline/skills/shared/core/multi-agent-autopilot-on/SKILL.md +35 -73
  112. package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +37 -337
  113. package/pipeline/skills/shared/core/multi-agent-design-check/SKILL.md +27 -122
  114. package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +8 -340
  115. package/pipeline/skills/shared/core/multi-agent-ios-coding-standard/SKILL.md +37 -180
  116. package/pipeline/skills/shared/core/multi-agent-manual-test/SKILL.md +12 -7
  117. package/pipeline/skills/shared/core/multi-agent-refactor/SKILL.md +23 -184
  118. package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +14 -82
  119. package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +25 -277
  120. package/pipeline/skills/shared/core/multi-agent-status/SKILL.md +13 -2
  121. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +73 -217
  122. package/pipeline/skills/shared/external/alarmkit/SKILL.md +11 -162
  123. package/pipeline/skills/shared/external/alarmkit/references/alarmkit-code.md +257 -0
  124. package/pipeline/skills/shared/external/android-architecture/SKILL.md +7 -58
  125. package/pipeline/skills/shared/external/android-architecture/references/code-examples.md +68 -0
  126. package/pipeline/skills/shared/external/app-intents/SKILL.md +3 -45
  127. package/pipeline/skills/shared/external/app-intents/references/code-examples.md +54 -0
  128. package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +5 -81
  129. package/pipeline/skills/shared/external/app-store-optimization/references/engagement-and-localization.md +96 -0
  130. package/pipeline/skills/shared/external/app-store-review/SKILL.md +6 -112
  131. package/pipeline/skills/shared/external/app-store-review/references/monetization-and-submission.md +138 -0
  132. package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +10 -117
  133. package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models-code.md +186 -0
  134. package/pipeline/skills/shared/external/authentication/SKILL.md +3 -50
  135. package/pipeline/skills/shared/external/authentication/references/code-examples.md +59 -0
  136. package/pipeline/skills/shared/external/callkit-voip/SKILL.md +3 -84
  137. package/pipeline/skills/shared/external/callkit-voip/references/code-examples.md +93 -0
  138. package/pipeline/skills/shared/external/ci-cd-pipelines/SKILL.md +7 -263
  139. package/pipeline/skills/shared/external/ci-cd-pipelines/references/pipeline-examples.md +281 -0
  140. package/pipeline/skills/shared/external/clean-code/SKILL.md +8 -41
  141. package/pipeline/skills/shared/external/clean-code/references/code-examples.md +56 -0
  142. package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +8 -146
  143. package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-code.md +214 -0
  144. package/pipeline/skills/shared/external/compose-components/SKILL.md +6 -80
  145. package/pipeline/skills/shared/external/compose-components/references/code-examples.md +89 -0
  146. package/pipeline/skills/shared/external/compose-navigation/SKILL.md +5 -64
  147. package/pipeline/skills/shared/external/compose-navigation/references/code-examples.md +70 -0
  148. package/pipeline/skills/shared/external/core-data/SKILL.md +9 -157
  149. package/pipeline/skills/shared/external/core-data/references/core-data-code.md +221 -0
  150. package/pipeline/skills/shared/external/core-nfc/SKILL.md +3 -89
  151. package/pipeline/skills/shared/external/core-nfc/references/code-examples.md +98 -0
  152. package/pipeline/skills/shared/external/coreml/SKILL.md +17 -133
  153. package/pipeline/skills/shared/external/coreml/references/coreml-code.md +208 -0
  154. package/pipeline/skills/shared/external/css-modern/SKILL.md +8 -131
  155. package/pipeline/skills/shared/external/css-modern/references/code-examples.md +142 -0
  156. package/pipeline/skills/shared/external/database-patterns/SKILL.md +6 -103
  157. package/pipeline/skills/shared/external/database-patterns/references/orm-examples.md +112 -0
  158. package/pipeline/skills/shared/external/html-semantic/SKILL.md +7 -48
  159. package/pipeline/skills/shared/external/html-semantic/references/code-examples.md +54 -0
  160. package/pipeline/skills/shared/external/humanizer/SKILL.md +6 -19
  161. package/pipeline/skills/shared/external/humanizer/references/voice.md +24 -0
  162. package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +13 -170
  163. package/pipeline/skills/shared/external/ios-accessibility/references/assistive-tech-review.md +224 -0
  164. package/pipeline/skills/shared/external/ios-localization/SKILL.md +2 -24
  165. package/pipeline/skills/shared/external/ios-localization/references/code-examples.md +30 -0
  166. package/pipeline/skills/shared/external/ios-networking/SKILL.md +3 -58
  167. package/pipeline/skills/shared/external/ios-networking/references/code-examples.md +67 -0
  168. package/pipeline/skills/shared/external/ios-security/SKILL.md +4 -64
  169. package/pipeline/skills/shared/external/ios-security/references/code-examples.md +74 -0
  170. package/pipeline/skills/shared/external/live-activities/SKILL.md +4 -90
  171. package/pipeline/skills/shared/external/live-activities/references/code-examples.md +108 -0
  172. package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +9 -23
  173. package/pipeline/skills/shared/external/localization-reuse-map/references/project-settings.md +22 -0
  174. package/pipeline/skills/shared/external/mapkit-location/SKILL.md +5 -95
  175. package/pipeline/skills/shared/external/mapkit-location/references/code-examples.md +117 -0
  176. package/pipeline/skills/shared/external/nextjs-app-router/SKILL.md +7 -106
  177. package/pipeline/skills/shared/external/nextjs-app-router/references/app-router-examples.md +124 -0
  178. package/pipeline/skills/shared/external/permissionkit/SKILL.md +2 -31
  179. package/pipeline/skills/shared/external/permissionkit/references/code-examples.md +37 -0
  180. package/pipeline/skills/shared/external/play-store-review/SKILL.md +6 -74
  181. package/pipeline/skills/shared/external/play-store-review/references/signing-and-policies.md +83 -0
  182. package/pipeline/skills/shared/external/push-notifications/SKILL.md +8 -125
  183. package/pipeline/skills/shared/external/push-notifications/references/notification-code.md +176 -0
  184. package/pipeline/skills/shared/external/python-patterns/SKILL.md +7 -158
  185. package/pipeline/skills/shared/external/python-patterns/references/python-examples.md +176 -0
  186. package/pipeline/skills/shared/external/rest-api-design/SKILL.md +7 -157
  187. package/pipeline/skills/shared/external/rest-api-design/references/api-examples.md +175 -0
  188. package/pipeline/skills/shared/external/swift-architecture/SKILL.md +11 -230
  189. package/pipeline/skills/shared/external/swift-architecture/references/pattern-examples.md +295 -0
  190. package/pipeline/skills/shared/external/swift-codable/SKILL.md +17 -149
  191. package/pipeline/skills/shared/external/swift-codable/references/codable-recipes.md +207 -0
  192. package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +12 -99
  193. package/pipeline/skills/shared/external/swift-concurrency/references/code-examples.md +100 -0
  194. package/pipeline/skills/shared/external/swift-concurrency/references/swift-6-2-additions.md +18 -0
  195. package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +9 -78
  196. package/pipeline/skills/shared/external/swift-formatstyle/references/formatstyle-examples.md +113 -0
  197. package/pipeline/skills/shared/external/swift-language/SKILL.md +3 -56
  198. package/pipeline/skills/shared/external/swift-language/references/code-examples.md +65 -0
  199. package/pipeline/skills/shared/external/swift-testing-pro/SKILL.md +1 -0
  200. package/pipeline/skills/shared/external/swiftdata/SKILL.md +6 -122
  201. package/pipeline/skills/shared/external/swiftdata/references/code-examples.md +140 -0
  202. package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +12 -139
  203. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/interop-code.md +198 -0
  204. package/pipeline/skills/shared/external/tailwind-css/SKILL.md +9 -81
  205. package/pipeline/skills/shared/external/tailwind-css/references/code-examples.md +91 -0
  206. package/pipeline/skills/shared/external/testing-backend/SKILL.md +7 -207
  207. package/pipeline/skills/shared/external/testing-backend/references/testing-examples.md +225 -0
  208. package/pipeline/skills/shared/external/typescript-patterns/SKILL.md +8 -89
  209. package/pipeline/skills/shared/external/typescript-patterns/references/code-examples.md +100 -0
  210. package/pipeline/skills/shared/external/vision-framework/SKILL.md +2 -20
  211. package/pipeline/skills/shared/external/vision-framework/references/code-examples.md +26 -0
  212. package/pipeline/skills/shared/external/vue-composition/SKILL.md +7 -169
  213. package/pipeline/skills/shared/external/vue-composition/references/vue-examples.md +187 -0
  214. package/pipeline/skills/shared/external/web-accessibility/SKILL.md +7 -174
  215. package/pipeline/skills/shared/external/web-accessibility/references/a11y-examples.md +192 -0
  216. package/pipeline/skills/shared/external/web-performance/SKILL.md +8 -66
  217. package/pipeline/skills/shared/external/web-performance/references/code-examples.md +77 -0
  218. package/pipeline/skills/shared/external/web-testing/SKILL.md +11 -186
  219. package/pipeline/skills/shared/external/web-testing/references/testing-examples.md +213 -0
  220. /package/pipeline/skills/shared/external/localization-reuse-map/{reference → references}/format-and-output.md +0 -0
  221. /package/pipeline/skills/shared/external/localization-reuse-map/{reference → references}/publish-and-snapshot.md +0 -0
  222. /package/pipeline/skills/shared/external/localization-reuse-map/{reference → references}/sources-and-recipes.md +0 -0
@@ -81,7 +81,7 @@ const PHASE_WAITING_FROM = PHASE_CONTRACT.thresholds.waitingFromPhase;
81
81
  */
82
82
  const LAST_PHASE = PHASE_CONTRACT.phases[PHASE_CONTRACT.phases.length - 1].id;
83
83
 
84
- export const CONTRACT_VERSION = "1.3.0";
84
+ export const CONTRACT_VERSION = "1.4.0";
85
85
 
86
86
  const STATE_MAX_BYTES = 16 * 1024 * 1024;
87
87
 
@@ -93,14 +93,22 @@ const GROUPS = {
93
93
  stopped: "Stopped mid-development",
94
94
  question: "Left at a question",
95
95
  unknown: "Status not recorded",
96
+ done: "Finished",
96
97
  };
97
98
 
99
+ /**
100
+ * A run in one of these finished or was given up; it asks nothing of anyone.
101
+ * `failed` is not here: a failed run can still be resumed or killed.
102
+ */
103
+ const DONE_STATUSES = new Set(["complete", "completed", "done", "abandoned", "cancelled"]);
104
+
98
105
  function parseArgs(argv) {
99
- const flags = { json: false, redact: false, group: null, taskId: null };
106
+ const flags = { json: false, redact: false, all: false, group: null, taskId: null };
100
107
  for (let i = 0; i < argv.length; i++) {
101
108
  const a = argv[i];
102
109
  if (a === "--json") flags.json = true;
103
110
  else if (a === "--redact") flags.redact = true;
111
+ else if (a === "--all") flags.all = true;
104
112
  else if (a === "--group") flags.group = argv[++i];
105
113
  else if (a.startsWith("--group=")) flags.group = a.slice(8);
106
114
  else if (a === "--task-id") flags.taskId = argv[++i];
@@ -379,11 +387,12 @@ function summarisePhases(tracker) {
379
387
  * The group a run belongs to, per status/SKILL.md 3b.
380
388
  *
381
389
  * @param {object|null} state
382
- * @returns {"waiting"|"stopped"|"question"|"unknown"}
390
+ * @returns {"waiting"|"stopped"|"question"|"unknown"|"done"}
383
391
  */
384
392
  function groupOf(state) {
385
393
  const status = state?.status;
386
394
  if (!status) return "unknown";
395
+ if (DONE_STATUSES.has(status)) return "done";
387
396
  const phase = Number(state?.currentPhase);
388
397
  const prUrl = state?.pr?.url ?? state?.prUrl ?? null;
389
398
  if (status === "awaiting_input" || status === "awaiting-user-test-main-checkout")
@@ -525,7 +534,17 @@ function fmtDuration(startedAt) {
525
534
  return `${Math.floor(h / 24)}d`;
526
535
  }
527
536
 
528
- function renderHuman(records) {
537
+ /**
538
+ * The Phase column. A phase past the contract's last one was recorded under an
539
+ * older, longer numbering; "current/last" would read as overflow, so it shows
540
+ * as the recorded number alone.
541
+ */
542
+ export function phaseCell(phase) {
543
+ if (phase === null) return " -";
544
+ return phase > LAST_PHASE ? `${phase} old` : `${phase}/${LAST_PHASE}`;
545
+ }
546
+
547
+ function renderHuman(records, { all = false } = {}) {
529
548
  const out = [];
530
549
  const total = records.length;
531
550
  out.push(`multi-agent runs - ${total} under ${logsRoot()}`);
@@ -541,13 +560,20 @@ function renderHuman(records) {
541
560
  for (const key of Object.keys(GROUPS)) {
542
561
  const rows = records.filter((r) => r.group === key);
543
562
  if (!rows.length) continue;
563
+ // Neither group asks anything of anyone, and together they are most of a
564
+ // machine's runs: a count keeps the actionable groups readable.
565
+ if ((key === "done" || key === "unknown") && !all) {
566
+ out.push("");
567
+ out.push(`${GROUPS[key]} (${rows.length}) - --all lists them`);
568
+ continue;
569
+ }
544
570
  out.push("");
545
571
  out.push(`${GROUPS[key]} (${rows.length})`);
546
572
  out.push(
547
573
  " ID Phase Status Branch Age ~USD",
548
574
  );
549
575
  for (const r of rows) {
550
- const phase = r.currentPhase === null ? " -" : `${r.currentPhase}/${LAST_PHASE}`;
576
+ const phase = phaseCell(r.currentPhase);
551
577
  out.push(
552
578
  " " +
553
579
  [
@@ -651,7 +677,7 @@ function main() {
651
677
  const flags = parseArgs(process.argv.slice(2));
652
678
  if (flags.help) {
653
679
  process.stdout.write(
654
- "Usage: runs-index.mjs [--json [--redact]] [--group waiting|stopped|question|unknown] [--task-id <id>]\n",
680
+ "Usage: runs-index.mjs [--json [--redact]] [--all] [--group waiting|stopped|question|unknown|done] [--task-id <id>]\n",
655
681
  );
656
682
  return 0;
657
683
  }
@@ -670,7 +696,8 @@ function main() {
670
696
  JSON.stringify(envelope(records, { redact: flags.redact }), null, 2) + "\n",
671
697
  );
672
698
  } else {
673
- process.stdout.write(renderHuman(records) + "\n");
699
+ // A group asked for by name is listed, not folded into its count.
700
+ process.stdout.write(renderHuman(records, { all: flags.all || flags.group !== null }) + "\n");
674
701
  }
675
702
  return 0;
676
703
  }
@@ -99,6 +99,17 @@ const LINE_COMMENT = /(\.(py|pyi|ya?ml|sh|bash|zsh|rb|toml|coffee)|(^|\/)(GNU)?[
99
99
  const TIGHT_LANGS =
100
100
  /\.(js|jsx|mjs|cjs|ts|tsx|java|go|cs|swift|kt|kts|json|rs|dart|scala|py|pyi|c|h|cc|cpp|hpp|m|mm)$/;
101
101
  const LEADING_OPERATOR_LANGS = /\.(kt|kts|swift|go|scala)$/;
102
+ // A newline ends the statement here, so joining two lines changes the program.
103
+ const NEWLINE_TERMINATED = new RegExp(
104
+ [
105
+ String.raw`\.(sh|bash|zsh|ksh|fish|rb|rake|gemspec|toml|dockerfile)$`,
106
+ String.raw`(^|\/)\.envrc$`,
107
+ String.raw`(^|\/)[^/]*Rakefile$`,
108
+ String.raw`(^|\/)(Gemfile|Podfile|Fastfile|Appfile|Matchfile|Snapfile|Scanfile|Gymfile|Deliverfile|Pluginfile|Dangerfile|Brewfile|Vagrantfile|Jenkinsfile)$`,
109
+ String.raw`(^|\/)(Dockerfile|Containerfile)([.-][^/]*)?$`,
110
+ ].join("|"),
111
+ "i",
112
+ );
102
113
  const BREAK_KEEPERS = /\b(return|throw|yield|break|continue)$/;
103
114
  const LEADING_OPERATOR = /^[+\-*/%&|^.?]/;
104
115
  const TIGHT = /\s*([(){}[\],;:])\s*/g;
@@ -135,8 +146,9 @@ function foldLine(line, hashComments) {
135
146
  /**
136
147
  * The layout-free form of some lines. Each line is folded on its own (linear
137
148
  * in the hunk), then the lines are joined by a space, except that a break
138
- * after return, throw, yield, break or continue, and in Kotlin, Swift, Go or
139
- * Scala a break before a leading operator, stays a break. Space next to
149
+ * after return, throw, yield, break or continue, in Kotlin, Swift, Go or
150
+ * Scala a break before a leading operator, and every break in shell, Ruby,
151
+ * TOML and Dockerfiles, stays a break. Space next to
140
152
  * brackets, commas, semicolons and colons is dropped only in TIGHT_LANGS, and
141
153
  * never in a hunk that holds a C preprocessor line.
142
154
  */
@@ -150,6 +162,7 @@ function outsideStrings(text, file = "") {
150
162
  return;
151
163
  }
152
164
  const keepBreak =
165
+ NEWLINE_TERMINATED.test(file) ||
153
166
  BREAK_KEEPERS.test(lines[i - 1]) ||
154
167
  (LEADING_OPERATOR_LANGS.test(file) && LEADING_OPERATOR.test(line));
155
168
  joined += (keepBreak ? "\u23ce" : " ") + line;
@@ -0,0 +1,401 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @file status-overview.mjs - what is happening on this machine right now,
4
+ * in one answer: the live Claude Code sessions and the run each one drives,
5
+ * the tokens they used, which accounts and credentials are set up, and the
6
+ * run list.
7
+ *
8
+ * Sources, all read on every call:
9
+ *
10
+ * 1. sessions: `claude agents --json` (lib/claude-sessions.mjs), matched to
11
+ * a run by the session id it recorded, else by a working directory inside
12
+ * the run's worktree (activity-snapshot.mjs matchRun);
13
+ * 2. tokens: the `usage` block of every assistant message in the session's
14
+ * transcript and its subagent transcripts, counted once per message id
15
+ * (a message split into several lines repeats its usage). The last-24h
16
+ * figure covers every transcript written in that window. Prices come from
17
+ * cost-table.json; cache writes are priced at the input rate, so the
18
+ * estimate errs low;
19
+ * 3. accounts: the github.com logins gh holds (usage-identity.mjs) and the
20
+ * author git would write in the current directory;
21
+ * 4. credentials: each logical name in prefs.global.keychainMapping is
22
+ * "registered" when its service name is in `credential-store.sh list`.
23
+ * Only service names are read: the store is never asked for a value, and
24
+ * nothing about a value (length, prefix) is printed;
25
+ * 5. runs: runs-index.mjs, the same records /multi-agent:status always used.
26
+ *
27
+ * Usage:
28
+ * status-overview.mjs [--json] [--all]
29
+ *
30
+ * Env: MA_CREDENTIAL_STORE (the store script), MA_PREFS (the preferences
31
+ * file), CLAUDE_PROJECTS_ROOT (the transcript root), MA_AP_AGENTS_JSON (the
32
+ * session list, see claude-sessions.mjs).
33
+ *
34
+ * Exit: 0 printed, 2 usage.
35
+ *
36
+ * @module pipeline/scripts/status-overview
37
+ */
38
+
39
+ import { createReadStream, existsSync, readdirSync, readFileSync, statSync } from "node:fs";
40
+ import { spawnSync } from "node:child_process";
41
+ import { homedir } from "node:os";
42
+ import { basename, join } from "node:path";
43
+ import { createInterface } from "node:readline";
44
+
45
+ import { listSessions } from "../lib/claude-sessions.mjs";
46
+ import { runMain } from "../lib/fatal.mjs";
47
+ import { invokedDirectly } from "../lib/invoked-directly.mjs";
48
+ import { costUsd } from "./_cost.mjs";
49
+ import { matchRun } from "./activity-snapshot.mjs";
50
+ import { buildIndex, phaseCell } from "./runs-index.mjs";
51
+ import { listAccounts } from "./usage-identity.mjs";
52
+
53
+ export const CONTRACT_VERSION = "1.0.0";
54
+ export const WINDOW_MS = 24 * 3_600_000;
55
+ const SAFE_SESSION_ID = /^[A-Za-z0-9-]{8,64}$/;
56
+ /**
57
+ * States of a session that is still open. Wider than claude-sessions.mjs
58
+ * LIVE_STATUSES, which asks whether a session is working: an idle or blocked
59
+ * session is not working, but it is open and it is what this overview counts.
60
+ */
61
+ const OPEN = new Set(["busy", "running", "waiting", "idle", "blocked"]);
62
+
63
+ const projectsRoot = (env) => env.CLAUDE_PROJECTS_ROOT || join(homedir(), ".claude", "projects");
64
+ const prefsPath = (env) =>
65
+ env.MA_PREFS || join(homedir(), ".claude", "multi-agent-preferences.json");
66
+ const storePath = (env) =>
67
+ env.MA_CREDENTIAL_STORE || new URL("../lib/credential-store.sh", import.meta.url).pathname;
68
+
69
+ function listDir(dir) {
70
+ try {
71
+ return readdirSync(dir);
72
+ } catch {
73
+ return [];
74
+ }
75
+ }
76
+
77
+ let PRICES = null;
78
+ /** The cost-table rate for a wire model id, by its `modelId`, else by rung name in the id. */
79
+ export function rateFor(model) {
80
+ if (!PRICES) {
81
+ try {
82
+ PRICES = JSON.parse(
83
+ readFileSync(new URL("./cost-table.json", import.meta.url), "utf8"),
84
+ ).prices;
85
+ } catch {
86
+ PRICES = {};
87
+ }
88
+ }
89
+ if (typeof model !== "string" || !model) return null;
90
+ for (const rate of Object.values(PRICES)) if (rate?.modelId === model) return rate;
91
+ for (const [rung, rate] of Object.entries(PRICES)) {
92
+ if (/^[a-z]+$/.test(rung) && model.includes(`-${rung}-`)) return rate;
93
+ }
94
+ return null;
95
+ }
96
+
97
+ const emptyUsage = () => ({ in: 0, out: 0, cacheRead: 0, cacheWrite: 0, usd: 0, unpriced: 0 });
98
+
99
+ function addUsage(acc, model, u) {
100
+ const tin = Number(u.input_tokens) || 0;
101
+ const tout = Number(u.output_tokens) || 0;
102
+ const read = Number(u.cache_read_input_tokens) || 0;
103
+ const write = Number(u.cache_creation_input_tokens) || 0;
104
+ acc.in += tin;
105
+ acc.out += tout;
106
+ acc.cacheRead += read;
107
+ acc.cacheWrite += write;
108
+ const usd = costUsd(rateFor(model), tin + write, tout, read);
109
+ if (usd === null) acc.unpriced++;
110
+ else acc.usd += usd;
111
+ }
112
+
113
+ /**
114
+ * Token usage in one transcript: the whole file, and the part at or after
115
+ * `since`. Each message id counts once in the file; `windowSeen`, shared
116
+ * across files, keeps a message a resumed or forked session copied into a
117
+ * second transcript from counting twice in the window. A file that cannot be
118
+ * read adds nothing and comes back `readable: false`.
119
+ *
120
+ * @returns {Promise<{total: object, window: object, readable: boolean}>}
121
+ */
122
+ export async function transcriptUsage(path, since = 0, windowSeen = new Set()) {
123
+ const total = emptyUsage();
124
+ const window = emptyUsage();
125
+ const seen = new Set();
126
+ try {
127
+ await scan(path, (rec, msg, id) => {
128
+ if (id) {
129
+ if (seen.has(id)) return;
130
+ seen.add(id);
131
+ }
132
+ addUsage(total, msg.model, msg.usage);
133
+ const t = Date.parse(rec.timestamp);
134
+ if (!Number.isFinite(t) || t < since) return;
135
+ if (id) {
136
+ if (windowSeen.has(id)) return;
137
+ windowSeen.add(id);
138
+ }
139
+ addUsage(window, msg.model, msg.usage);
140
+ });
141
+ } catch {
142
+ return { total: emptyUsage(), window: emptyUsage(), readable: false };
143
+ }
144
+ return { total, window, readable: true };
145
+ }
146
+
147
+ async function scan(path, visit) {
148
+ const lines = createInterface({ input: createReadStream(path), crlfDelay: Infinity });
149
+ for await (const line of lines) {
150
+ if (!line.includes('"usage"')) continue;
151
+ let rec;
152
+ try {
153
+ rec = JSON.parse(line);
154
+ } catch {
155
+ continue;
156
+ }
157
+ const msg = rec?.message;
158
+ if (rec?.type !== "assistant" || !msg?.usage || typeof msg.usage !== "object") continue;
159
+ // The host writes its own notices as "<synthetic>" messages; no model ran.
160
+ if (msg.model === "<synthetic>") continue;
161
+ visit(rec, msg, typeof msg.id === "string" ? msg.id : null);
162
+ }
163
+ }
164
+
165
+ function merge(a, b) {
166
+ for (const k of Object.keys(a)) a[k] += b[k];
167
+ return a;
168
+ }
169
+
170
+ /** The session transcript and its subagent transcripts, or [] when the id is unsafe or absent. */
171
+ export function sessionFiles(root, sessionId) {
172
+ if (typeof sessionId !== "string" || !SAFE_SESSION_ID.test(sessionId)) return [];
173
+ for (const slug of listDir(root)) {
174
+ const main = join(root, slug, `${sessionId}.jsonl`);
175
+ if (!existsSync(main)) continue;
176
+ const sub = join(root, slug, sessionId, "subagents");
177
+ return [
178
+ main,
179
+ ...listDir(sub)
180
+ .filter((f) => f.endsWith(".jsonl"))
181
+ .map((f) => join(sub, f)),
182
+ ];
183
+ }
184
+ return [];
185
+ }
186
+
187
+ /** Every transcript under the root written at or after `since`. */
188
+ function recentFiles(root, since) {
189
+ const out = [];
190
+ const visit = (dir, depth) => {
191
+ for (const name of listDir(dir)) {
192
+ const p = join(dir, name);
193
+ let st;
194
+ try {
195
+ st = statSync(p);
196
+ } catch {
197
+ continue;
198
+ }
199
+ if (st.isDirectory() && depth < 3) visit(p, depth + 1);
200
+ else if (st.isFile() && name.endsWith(".jsonl") && st.mtimeMs >= since) out.push(p);
201
+ }
202
+ };
203
+ visit(root, 0);
204
+ return out;
205
+ }
206
+
207
+ /** Logical credential names and whether each is registered; values are never read. */
208
+ export function credentialStatus({ env = process.env } = {}) {
209
+ let mapping;
210
+ try {
211
+ mapping = JSON.parse(readFileSync(prefsPath(env), "utf8"))?.global?.keychainMapping ?? {};
212
+ } catch {
213
+ return { readable: false, items: [] };
214
+ }
215
+ const listed = spawnSync("bash", [storePath(env), "list"], {
216
+ encoding: "utf8",
217
+ timeout: 15000,
218
+ env,
219
+ });
220
+ if (listed.status !== 0) return { readable: false, items: [] };
221
+ const services = new Set(String(listed.stdout).split("\n").filter(Boolean));
222
+ const items = Object.entries(mapping)
223
+ .filter(([k, v]) => typeof k === "string" && typeof v === "string" && v)
224
+ .map(([name, service]) => ({ name, registered: services.has(service) }))
225
+ .sort((a, b) => a.name.localeCompare(b.name));
226
+ return { readable: true, items };
227
+ }
228
+
229
+ /** The author git would write in the current directory. */
230
+ function gitIdentity(env) {
231
+ const read = (key) => {
232
+ const r = spawnSync("git", ["config", "--get", key], { encoding: "utf8", timeout: 5000, env });
233
+ return r.status === 0 ? r.stdout.trim() || null : null;
234
+ };
235
+ const name = read("user.name");
236
+ return name ? { name, email: read("user.email") } : null;
237
+ }
238
+
239
+ /**
240
+ * The whole overview as one document.
241
+ *
242
+ * @param {{env?: object, now?: number, all?: boolean}} [opts]
243
+ */
244
+ export async function statusOverview({ env = process.env, now = Date.now(), all = false } = {}) {
245
+ const root = projectsRoot(env);
246
+ const since = now - WINDOW_MS;
247
+ const runs = buildIndex();
248
+ const rows = listSessions(env);
249
+
250
+ // One read per transcript: a live session's file is usually also one of the
251
+ // files written in the window.
252
+ const open = (Array.isArray(rows) ? rows : []).filter((s) => {
253
+ const state = s?.status ?? s?.state ?? null;
254
+ return !state || OPEN.has(state);
255
+ });
256
+ const files = new Map();
257
+ for (const s of open) for (const f of sessionFiles(root, s?.sessionId)) files.set(f, null);
258
+ for (const f of recentFiles(root, since)) files.set(f, null);
259
+ const windowSeen = new Set();
260
+ const last24h = emptyUsage();
261
+ let unreadable = 0;
262
+ for (const f of files.keys()) {
263
+ const u = await transcriptUsage(f, since, windowSeen);
264
+ if (!u.readable) unreadable++;
265
+ files.set(f, u);
266
+ merge(last24h, u.window);
267
+ }
268
+
269
+ const sessions = [];
270
+ for (const s of open) {
271
+ const state = s?.status ?? s?.state ?? null;
272
+ const { run, matchedBy } = matchRun(s?.sessionId, s?.cwd ?? null, null, runs);
273
+ const usage = emptyUsage();
274
+ for (const f of sessionFiles(root, s?.sessionId))
275
+ merge(usage, files.get(f)?.total ?? emptyUsage());
276
+ sessions.push({
277
+ sessionId: typeof s?.sessionId === "string" ? s.sessionId : null,
278
+ kind: typeof s?.kind === "string" ? s.kind : null,
279
+ state,
280
+ project: s?.cwd ? basename(s.cwd) : null,
281
+ startedAt: s?.startedAt ?? null,
282
+ run: run ? { taskId: run.taskId, currentPhase: run.currentPhase, status: run.status } : null,
283
+ matchedBy,
284
+ usage,
285
+ });
286
+ }
287
+
288
+ const accounts = listAccounts({ env });
289
+ return {
290
+ contractVersion: CONTRACT_VERSION,
291
+ generatedAt: new Date(now).toISOString(),
292
+ sessions,
293
+ sessionListReadable: Array.isArray(rows),
294
+ usage: {
295
+ liveSessions: sessions.reduce((acc, s) => merge(acc, { ...s.usage }), emptyUsage()),
296
+ last24h,
297
+ unreadableTranscripts: unreadable,
298
+ },
299
+ accounts: {
300
+ github: accounts.map((a) => ({ login: a.login, active: a.active })),
301
+ git: gitIdentity(env),
302
+ },
303
+ credentials: credentialStatus({ env }),
304
+ runs: all ? runs : runs.filter((r) => r.group !== "done" && r.group !== "unknown"),
305
+ runCounts: runs.reduce((acc, r) => ({ ...acc, [r.group]: (acc[r.group] ?? 0) + 1 }), {}),
306
+ };
307
+ }
308
+
309
+ const n = (x) =>
310
+ x >= 1e9
311
+ ? `${(x / 1e9).toFixed(1)}B`
312
+ : x >= 1e6
313
+ ? `${(x / 1e6).toFixed(1)}M`
314
+ : x >= 1e3
315
+ ? `${(x / 1e3).toFixed(1)}k`
316
+ : String(x);
317
+ const usageLine = (u) =>
318
+ `in ${n(u.in)} out ${n(u.out)} cache read ${n(u.cacheRead)} cache write ${n(u.cacheWrite)} ~$${u.usd.toFixed(2)}` +
319
+ (u.unpriced ? ` (${u.unpriced} unpriced)` : "");
320
+
321
+ export function renderHuman(doc) {
322
+ const out = [];
323
+ const busy = doc.sessions.filter((s) => s.state === "busy" || s.state === "running").length;
324
+ out.push(`Live sessions (${doc.sessions.length}, ${busy} busy)`);
325
+ if (!doc.sessionListReadable)
326
+ out.push(" the session list could not be read (claude agents --json)");
327
+ for (const s of doc.sessions) {
328
+ const run = s.run ? `${s.run.taskId} ${phaseCell(s.run.currentPhase)}` : "no run";
329
+ out.push(
330
+ ` ${(s.sessionId ?? "-").slice(0, 8)} ${(s.kind ?? "-").padEnd(11)} ${(s.state ?? "-").padEnd(7)} ` +
331
+ `${(s.project ?? "-").padEnd(24).slice(0, 24)} ${run.padEnd(22)} ~$${s.usage.usd.toFixed(2)}`,
332
+ );
333
+ }
334
+ out.push("");
335
+ out.push("Tokens");
336
+ out.push(` live sessions ${usageLine(doc.usage.liveSessions)}`);
337
+ out.push(` last 24h ${usageLine(doc.usage.last24h)}`);
338
+ if (doc.usage.unreadableTranscripts)
339
+ out.push(
340
+ ` ${doc.usage.unreadableTranscripts} transcript(s) could not be read; the totals leave them out`,
341
+ );
342
+ out.push("");
343
+ out.push("Accounts");
344
+ const gh = doc.accounts.github;
345
+ out.push(
346
+ ` github ${gh.length ? gh.map((a) => (a.active ? `${a.login} (active)` : a.login)).join(", ") : "none logged in"}`,
347
+ );
348
+ if (doc.accounts.git)
349
+ out.push(` git ${doc.accounts.git.name} <${doc.accounts.git.email ?? "-"}>`);
350
+ out.push("");
351
+ const c = doc.credentials;
352
+ if (!c.readable) out.push("Credentials could not be read");
353
+ else {
354
+ const missing = c.items.filter((i) => !i.registered).map((i) => i.name);
355
+ out.push(`Credentials ${c.items.length - missing.length}/${c.items.length} registered`);
356
+ out.push(
357
+ ` ${
358
+ c.items
359
+ .filter((i) => i.registered)
360
+ .map((i) => i.name)
361
+ .join(", ") || "-"
362
+ }`,
363
+ );
364
+ if (missing.length) out.push(` missing: ${missing.join(", ")}`);
365
+ }
366
+ out.push("");
367
+ const counts = Object.entries(doc.runCounts)
368
+ .map(([g, k]) => `${g} ${k}`)
369
+ .join(", ");
370
+ out.push(`Runs (${counts || "none"}) - runs-index.mjs lists them`);
371
+ const driven = new Set(doc.sessions.map((x) => x.run?.taskId).filter(Boolean));
372
+ for (const r of doc.runs.filter((x) => x.group === "waiting" || driven.has(x.taskId))) {
373
+ out.push(
374
+ ` ${r.taskId.padEnd(30).slice(0, 30)} ${phaseCell(r.currentPhase).padStart(5)} ${r.group}`,
375
+ );
376
+ }
377
+ return out.join("\n");
378
+ }
379
+
380
+ async function main() {
381
+ const args = process.argv.slice(2);
382
+ const flags = { json: false, all: false };
383
+ for (const a of args) {
384
+ if (a === "--json") flags.json = true;
385
+ else if (a === "--all") flags.all = true;
386
+ else if (a === "-h" || a === "--help") {
387
+ process.stdout.write("Usage: status-overview.mjs [--json] [--all]\n");
388
+ return 0;
389
+ } else {
390
+ process.stderr.write(`status-overview: unknown argument ${a}\n`);
391
+ return 2;
392
+ }
393
+ }
394
+ const doc = await statusOverview({ all: flags.all });
395
+ process.stdout.write((flags.json ? JSON.stringify(doc, null, 2) : renderHuman(doc)) + "\n");
396
+ return 0;
397
+ }
398
+
399
+ if (invokedDirectly(import.meta.url)) {
400
+ runMain("status-overview", main);
401
+ }