@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.
- package/CHANGELOG.md +84 -0
- package/package.json +1 -1
- package/pipeline/commands/multi-agent/analysis/SKILL.md +68 -32
- package/pipeline/commands/multi-agent/forget/SKILL.md +29 -0
- package/pipeline/commands/multi-agent/help/SKILL.md +18 -0
- package/pipeline/commands/multi-agent/routines/SKILL.md +30 -0
- package/pipeline/commands/multi-agent/save/SKILL.md +46 -0
- package/pipeline/commands/multi-agent/sync/SKILL.md +4 -4
- package/pipeline/lib/fetch-figma-annotations.sh +237 -0
- package/pipeline/multi-agent-refs/analysis-template.md +233 -123
- package/pipeline/multi-agent-refs/cross-cli-contract.md +4 -3
- package/pipeline/schemas/analysis-spec.schema.json +78 -0
- package/pipeline/schemas/figma-project-config.schema.json +49 -0
- package/pipeline/schemas/migrations/prefs-2.3.0-to-2.4.0.mjs +32 -0
- package/pipeline/schemas/prefs.schema.json +58 -1
- package/pipeline/scripts/fixtures/install-layout.tsv +8 -8
- package/pipeline/scripts/migrate-prefs.mjs +8 -1
- package/pipeline/scripts/routine-registry.mjs +226 -0
- package/pipeline/scripts/smoke-fetchers-offline.sh +41 -0
- package/pipeline/scripts/smoke-generate-issue.sh +3 -3
- package/pipeline/scripts/smoke-migrate-state.sh +3 -3
- package/pipeline/scripts/smoke-pref-migration.sh +8 -6
- package/pipeline/scripts/smoke-review-readiness.sh +1 -1
- package/pipeline/scripts/smoke-routines.sh +84 -0
- package/pipeline/scripts/smoke-validate-analysis-doc.sh +161 -0
- package/pipeline/scripts/validate-analysis-doc.mjs +196 -0
- package/pipeline/skills/shared/core/multi-agent-forget/SKILL.md +22 -0
- package/pipeline/skills/shared/core/multi-agent-routines/SKILL.md +20 -0
- package/pipeline/skills/shared/core/multi-agent-save/SKILL.md +27 -0
- 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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
88
|
-
F3_FILE="$TMP/f3-v2.
|
|
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.
|
|
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 (
|
|
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 +
|
|
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
|
-
**
|
|
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
|