@mmerterden/multi-agent-pipeline 17.6.0 → 18.0.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 (103) hide show
  1. package/CHANGELOG.md +127 -0
  2. package/README.md +43 -1
  3. package/README.tr.md +41 -0
  4. package/docs/adr/0011-dormant-ci.md +25 -1
  5. package/docs/server-readiness.md +188 -0
  6. package/index.js +16 -1
  7. package/install/_common.mjs +42 -17
  8. package/install/_dev-only-files.mjs +8 -0
  9. package/install/_unattended-profile.mjs +113 -0
  10. package/install/index.mjs +48 -0
  11. package/manifest.json +1049 -0
  12. package/package.json +5 -2
  13. package/pipeline/commands/multi-agent/status/SKILL.md +52 -21
  14. package/pipeline/lib/_jira-auth.sh +8 -0
  15. package/pipeline/lib/analysis-jira-write.sh +32 -0
  16. package/pipeline/lib/ask-choice.sh +13 -2
  17. package/pipeline/lib/autopilot-state.sh +8 -0
  18. package/pipeline/lib/fatal.mjs +129 -0
  19. package/pipeline/lib/figma-mcp-refresh.sh +18 -0
  20. package/pipeline/lib/figma-screenshot.sh +18 -0
  21. package/pipeline/lib/invoked-directly.mjs +43 -0
  22. package/pipeline/lib/jira-publish.sh +42 -0
  23. package/pipeline/lib/md2confluence-v3.py +47 -0
  24. package/pipeline/lib/outbound-gate.mjs +175 -0
  25. package/pipeline/lib/plan-todos.sh +27 -6
  26. package/pipeline/lib/post-pr-review.sh +77 -8
  27. package/pipeline/lib/repo-hygiene.sh +8 -3
  28. package/pipeline/lib/require-jq.sh +40 -0
  29. package/pipeline/lib/run-paths.sh +335 -0
  30. package/pipeline/multi-agent-refs/features/autopilot-circuit-breaker.md +70 -0
  31. package/pipeline/multi-agent-refs/features/cost-analysis.md +93 -0
  32. package/pipeline/multi-agent-refs/features/doctor.md +45 -0
  33. package/pipeline/multi-agent-refs/features/verify.md +83 -0
  34. package/pipeline/multi-agent-refs/phases/operations.md +13 -2
  35. package/pipeline/multi-agent-refs/phases/phase-0-init.md +1 -1
  36. package/pipeline/multi-agent-refs/unattended-contract.md +129 -0
  37. package/pipeline/scripts/_run-paths.mjs +372 -0
  38. package/pipeline/scripts/aggregate-metrics.mjs +64 -64
  39. package/pipeline/scripts/autopilot-arming.mjs +2 -1
  40. package/pipeline/scripts/autopilot-intake.mjs +2 -1
  41. package/pipeline/scripts/autopilot-runner.mjs +206 -2
  42. package/pipeline/scripts/build-references.mjs +2 -1
  43. package/pipeline/scripts/build-stack-plugins.mjs +10 -2
  44. package/pipeline/scripts/capture-evidence.sh +7 -2
  45. package/pipeline/scripts/classify-plan-safety.mjs +2 -1
  46. package/pipeline/scripts/cost-analyze.mjs +600 -0
  47. package/pipeline/scripts/cost-budget-check.mjs +4 -12
  48. package/pipeline/scripts/council-view.mjs +2 -1
  49. package/pipeline/scripts/crush-json.mjs +2 -1
  50. package/pipeline/scripts/diff-explain.mjs +6 -9
  51. package/pipeline/scripts/diff-risk-score.mjs +2 -1
  52. package/pipeline/scripts/doctor.mjs +138 -4
  53. package/pipeline/scripts/evidence-gate.mjs +9 -3
  54. package/pipeline/scripts/feedback-send.mjs +12 -2
  55. package/pipeline/scripts/gc-abandoned.sh +29 -13
  56. package/pipeline/scripts/gc-worktrees.sh +11 -4
  57. package/pipeline/scripts/github-ssh-setup.sh +64 -7
  58. package/pipeline/scripts/graph-mermaid.mjs +4 -2
  59. package/pipeline/scripts/keychain-save.sh +101 -30
  60. package/pipeline/scripts/learn-from-transcripts.mjs +2 -1
  61. package/pipeline/scripts/learning-curve.mjs +34 -29
  62. package/pipeline/scripts/make-manifest.mjs +199 -0
  63. package/pipeline/scripts/migrate-prefs.mjs +2 -1
  64. package/pipeline/scripts/migrate-state.mjs +94 -4
  65. package/pipeline/scripts/phase-banner.sh +6 -2
  66. package/pipeline/scripts/phase-tracker.sh +41 -3
  67. package/pipeline/scripts/plan-coverage-gate.mjs +6 -2
  68. package/pipeline/scripts/pre-commit-check.sh +7 -0
  69. package/pipeline/scripts/pre-push-check.sh +7 -0
  70. package/pipeline/scripts/purge.sh +23 -6
  71. package/pipeline/scripts/render-agent-log-cost.sh +9 -2
  72. package/pipeline/scripts/render-cost-summary.sh +9 -2
  73. package/pipeline/scripts/render-work-summary.sh +11 -4
  74. package/pipeline/scripts/review-file-filter.mjs +4 -2
  75. package/pipeline/scripts/review-scope.mjs +2 -1
  76. package/pipeline/scripts/routine-registry.mjs +2 -1
  77. package/pipeline/scripts/run-aggregator.mjs +13 -14
  78. package/pipeline/scripts/run-metrics.mjs +3 -1
  79. package/pipeline/scripts/runs-index.mjs +343 -0
  80. package/pipeline/scripts/scorecard-snapshot.mjs +178 -0
  81. package/pipeline/scripts/search-logs.sh +18 -0
  82. package/pipeline/scripts/test-gap-scan.mjs +2 -1
  83. package/pipeline/scripts/test-integrity-gate.mjs +2 -1
  84. package/pipeline/scripts/update-issue-progress.sh +56 -7
  85. package/pipeline/scripts/usage-report.mjs +12 -1
  86. package/pipeline/scripts/validate-analysis-doc.mjs +2 -1
  87. package/pipeline/scripts/validate-code-graph.mjs +6 -3
  88. package/pipeline/scripts/validate-complaint-doc.mjs +2 -1
  89. package/pipeline/scripts/validate-diff-risk.mjs +6 -3
  90. package/pipeline/scripts/validate-test-gap.mjs +6 -3
  91. package/pipeline/scripts/validate-triage.mjs +3 -1
  92. package/pipeline/scripts/verify-citations.mjs +4 -2
  93. package/pipeline/scripts/verify.mjs +327 -0
  94. package/pipeline/scripts/worktree-finalize.sh +13 -4
  95. package/pipeline/scripts/write-state.mjs +154 -15
  96. package/pipeline/skills/.skill-manifest.json +2 -2
  97. package/pipeline/skills/.skills-index.json +56 -1
  98. package/pipeline/skills/shared/README.md +8 -3
  99. package/pipeline/skills/shared/core/multi-agent-status/SKILL.md +33 -9
  100. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/package_app.sh +4 -1
  101. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/setup_dev_signing.sh +4 -1
  102. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/sign-and-notarize.sh +2 -1
  103. package/pipeline/skills/skills-index.md +6 -1
@@ -0,0 +1,113 @@
1
+ /**
2
+ * _unattended-profile.mjs - the permission posture a machine with nobody at
3
+ * the keyboard needs, written only when asked for.
4
+ *
5
+ * autopilot spawns its child with `--permission-prompts none`. That stops Claude
6
+ * Code from ASKING; it grants nothing. The tools the child then calls still have
7
+ * to be allowed somewhere, and on a fresh machine they are not - so the run
8
+ * stops at the first tool call, silently, with no prompt anywhere for a person
9
+ * to answer. It looks exactly like a queue that has nothing to do.
10
+ *
11
+ * This is opt-in and always will be. A default install writes no permissions at
12
+ * all, because widening a permission set is not a thing an installer should do
13
+ * to somebody who only wanted the pipeline files copied. `--unattended` prints
14
+ * the full list and the reason for each line BEFORE writing anything, because
15
+ * "the installer changed my permissions" should never be something a person
16
+ * discovers afterwards.
17
+ *
18
+ * The entries are broad on purpose, and that breadth is the cost of unattended
19
+ * operation rather than an oversight. A pipeline run builds, tests, edits, and
20
+ * shells out to whatever the repo's own toolchain is; an allowlist narrow
21
+ * enough to be interesting would be one the first unfamiliar repo breaks. The
22
+ * honest framing is: this is a machine you have decided to let run on its own.
23
+ *
24
+ * @module install/_unattended-profile
25
+ */
26
+
27
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
28
+ import { join } from "node:path";
29
+
30
+ /**
31
+ * Tool name plus why an unattended run cannot proceed without it. The reason is
32
+ * printed, not just stored: a list of fifteen tool names with no reasons is a
33
+ * list nobody audits.
34
+ */
35
+ export const UNATTENDED_ALLOW = [
36
+ ["Bash", "every build, test, git and gh call the pipeline makes"],
37
+ ["Edit", "Phase 3 cannot change a file without it"],
38
+ ["Write", "new files, and every artefact a phase leaves behind"],
39
+ ["Read", "reading the repo it was pointed at"],
40
+ ["Glob", "finding files by pattern instead of shelling out to find"],
41
+ ["Grep", "the same, for content"],
42
+ ["Agent", "Phase 1 and Phase 4 dispatch subagents"],
43
+ ["Skill", "the phase specs are skills"],
44
+ ["WebFetch", "issue and ticket bodies referenced by URL"],
45
+ ["TaskCreate", "the phase tracker's task list"],
46
+ ["TaskUpdate", "the same"],
47
+ ["TaskList", "the same"],
48
+ ["TaskGet", "the same"],
49
+ ];
50
+
51
+ /**
52
+ * Render the profile for a human to read before it is applied.
53
+ *
54
+ * @param {string[]} already entries the settings file already carries
55
+ * @returns {string}
56
+ */
57
+ export function describeUnattendedProfile(already = []) {
58
+ const lines = [
59
+ " Unattended permission profile - these go into ~/.claude/settings.json",
60
+ " under permissions.allow. Nothing is removed; existing entries are kept.",
61
+ "",
62
+ ];
63
+ for (const [tool, why] of UNATTENDED_ALLOW) {
64
+ const mark = already.includes(tool) ? "already there" : "will be added";
65
+ lines.push(` ${tool.padEnd(12)} ${why} (${mark})`);
66
+ }
67
+ lines.push("");
68
+ lines.push(" This is broad, and that breadth is what unattended operation costs: a run");
69
+ lines.push(" builds and tests whatever the target repo uses, so a narrow list is one the");
70
+ lines.push(" first unfamiliar repo stops at, with nobody there to answer.");
71
+ return lines.join("\n");
72
+ }
73
+
74
+ /**
75
+ * Merge the profile into settings.json. Additive only, and idempotent: running
76
+ * it twice changes nothing the second time, and an entry a person added by hand
77
+ * survives.
78
+ *
79
+ * @param {{home:string, dryRun?:boolean}} opts
80
+ * @returns {{path:string, added:string[], already:string[], written:boolean}}
81
+ */
82
+ export function applyUnattendedProfile({ home, dryRun = false }) {
83
+ const path = join(home, ".claude", "settings.json");
84
+ let settings = {};
85
+ if (existsSync(path)) {
86
+ try {
87
+ settings = JSON.parse(readFileSync(path, "utf-8"));
88
+ } catch (err) {
89
+ // A settings file that does not parse is the user's, not ours to
90
+ // rewrite: overwriting it would lose whatever is in there.
91
+ throw new Error(`${path} is not valid JSON, so it will not be modified: ${err.message}`, {
92
+ cause: err,
93
+ });
94
+ }
95
+ }
96
+ const permissions = settings.permissions || {};
97
+ const allow = Array.isArray(permissions.allow) ? [...permissions.allow] : [];
98
+ const already = [];
99
+ const added = [];
100
+ for (const [tool] of UNATTENDED_ALLOW) {
101
+ if (allow.some((a) => String(a) === tool || String(a).startsWith(`${tool}(`)))
102
+ already.push(tool);
103
+ else {
104
+ allow.push(tool);
105
+ added.push(tool);
106
+ }
107
+ }
108
+ if (dryRun || !added.length) return { path, added, already, written: false };
109
+
110
+ settings.permissions = { ...permissions, allow };
111
+ writeFileSync(path, `${JSON.stringify(settings, null, 2)}\n`);
112
+ return { path, added, already, written: true };
113
+ }
package/install/index.mjs CHANGED
@@ -20,6 +20,7 @@ import { installCopilot } from "./copilot.mjs";
20
20
  import { installCodex } from "./codex.mjs";
21
21
  import { setDryRun, writeFile } from "./_common.mjs";
22
22
  import { sendInstallTelemetry } from "./_telemetry.mjs";
23
+ import { applyUnattendedProfile, describeUnattendedProfile } from "./_unattended-profile.mjs";
23
24
 
24
25
  const __dirname = dirname(fileURLToPath(import.meta.url));
25
26
  /** Repository root (one level above install/). */
@@ -63,6 +64,7 @@ export async function runInstall(argv) {
63
64
  "--index-only",
64
65
  "--dry-run",
65
66
  "--prune-external",
67
+ "--unattended",
66
68
  ];
67
69
  const KNOWN_PREFIXES = ["--platform="];
68
70
  const unknown = flags.filter(
@@ -91,6 +93,7 @@ export async function runInstall(argv) {
91
93
  const useSymlinks = flags.includes("--link");
92
94
  const indexOnly = flags.includes("--index-only");
93
95
  const pruneExternal = flags.includes("--prune-external");
96
+ const unattended = flags.includes("--unattended");
94
97
  const platformFlag = parsePlatformFlag(flags);
95
98
 
96
99
  const installerCtx = {
@@ -117,12 +120,15 @@ export async function runInstall(argv) {
117
120
  writeVersionMarkers({ home: HOME, forClaude, forCopilot, forCodex });
118
121
 
119
122
  if (dryRun) {
123
+ if (unattended) applyUnattended({ home: HOME, forClaude, dryRun: true });
120
124
  console.log("");
121
125
  console.log(" Dry-run complete - nothing was written. Re-run without --dry-run to install.");
122
126
  console.log("");
123
127
  return;
124
128
  }
125
129
 
130
+ if (unattended) applyUnattended({ home: HOME, forClaude });
131
+
126
132
  printSummary({ forClaude, forCopilot, forCodex });
127
133
 
128
134
  // Fire-and-forget; install is already done.
@@ -135,6 +141,48 @@ export async function runInstall(argv) {
135
141
  *
136
142
  * @param {{home: string, forClaude: boolean, forCopilot: boolean, forCodex?: boolean}} opts
137
143
  */
144
+ /**
145
+ * Print the unattended permission profile, then apply it. Printing first is the
146
+ * contract: an installer that widens a permission set has to say what it is
147
+ * about to allow while the person can still stop it, not report it afterwards.
148
+ *
149
+ * Claude Code only. The profile is a ~/.claude/settings.json shape, and writing
150
+ * it for a host that does not read that file would be a line in a report that
151
+ * nothing acts on.
152
+ *
153
+ * @param {{home:string, forClaude:boolean, dryRun?:boolean}} opts
154
+ */
155
+ function applyUnattended({ home, forClaude, dryRun = false }) {
156
+ if (!forClaude) {
157
+ console.log("");
158
+ console.log(" --unattended applies to Claude Code only; nothing written.");
159
+ return;
160
+ }
161
+ let existing = [];
162
+ try {
163
+ const settings = JSON.parse(readFileSync(join(home, ".claude", "settings.json"), "utf-8"));
164
+ existing = (settings?.permissions?.allow ?? []).map(String);
165
+ } catch {
166
+ // No settings file yet, or one that does not parse. Either way there is
167
+ // nothing to mark as "already there"; applyUnattendedProfile decides what
168
+ // to do about the unparseable case.
169
+ }
170
+ console.log("");
171
+ console.log(describeUnattendedProfile(existing));
172
+ try {
173
+ const res = applyUnattendedProfile({ home, dryRun });
174
+ console.log("");
175
+ if (dryRun) console.log(` (dry-run) would add ${res.added.length} entr(ies) to ${res.path}`);
176
+ else if (!res.added.length) console.log(` nothing to add - ${res.path} already covers it`);
177
+ else console.log(` added ${res.added.join(", ")} to ${res.path}`);
178
+ } catch (err) {
179
+ // A settings file we cannot parse is the user's, and rewriting it would
180
+ // lose whatever is in there. Refusing loudly beats a silent overwrite.
181
+ console.log("");
182
+ console.log(` permission profile NOT written: ${err.message}`);
183
+ }
184
+ }
185
+
138
186
  export function writeVersionMarkers({ home, forClaude, forCopilot, forCodex }) {
139
187
  let version;
140
188
  try {