@mmerterden/multi-agent-pipeline 12.2.0 → 12.4.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 +84 -0
  2. package/package.json +1 -1
  3. package/pipeline/commands/multi-agent/analysis/SKILL.md +68 -32
  4. package/pipeline/commands/multi-agent/forget/SKILL.md +29 -0
  5. package/pipeline/commands/multi-agent/help/SKILL.md +18 -0
  6. package/pipeline/commands/multi-agent/routines/SKILL.md +30 -0
  7. package/pipeline/commands/multi-agent/save/SKILL.md +46 -0
  8. package/pipeline/commands/multi-agent/sync/SKILL.md +4 -4
  9. package/pipeline/lib/fetch-figma-annotations.sh +237 -0
  10. package/pipeline/multi-agent-refs/analysis-template.md +233 -123
  11. package/pipeline/multi-agent-refs/cross-cli-contract.md +4 -3
  12. package/pipeline/schemas/analysis-spec.schema.json +78 -0
  13. package/pipeline/schemas/figma-project-config.schema.json +49 -0
  14. package/pipeline/schemas/migrations/prefs-2.3.0-to-2.4.0.mjs +32 -0
  15. package/pipeline/schemas/prefs.schema.json +58 -1
  16. package/pipeline/scripts/fixtures/install-layout.tsv +8 -8
  17. package/pipeline/scripts/migrate-prefs.mjs +8 -1
  18. package/pipeline/scripts/routine-registry.mjs +226 -0
  19. package/pipeline/scripts/smoke-fetchers-offline.sh +41 -0
  20. package/pipeline/scripts/smoke-generate-issue.sh +3 -3
  21. package/pipeline/scripts/smoke-migrate-state.sh +3 -3
  22. package/pipeline/scripts/smoke-pref-migration.sh +8 -6
  23. package/pipeline/scripts/smoke-review-readiness.sh +1 -1
  24. package/pipeline/scripts/smoke-routines.sh +84 -0
  25. package/pipeline/scripts/smoke-validate-analysis-doc.sh +161 -0
  26. package/pipeline/scripts/validate-analysis-doc.mjs +196 -0
  27. package/pipeline/skills/shared/core/multi-agent-forget/SKILL.md +22 -0
  28. package/pipeline/skills/shared/core/multi-agent-routines/SKILL.md +20 -0
  29. package/pipeline/skills/shared/core/multi-agent-save/SKILL.md +27 -0
  30. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +4 -4
@@ -75,12 +75,12 @@ else
75
75
  fail "unknown flag did not exit 1"
76
76
  fi
77
77
 
78
- # Test 7 - type auto-detection works
78
+ # Test 7 - type auto-detection works (feed a doc already at the latest version)
79
79
  cat > "$TMPDIR/multi-agent-preferences.json" <<'EOF'
80
- {"schemaVersion":"2.3.0","global":{"identities":[],"keychainMapping":{},"recentAccounts":[],"accounts":[]},"projects":{}}
80
+ {"schemaVersion":"2.4.0","global":{"identities":[],"keychainMapping":{},"recentAccounts":[],"accounts":[],"routines":[]},"projects":{}}
81
81
  EOF
82
82
  OUT=$(node "$RUNNER" "$TMPDIR/multi-agent-preferences.json" 2>&1)
83
- if echo "$OUT" | grep -q "already at schemaVersion 2.3.0"; then
83
+ if echo "$OUT" | grep -q "already at schemaVersion 2.4.0"; then
84
84
  pass "prefs type auto-detected from filename"
85
85
  else
86
86
  fail "prefs auto-detection failed (got: $OUT)"
@@ -64,7 +64,7 @@ F1=$(run_fixture "f1-v2.0.0-legacy" '{
64
64
  },
65
65
  "projects": {}
66
66
  }')
67
- assert_json_has "f1" "$F1" ".schemaVersion" "2.3.0"
67
+ assert_json_has "f1" "$F1" ".schemaVersion" "2.4.0"
68
68
  assert_json_has "f1" "$F1" ".global.identities" "array:1"
69
69
  assert_json_has "f1" "$F1" ".global.gitIdentities" "undefined"
70
70
  assert_json_has "f1" "$F1" ".global.settings" "object:8"
@@ -73,6 +73,7 @@ assert_json_has "f1" "$F1" ".global.platformIdentityRouting" "object:0"
73
73
  assert_json_has "f1" "$F1" ".global.recentGroups" "array:0"
74
74
  assert_json_has "f1" "$F1" ".global.recentAccounts" "array:0"
75
75
  assert_json_has "f1" "$F1" ".global.accounts" "array:0"
76
+ assert_json_has "f1" "$F1" ".global.routines" "array:0"
76
77
 
77
78
  echo ""
78
79
  echo "→ Fixture 2: no schemaVersion (pre-2.0.0 fossil)"
@@ -80,15 +81,15 @@ F2=$(run_fixture "f2-no-version" '{
80
81
  "global": {"gitIdentities": []},
81
82
  "projects": {}
82
83
  }')
83
- assert_json_has "f2" "$F2" ".schemaVersion" "2.3.0"
84
+ assert_json_has "f2" "$F2" ".schemaVersion" "2.4.0"
84
85
  assert_json_has "f2" "$F2" ".global.identities" "array:0"
85
86
 
86
87
  echo ""
87
- echo "→ Fixture 3: v2.3.0 already (no-op)"
88
- F3_FILE="$TMP/f3-v2.3.0.json"
88
+ echo "→ Fixture 3: v2.4.0 already (no-op)"
89
+ F3_FILE="$TMP/f3-v2.4.0.json"
89
90
  cat > "$F3_FILE" <<'EOF'
90
91
  {
91
- "schemaVersion": "2.3.0",
92
+ "schemaVersion": "2.4.0",
92
93
  "global": {
93
94
  "identities": [],
94
95
  "keychainMapping": {
@@ -117,7 +118,8 @@ cat > "$F3_FILE" <<'EOF'
117
118
  "wikiScope": ["main", "ios", "screenshots", "index"],
118
119
  "autopilotReportTimeoutSeconds": 1800,
119
120
  "recentAccounts": [],
120
- "accounts": []
121
+ "accounts": [],
122
+ "routines": []
121
123
  },
122
124
  "projects": {}
123
125
  }
@@ -66,7 +66,7 @@ grep -Fq '| `review-jira ' "$DISPATCHER" && pass "dispatcher desc: review-jira"
66
66
  grep -Fq '| `review-issue ' "$DISPATCHER" && pass "dispatcher desc: review-issue" || fail "dispatcher desc missing review-issue"
67
67
  grep -Fq "review-jira/SKILL.md" "$DISPATCHER" && pass "dispatcher routing: review-jira" || fail "dispatcher routing missing review-jira"
68
68
  grep -Fq "review-issue/SKILL.md" "$DISPATCHER" && pass "dispatcher routing: review-issue" || fail "dispatcher routing missing review-issue"
69
- grep -Fq "## 1. Command Inventory (38 commands)" "$CONTRACT" && pass "contract count = 38" || fail "contract count wrong"
69
+ grep -Fq "## 1. Command Inventory (41 commands)" "$CONTRACT" && pass "contract count = 41" || fail "contract count wrong"
70
70
  grep -Eq '(^|[ ,])review-jira([ ,]|$)' "$CONTRACT" && pass "contract inventory has review-jira" || fail "contract missing review-jira"
71
71
  grep -Eq '(^|[ ,])review-issue([ ,]|$)' "$CONTRACT" && pass "contract inventory has review-issue" || fail "contract missing review-issue"
72
72
  grep -Fq "multi-agent:review-jira" "$HELP" && pass "help lists review-jira" || fail "help missing review-jira"
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env bash
2
+ # smoke-routines.sh - contract for routine-registry.mjs, the backend of the
3
+ # user-defined routine commands (/multi-agent:save, :routines, :forget).
4
+
5
+ set -uo pipefail
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
8
+ RR="$SCRIPT_DIR/routine-registry.mjs"
9
+
10
+ PASS=0
11
+ FAIL=0
12
+ record_pass() { PASS=$((PASS + 1)); printf ' \342\234\223 %s\n' "$1"; }
13
+ record_fail() { FAIL=$((FAIL + 1)); printf ' \342\234\227 %s\n' "$1"; }
14
+
15
+ printf '\342\206\222 smoke-routines: routine-registry backend contract\n'
16
+
17
+ tmp=$(mktemp -d "${TMPDIR:-/tmp}/routines.XXXXXX")
18
+ trap 'rm -rf "$tmp"' EXIT
19
+ H="$tmp/home"
20
+ mkdir -p "$H/.claude/commands/multi-agent"
21
+ printf '{"global":{}}\n' > "$H/.claude/multi-agent-preferences.json"
22
+ printf 'Run steps 1-6 of the checkin service migration.\n' > "$tmp/body.md"
23
+
24
+ # 1. add -> writes a local-only command dir + registry entry
25
+ node "$RR" add --name checkin-services --description "Checkin servis kontrolu" \
26
+ --source "CLAUDE.md" --body-file "$tmp/body.md" --home "$H" >/dev/null 2>&1
27
+ skill="$H/.claude/commands/multi-agent/checkin-services/SKILL.md"
28
+ if [ -f "$skill" ] && grep -qx "local-only: true" "$skill"; then
29
+ record_pass "add writes a local-only command SKILL.md"
30
+ else
31
+ record_fail "add did not write a local-only routine command"
32
+ fi
33
+ if node "$RR" list --json --home "$H" | grep -q '"name": "checkin-services"'; then
34
+ record_pass "registry records the routine"
35
+ else
36
+ record_fail "registry missing the routine"
37
+ fi
38
+
39
+ # 2. add is name-collision safe (same name rejected)
40
+ node "$RR" add --name checkin-services --description x --source y --body-file "$tmp/body.md" --home "$H" >/dev/null 2>&1
41
+ [ "$?" -eq 1 ] && record_pass "duplicate name rejected" || record_fail "duplicate name should reject"
42
+
43
+ # 3. invalid name rejected
44
+ node "$RR" add --name "Bad Name" --description x --source y --body-file "$tmp/body.md" --home "$H" >/dev/null 2>&1
45
+ [ "$?" -eq 1 ] && record_pass "invalid name rejected" || record_fail "invalid name should reject"
46
+
47
+ # 4. shipped-command guard: never remove a non-local-only command
48
+ mkdir -p "$H/.claude/commands/multi-agent/status"
49
+ printf -- '---\ndescription: "shipped"\n---\nbody\n' > "$H/.claude/commands/multi-agent/status/SKILL.md"
50
+ node "$RR" remove --name status --home "$H" >/dev/null 2>&1
51
+ if [ "$?" -eq 1 ] && [ -d "$H/.claude/commands/multi-agent/status" ]; then
52
+ record_pass "refuses to remove a shipped command"
53
+ else
54
+ record_fail "shipped command must be protected"
55
+ fi
56
+
57
+ # 5. sync backstop proxy: the generated routine carries local-only:true, which is
58
+ # exactly what sync/SKILL.md greps to keep it out of the public repo.
59
+ if grep -qx "local-only: true" "$skill"; then
60
+ record_pass "routine is sync-excluded (local-only marker present)"
61
+ else
62
+ record_fail "routine missing local-only marker (would leak on sync)"
63
+ fi
64
+
65
+ # 6. remove -> deletes dir + registry entry
66
+ node "$RR" remove --name checkin-services --home "$H" >/dev/null 2>&1
67
+ if [ ! -d "$H/.claude/commands/multi-agent/checkin-services" ] \
68
+ && ! node "$RR" list --json --home "$H" | grep -q "checkin-services"; then
69
+ record_pass "remove deletes dir + registry entry"
70
+ else
71
+ record_fail "remove left dir or registry entry behind"
72
+ fi
73
+
74
+ # 7. install preservation proxy: snapshotLocalOnlyWrappers keys on local-only:true.
75
+ # Re-add, then confirm the marker survives a simulated namespace re-read.
76
+ node "$RR" add --name my-routine --description d --source s --body-file "$tmp/body.md" --home "$H" >/dev/null 2>&1
77
+ if grep -qx "local-only: true" "$H/.claude/commands/multi-agent/my-routine/SKILL.md"; then
78
+ record_pass "re-added routine keeps the install-preserving marker"
79
+ else
80
+ record_fail "re-added routine lost its local-only marker"
81
+ fi
82
+
83
+ printf '\n\342\225\220\342\225\220 smoke-routines: %d passed, %d failed \342\225\220\342\225\220\n' "$PASS" "$FAIL"
84
+ [ "$FAIL" -eq 0 ]
@@ -0,0 +1,161 @@
1
+ #!/usr/bin/env bash
2
+ # smoke-validate-analysis-doc.sh - contract for validate-analysis-doc.mjs,
3
+ # the deterministic gate over an emitted /multi-agent:analysis document.
4
+
5
+ set -uo pipefail
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
8
+ V="$SCRIPT_DIR/validate-analysis-doc.mjs"
9
+
10
+ PASS=0
11
+ FAIL=0
12
+ record_pass() { PASS=$((PASS + 1)); printf ' \342\234\223 %s\n' "$1"; }
13
+ record_fail() { FAIL=$((FAIL + 1)); printf ' \342\234\227 %s\n' "$1"; }
14
+
15
+ printf '\342\206\222 smoke-validate-analysis-doc: emitted-doc gate\n'
16
+
17
+ tmp=$(mktemp -d "${TMPDIR:-/tmp}/vdoc.XXXXXX")
18
+ trap 'rm -rf "$tmp"' EXIT
19
+
20
+ # A well-formed Full-mode doc (neutral UserProfile domain, BR referenced downstream).
21
+ cat > "$tmp/good.md" <<'MD'
22
+ ---
23
+ feature: UserProfile
24
+ platform: ios
25
+ language: tr
26
+ mode: full
27
+ template_version: v3
28
+ ---
29
+ ## 1. Özet
30
+ Profile screen.
31
+ ## 2. Hedefler ve Karşı Hedefler
32
+ goals.
33
+ ## 4. Kullanıcı Hikayeleri
34
+ ### 4.4 İş Kuralları
35
+ | BR-profile-01 | rule | source | Given/When/Then |
36
+ ## 9. API Kontratları
37
+ api.
38
+ ## 13. Mimari Plan
39
+ arch.
40
+ ## 14. Eklenecek Dosyalar
41
+ files.
42
+ ## 15. Test Planı
43
+ BR-profile-01 happy path.
44
+ ## 20. Riskler ve Açık Sorular
45
+ risk.
46
+ ## 21. Referanslar
47
+ refs.
48
+ MD
49
+
50
+ if node "$V" "$tmp/good.md" >/dev/null 2>&1; then
51
+ record_pass "well-formed Full doc passes (exit 0)"
52
+ else
53
+ record_fail "well-formed doc should pass"
54
+ fi
55
+
56
+ # Lite doc without Section 15 must still pass (Locked 31 mode-aware).
57
+ cat > "$tmp/lite.md" <<'MD'
58
+ ---
59
+ feature: UserProfile
60
+ platform: ios
61
+ language: en
62
+ mode: lite
63
+ template_version: v3
64
+ ---
65
+ ## 1. Summary
66
+ s
67
+ ## 2. Goals and Non-Goals
68
+ g
69
+ ## 4. User Stories
70
+ ### 4.4 Business Rules
71
+ | BR-profile-01 | rule | source | Given/When/Then |
72
+ ## 9. API Contracts
73
+ a
74
+ ## 13. Architecture
75
+ x
76
+ ## 14. Files to Add
77
+ f
78
+ ## 20. Risks and Open Questions
79
+ r
80
+ ## 21. References
81
+ ref
82
+ MD
83
+ if node "$V" "$tmp/lite.md" >/dev/null 2>&1; then
84
+ record_pass "Lite doc (no Section 15) passes - mode-aware Locked 31"
85
+ else
86
+ record_fail "Lite doc without Section 15 must pass"
87
+ fi
88
+
89
+ # Missing front-matter -> ERROR.
90
+ printf '## 1. Summary\nno front matter\n' > "$tmp/nofm.md"
91
+ node "$V" "$tmp/nofm.md" >/dev/null 2>&1
92
+ [ "$?" -eq 1 ] && record_pass "missing front-matter fails (exit 1)" || record_fail "missing front-matter should fail"
93
+
94
+ # Banned punctuation (em-dash) -> ERROR.
95
+ cp "$tmp/good.md" "$tmp/emdash.md"
96
+ printf 'A line with an em-dash \342\200\224 here.\n' >> "$tmp/emdash.md"
97
+ # Capture first (pipefail + node's nonzero exit would poison a direct pipe).
98
+ emdash_out=$(node "$V" "$tmp/emdash.md" 2>&1 || true)
99
+ if printf '%s' "$emdash_out" | grep -q "em-dash"; then
100
+ record_pass "em-dash flagged (Locked 7)"
101
+ else
102
+ record_fail "em-dash should be flagged"
103
+ fi
104
+
105
+ # Missing a never-omitted section (References) -> ERROR.
106
+ grep -v "Referanslar\|References" "$tmp/good.md" > "$tmp/noref.md"
107
+ node "$V" "$tmp/noref.md" >/dev/null 2>&1
108
+ [ "$?" -eq 1 ] && record_pass "missing References section fails" || record_fail "missing References should fail"
109
+
110
+ # Define-only BR under --strict -> fail (WARN promoted).
111
+ cat > "$tmp/orphan.md" <<'MD'
112
+ ---
113
+ feature: UserProfile
114
+ platform: ios
115
+ language: en
116
+ mode: full
117
+ template_version: v3
118
+ ---
119
+ ## 1. Summary
120
+ s
121
+ ## 2. Goals
122
+ g
123
+ ## 4. User Stories
124
+ ### 4.4 Business Rules
125
+ | BR-profile-42 | rule | source | Given/When/Then |
126
+ ## 9. API Contracts
127
+ a
128
+ ## 13. Architecture
129
+ x
130
+ ## 14. Files to Add
131
+ f
132
+ ## 15. Test Plan
133
+ no BR reference here
134
+ ## 20. Risks
135
+ r
136
+ ## 21. References
137
+ ref
138
+ MD
139
+ node "$V" "$tmp/orphan.md" >/dev/null 2>&1
140
+ [ "$?" -eq 0 ] && record_pass "define-only BR is WARN, not error (default)" || record_fail "orphan BR should not hard-fail by default"
141
+ node "$V" "$tmp/orphan.md" --strict >/dev/null 2>&1
142
+ [ "$?" -eq 1 ] && record_pass "define-only BR fails under --strict" || record_fail "orphan BR should fail under --strict"
143
+
144
+ # ui_tests: true but no Section 15.6 -> ERROR.
145
+ sed 's/^mode: full/mode: full\nui_tests: true\na11y_depth: basic/' "$tmp/good.md" > "$tmp/uimiss.md"
146
+ node "$V" "$tmp/uimiss.md" >/dev/null 2>&1
147
+ [ "$?" -eq 1 ] && record_pass "ui_tests:true without Section 15.6 fails" || record_fail "ui_tests:true must require 15.6"
148
+
149
+ # ui_tests: true WITH a Section 15.6 heading -> passes.
150
+ cp "$tmp/uimiss.md" "$tmp/uiok.md"
151
+ printf '## 15.6 UI test senaryolari\nUI-profile-01 flow.\n' >> "$tmp/uiok.md"
152
+ node "$V" "$tmp/uiok.md" >/dev/null 2>&1
153
+ [ "$?" -eq 0 ] && record_pass "ui_tests:true with Section 15.6 passes" || record_fail "ui_tests:true + 15.6 should pass"
154
+
155
+ # a11y_depth: full but no Section 16.2 walkthrough -> ERROR.
156
+ sed 's/^mode: full/mode: full\nui_tests: false\na11y_depth: full/' "$tmp/good.md" > "$tmp/a11ymiss.md"
157
+ node "$V" "$tmp/a11ymiss.md" >/dev/null 2>&1
158
+ [ "$?" -eq 1 ] && record_pass "a11y_depth:full without Section 16.2 fails" || record_fail "a11y_depth:full must require 16.2"
159
+
160
+ printf '\n\342\225\220\342\225\220 smoke-validate-analysis-doc: %d passed, %d failed \342\225\220\342\225\220\n' "$PASS" "$FAIL"
161
+ [ "$FAIL" -eq 0 ]
@@ -0,0 +1,196 @@
1
+ #!/usr/bin/env node
2
+ // validate-analysis-doc.mjs - deterministic validator for an EMITTED
3
+ // /multi-agent:analysis document (analysis/<feature>-<platform>.md).
4
+ //
5
+ // The analysis SKILL states many "fails the dispatch gate" invariants as prose
6
+ // the model is asked to self-enforce. This turns the high-value, mechanically
7
+ // checkable ones into a real gate so a malformed spec is caught before it is
8
+ // handed to a human or to /multi-agent:dev.
9
+ //
10
+ // Zero deps. Reads one markdown file (path arg or STDIN).
11
+ //
12
+ // Checks (ERROR = blocking exit 1; WARN = advisory, still exit 0 unless --strict):
13
+ // - Front-matter block with required keys (feature, platform, language, mode,
14
+ // template_version); mode in {full, lite}; platform in the known set.
15
+ // - Never-omitted sections present by title keyword (Locked 2): Summary,
16
+ // Goals, User Stories, API Contracts, Architecture, Files to Add, Risks,
17
+ // References.
18
+ // - Humanizer punctuation policy (Locked 7): no em-dash / en-dash / ellipsis /
19
+ // section-sign / curly quotes anywhere in the body.
20
+ // - Traceability (Full mode only, Locked 31): every BR-<slug>-NN id defined in
21
+ // the Business Rules area is referenced again later (test / acceptance /
22
+ // analytics). A define-only rule is a WARN (heuristic).
23
+ // - No whole-section "TBD" / "Not applicable" placeholder bodies (Locked 2);
24
+ // the inline "[label TBD - see Open Questions]" marker is allowed.
25
+ //
26
+ // Usage:
27
+ // node validate-analysis-doc.mjs analysis/UserProfile-ios.md
28
+ // cat doc.md | node validate-analysis-doc.mjs -
29
+ // node validate-analysis-doc.mjs doc.md --strict # WARN also fails
30
+ //
31
+ // Exit: 0 valid, 1 invalid (or WARN under --strict), 64 usage error.
32
+
33
+ import { readFileSync } from "node:fs";
34
+
35
+ const KNOWN_PLATFORMS = new Set(["ios", "android", "backend", "frontend"]);
36
+ const REQUIRED_FM = ["feature", "platform", "language", "mode", "template_version"];
37
+
38
+ // Never-omitted sections (Locked 2), matched by bilingual title keyword so the
39
+ // re-flowed section numbering does not matter.
40
+ const REQUIRED_SECTIONS = [
41
+ { key: "summary", any: ["Summary", "Özet", "Ozet"] },
42
+ { key: "goals", any: ["Goals", "Hedefler"] },
43
+ { key: "user stories", any: ["User Stories", "Kullanıcı Hikayeleri", "Kullanici Hikayeleri"] },
44
+ { key: "api contracts", any: ["API Contracts", "API Kontratları", "API Kontratlari"] },
45
+ { key: "architecture", any: ["Architecture", "Mimari"] },
46
+ { key: "files to add", any: ["Files to Add", "Eklenecek Dosyalar"] },
47
+ { key: "risks", any: ["Risks", "Riskler"] },
48
+ { key: "references", any: ["References", "Referanslar"] },
49
+ ];
50
+
51
+ // Humanizer punctuation policy (Locked 7) - banned codepoints.
52
+ const BANNED_PUNCT = [
53
+ { ch: "—", name: "em-dash" },
54
+ { ch: "–", name: "en-dash" },
55
+ { ch: "…", name: "ellipsis" },
56
+ { ch: "§", name: "section-sign" },
57
+ { ch: "“", name: "curly-double-open" },
58
+ { ch: "”", name: "curly-double-close" },
59
+ { ch: "‘", name: "curly-single-open" },
60
+ { ch: "’", name: "curly-single-close" },
61
+ ];
62
+
63
+ function readInput() {
64
+ const args = process.argv.slice(2).filter((a) => a !== "--strict");
65
+ const arg = args[0];
66
+ if (!arg) {
67
+ console.error("usage: validate-analysis-doc.mjs <path|-> [--strict]");
68
+ process.exit(64);
69
+ }
70
+ if (arg === "-") return readFileSync(0, "utf-8");
71
+ return readFileSync(arg, "utf-8");
72
+ }
73
+
74
+ function parseFrontMatter(text) {
75
+ const lines = text.split("\n");
76
+ if (lines[0].trim() !== "---") return null;
77
+ const end = lines.indexOf("---", 1);
78
+ if (end < 0) return null;
79
+ const fm = {};
80
+ for (let i = 1; i < end; i++) {
81
+ const m = lines[i].match(/^([A-Za-z_][A-Za-z0-9_]*):\s*(.*)$/);
82
+ if (m) fm[m[1]] = m[2].trim();
83
+ }
84
+ return { fm, bodyStart: end + 1 };
85
+ }
86
+
87
+ function main() {
88
+ const text = readInput();
89
+ const strict = process.argv.includes("--strict");
90
+ const errors = [];
91
+ const warns = [];
92
+
93
+ // 1. Front-matter
94
+ const parsed = parseFrontMatter(text);
95
+ if (!parsed) {
96
+ errors.push("missing YAML front-matter block (--- ... ---) at the top");
97
+ } else {
98
+ for (const k of REQUIRED_FM) {
99
+ if (!parsed.fm[k]) errors.push(`front-matter missing required key: ${k}`);
100
+ }
101
+ const mode = parsed.fm.mode;
102
+ if (mode && mode !== "full" && mode !== "lite") {
103
+ errors.push(`front-matter mode must be full|lite, got: ${mode}`);
104
+ }
105
+ const plat = parsed.fm.platform;
106
+ if (plat && !KNOWN_PLATFORMS.has(plat)) {
107
+ errors.push(`front-matter platform not in known set: ${plat}`);
108
+ }
109
+ }
110
+ const mode = parsed?.fm?.mode || "full";
111
+
112
+ // 2. Never-omitted sections (by heading keyword)
113
+ const headings = text
114
+ .split("\n")
115
+ .filter((l) => /^#{1,3}\s/.test(l))
116
+ .map((l) => l.replace(/^#{1,3}\s/, "").trim());
117
+ const headingBlob = headings.join("\n");
118
+ for (const sec of REQUIRED_SECTIONS) {
119
+ if (!sec.any.some((kw) => headingBlob.includes(kw))) {
120
+ errors.push(`missing required section (Locked 2): ${sec.key}`);
121
+ }
122
+ }
123
+
124
+ // 2b. Opt-in coverage sections must be present when the front-matter says so.
125
+ const uiTests = String(parsed?.fm?.ui_tests || "false").toLowerCase() === "true";
126
+ const a11yDepth = String(parsed?.fm?.a11y_depth || "basic").toLowerCase();
127
+ const headingBlobLower = headingBlob.toLowerCase();
128
+ if (uiTests) {
129
+ const hasUi =
130
+ headingBlob.includes("15.6") ||
131
+ headingBlobLower.includes("ui test") ||
132
+ headingBlobLower.includes("ui-test");
133
+ if (!hasUi) {
134
+ errors.push("front-matter ui_tests: true but no Section 15.6 UI test scenarios found (Locked 31)");
135
+ }
136
+ }
137
+ if (a11yDepth === "full") {
138
+ const hasWalkthrough =
139
+ headingBlob.includes("16.2") ||
140
+ headingBlobLower.includes("walkthrough") ||
141
+ headingBlobLower.includes("gezinme");
142
+ if (!hasWalkthrough) {
143
+ errors.push("front-matter a11y_depth: full but no Section 16.2 screen-reader walkthrough found");
144
+ }
145
+ }
146
+
147
+ // 3. Humanizer punctuation
148
+ const bodyLines = text.split("\n");
149
+ for (let i = 0; i < bodyLines.length; i++) {
150
+ for (const b of BANNED_PUNCT) {
151
+ if (bodyLines[i].includes(b.ch)) {
152
+ errors.push(`banned punctuation ${b.name} at line ${i + 1} (Locked 7 humanizer policy)`);
153
+ }
154
+ }
155
+ }
156
+
157
+ // 4. Placeholder whole-section bodies (Locked 2)
158
+ if (/^\s*(TBD|Not applicable|N\/A)\s*$/im.test(text)) {
159
+ warns.push('a line is a bare "TBD"/"Not applicable"/"N/A" body; omit the section instead (Locked 2)');
160
+ }
161
+
162
+ // 5. Traceability (Full mode only, Locked 31)
163
+ if (mode === "full") {
164
+ const idRe = /BR-[a-z0-9]+(?:-[a-z0-9]+)*-\d+/gi;
165
+ const all = text.match(idRe) || [];
166
+ const counts = new Map();
167
+ for (const id of all) {
168
+ const key = id.toUpperCase();
169
+ counts.set(key, (counts.get(key) || 0) + 1);
170
+ }
171
+ if (counts.size === 0) {
172
+ warns.push("Full mode but no BR-<slug>-NN business-rule ids found (Section 4.4 / Locked 31)");
173
+ }
174
+ for (const [id, n] of counts) {
175
+ if (n < 2) {
176
+ warns.push(`business rule ${id} is defined but never referenced downstream (test/acceptance/analytics) - Locked 31 traceability`);
177
+ }
178
+ }
179
+ }
180
+
181
+ // Report
182
+ for (const w of warns) console.error(`WARN: ${w}`);
183
+ for (const e of errors) console.error(`ERROR: ${e}`);
184
+ if (errors.length > 0) {
185
+ console.error(`validate-analysis-doc: FAIL (${errors.length} error(s), ${warns.length} warning(s))`);
186
+ process.exit(1);
187
+ }
188
+ if (strict && warns.length > 0) {
189
+ console.error(`validate-analysis-doc: FAIL under --strict (${warns.length} warning(s))`);
190
+ process.exit(1);
191
+ }
192
+ console.log(`validate-analysis-doc: OK (mode=${mode}, ${warns.length} warning(s))`);
193
+ process.exit(0);
194
+ }
195
+
196
+ main();
@@ -0,0 +1,22 @@
1
+ ---
2
+ name: multi-agent-forget
3
+ language: en
4
+ description: "Remove a saved /multi-agent routine (created by /multi-agent:save): deletes its local-only command and its registry entry. Asks which one and confirms."
5
+ user-invocable: true
6
+ argument-hint: "[name]"
7
+ ---
8
+
9
+ # multi-agent-forget - Remove a Routine
10
+
11
+ Delete a routine saved via `/multi-agent:save`. Backed by `$HOME/.copilot/scripts/routine-registry.mjs`.
12
+
13
+ ## Steps
14
+
15
+ 1. Resolve which routine: from `$ARGUMENTS`, else `routine-registry.mjs list --json` + `AskUserQuestion` (in `outputLanguage`). None -> nothing to forget.
16
+ 2. Confirm via `AskUserQuestion`: "Remove /multi-agent:<name>?" (Remove / Cancel). Anything but Remove -> stop.
17
+ 3. `node "$HOME/.copilot/scripts/routine-registry.mjs" remove --name "<name>"` (backend refuses to remove a shipped command; only a registered `local-only: true` dir).
18
+ 4. Report removed; note the autocomplete entry clears on the next session reload.
19
+
20
+ ## Notes
21
+
22
+ - Only user-saved routines can be forgotten. See `/multi-agent:routines` for the list.
@@ -0,0 +1,20 @@
1
+ ---
2
+ name: multi-agent-routines
3
+ language: en
4
+ description: "List your saved /multi-agent routines (from /multi-agent:save) with what each one does, rendered in outputLanguage."
5
+ user-invocable: true
6
+ ---
7
+
8
+ # multi-agent-routines - List Saved Routines
9
+
10
+ Show every routine saved via `/multi-agent:save`. Read-only. Backed by `$HOME/.copilot/scripts/routine-registry.mjs`.
11
+
12
+ ## Steps
13
+
14
+ 1. `node "$HOME/.copilot/scripts/routine-registry.mjs" list --json`
15
+ 2. Render in `outputLanguage`: empty -> say there are none yet and point to `/multi-agent:save`; otherwise a compact table of `/multi-agent:<name>` + description + created date.
16
+ 3. Flag any registry entry with no matching command dir (or vice versa) as `needs repair`; never modify anything.
17
+
18
+ ## Notes
19
+
20
+ - Add with `/multi-agent:save`, remove with `/multi-agent:forget <name>`. Routines are local-only and never synced.
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: multi-agent-save
3
+ language: en
4
+ description: "Save a recurring job as a reusable /multi-agent:<name> command. Reviews the conversation + your CLAUDE.md for candidate routines, you pick one and name it; stored local-only, never synced."
5
+ user-invocable: true
6
+ argument-hint: "[name]"
7
+ ---
8
+
9
+ # multi-agent-save - Save a Routine
10
+
11
+ **Input**: $ARGUMENTS (optional routine name)
12
+
13
+ Turn a recurring procedure into a reusable `/multi-agent:<name>` command. The routine is stored local-only (a `local-only: true` command + a `prefs.global.routines` entry) and is never synced. Backed by `$HOME/.copilot/scripts/routine-registry.mjs`.
14
+
15
+ ## Steps
16
+
17
+ 1. Distill candidates from what was just done (primary): group the tasks executed this session into coherent, repeatable jobs (suggested name + one-line summary + ordered steps). Then add named end-to-end procedures from `$HOME/.claude/CLAUDE.md`.
18
+ 2. `AskUserQuestion` with `multiSelect: true` (in `outputLanguage`): one option per candidate + an always-present `Other` free-text. One selected -> that routine; several selected -> combine into ONE routine (steps concatenated in shown order, single name); `Other` -> user types it. Empty is not consent.
19
+ 3. Name the command (from `$ARGUMENTS` or ask); validate `^[a-z0-9][a-z0-9-]*$`, <= 40 chars; the backend rejects collisions.
20
+ 4. Write the full procedure body to a temp file (single candidate: embed its steps, reference the CLAUDE.md section by name when that is the source; combined: concatenate the selected candidates' steps in order under numbered phase headings). Do not invent steps.
21
+ 5. Save: `node "$HOME/.copilot/scripts/routine-registry.mjs" add --name "<name>" --description "<summary>" --source "<source>" --body-file "/tmp/routine-<name>.md"`.
22
+ 6. Report it is available as `/multi-agent:<name>` after a session reload; offer to run it now.
23
+
24
+ ## Notes
25
+
26
+ - Never writes under a repo `pipeline/` tree; routines live only under the CLI home.
27
+ - List with `/multi-agent:routines`; remove with `/multi-agent:forget <name>`.
@@ -29,7 +29,7 @@ Run all steps automatically:
29
29
 
30
30
  ```
31
31
  Step 1: DETECT Compare timestamps, find stale targets
32
- Step 2: COPILOT Claude Code -> Copilot CLI (instructions + 38 sub-command skills)
32
+ Step 2: COPILOT Claude Code -> Copilot CLI (instructions + 41 sub-command skills)
33
33
  Step 3: REPO Claude Code -> pipeline repo (genericized, personal data scrub)
34
34
  Step 4: WEBSITE Version + phase/model counts -> {website-host} (i18n + projects.ts)
35
35
  Step 5: Commit Commit + push all changed repos
@@ -136,14 +136,14 @@ When invoked with the `release` argument:
136
136
  |-------------|-------------|
137
137
  | `~/.claude/commands/multi-agent/{cmd}.md` | `~/.copilot/skills/multi-agent-{cmd}/SKILL.md` |
138
138
 
139
- **38 commands are synced** (canonical inventory - must match `cross-cli-contract.md` section 1; drift = contract violation):
139
+ **41 commands are synced** (canonical inventory - must match `cross-cli-contract.md` section 1; drift = contract violation):
140
140
 
141
141
  ```
142
142
  analysis, analysis-resolve, autopilot, build-optimize, channels, create-jira, dev,
143
- dev-autopilot, dev-local, dev-local-autopilot, diff-explain, finish, garbage-collect,
143
+ dev-autopilot, dev-local, dev-local-autopilot, diff-explain, finish, forget, garbage-collect,
144
144
  help, issue, jira, kill, language, local,
145
145
  local-autopilot, log, manual-test, prune-logs, purge, refactor, resume, review, review-issue, review-jira,
146
- scan, search, setup, stack, status, sync, test, uninstall, update
146
+ routines, save, scan, search, setup, stack, status, sync, test, uninstall, update
147
147
  ```
148
148
 
149
149
  **NOT synced**: `refs/*` - Lazy-load references, Claude Code specific