@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.
- package/CHANGELOG.md +46 -0
- package/README.md +4 -4
- package/README.tr.md +4 -4
- package/docs/architecture.md +2 -2
- package/docs/ecosystem.md +5 -5
- package/package.json +1 -1
- package/pipeline/commands/multi-agent/analysis/SKILL.md +18 -5
- package/pipeline/commands/multi-agent/feedback/SKILL.md +51 -0
- package/pipeline/commands/multi-agent/review-analysis/SKILL.md +32 -0
- package/pipeline/commands/multi-agent/sync/SKILL.md +20 -18
- package/pipeline/commands/multi-agent/update/SKILL.md +1 -1
- package/pipeline/multi-agent-refs/analysis/intake.md +30 -1
- package/pipeline/multi-agent-refs/analysis/locked.md +11 -6
- package/pipeline/multi-agent-refs/analysis/render.md +20 -5
- package/pipeline/multi-agent-refs/analysis/review.md +86 -0
- package/pipeline/multi-agent-refs/analysis/synthesis.md +1 -1
- package/pipeline/multi-agent-refs/analysis-template-corporate.md +436 -0
- package/pipeline/multi-agent-refs/analysis-template.md +31 -13
- package/pipeline/multi-agent-refs/cross-cli-contract.md +10 -7
- package/pipeline/multi-agent-refs/website-deploy.md +87 -0
- package/pipeline/schemas/analysis-spec.schema.json +21 -1
- package/pipeline/schemas/prefs.schema.json +41 -0
- package/pipeline/scripts/build-references.mjs +368 -0
- package/pipeline/scripts/feedback-send.mjs +181 -0
- package/pipeline/scripts/validate-analysis-doc.mjs +130 -9
- package/pipeline/scripts/website-deploy-commit.sh +102 -0
- package/pipeline/skills/shared/core/multi-agent-analysis/SKILL.md +18 -2
- package/pipeline/skills/shared/core/multi-agent-feedback/SKILL.md +30 -0
- package/pipeline/skills/shared/core/multi-agent-review-analysis/SKILL.md +31 -0
- 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
|
-
|
|
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
|
-
|
|
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 (
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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 +
|
|
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
|
|
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
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
**
|
|
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,
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|