@mmerterden/multi-agent-pipeline 16.5.0 → 16.7.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 (30) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +4 -4
  3. package/README.tr.md +4 -4
  4. package/docs/architecture.md +2 -2
  5. package/docs/ecosystem.md +5 -5
  6. package/package.json +1 -1
  7. package/pipeline/commands/multi-agent/analysis/SKILL.md +18 -5
  8. package/pipeline/commands/multi-agent/feedback/SKILL.md +51 -0
  9. package/pipeline/commands/multi-agent/review-analysis/SKILL.md +32 -0
  10. package/pipeline/commands/multi-agent/sync/SKILL.md +20 -18
  11. package/pipeline/commands/multi-agent/update/SKILL.md +1 -1
  12. package/pipeline/multi-agent-refs/analysis/intake.md +30 -1
  13. package/pipeline/multi-agent-refs/analysis/locked.md +11 -6
  14. package/pipeline/multi-agent-refs/analysis/render.md +20 -5
  15. package/pipeline/multi-agent-refs/analysis/review.md +86 -0
  16. package/pipeline/multi-agent-refs/analysis/synthesis.md +1 -1
  17. package/pipeline/multi-agent-refs/analysis-template-corporate.md +436 -0
  18. package/pipeline/multi-agent-refs/analysis-template.md +31 -13
  19. package/pipeline/multi-agent-refs/cross-cli-contract.md +10 -7
  20. package/pipeline/multi-agent-refs/website-deploy.md +87 -0
  21. package/pipeline/schemas/analysis-spec.schema.json +21 -1
  22. package/pipeline/schemas/prefs.schema.json +41 -0
  23. package/pipeline/scripts/build-references.mjs +368 -0
  24. package/pipeline/scripts/feedback-send.mjs +181 -0
  25. package/pipeline/scripts/validate-analysis-doc.mjs +130 -9
  26. package/pipeline/scripts/website-deploy-commit.sh +102 -0
  27. package/pipeline/skills/shared/core/multi-agent-analysis/SKILL.md +18 -2
  28. package/pipeline/skills/shared/core/multi-agent-feedback/SKILL.md +30 -0
  29. package/pipeline/skills/shared/core/multi-agent-review-analysis/SKILL.md +31 -0
  30. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +18 -14
@@ -0,0 +1,181 @@
1
+ #!/usr/bin/env node
2
+ // feedback-send.mjs - send one user-written feedback message to the
3
+ // maintainer's admin panel.
4
+ //
5
+ // WHAT IS SENT: the text the user typed, and nothing else that the user did not
6
+ // type. No logs, no repo names, no branch names, no file paths, no diffs, no
7
+ // prompts. The only fields this script adds are the ones the maintainer cannot
8
+ // act on the report without: which pipeline version it came from, which host
9
+ // CLI, and when.
10
+ //
11
+ // That restraint is the whole design. Attaching a run log would make a report
12
+ // easier to act on and would also mean a corporate user's internal identifiers
13
+ // leave their machine and land in someone else's database, from a package
14
+ // anyone can install off npm. A person can paste the one line they think
15
+ // matters; a script cannot know which line that is.
16
+ //
17
+ // Auth reuses the usage ingest token (`prefs.global.keychainMapping.usage_ingest`)
18
+ // so nothing new has to be onboarded. Unlike usage telemetry, `optOut` does NOT
19
+ // silence this: telemetry is passive collection, feedback is a deliberate act by
20
+ // the person running the command, and silently dropping something a user chose
21
+ // to send is worse than not offering the command.
22
+ //
23
+ // Usage:
24
+ // node feedback-send.mjs --text "<message>" [--kind bug|idea|question]
25
+ // [--endpoint <url>] [--dry-run]
26
+ //
27
+ // Exit: 0 sent (or dry run), 1 nothing to send / rejected, 2 usage error.
28
+
29
+ import { execFileSync } from "node:child_process";
30
+ import { readFileSync, existsSync } from "node:fs";
31
+ import { homedir } from "node:os";
32
+ import { join } from "node:path";
33
+
34
+ const ENDPOINT_DEFAULT = "https://mmerterden.vercel.app/api/feedback/ingest";
35
+ const TIMEOUT_MS = 8000;
36
+ const TEXT_MAX = 4000;
37
+ const KINDS = new Set(["bug", "idea", "question"]);
38
+
39
+ function arg(name, fallback = null) {
40
+ const i = process.argv.indexOf(name);
41
+ return i === -1 ? fallback : (process.argv[i + 1] ?? fallback);
42
+ }
43
+
44
+ function readPrefs() {
45
+ const p = join(homedir(), ".claude", "multi-agent-preferences.json");
46
+ try {
47
+ return JSON.parse(readFileSync(p, "utf-8")).global ?? {};
48
+ } catch {
49
+ return {};
50
+ }
51
+ }
52
+
53
+ function packageVersion() {
54
+ for (const p of [
55
+ join(homedir(), ".claude", ".pipeline-version"),
56
+ join(homedir(), ".copilot", ".pipeline-version"),
57
+ join(homedir(), ".codex", ".pipeline-version"),
58
+ ]) {
59
+ try {
60
+ const v = readFileSync(p, "utf-8").trim();
61
+ if (v) return v;
62
+ } catch {
63
+ /* try the next host */
64
+ }
65
+ }
66
+ return "unknown";
67
+ }
68
+
69
+ // Which CLI the person is running. Useful because a report that only reproduces
70
+ // on one host is a different bug from one that reproduces everywhere.
71
+ function host() {
72
+ if (existsSync(join(homedir(), ".claude", ".pipeline-version"))) return "claude";
73
+ if (existsSync(join(homedir(), ".copilot", ".pipeline-version"))) return "copilot";
74
+ if (existsSync(join(homedir(), ".codex", ".pipeline-version"))) return "codex";
75
+ return "unknown";
76
+ }
77
+
78
+ function resolveToken() {
79
+ if (process.env.MULTI_AGENT_USAGE_TOKEN) return process.env.MULTI_AGENT_USAGE_TOKEN;
80
+ const g = readPrefs();
81
+ const inline = g.usageLog?.token;
82
+ if (typeof inline === "string" && inline.trim()) return inline.trim();
83
+ const name = g.keychainMapping?.usage_ingest;
84
+ if (typeof name === "string" && name.trim()) {
85
+ const store = join(homedir(), ".claude", "lib", "credential-store.sh");
86
+ if (existsSync(store)) {
87
+ try {
88
+ const out = execFileSync("bash", [store, "get", name.trim()], {
89
+ encoding: "utf-8",
90
+ timeout: 4000,
91
+ stdio: ["ignore", "pipe", "ignore"],
92
+ }).trim();
93
+ if (out) return out;
94
+ } catch {
95
+ /* keychain unavailable */
96
+ }
97
+ }
98
+ }
99
+ return "";
100
+ }
101
+
102
+ // The token rides in a header, so http would put it on the wire in cleartext.
103
+ // Same rule the usage sender follows: https anywhere, http only for loopback.
104
+ function endpointAllowed(endpoint) {
105
+ let u;
106
+ try {
107
+ u = new URL(endpoint);
108
+ } catch {
109
+ return false;
110
+ }
111
+ if (u.protocol === "https:") return true;
112
+ return u.protocol === "http:" && ["localhost", "127.0.0.1", "::1"].includes(u.hostname);
113
+ }
114
+
115
+ async function main() {
116
+ const text = String(arg("--text", "") ?? "").trim();
117
+ if (!text) {
118
+ process.stderr.write('usage: feedback-send.mjs --text "<message>" [--kind bug|idea|question]\n');
119
+ process.exit(2);
120
+ }
121
+ if (text.length > TEXT_MAX) {
122
+ process.stderr.write(`ERROR: message is ${text.length} chars; the cap is ${TEXT_MAX}\n`);
123
+ process.exit(2);
124
+ }
125
+
126
+ const kindRaw = String(arg("--kind", "bug") ?? "bug").toLowerCase();
127
+ const kind = KINDS.has(kindRaw) ? kindRaw : "bug";
128
+ const endpoint = String(arg("--endpoint", ENDPOINT_DEFAULT) ?? ENDPOINT_DEFAULT);
129
+
130
+ const payload = {
131
+ text,
132
+ kind,
133
+ v: packageVersion(),
134
+ host: host(),
135
+ at: new Date().toISOString(),
136
+ };
137
+
138
+ if (process.argv.includes("--dry-run")) {
139
+ process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
140
+ process.exit(0);
141
+ }
142
+
143
+ if (!endpointAllowed(endpoint)) {
144
+ process.stderr.write(`ERROR: refusing to send over a non-TLS endpoint: ${endpoint}\n`);
145
+ process.exit(1);
146
+ }
147
+
148
+ const token = resolveToken();
149
+ if (!token) {
150
+ process.stderr.write(
151
+ "ERROR: no ingest token. Run /multi-agent:update once (it registers one) or /multi-agent:setup.\n",
152
+ );
153
+ process.exit(1);
154
+ }
155
+
156
+ const ctrl = new AbortController();
157
+ const timer = setTimeout(() => ctrl.abort(), TIMEOUT_MS);
158
+ try {
159
+ const res = await fetch(endpoint, {
160
+ method: "POST",
161
+ headers: { "Content-Type": "application/json", "X-Usage-Token": token },
162
+ body: JSON.stringify(payload),
163
+ signal: ctrl.signal,
164
+ });
165
+ if (!res.ok) {
166
+ // Unlike telemetry, this failure is reported: the person is standing there
167
+ // waiting to hear whether their message went. Swallowing it would leave
168
+ // them believing they had been heard.
169
+ process.stderr.write(`ERROR: the endpoint answered ${res.status}\n`);
170
+ process.exit(1);
171
+ }
172
+ process.stdout.write("feedback sent\n");
173
+ } catch (err) {
174
+ process.stderr.write(`ERROR: could not reach the endpoint (${err.name})\n`);
175
+ process.exit(1);
176
+ } finally {
177
+ clearTimeout(timer);
178
+ }
179
+ }
180
+
181
+ main();
@@ -42,7 +42,9 @@
42
42
 
43
43
  import { readFileSync } from "node:fs";
44
44
 
45
- const KNOWN_PLATFORMS = new Set(["ios", "android", "backend", "frontend"]);
45
+ // "none" is the stack-optional render (Locked 35): a document produced before
46
+ // any repo was chosen. It is a real platform value, not a missing one.
47
+ const KNOWN_PLATFORMS = new Set(["ios", "android", "backend", "frontend", "none"]);
46
48
  const REQUIRED_FM = ["feature", "platform", "language", "mode", "template_version"];
47
49
 
48
50
  // Never-omitted sections (Locked 2), matched by bilingual title keyword so the
@@ -58,6 +60,26 @@ const REQUIRED_SECTIONS = [
58
60
  { key: "references", any: ["References", "Referanslar"] },
59
61
  ];
60
62
 
63
+ // Corporate profile backbone (Locked 33). These render even with zero evidence,
64
+ // carrying N/A or EKLENECEK, so their absence is a structural failure rather
65
+ // than a legitimate omission.
66
+ const CORPORATE_SECTIONS = [
67
+ { key: "purpose and scope", any: ["Amaç ve Kapsam", "Amac ve Kapsam", "Purpose and Scope"] },
68
+ { key: "business analysis", any: ["İş Analizi", "Is Analizi", "Business Analysis"] },
69
+ { key: "business requirements", any: ["İş Gereksinimleri", "Is Gereksinimleri", "Business Requirements"] },
70
+ { key: "ai requirements", any: ["Yapay Zeka", "AI Requirements"] },
71
+ { key: "use cases", any: ["Kullanım Senaryoları", "Kullanim Senaryolari", "Use Cases"] },
72
+ { key: "hardware and infrastructure", any: ["Donanım", "Donanim", "Hardware"] },
73
+ { key: "quality requirements", any: ["Kalite Gereksinimleri", "Quality Requirements"] },
74
+ { key: "regulatory requirements", any: ["Regülasyonel", "Regulasyonel", "Regulatory"] },
75
+ { key: "content requirements", any: ["İçerik Gereksinimleri", "Icerik Gereksinimleri", "Content Requirements"] },
76
+ { key: "risks", any: ["Riskler", "Risks"] },
77
+ { key: "references", any: ["Referanslar", "References"] },
78
+ ];
79
+
80
+ // Sections that need a target repository. Dropped by the stack-optional render.
81
+ const REPO_DEPENDENT_KEYS = new Set(["architecture", "files to add"]);
82
+
61
83
  // Humanizer punctuation policy (Locked 7) - banned codepoints.
62
84
  const BANNED_PUNCT = [
63
85
  { ch: "—", name: "em-dash" },
@@ -163,6 +185,11 @@ function main() {
163
185
  }
164
186
  }
165
187
  const mode = parsed?.fm?.mode || "full";
188
+ const profile = (parsed?.fm?.profile || "global").toLowerCase();
189
+ if (profile !== "global" && profile !== "corporate") {
190
+ errors.push(`front-matter profile must be global|corporate, got: ${profile}`);
191
+ }
192
+ const noPlatform = (parsed?.fm?.platform || "") === "none";
166
193
 
167
194
  // 2. Never-omitted sections (by heading keyword)
168
195
  // Numbered section headings only. Layer headings (`# Bölüm B - Teknik Analiz`)
@@ -173,9 +200,94 @@ function main() {
173
200
  .filter((l) => /^#{2,3}\s+\d+(\.\d+)*\.?\s/.test(l))
174
201
  .map((l) => l.replace(/^#{2,3}\s/, "").trim());
175
202
  const headingBlob = headings.join("\n");
176
- for (const sec of REQUIRED_SECTIONS) {
203
+ const requiredSections = (profile === "corporate" ? CORPORATE_SECTIONS : REQUIRED_SECTIONS)
204
+ // The development layer is legitimately absent when no repo was selected,
205
+ // so requiring it there would fail every stack-optional document.
206
+ .filter((sec) => !(noPlatform && REPO_DEPENDENT_KEYS.has(sec.key)));
207
+ const rule = profile === "corporate" ? "Locked 33" : "Locked 2";
208
+ for (const sec of requiredSections) {
177
209
  if (!sec.any.some((kw) => headingBlob.includes(kw))) {
178
- errors.push(`missing required section (Locked 2): ${sec.key}`);
210
+ errors.push(`missing required section (${rule}): ${sec.key}`);
211
+ }
212
+ }
213
+
214
+ // 2a. Corporate profile: every EKLENECEK owes an open question.
215
+ //
216
+ // A placeholder with nobody assigned to resolve it is how "to be filled in"
217
+ // reaches production. The pairing is what makes the backbone guarantee worth
218
+ // having: the section renders, AND the gap it admits to is tracked.
219
+ if (profile === "corporate") {
220
+ const lines = text.split("\n");
221
+ const risksStart = lines.findIndex((l) =>
222
+ /^#{2,3}\s+\d+/.test(l) && /(Riskler|Risks)/.test(l),
223
+ );
224
+ let risksBlock = "";
225
+ if (risksStart !== -1) {
226
+ for (let i = risksStart + 1; i < lines.length; i++) {
227
+ if (/^#{2,3}\s+\d+/.test(lines[i])) break;
228
+ risksBlock += `${lines[i]}\n`;
229
+ }
230
+ }
231
+ let currentSection = null;
232
+ const unpaired = new Set();
233
+ for (let i = 0; i < lines.length; i++) {
234
+ const m = lines[i].match(/^#{2,4}\s+(\d+(?:\.\d+)*)\.?\s/);
235
+ if (m) currentSection = m[1];
236
+ if (!lines[i].includes("EKLENECEK")) continue;
237
+ if (risksStart !== -1 && i > risksStart) continue;
238
+ if (!currentSection) continue;
239
+ // The number has to stand alone: substring matching would let section "2"
240
+ // be satisfied by a "2.3" in some other row, and "9" by a "19". The
241
+ // trailing guard rejects a following digit and a following ".<digit>",
242
+ // but NOT a sentence-ending period - "etkilediği bölüm 9." is a real
243
+ // pairing, and rejecting it would block a correct document.
244
+ const token = new RegExp(
245
+ `(^|[^\\d.])${currentSection.replace(/\./g, "\\.")}(?!\\d|\\.\\d)`,
246
+ "m",
247
+ );
248
+ if (!token.test(risksBlock)) unpaired.add(currentSection);
249
+ }
250
+ for (const sec of unpaired) {
251
+ errors.push(
252
+ `section ${sec} carries EKLENECEK but no Risks and Open Questions row references it (Locked 33)`,
253
+ );
254
+ }
255
+
256
+ // The traceability matrix is the most expensive thing in the document to get
257
+ // wrong, because every downstream reader trusts it instead of re-deriving
258
+ // the IG -> UC -> FG chain themselves. A matrix that merely exists is not
259
+ // the claim worth making; a matrix that agrees with the sections is.
260
+ const MATRIX_HEADING = /(İzlenebilirlik Matrisi|Izlenebilirlik Matrisi|Traceability Matrix)/;
261
+ if (mode === "full") {
262
+ const matrixStart = lines.findIndex((l) => /^#{2,4}\s/.test(l) && MATRIX_HEADING.test(l));
263
+ if (matrixStart === -1) {
264
+ errors.push("corporate Full mode requires the requirement traceability matrix (Locked 33)");
265
+ } else {
266
+ let matrixBlock = "";
267
+ for (let i = matrixStart + 1; i < lines.length; i++) {
268
+ if (/^#{2,4}\s/.test(lines[i])) break;
269
+ matrixBlock += `${lines[i]}\n`;
270
+ }
271
+ // Everything outside the matrix is where ids are defined and used.
272
+ const outside = lines.slice(0, matrixStart).join("\n");
273
+
274
+ for (const prefix of ["IG", "UC", "FG"]) {
275
+ const idRe = new RegExp(`\\b${prefix}-\\d{2,3}\\b`, "g");
276
+ const defined = new Set(outside.match(idRe) || []);
277
+ const inMatrix = new Set(matrixBlock.match(idRe) || []);
278
+
279
+ for (const id of defined) {
280
+ if (!inMatrix.has(id)) {
281
+ errors.push(`${id} is defined in the document but missing from the traceability matrix`);
282
+ }
283
+ }
284
+ for (const id of inMatrix) {
285
+ if (!defined.has(id)) {
286
+ errors.push(`${id} appears in the traceability matrix but is defined nowhere else`);
287
+ }
288
+ }
289
+ }
290
+ }
179
291
  }
180
292
  }
181
293
 
@@ -206,9 +318,13 @@ function main() {
206
318
  }
207
319
  }
208
320
 
209
- // 2c. Section 15 is Phase 3's RED input (phase-3-dev.md step 5b): the tests
321
+ // 2c. The Test Plan is Phase 3's RED input (phase-3-dev.md step 5b): the tests
210
322
  // written first come from it. Full mode without it hands dev an empty matrix.
211
- if (mode === "full") {
323
+ // It is Section 15 in the global profile and Section 19 in the corporate one;
324
+ // the check matches on the title, so only the stack-optional case needs an
325
+ // exemption - there the whole development layer is legitimately absent
326
+ // (Locked 35) and there is no repo for a test to run against yet.
327
+ if (mode === "full" && !noPlatform) {
212
328
  const hasTestPlan = ["Test Planı", "Test Plani", "Test Plan"].some((kw) =>
213
329
  headingBlob.includes(kw),
214
330
  );
@@ -244,7 +360,9 @@ function main() {
244
360
  const hasMermaid = /^\s*```mermaid/m.test(text);
245
361
  if (hasFlowSection && !hasMermaid) {
246
362
  errors.push("Section 3 is rendered but carries no mermaid block");
247
- } else if (!hasFlowSection && mode === "full") {
363
+ } else if (!hasFlowSection && mode === "full" && profile === "global") {
364
+ // The corporate profile numbers Section 3 as Business Requirements and puts
365
+ // its diagrams under 5.(N+1), so "no Section 3 flow chart" is meaningless there.
248
366
  warns.push(
249
367
  "no Section 3 flow chart; legitimate only for a single screen with a single service and a simple rule",
250
368
  );
@@ -319,8 +437,11 @@ function main() {
319
437
  }
320
438
  }
321
439
 
322
- // 4. Placeholder whole-section bodies (Locked 2)
323
- if (/^\s*(TBD|Not applicable|N\/A)\s*$/im.test(text)) {
440
+ // 4. Placeholder whole-section bodies (Locked 2).
441
+ // Not a defect in the corporate profile: there N/A and EKLENECEK are how a
442
+ // backbone section reports "considered, nothing to state" (Locked 33), and the
443
+ // EKLENECEK-to-open-question pairing above is what keeps them honest.
444
+ if (profile === "global" && /^\s*(TBD|Not applicable|N\/A)\s*$/im.test(text)) {
324
445
  warns.push(
325
446
  'a line is a bare "TBD"/"Not applicable"/"N/A" body; omit the section instead (Locked 2)',
326
447
  );
@@ -335,7 +456,7 @@ function main() {
335
456
  const key = id.toUpperCase();
336
457
  counts.set(key, (counts.get(key) || 0) + 1);
337
458
  }
338
- if (counts.size === 0) {
459
+ if (counts.size === 0 && profile === "global") {
339
460
  warns.push("Full mode but no BR-<slug>-NN business-rule ids found (Section 4.4 / Locked 31)");
340
461
  }
341
462
  for (const [id, n] of counts) {
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # website-deploy-commit.sh - commit and push the website sync under the identity
4
+ # the deploy platform will actually build, then prove the build happened.
5
+ #
6
+ # Why this is a script and not three lines in the sync doc: the deploy platform
7
+ # refuses to build a commit whose author is not a contributor on the project. The
8
+ # push still succeeds, the deployment is created and sits at UNKNOWN with a 0ms
9
+ # build (API `readyState: BLOCKED`), and the live site keeps serving the previous
10
+ # version. v16.4.0 and v16.5.0 were both pushed that way and neither was built,
11
+ # while the sync reported the website as synced both times.
12
+ #
13
+ # Usage:
14
+ # website-deploy-commit.sh <identity-name> <identity-email> <version> [repo-dir]
15
+ #
16
+ # Env:
17
+ # WEBSITE_SYNC_NO_PUSH=1 commit and verify locally, never push (smoke/dry-run)
18
+ # WEBSITE_SYNC_NO_VERIFY=1 skip the deployment check (offline, or no CLI)
19
+ # WEBSITE_SYNC_WAIT=<sec> how long to wait for a Ready build (default 45)
20
+ #
21
+ # Exit: 0 committed+pushed (or nothing to do), 1 wrong author (nothing pushed),
22
+ # 2 usage or environment, 3 pushed but no Ready production build.
23
+
24
+ set -uo pipefail
25
+
26
+ NAME="${1:-}"
27
+ EMAIL="${2:-}"
28
+ VERSION="${3:-}"
29
+ DIR="${4:-$PWD}"
30
+
31
+ if [ -z "$NAME" ] || [ -z "$EMAIL" ] || [ -z "$VERSION" ]; then
32
+ echo "usage: website-deploy-commit.sh <identity-name> <identity-email> <version> [repo-dir]" >&2
33
+ exit 2
34
+ fi
35
+
36
+ # An empty fourth argument is a caller bug, not a request for the current
37
+ # directory: falling back to $PWD there commits whatever repo the caller happens
38
+ # to be standing in.
39
+ [ -n "$DIR" ] || { echo "FAIL: empty repo directory argument" >&2; exit 2; }
40
+ cd "$DIR" 2>/dev/null || { echo "FAIL: cannot enter $DIR" >&2; exit 2; }
41
+ git rev-parse --git-dir >/dev/null 2>&1 || { echo "FAIL: $DIR is not a git repository" >&2; exit 2; }
42
+
43
+ # Read before writing. The website clone's own config is usually already correct,
44
+ # and overwriting it with whatever identity the caller passed is the failure mode
45
+ # this guard exists to prevent, not a convenience.
46
+ [ "$(git config user.email || true)" = "$EMAIL" ] || git config user.email "$EMAIL"
47
+ [ "$(git config user.name || true)" = "$NAME" ] || git config user.name "$NAME"
48
+
49
+ git add -A
50
+ if git diff --cached --quiet; then
51
+ echo "website: already in sync, nothing to commit"
52
+ exit 0
53
+ fi
54
+
55
+ git commit -q -m "chore: sync pipeline v${VERSION}" || { echo "FAIL: commit failed" >&2; exit 2; }
56
+
57
+ # git config loses to an exported GIT_AUTHOR_EMAIL, so the recorded author is read
58
+ # back off the commit itself. Anything else is a hope, not a check.
59
+ ACTUAL="$(git log -1 --format=%ae)"
60
+ if [ "$ACTUAL" != "$EMAIL" ]; then
61
+ echo "HALT: website commit authored by $ACTUAL, expected $EMAIL." >&2
62
+ echo "Nothing was pushed. Re-author it (git commit --amend --reset-author) and run this again;" >&2
63
+ echo "a commit the deploy platform does not recognise is accepted by the push and never built." >&2
64
+ exit 1
65
+ fi
66
+
67
+ if [ "${WEBSITE_SYNC_NO_PUSH:-0}" = "1" ]; then
68
+ echo "website: committed as $ACTUAL (push skipped)"
69
+ exit 0
70
+ fi
71
+
72
+ git push -q origin HEAD || { echo "FAIL: push rejected" >&2; exit 2; }
73
+ echo "website: pushed v${VERSION} as $ACTUAL"
74
+
75
+ # A push is not a deploy.
76
+ if [ "${WEBSITE_SYNC_NO_VERIFY:-0}" = "1" ] || ! command -v vercel >/dev/null 2>&1 \
77
+ || [ ! -f "$DIR/.vercel/project.json" ]; then
78
+ echo "website: deployment not verified (no CLI or verification skipped)"
79
+ exit 0
80
+ fi
81
+
82
+ WAIT="${WEBSITE_SYNC_WAIT:-45}"
83
+ ELAPSED=0
84
+ while [ "$ELAPSED" -lt "$WAIT" ]; do
85
+ # `vercel ls` prints its table on STDERR, not stdout. Discarding stderr threw
86
+ # away the very rows this grep reads, so ROW was always empty and the check
87
+ # reported "no Ready production build" on every run - including the ones that
88
+ # deployed fine. A guard that cannot pass is worse than no guard: it trains
89
+ # the reader to ignore the one message meant to catch a real failure.
90
+ ROW="$(vercel ls --yes 2>&1 | grep -m1 'Production' || true)"
91
+ case "$ROW" in
92
+ *Ready*) echo "website: production build Ready"; exit 0 ;;
93
+ *Error*) break ;;
94
+ esac
95
+ sleep 5
96
+ ELAPSED=$((ELAPSED + 5))
97
+ done
98
+
99
+ echo "website: no Ready production build after ${WAIT}s -> ${ROW:-no deployment listed}" >&2
100
+ echo "UNKNOWN with a 0ms build means the commit author was rejected: fix the author and push again," >&2
101
+ echo "or deploy from the CLI with: (cd \"$DIR\" && vercel --prod --yes)" >&2
102
+ exit 3
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: multi-agent-analysis
3
3
  language: en
4
- description: "Standalone feature-spec analysis (v3 template). Platform-agnostic concept layer with repo-driven convention extraction and per-platform Pass B render. 23 sections in Full mode; 8 of them in Lite mode (auto for small features). Collects Figma / Swagger / Confluence / Jira / Standards / Firebase / repo inputs, then stops. Does not chain into dev or create branches. Use when a feature needs a written specification before any code, from Figma, Swagger, Confluence, Jira or repo inputs."
4
+ description: "Standalone feature-spec analysis. Two profiles picked at intake: global (23-section development handoff, 8 of them in Lite mode) or corporate (IG/UC/FG requirements document with traceability matrices). Platform-agnostic concept layer with repo-driven convention extraction and per-platform Pass B render; stack selection is optional. Collects Figma / Swagger / Confluence / Jira / Standards / Firebase / repo inputs, then stops. Does not chain into dev or create branches. Use when a feature needs a written specification before any code."
5
5
  user-invocable: true
6
6
  argument-hint: "[\"<analysis-name>\"] [--lite | --full] [--no-cache] [--preview-conventions]"
7
7
  ---
@@ -20,6 +20,21 @@ Produces a stakeholder-ready, platform-agnostic feature-spec document set (one m
20
20
  - To refresh a stale Confluence spec page
21
21
  - For API-only work (no Figma) or frontend-only work (no API) - the omission rule keeps the output clean
22
22
 
23
+ ## Profile
24
+
25
+ Phase 0 Step 1b picks the analysis standard, and that selects the template. Both profiles read the same evidence; only the projection differs, which is what keeps them from drifting into two products.
26
+
27
+ | Profile | Template | Shape |
28
+ |---|---|---|
29
+ | `global` (default) | `analysis-template.md` | Development handoff. Business rules with Gherkin acceptance criteria, architecture, files, tests. Zero-evidence sections drop. |
30
+ | `corporate` | `analysis-template-corporate.md` | Requirements document. `IG -> UC -> FG` spine with three traceability matrices, current and target state with impact analysis, then technical and development analysis. The Part A backbone always renders, carrying `N/A` or `EKLENECEK`, and each `EKLENECEK` owes an open-question row. |
31
+
32
+ One run emits one profile. A missing input never blocks either: the gap is written as `EKLENECEK` and raised as an open question instead of halting the run.
33
+
34
+ **Stack is optional.** With no platform selected the run still completes: everything that does not need a target repository renders in full, only the development layer and the Pass B projection are skipped, and the output is a single file at `~/Desktop/multiAgentAnalysis/<feature-name>/<feature>.md` (never the current working directory, which for a repo-less run is arbitrary).
35
+
36
+ **References are built, not written.** `build-references.mjs` projects `state.analysisSpec.evidence.*` into Section 21, carrying a precision anchor per row (Figma node id, Confluence pageId plus version, repo commit sha) and an access cell, so a source that could not be fetched is listed as unreachable rather than dropped. A coverage gate blocks dispatch on a consumed-but-unlisted source and on an invented row.
37
+
23
38
  ## Template (v3)
24
39
 
25
40
  Full mode renders 23 sections (Glossary, Changelog, References). Lite mode renders 7 sections (Summary, Goals + Non-Goals, User Stories, API Contracts, Architecture, Files to Add, References) and auto-activates for small features via three scored signals; `--lite` / `--full` flags always win.
@@ -44,7 +59,8 @@ When the rendered Section 20 (Risks and Open Questions) has open rows, the repor
44
59
  ## Detailed implementation
45
60
 
46
61
  Full steps: `$HOME/.claude/commands/multi-agent/analysis/SKILL.md`.
47
- Template master copy: `$HOME/.claude/multi-agent-refs/analysis-template.md`.
62
+ Template master copies: `$HOME/.claude/multi-agent-refs/analysis-template.md` (global) and `$HOME/.claude/multi-agent-refs/analysis-template-corporate.md` (corporate).
63
+ References builder: `$HOME/.claude/scripts/build-references.mjs`.
48
64
  Schema: `$HOME/.claude/schemas/analysis-spec.schema.json`.
49
65
  Convention extractor: `$HOME/.claude/lib/extract-conventions.sh` (output contract: `$HOME/.claude/schemas/conventions-output.schema.json`).
50
66
 
@@ -0,0 +1,30 @@
1
+ ---
2
+ name: multi-agent-feedback
3
+ language: en
4
+ description: "Send one message to the maintainer: a bug, an idea or a question. Only the text you type is sent - no logs, no repo names, no paths. Shows the payload and asks before sending. Use when a run went wrong and the maintainer should know."
5
+ user-invocable: true
6
+ argument-hint: "\"<message>\" [bug | idea | question] - with no argument, you are asked for the text"
7
+ ---
8
+
9
+ # multi-agent feedback - tell the maintainer
10
+
11
+ **Input**: $ARGUMENTS
12
+
13
+ One message to the maintainer's admin panel. It exists because the alternative is a problem nobody hears about: a run goes wrong, the person works around it, and the defect is still there a month later.
14
+
15
+ ## What is sent
16
+
17
+ Only the text you type, plus `kind` (bug / idea / question), the installed pipeline version, which host CLI, and the timestamp.
18
+
19
+ **No logs are attached, ever** - not the agent log, the diff, the repo name, the branch, or a file path. This package installs from a public registry, so an automatic log attachment would take a corporate user's internal identifiers off their machine into someone else's database. Paste the one line you think matters; a script cannot know which line that is.
20
+
21
+ ## Flow
22
+
23
+ 1. Take the message from `$ARGUMENTS`; with no argument, ask for the text and then the kind.
24
+ 2. Print the exact payload via the script's own dry run, so what is shown is what goes:
25
+ `node "$HOME/.copilot/scripts/feedback-send.mjs" --text "<message>" --kind <kind> --dry-run`
26
+ 3. **Confirm before sending.** Nothing leaves the machine before a yes; autopilot does not exempt this, because a message to a person is never fired unattended.
27
+ 4. Send the same command without `--dry-run`.
28
+ 5. Report the outcome plainly. A failure is surfaced, not swallowed - the person is waiting to hear whether their message went.
29
+
30
+ Auth reuses the usage ingest token, so nothing extra is onboarded. `usageLog.optOut` does not silence this: telemetry is passive collection, feedback is a deliberate act. The endpoint must be TLS; only loopback is exempt.
@@ -0,0 +1,31 @@
1
+ ---
2
+ name: multi-agent-review-analysis
3
+ language: en
4
+ description: "Review a written analysis document instead of a diff: resolve it from a path, a Confluence page or a Jira issue, run the deterministic gates first, then a parallel model review. Findings cite the Locked rule they break. Never edits the document. Use when an analysis needs judging before development starts."
5
+ user-invocable: true
6
+ argument-hint: "[path | Confluence URL | pageId | JIRA-KEY] [--state <state.json>] - optional; with no argument, pick from recent analyses"
7
+ ---
8
+
9
+ # multi-agent review-analysis - analysis document review
10
+
11
+ **Input**: $ARGUMENTS
12
+
13
+ `multi-agent-review` judges a diff. This judges the document that diff was supposed to come from, before the code exists. No worktree, no branch, no commit, and the reviewed document is never edited in place.
14
+
15
+ ## Flow
16
+
17
+ Read `$HOME/.copilot/multi-agent-refs/analysis/review.md` and execute it:
18
+
19
+ 1. **Resolve** from a local path, a Confluence URL or `pageId`, or a Jira key. No argument -> pick from recent analyses in `~/Desktop/multiAgentAnalysis/` and each repo's `analysis/`.
20
+ 2. **Deterministic gates first**, output reported verbatim before any model reads the document:
21
+ `validate-analysis-doc.mjs <doc>`, and `build-references.mjs <state> --check <doc>` when a state JSON is available.
22
+ 3. **Parallel model review** against the rubric: buildability, evidence, spine, altitude, admitted gaps, contradiction.
23
+ 4. **Triage** into Blocker / Important / Suggestion.
24
+ 5. **Report** the profile it judged against and, explicitly, what was NOT checked.
25
+ 6. **Output** on request: chat (default), Confluence comment, Jira comment, or a `-review.md` beside the document.
26
+
27
+ Findings cite `Locked <n>` where one applies, so the author gets a rule and a fix rather than a preference. Anything with no rule behind it is marked as judgement.
28
+
29
+ ## What it never does
30
+
31
+ Edits the reviewed document (that is `multi-agent-analysis-resolve`), touches a Jira description or a Confluence page body, chains into a dev run, branches, or commits.
@@ -31,7 +31,7 @@ Run all steps automatically:
31
31
 
32
32
  ```
33
33
  Step 1: DETECT Compare timestamps, find stale targets
34
- Step 2: COPILOT Claude Code -> Copilot CLI (instructions + 51 sub-command skills)
34
+ Step 2: COPILOT Claude Code -> Copilot CLI (instructions + 53 sub-command skills)
35
35
  Step 2b: CODEX Claude Code -> Codex CLI (1 router skill + 51 specs as refs + 8 agent TOML)
36
36
  Step 3: REPO Claude Code -> pipeline repo (genericized, personal data scrub)
37
37
  Step 3d: DEV-TOOLKIT Companion MCP server -> detect movement, ship gates, commit + publish
@@ -98,7 +98,7 @@ If nothing is stale -> report "All targets up to date" and stop.
98
98
  ## Codex Sync (Step 2b)
99
99
 
100
100
  This step does **not** hand-copy files. The Codex tree is a *transform* of the Claude
101
- tree, not a mirror: the 51 sub-command specs become reference files (Codex silently
101
+ tree, not a mirror: the 53 sub-command specs become reference files (Codex silently
102
102
  truncates its skills block - see `cross-cli-contract.md` 2.6), every reference to a
103
103
  CLI-owned tree is retargeted (`agents/<persona>.md` becomes `.toml`, the dispatcher
104
104
  becomes the router skill), the 8 personas are regenerated as TOML with a model +
@@ -175,7 +175,7 @@ npm publish --userconfig "$NPMRC"
175
175
 
176
176
  ## Website Sync (Step 4)
177
177
 
178
- Propagate the pipeline version, phase count, model count, and feature descriptions to the website.
178
+ Propagate version, phase and model counts and feature descriptions to the website.
179
179
 
180
180
  ```bash
181
181
  gh auth switch --user {owner}
@@ -190,9 +190,10 @@ cd "$WEBSITE_DIR" && git pull origin main
190
190
  | `src/data/projects.ts` | Version number, tagline, description, feature list |
191
191
 
192
192
  ```bash
193
- cd "$WEBSITE_DIR"
194
- git add -A && git commit -m "chore: sync pipeline v{VERSION}"
195
- git push origin main # Vercel auto-deploy
193
+ # A commit the platform does not recognise is pushed fine and never built, so the site
194
+ # keeps the old version. {identity} is the one routed to {owner}, not the run's own.
195
+ # Why, signature, recovery: `$HOME/.claude/multi-agent-refs/website-deploy.md`.
196
+ bash "$HOME/.claude/scripts/website-deploy-commit.sh" "{identity.name}" "{identity.email}" "{VERSION}" "$WEBSITE_DIR"
196
197
  ```
197
198
 
198
199
 
@@ -208,7 +209,7 @@ When invoked with the `release` argument:
208
209
  5. Commit + TAG git commit + git tag v{VERSION}
209
210
  6. PUSH git push --tags -> release.yml auto-publish
210
211
  7. DEV-TOOLKIT Ship the companion MCP server if it moved (Step 3d gates, then publish)
211
- 8. WEBSITE Version + features -> {website-host}
212
+ 8. WEBSITE Version + features -> {website-host} (maintainer identity, build verified Ready)
212
213
  9. COPILOT Copilot CLI instructions + skills sync
213
214
  9b. CODEX Codex CLI router skill + refs + agent TOML (node install.js --codex)
214
215
  10. Report Summary: version, touched repos, deploy status
@@ -223,15 +224,18 @@ When invoked with the `release` argument:
223
224
  |-------------|-------------|
224
225
  | `~/.claude/commands/multi-agent/{cmd}.md` | `~/.copilot/skills/multi-agent-{cmd}/SKILL.md` |
225
226
 
226
- **51 commands are synced** (canonical inventory - must match `cross-cli-contract.md` section 1; drift = contract violation):
227
+ **53 commands are synced** (canonical inventory - must match `cross-cli-contract.md` section 1; drift = contract violation):
227
228
 
228
229
  ```
229
- analysis, analysis-resolve, autopilot, build-optimize, channels, complaint-analysis, create-jira, design-check, dev,
230
- dev-autopilot, dev-local, dev-local-autopilot, diff-explain, forget, garbage-collect,
231
- help, ios-coding-standard, issue, jira, kill, language, local,
232
- local-autopilot, log, manual-test, prune-logs, prune-prompts, purge, refactor, resume, review, review-issue, review-jira,
233
- routines, save, scan, search, setup, resume-local, stack, status, store-ready, sync, test, test-accessibility,
234
- test-dark-mode, test-dynamic-type, test-screenshots, testflight-validation, uninstall, update
230
+ analysis, analysis-resolve, autopilot, build-optimize, channels,
231
+ complaint-analysis, create-jira, design-check, dev, dev-autopilot, dev-local,
232
+ dev-local-autopilot, diff-explain, feedback, forget, garbage-collect, help,
233
+ ios-coding-standard, issue, jira, kill, language, local, local-autopilot,
234
+ log, manual-test, prune-logs, prune-prompts, purge, refactor, resume,
235
+ resume-local, review, review-analysis, review-issue, review-jira, routines,
236
+ save, scan, search, setup, stack, status, store-ready, sync, test,
237
+ test-accessibility, test-dark-mode, test-dynamic-type, test-screenshots,
238
+ testflight-validation, uninstall, update
235
239
  ```
236
240
 
237
241
  **NOT synced**: `refs/*` - Lazy-load references, Claude Code specific