qualia-framework 6.14.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +8 -5
- package/CHANGELOG.md +316 -0
- package/CLAUDE.md +3 -1
- package/agents/roadmapper.md +16 -14
- package/bin/agent-status.js +24 -11
- package/bin/batch-plan.js +111 -0
- package/bin/branch-hygiene.js +135 -0
- package/bin/command-surface.js +2 -0
- package/bin/compile-instructions.js +82 -0
- package/bin/design-tokens.js +131 -0
- package/bin/erp-event.js +177 -0
- package/bin/erp-retry.js +12 -1
- package/bin/eval-runner.js +218 -0
- package/bin/host-adapters.js +84 -12
- package/bin/install.js +44 -13
- package/bin/knowledge-flush.js +6 -3
- package/bin/last-report.js +207 -0
- package/bin/project-sync.js +315 -0
- package/bin/recall.js +172 -0
- package/bin/repo-map.js +188 -0
- package/bin/runtime-manifest.js +12 -0
- package/bin/state.js +112 -1
- package/bin/vault-access.js +82 -0
- package/bin/verify-panel.js +294 -0
- package/bin/wave-plan.js +211 -0
- package/docs/erp-contract.md +180 -0
- package/mcp/memory-mcp/server.js +257 -0
- package/package.json +6 -3
- package/qualia-design/design-dials.md +72 -0
- package/qualia-design/design-reference.md +24 -0
- package/rules/access.md +42 -0
- package/rules/codex-goal.md +28 -26
- package/rules/infrastructure.md +1 -1
- package/skills/qualia/SKILL.md +6 -0
- package/skills/qualia-build/SKILL.md +43 -9
- package/skills/qualia-eval/SKILL.md +83 -0
- package/skills/qualia-feature/SKILL.md +20 -4
- package/skills/qualia-fix/SKILL.md +13 -1
- package/skills/qualia-map/SKILL.md +15 -0
- package/skills/qualia-milestone/SKILL.md +12 -6
- package/skills/qualia-new/REFERENCE.md +6 -4
- package/skills/qualia-new/SKILL.md +41 -15
- package/skills/qualia-plan/SKILL.md +2 -2
- package/skills/qualia-polish/SKILL.md +3 -2
- package/skills/qualia-recall/SKILL.md +76 -0
- package/skills/qualia-report/SKILL.md +10 -0
- package/skills/qualia-scope/SKILL.md +3 -3
- package/skills/qualia-ship/SKILL.md +34 -4
- package/skills/qualia-update/SKILL.md +4 -0
- package/skills/qualia-verify/SKILL.md +53 -24
- package/templates/DESIGN.md +15 -0
- package/templates/instructions.md +32 -0
- package/templates/journey.md +1 -1
- package/templates/project-discovery.md +30 -23
- package/templates/requirements.md +7 -7
- package/tests/agent-status.test.sh +15 -0
- package/tests/batch-plan.test.sh +56 -0
- package/tests/branch-hygiene.test.sh +93 -0
- package/tests/design-tokens.test.sh +53 -0
- package/tests/erp-event.test.sh +78 -0
- package/tests/eval-runner.test.sh +147 -0
- package/tests/instructions.test.sh +109 -0
- package/tests/last-report.test.sh +156 -0
- package/tests/lib.test.sh +29 -4
- package/tests/project-sync.test.sh +175 -0
- package/tests/recall.test.sh +91 -0
- package/tests/repo-map.test.sh +70 -0
- package/tests/run-all.sh +12 -0
- package/tests/runner.js +363 -33
- package/tests/state.test.sh +92 -0
- package/tests/verify-panel.test.sh +162 -0
- package/tests/wave-plan.test.sh +153 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# last-report.test.sh — bin/last-report.js (surface the latest session report)
|
|
3
|
+
# Run: bash tests/last-report.test.sh
|
|
4
|
+
|
|
5
|
+
PASS=0
|
|
6
|
+
FAIL=0
|
|
7
|
+
BIN_DIR="$(cd "$(dirname "$0")/../bin" && pwd)"
|
|
8
|
+
NODE="${NODE:-node}"
|
|
9
|
+
LR="$BIN_DIR/last-report.js"
|
|
10
|
+
|
|
11
|
+
assert_exit() {
|
|
12
|
+
local name="$1" expected="$2" actual="$3"
|
|
13
|
+
if [ "$expected" = "$actual" ]; then echo " ✓ $name"; PASS=$((PASS+1));
|
|
14
|
+
else echo " ✗ $name (expected exit $expected, got $actual)"; FAIL=$((FAIL+1)); fi
|
|
15
|
+
}
|
|
16
|
+
assert_contains() {
|
|
17
|
+
local name="$1" hay="$2" needle="$3"
|
|
18
|
+
if echo "$hay" | grep -qF "$needle"; then echo " ✓ $name"; PASS=$((PASS+1));
|
|
19
|
+
else echo " ✗ $name (missing '$needle' in: $hay)"; FAIL=$((FAIL+1)); fi
|
|
20
|
+
}
|
|
21
|
+
assert_not_contains() {
|
|
22
|
+
local name="$1" hay="$2" needle="$3"
|
|
23
|
+
if echo "$hay" | grep -qF "$needle"; then echo " ✗ $name (unexpected '$needle' in: $hay)"; FAIL=$((FAIL+1));
|
|
24
|
+
else echo " ✓ $name"; PASS=$((PASS+1)); fi
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
# Write a well-formed report at $1/.planning/reports/report-$2.md with summary $3, next $4
|
|
28
|
+
write_report() {
|
|
29
|
+
local root="$1" datepart="$2" summary="$3" next="$4" date="$5"
|
|
30
|
+
mkdir -p "$root/.planning/reports"
|
|
31
|
+
cat > "$root/.planning/reports/report-$datepart.md" <<EOF
|
|
32
|
+
# Session Report — $date
|
|
33
|
+
|
|
34
|
+
**Project:** demo
|
|
35
|
+
**Employee:** T
|
|
36
|
+
**Branch:** main
|
|
37
|
+
**Date:** $date
|
|
38
|
+
|
|
39
|
+
## What Was Done
|
|
40
|
+
- $summary
|
|
41
|
+
|
|
42
|
+
## Blockers
|
|
43
|
+
None.
|
|
44
|
+
|
|
45
|
+
## Next Steps
|
|
46
|
+
1. $next
|
|
47
|
+
|
|
48
|
+
## Commits (1)
|
|
49
|
+
- abc123 seed
|
|
50
|
+
EOF
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
echo "last-report.test.sh — bin/last-report.js"
|
|
54
|
+
echo ""
|
|
55
|
+
|
|
56
|
+
$NODE -c "$LR" 2>/dev/null && { echo " ✓ syntax valid"; PASS=$((PASS+1)); } || { echo " ✗ syntax invalid"; FAIL=$((FAIL+1)); }
|
|
57
|
+
|
|
58
|
+
# --- no reports dir → exit 1, found:false ---
|
|
59
|
+
TMP=$(mktemp -d)
|
|
60
|
+
(cd "$TMP" && $NODE "$LR" >/dev/null 2>&1)
|
|
61
|
+
assert_exit "no reports dir → exit 1" 1 $?
|
|
62
|
+
OUT=$(cd "$TMP" && $NODE "$LR" --json 2>&1)
|
|
63
|
+
assert_contains "json found:false when none" "$OUT" '"found": false'
|
|
64
|
+
OUT=$(cd "$TMP" && $NODE "$LR" 2>&1)
|
|
65
|
+
assert_contains "human: nothing to surface" "$OUT" "No session reports"
|
|
66
|
+
rm -rf "$TMP"
|
|
67
|
+
|
|
68
|
+
# --- single report → extracts date + summary + next, exit 0 ---
|
|
69
|
+
TMP=$(mktemp -d)
|
|
70
|
+
write_report "$TMP" "2026-05-28" "Built the login flow" "Wire the dashboard" "2026-05-28"
|
|
71
|
+
(cd "$TMP" && $NODE "$LR" >/dev/null 2>&1)
|
|
72
|
+
assert_exit "single report → exit 0" 0 $?
|
|
73
|
+
OUT=$(cd "$TMP" && $NODE "$LR" 2>&1)
|
|
74
|
+
assert_contains "human shows date" "$OUT" "2026-05-28"
|
|
75
|
+
assert_contains "human shows summary" "$OUT" "Built the login flow"
|
|
76
|
+
assert_contains "human shows next" "$OUT" "Wire the dashboard"
|
|
77
|
+
assert_contains "human has 'Last session' prefix" "$OUT" "Last session"
|
|
78
|
+
rm -rf "$TMP"
|
|
79
|
+
|
|
80
|
+
# --- json shape ---
|
|
81
|
+
TMP=$(mktemp -d)
|
|
82
|
+
write_report "$TMP" "2026-05-28" "Built the login flow" "Wire the dashboard" "2026-05-28"
|
|
83
|
+
OUT=$(cd "$TMP" && $NODE "$LR" --json 2>&1)
|
|
84
|
+
assert_contains "json found:true" "$OUT" '"found": true'
|
|
85
|
+
assert_contains "json file field" "$OUT" '"file": "report-2026-05-28.md"'
|
|
86
|
+
assert_contains "json date field" "$OUT" '"date": "2026-05-28"'
|
|
87
|
+
assert_contains "json summary field" "$OUT" '"summary": "Built the login flow"'
|
|
88
|
+
assert_contains "json next field" "$OUT" '"next": "Wire the dashboard"'
|
|
89
|
+
rm -rf "$TMP"
|
|
90
|
+
|
|
91
|
+
# --- multiple reports → picks the newest BY DATE (not file order) ---
|
|
92
|
+
TMP=$(mktemp -d)
|
|
93
|
+
write_report "$TMP" "2026-05-28" "Older work" "Old next" "2026-05-28"
|
|
94
|
+
write_report "$TMP" "2026-06-15" "Newest work" "New next" "2026-06-15"
|
|
95
|
+
write_report "$TMP" "2026-06-01" "Middle work" "Mid next" "2026-06-01"
|
|
96
|
+
OUT=$(cd "$TMP" && $NODE "$LR" --json 2>&1)
|
|
97
|
+
assert_contains "picks newest date file" "$OUT" '"file": "report-2026-06-15.md"'
|
|
98
|
+
assert_contains "picks newest summary" "$OUT" "Newest work"
|
|
99
|
+
assert_not_contains "does not pick older summary" "$OUT" "Older work"
|
|
100
|
+
rm -rf "$TMP"
|
|
101
|
+
|
|
102
|
+
# --- dated suffix filenames (report-2026-06-20-session2.md) parse correctly ---
|
|
103
|
+
TMP=$(mktemp -d)
|
|
104
|
+
write_report "$TMP" "2026-06-10" "Earlier" "e" "2026-06-10"
|
|
105
|
+
write_report "$TMP" "2026-06-20-session2" "Suffixed newest" "s" "2026-06-20"
|
|
106
|
+
OUT=$(cd "$TMP" && $NODE "$LR" --json 2>&1)
|
|
107
|
+
assert_contains "suffixed filename wins by date" "$OUT" "Suffixed newest"
|
|
108
|
+
assert_contains "suffixed date extracted" "$OUT" '"date": "2026-06-20"'
|
|
109
|
+
rm -rf "$TMP"
|
|
110
|
+
|
|
111
|
+
# --- --now makes age_days deterministic ---
|
|
112
|
+
TMP=$(mktemp -d)
|
|
113
|
+
write_report "$TMP" "2026-06-01" "Work" "Next" "2026-06-01"
|
|
114
|
+
OUT=$(cd "$TMP" && $NODE "$LR" --json --now 2026-06-11T00:00:00Z 2>&1)
|
|
115
|
+
assert_contains "age_days deterministic (10)" "$OUT" '"age_days": 10'
|
|
116
|
+
OUT=$(cd "$TMP" && $NODE "$LR" --now 2026-06-11T00:00:00Z 2>&1)
|
|
117
|
+
assert_contains "human age uses --now" "$OUT" "10d ago"
|
|
118
|
+
rm -rf "$TMP"
|
|
119
|
+
|
|
120
|
+
# --- library: latestReport() returns structured object ---
|
|
121
|
+
TMP=$(mktemp -d)
|
|
122
|
+
write_report "$TMP" "2026-06-01" "Lib work" "Lib next" "2026-06-01"
|
|
123
|
+
RES=$($NODE -e "console.log(require('$LR').latestReport('$TMP').summary)" 2>&1)
|
|
124
|
+
assert_contains "latestReport() returns summary" "$RES" "Lib work"
|
|
125
|
+
RES=$($NODE -e "console.log(require('$LR').latestReport('$TMP',{now:'2026-06-06T00:00:00Z'}).age_days)" 2>&1)
|
|
126
|
+
assert_contains "latestReport() honors now opt (5)" "$RES" "5"
|
|
127
|
+
rm -rf "$TMP"
|
|
128
|
+
|
|
129
|
+
# --- malformed / empty report handled gracefully (no crash, still exit 0) ---
|
|
130
|
+
TMP=$(mktemp -d)
|
|
131
|
+
mkdir -p "$TMP/.planning/reports"
|
|
132
|
+
: > "$TMP/.planning/reports/report-2026-06-02.md" # completely empty
|
|
133
|
+
(cd "$TMP" && $NODE "$LR" >/dev/null 2>&1)
|
|
134
|
+
assert_exit "empty report → exit 0 (found by filename)" 0 $?
|
|
135
|
+
OUT=$(cd "$TMP" && $NODE "$LR" --json 2>&1)
|
|
136
|
+
assert_contains "empty report found:true" "$OUT" '"found": true'
|
|
137
|
+
assert_contains "empty report date from filename" "$OUT" '"date": "2026-06-02"'
|
|
138
|
+
rm -rf "$TMP"
|
|
139
|
+
|
|
140
|
+
# --- malformed report (no headings, just prose) still yields a summary ---
|
|
141
|
+
TMP=$(mktemp -d)
|
|
142
|
+
mkdir -p "$TMP/.planning/reports"
|
|
143
|
+
printf 'just some freeform notes about the day\n' > "$TMP/.planning/reports/report-2026-06-03.md"
|
|
144
|
+
OUT=$(cd "$TMP" && $NODE "$LR" --json 2>&1)
|
|
145
|
+
assert_contains "malformed report falls back to first line" "$OUT" "just some freeform notes"
|
|
146
|
+
rm -rf "$TMP"
|
|
147
|
+
|
|
148
|
+
# --- bad argument → exit 2 ---
|
|
149
|
+
TMP=$(mktemp -d)
|
|
150
|
+
(cd "$TMP" && $NODE "$LR" --bogus >/dev/null 2>&1)
|
|
151
|
+
assert_exit "unknown flag → exit 2" 2 $?
|
|
152
|
+
rm -rf "$TMP"
|
|
153
|
+
|
|
154
|
+
echo ""
|
|
155
|
+
echo "=== Results: $PASS passed, $FAIL failed ==="
|
|
156
|
+
[ "$FAIL" -eq 0 ] && exit 0 || exit 1
|
package/tests/lib.test.sh
CHANGED
|
@@ -365,6 +365,31 @@ console.log(c.includes(".codex/bin/state.js") && c.includes(".codex/agents/plann
|
|
|
365
365
|
')
|
|
366
366
|
[ "$RES" = "HOST-RENDER-OK" ] && ok "host-adapters renders Qualia path tokens per host" || fail "host-adapters render: $RES"
|
|
367
367
|
|
|
368
|
+
RES=$($NODE -e '
|
|
369
|
+
const { adapter, hostForHome } = require("'"$HA"'");
|
|
370
|
+
const c = adapter("claude"), x = adapter("codex");
|
|
371
|
+
const ok =
|
|
372
|
+
c.agentCli === "claude" && JSON.stringify(c.agentExec("P")) === JSON.stringify(["-p","P"]) &&
|
|
373
|
+
x.agentCli === "codex" && JSON.stringify(x.agentExec("P")) === JSON.stringify(["exec","P"]) &&
|
|
374
|
+
hostForHome("/x/.codex") === "codex" && hostForHome("/x/.claude") === "claude";
|
|
375
|
+
console.log(ok ? "ADAPTER-EXEC-OK" : JSON.stringify({c:c.agentExec("P"),x:x.agentExec("P")}));
|
|
376
|
+
')
|
|
377
|
+
[ "$RES" = "ADAPTER-EXEC-OK" ] && ok "host-adapters owns agent-CLI invocation (agentCli/agentExec) + hostForHome" || fail "host-adapters agent-exec: $RES"
|
|
378
|
+
|
|
379
|
+
# ─── R9 design dials: the 3 dials exist in the reference AND the DESIGN template ─
|
|
380
|
+
DIALS="$FRAMEWORK_DIR/qualia-design/design-dials.md"
|
|
381
|
+
DTMPL="$FRAMEWORK_DIR/templates/DESIGN.md"
|
|
382
|
+
RES=$($NODE -e '
|
|
383
|
+
const fs = require("fs");
|
|
384
|
+
const ref = fs.readFileSync("'"$DIALS"'", "utf8");
|
|
385
|
+
const tpl = fs.readFileSync("'"$DTMPL"'", "utf8");
|
|
386
|
+
const dials = ["DESIGN_VARIANCE", "MOTION_INTENSITY", "VISUAL_DENSITY"];
|
|
387
|
+
const refOk = dials.every(d => ref.includes(d)) && /ban\b/i.test(ref) && /do instead/i.test(ref);
|
|
388
|
+
const tplOk = dials.every(d => tpl.includes(d));
|
|
389
|
+
console.log(refOk && tplOk ? "DIALS-OK" : "FAIL ref="+refOk+" tpl="+tplOk);
|
|
390
|
+
')
|
|
391
|
+
[ "$RES" = "DIALS-OK" ] && ok "design dials + ban→do-instead present in design-dials.md AND DESIGN.md template" || fail "design dials: $RES"
|
|
392
|
+
|
|
368
393
|
CS="$FRAMEWORK_DIR/bin/command-surface.js"
|
|
369
394
|
$NODE --check "$CS" >/dev/null 2>&1 && ok "command-surface.js parses" || fail "command-surface.js parse"
|
|
370
395
|
RES=$($NODE -e '
|
|
@@ -507,14 +532,14 @@ TMP=$(mktmp)
|
|
|
507
532
|
mkdir -p "$TMP/home/.claude/bin" "$TMP/home/.claude/hooks" "$TMP/home/.claude/knowledge/daily-log" "$TMP/home/.claude/qualia-design" "$TMP/home/.claude/agents" "$TMP/home/.claude/qualia-templates" "$TMP/project"
|
|
508
533
|
echo '{"installed_by":"Test","role":"OWNER","version":"6.3.0","erp":{"enabled":false}}' > "$TMP/home/.claude/.qualia-config.json"
|
|
509
534
|
touch "$TMP/home/.claude/CLAUDE.md" "$TMP/home/.claude/settings.json"
|
|
510
|
-
for f in runtime-manifest.js command-surface.js host-adapters.js state.js qualia-ui.js statusline.js knowledge.js knowledge-flush.js state-ledger.js plan-contract.js contract-runner.js agent-status.js analyze-gate.js harness-eval.js trust-score.js agent-runs.js slop-detect.mjs erp-retry.js work-packet.js report-payload.js project-snapshot.js codex-goal.js planning-hygiene.js prune-deprecated.js learning-candidates.js status-snapshot.js security-scan.js auto-report.js; do
|
|
535
|
+
for f in runtime-manifest.js command-surface.js host-adapters.js state.js qualia-ui.js statusline.js knowledge.js knowledge-flush.js recall.js vault-access.js repo-map.js design-tokens.js batch-plan.js state-ledger.js plan-contract.js contract-runner.js agent-status.js analyze-gate.js verify-panel.js wave-plan.js eval-runner.js branch-hygiene.js last-report.js harness-eval.js trust-score.js agent-runs.js slop-detect.mjs erp-retry.js erp-event.js work-packet.js report-payload.js project-snapshot.js project-sync.js codex-goal.js planning-hygiene.js prune-deprecated.js learning-candidates.js status-snapshot.js security-scan.js auto-report.js; do
|
|
511
536
|
touch "$TMP/home/.claude/bin/$f"
|
|
512
537
|
done
|
|
513
538
|
for h in session-start.js auto-update.js branch-guard.js pre-push.js pre-deploy-gate.js migration-guard.js git-guardrails.js stop-session-log.js fawzi-approval-guard.js vercel-account-guard.js env-empty-guard.js supabase-destructive-guard.js; do
|
|
514
539
|
touch "$TMP/home/.claude/hooks/$h"
|
|
515
540
|
done
|
|
516
541
|
touch "$TMP/home/.claude/knowledge/index.md" "$TMP/home/.claude/knowledge/agents.md" "$TMP/home/.claude/agents/visual-evaluator.md" "$TMP/home/.claude/qualia-guide.md" "$TMP/home/.claude/qualia-templates/help.html"
|
|
517
|
-
for f in design-laws.md design-rubric.md design-brand.md design-product.md design-reference.md frontend.md graphics.md; do
|
|
542
|
+
for f in design-laws.md design-rubric.md design-brand.md design-product.md design-reference.md design-dials.md frontend.md graphics.md; do
|
|
518
543
|
touch "$TMP/home/.claude/qualia-design/$f"
|
|
519
544
|
done
|
|
520
545
|
for s in $($NODE -e 'console.log(require(process.argv[1]).ACTIVE_SKILLS.join(" "))' "$CS"); do
|
|
@@ -622,14 +647,14 @@ TMP=$(mktmp)
|
|
|
622
647
|
mkdir -p "$TMP/.claude/bin" "$TMP/.claude/hooks" "$TMP/.claude/knowledge/daily-log" "$TMP/.claude/qualia-design" "$TMP/.claude/agents" "$TMP/.claude/qualia-templates" "$TMP/project/.planning"
|
|
623
648
|
echo '{"installed_by":"Test","role":"OWNER","erp":{"enabled":false}}' > "$TMP/.claude/.qualia-config.json"
|
|
624
649
|
touch "$TMP/.claude/CLAUDE.md" "$TMP/.claude/settings.json"
|
|
625
|
-
for f in runtime-manifest.js command-surface.js host-adapters.js state.js qualia-ui.js statusline.js knowledge.js knowledge-flush.js state-ledger.js plan-contract.js contract-runner.js agent-status.js analyze-gate.js harness-eval.js trust-score.js agent-runs.js slop-detect.mjs erp-retry.js work-packet.js report-payload.js project-snapshot.js codex-goal.js planning-hygiene.js prune-deprecated.js learning-candidates.js status-snapshot.js security-scan.js auto-report.js; do
|
|
650
|
+
for f in runtime-manifest.js command-surface.js host-adapters.js state.js qualia-ui.js statusline.js knowledge.js knowledge-flush.js recall.js vault-access.js repo-map.js design-tokens.js batch-plan.js state-ledger.js plan-contract.js contract-runner.js agent-status.js analyze-gate.js verify-panel.js wave-plan.js eval-runner.js branch-hygiene.js last-report.js harness-eval.js trust-score.js agent-runs.js slop-detect.mjs erp-retry.js erp-event.js work-packet.js report-payload.js project-snapshot.js project-sync.js codex-goal.js planning-hygiene.js prune-deprecated.js learning-candidates.js status-snapshot.js security-scan.js auto-report.js; do
|
|
626
651
|
touch "$TMP/.claude/bin/$f"
|
|
627
652
|
done
|
|
628
653
|
for h in session-start.js auto-update.js branch-guard.js pre-push.js pre-deploy-gate.js migration-guard.js git-guardrails.js stop-session-log.js fawzi-approval-guard.js vercel-account-guard.js env-empty-guard.js supabase-destructive-guard.js; do
|
|
629
654
|
touch "$TMP/.claude/hooks/$h"
|
|
630
655
|
done
|
|
631
656
|
touch "$TMP/.claude/knowledge/index.md" "$TMP/.claude/knowledge/agents.md"
|
|
632
|
-
for f in design-laws.md design-rubric.md design-brand.md design-product.md design-reference.md frontend.md graphics.md; do
|
|
657
|
+
for f in design-laws.md design-rubric.md design-brand.md design-product.md design-reference.md design-dials.md frontend.md graphics.md; do
|
|
633
658
|
touch "$TMP/.claude/qualia-design/$f"
|
|
634
659
|
done
|
|
635
660
|
for s in $($NODE -e 'console.log(require(process.argv[1]).ACTIVE_SKILLS.join(" "))' "$CS"); do
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# project-sync.test.sh — bin/project-sync.js (Framework→ERP project-sync payload)
|
|
3
|
+
# Run: bash tests/project-sync.test.sh
|
|
4
|
+
|
|
5
|
+
PASS=0
|
|
6
|
+
FAIL=0
|
|
7
|
+
BIN_DIR="$(cd "$(dirname "$0")/../bin" && pwd)"
|
|
8
|
+
NODE="${NODE:-node}"
|
|
9
|
+
PS="$BIN_DIR/project-sync.js"
|
|
10
|
+
|
|
11
|
+
assert_exit() {
|
|
12
|
+
local name="$1" expected="$2" actual="$3"
|
|
13
|
+
if [ "$expected" = "$actual" ]; then echo " ✓ $name"; PASS=$((PASS+1));
|
|
14
|
+
else echo " ✗ $name (expected exit $expected, got $actual)"; FAIL=$((FAIL+1)); fi
|
|
15
|
+
}
|
|
16
|
+
assert_contains() {
|
|
17
|
+
local name="$1" hay="$2" needle="$3"
|
|
18
|
+
if echo "$hay" | grep -qF "$needle"; then echo " ✓ $name"; PASS=$((PASS+1));
|
|
19
|
+
else echo " ✗ $name (missing '$needle' in output)"; FAIL=$((FAIL+1)); fi
|
|
20
|
+
}
|
|
21
|
+
assert_not_contains() {
|
|
22
|
+
local name="$1" hay="$2" needle="$3"
|
|
23
|
+
if echo "$hay" | grep -qF "$needle"; then echo " ✗ $name (unexpected '$needle')"; FAIL=$((FAIL+1));
|
|
24
|
+
else echo " ✓ $name"; PASS=$((PASS+1)); fi
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
# A fully-populated .planning fixture: milestones, REQ-IDs, offroad, lifetime.
|
|
28
|
+
setup_full() {
|
|
29
|
+
local tmp
|
|
30
|
+
tmp=$(mktemp -d)
|
|
31
|
+
mkdir -p "$tmp/.planning"
|
|
32
|
+
cat > "$tmp/.planning/tracking.json" <<'EOF'
|
|
33
|
+
{
|
|
34
|
+
"project": "acme-portal",
|
|
35
|
+
"project_id": "qs-acme-portal",
|
|
36
|
+
"client": "Acme",
|
|
37
|
+
"milestone": 2,
|
|
38
|
+
"milestone_name": "Product",
|
|
39
|
+
"milestones": [
|
|
40
|
+
{ "num": 1, "name": "Foundation", "closed_at": "2026-04-10T18:00:00Z", "phases_completed": 3, "tasks_completed": 12, "deployed_url": "https://m1.vercel.app" }
|
|
41
|
+
],
|
|
42
|
+
"phase": 2,
|
|
43
|
+
"phase_name": "Dashboard",
|
|
44
|
+
"total_phases": 4,
|
|
45
|
+
"status": "built",
|
|
46
|
+
"tasks_done": 3,
|
|
47
|
+
"tasks_total": 5,
|
|
48
|
+
"verification": "pending",
|
|
49
|
+
"gap_cycles": { "2": 1 },
|
|
50
|
+
"build_count": 4,
|
|
51
|
+
"deploy_count": 1,
|
|
52
|
+
"deployed_url": "https://client.vercel.app",
|
|
53
|
+
"lifecycle": "build",
|
|
54
|
+
"lifetime": { "tasks_completed": 15, "phases_completed": 4, "milestones_completed": 1, "total_phases": 7, "offroad_count": 2 },
|
|
55
|
+
"offroad": [
|
|
56
|
+
{ "at": "2026-06-01T10:00:00Z", "milestone": 2, "ref": "BUG-7", "note": "hotfix login" },
|
|
57
|
+
{ "at": "2026-06-02T10:00:00Z", "milestone": 2, "ref": null, "note": "tweak" }
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
EOF
|
|
61
|
+
cat > "$tmp/.planning/JOURNEY.md" <<'EOF'
|
|
62
|
+
## Milestone 1 · Foundation
|
|
63
|
+
## Milestone 2 · Product [CURRENT]
|
|
64
|
+
## Milestone 3 · Handoff [FINAL]
|
|
65
|
+
EOF
|
|
66
|
+
cat > "$tmp/.planning/REQUIREMENTS.md" <<'EOF'
|
|
67
|
+
| ID | Milestone | Phase | Status |
|
|
68
|
+
|----|-----------|-------|--------|
|
|
69
|
+
| CORE-01 | M1: Foundation | Phase 1 | Complete |
|
|
70
|
+
| CORE-02 | M2: Product | Phase 2 | Complete |
|
|
71
|
+
| CORE-03 | M2: Product | Phase 3 | Incomplete |
|
|
72
|
+
EOF
|
|
73
|
+
echo "$tmp"
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
echo "project-sync.test.sh — bin/project-sync.js"
|
|
77
|
+
echo ""
|
|
78
|
+
|
|
79
|
+
# --- syntax ---
|
|
80
|
+
$NODE -c "$PS" 2>/dev/null && { echo " ✓ syntax valid"; PASS=$((PASS+1)); } || { echo " ✗ syntax invalid"; FAIL=$((FAIL+1)); }
|
|
81
|
+
|
|
82
|
+
NOW="2026-06-21T00:00:00.000Z"
|
|
83
|
+
|
|
84
|
+
# --- no .planning → exit 2 ---
|
|
85
|
+
TMP=$(mktemp -d)
|
|
86
|
+
(cd "$TMP" && $NODE "$PS" >/dev/null 2>&1)
|
|
87
|
+
assert_exit "no .planning → exit 2" 2 $?
|
|
88
|
+
rm -rf "$TMP"
|
|
89
|
+
|
|
90
|
+
# --- full fixture → exit 0 + valid JSON ---
|
|
91
|
+
TMP=$(setup_full)
|
|
92
|
+
OUT=$($NODE "$PS" --cwd "$TMP" --json --now "$NOW" 2>&1)
|
|
93
|
+
(cd "$TMP" && $NODE "$PS" --json --now "$NOW" >/dev/null 2>&1)
|
|
94
|
+
assert_exit "full fixture → exit 0" 0 $?
|
|
95
|
+
echo "$OUT" | $NODE -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{JSON.parse(s);process.exit(0)})" 2>/dev/null \
|
|
96
|
+
&& { echo " ✓ --json emits valid JSON"; PASS=$((PASS+1)); } \
|
|
97
|
+
|| { echo " ✗ --json invalid JSON"; FAIL=$((FAIL+1)); }
|
|
98
|
+
|
|
99
|
+
# --- schema_version + payload marker ---
|
|
100
|
+
assert_contains "carries schema_version" "$OUT" '"schema_version": 1'
|
|
101
|
+
assert_contains "payload marker is project-sync" "$OUT" '"payload": "project-sync"'
|
|
102
|
+
|
|
103
|
+
# --- lifecycle + launched_at handling ---
|
|
104
|
+
assert_contains "lifecycle present (build)" "$OUT" '"lifecycle": "build"'
|
|
105
|
+
|
|
106
|
+
# --- milestones[] with status + REQ completion ---
|
|
107
|
+
assert_contains "milestone 1 closed" "$OUT" '"status": "closed"'
|
|
108
|
+
assert_contains "milestone 2 current" "$OUT" '"status": "current"'
|
|
109
|
+
assert_contains "milestone 3 future" "$OUT" '"status": "future"'
|
|
110
|
+
assert_contains "closed milestone phases count" "$OUT" '"phases": 3'
|
|
111
|
+
assert_contains "closed milestone tasks_completed" "$OUT" '"tasks_completed": 12'
|
|
112
|
+
assert_contains "REQ total tracked for M2" "$OUT" '"total": 2'
|
|
113
|
+
assert_contains "REQ complete count" "$OUT" '"complete": 1'
|
|
114
|
+
assert_contains "incomplete REQ id surfaced" "$OUT" '"id": "CORE-03"'
|
|
115
|
+
assert_contains "future milestone REQ untracked" "$OUT" '"tracked": false'
|
|
116
|
+
assert_contains "per-milestone deployed_url" "$OUT" '"deployed_url": "https://m1.vercel.app"'
|
|
117
|
+
assert_contains "total_milestones from journey" "$OUT" '"total_milestones": 3'
|
|
118
|
+
|
|
119
|
+
# --- current position ---
|
|
120
|
+
assert_contains "current phase" "$OUT" '"phase": 2'
|
|
121
|
+
assert_contains "current verification" "$OUT" '"verification": "pending"'
|
|
122
|
+
|
|
123
|
+
# --- task rollup ---
|
|
124
|
+
assert_contains "rollup tasks_completed" "$OUT" '"tasks_completed": 15'
|
|
125
|
+
assert_contains "rollup build_count" "$OUT" '"build_count": 4'
|
|
126
|
+
assert_contains "rollup deploy_count" "$OUT" '"deploy_count": 1'
|
|
127
|
+
assert_contains "rollup gap_cycles (flattened to number)" "$OUT" '"current_phase_gap_cycles": 1'
|
|
128
|
+
|
|
129
|
+
# --- accountability / offroad ---
|
|
130
|
+
assert_contains "offroad_count" "$OUT" '"offroad_count": 2'
|
|
131
|
+
assert_contains "recent offroad entry" "$OUT" '"ref": "BUG-7"'
|
|
132
|
+
|
|
133
|
+
# --- integration / merge model ---
|
|
134
|
+
assert_contains "trunk integration model" "$OUT" '"model": "trunk"'
|
|
135
|
+
assert_contains "integrates at ship" "$OUT" '"integrates_at": "/qualia-ship"'
|
|
136
|
+
assert_contains "main-push event type" "$OUT" '"main_push_event_type": "employee_main_push"'
|
|
137
|
+
|
|
138
|
+
rm -rf "$TMP"
|
|
139
|
+
|
|
140
|
+
# --- graceful when fields absent: minimal tracking.json, no JOURNEY/REQUIREMENTS ---
|
|
141
|
+
TMP=$(mktemp -d)
|
|
142
|
+
mkdir -p "$TMP/.planning"
|
|
143
|
+
echo '{ "project": "bare", "milestone": 1, "phase": 1 }' > "$TMP/.planning/tracking.json"
|
|
144
|
+
OUT=$($NODE "$PS" --cwd "$TMP" --json --now "$NOW" 2>&1)
|
|
145
|
+
(cd "$TMP" && $NODE "$PS" --json --now "$NOW" >/dev/null 2>&1)
|
|
146
|
+
assert_exit "bare fixture → exit 0" 0 $?
|
|
147
|
+
assert_contains "bare still has schema_version" "$OUT" '"schema_version": 1'
|
|
148
|
+
assert_contains "bare offroad_count defaults to 0" "$OUT" '"offroad_count": 0'
|
|
149
|
+
assert_contains "bare emits a current milestone" "$OUT" '"status": "current"'
|
|
150
|
+
assert_contains "bare REQ untracked (no REQUIREMENTS.md)" "$OUT" '"tracked": false'
|
|
151
|
+
rm -rf "$TMP"
|
|
152
|
+
|
|
153
|
+
# --- library: buildProjectSync() returns the object ---
|
|
154
|
+
TMP=$(setup_full)
|
|
155
|
+
RES=$($NODE -e "const {buildProjectSync}=require('$PS');const o=buildProjectSync({cwd:'$TMP',now:'$NOW'});console.log(o.schema_version, o.milestones.length, o.milestones[1].requirements.incomplete[0].id)" 2>&1)
|
|
156
|
+
assert_contains "buildProjectSync() returns enriched object" "$RES" "1 3 CORE-03"
|
|
157
|
+
rm -rf "$TMP"
|
|
158
|
+
|
|
159
|
+
# --- library: milestoneRequirements() parses a fixture dir ---
|
|
160
|
+
TMP=$(setup_full)
|
|
161
|
+
RES=$($NODE -e "const {milestoneRequirements}=require('$PS');const r=milestoneRequirements('$TMP/.planning',2);console.log(r.tracked, r.total, r.complete, r.incomplete.length)" 2>&1)
|
|
162
|
+
assert_contains "milestoneRequirements() counts REQ completion" "$RES" "true 2 1 1"
|
|
163
|
+
rm -rf "$TMP"
|
|
164
|
+
|
|
165
|
+
# --- --write persists a snapshot file ---
|
|
166
|
+
TMP=$(setup_full)
|
|
167
|
+
FILE=$($NODE "$PS" --cwd "$TMP" --write --now "$NOW" 2>&1)
|
|
168
|
+
if [ -f "$FILE" ]; then echo " ✓ --write persists a file"; PASS=$((PASS+1));
|
|
169
|
+
else echo " ✗ --write did not persist ($FILE)"; FAIL=$((FAIL+1)); fi
|
|
170
|
+
assert_contains "written file is project-sync-*" "$FILE" "project-sync-"
|
|
171
|
+
rm -rf "$TMP"
|
|
172
|
+
|
|
173
|
+
echo ""
|
|
174
|
+
echo "=== Results: $PASS passed, $FAIL failed ==="
|
|
175
|
+
[ "$FAIL" -eq 0 ] && exit 0 || exit 1
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# recall.test.sh — bin/recall.js (read-side memory recall, role-filtered vault)
|
|
3
|
+
# Run: bash tests/recall.test.sh
|
|
4
|
+
|
|
5
|
+
PASS=0
|
|
6
|
+
FAIL=0
|
|
7
|
+
BIN_DIR="$(cd "$(dirname "$0")/../bin" && pwd)"
|
|
8
|
+
NODE="${NODE:-node}"
|
|
9
|
+
RECALL="$BIN_DIR/recall.js"
|
|
10
|
+
|
|
11
|
+
assert_exit() { if [ "$2" = "$3" ]; then echo " ✓ $1"; PASS=$((PASS+1)); else echo " ✗ $1 (expected exit $2, got $3)"; FAIL=$((FAIL+1)); fi; }
|
|
12
|
+
assert_contains() { if echo "$2" | grep -qF "$3"; then echo " ✓ $1"; PASS=$((PASS+1)); else echo " ✗ $1 (missing '$3')"; FAIL=$((FAIL+1)); fi; }
|
|
13
|
+
assert_absent() { if echo "$2" | grep -qF "$3"; then echo " ✗ $1 (LEAK: found '$3')"; FAIL=$((FAIL+1)); else echo " ✓ $1"; PASS=$((PASS+1)); fi; }
|
|
14
|
+
|
|
15
|
+
echo "recall.test.sh — bin/recall.js"
|
|
16
|
+
echo ""
|
|
17
|
+
|
|
18
|
+
$NODE -c "$RECALL" 2>/dev/null && { echo " ✓ syntax valid"; PASS=$((PASS+1)); } || { echo " ✗ syntax invalid"; FAIL=$((FAIL+1)); }
|
|
19
|
+
|
|
20
|
+
# ── Fixture: a temp knowledge home + a temp vault with an access manifest ──
|
|
21
|
+
HOME_DIR=$(mktemp -d)
|
|
22
|
+
VAULT=$(mktemp -d)
|
|
23
|
+
mkdir -p "$HOME_DIR/knowledge" "$VAULT/wiki/concepts" "$VAULT/wiki/_meta"
|
|
24
|
+
|
|
25
|
+
# knowledge layer hit
|
|
26
|
+
cat > "$HOME_DIR/knowledge/learned-patterns.md" <<'EOF'
|
|
27
|
+
# Patterns
|
|
28
|
+
- The zebrafish pattern: batch writes before fanout.
|
|
29
|
+
EOF
|
|
30
|
+
# vault ALL_ROLES hit
|
|
31
|
+
cat > "$VAULT/wiki/concepts/notes.md" <<'EOF'
|
|
32
|
+
A lesson about the zebrafish approach to retries.
|
|
33
|
+
EOF
|
|
34
|
+
# vault OWNER_ONLY hit (the access manifest itself names wiki/_meta/access.md)
|
|
35
|
+
cat > "$VAULT/wiki/_meta/access.md" <<'EOF'
|
|
36
|
+
# Vault Access Manifest
|
|
37
|
+
## OWNER_ONLY
|
|
38
|
+
| Path | Why |
|
|
39
|
+
|---|---|
|
|
40
|
+
| `Clients/*.md` | commercial |
|
|
41
|
+
| `wiki/_meta/access.md` | this file (zebrafish marker) |
|
|
42
|
+
## ALL_ROLES
|
|
43
|
+
| `wiki/concepts/` | lessons |
|
|
44
|
+
EOF
|
|
45
|
+
|
|
46
|
+
export QUALIA_MEMORY_ROOT="$VAULT"
|
|
47
|
+
RUN() { QUALIA_HOME="$HOME_DIR" "$@"; }
|
|
48
|
+
|
|
49
|
+
# ── Invocation guards ──
|
|
50
|
+
RUN $NODE "$RECALL" >/dev/null 2>&1; assert_exit "no query → exit 2" 2 $?
|
|
51
|
+
RUN $NODE "$RECALL" zebrafish --scope bogus >/dev/null 2>&1; assert_exit "bad scope → exit 2" 2 $?
|
|
52
|
+
|
|
53
|
+
# ── Hits across both stores ──
|
|
54
|
+
OUT=$(QUALIA_ROLE=OWNER RUN $NODE "$RECALL" zebrafish 2>&1); assert_exit "valid query → exit 0" 0 $?
|
|
55
|
+
assert_contains "knowledge-layer hit" "$OUT" "learned-patterns.md"
|
|
56
|
+
assert_contains "vault ALL_ROLES hit" "$OUT" "concepts/notes.md"
|
|
57
|
+
|
|
58
|
+
# ── Role enforcement (the security boundary) ──
|
|
59
|
+
OWNER_OUT=$(QUALIA_ROLE=OWNER RUN $NODE "$RECALL" zebrafish --scope vault 2>&1)
|
|
60
|
+
assert_contains "OWNER sees OWNER_ONLY path" "$OWNER_OUT" "_meta/access.md"
|
|
61
|
+
EMP_OUT=$(QUALIA_ROLE=EMPLOYEE RUN $NODE "$RECALL" zebrafish --scope vault 2>&1)
|
|
62
|
+
assert_contains "EMPLOYEE still sees ALL_ROLES" "$EMP_OUT" "concepts/notes.md"
|
|
63
|
+
assert_absent "EMPLOYEE blocked from OWNER_ONLY" "$EMP_OUT" "_meta/access.md"
|
|
64
|
+
# fail-closed: no role config + no QUALIA_ROLE → RESTRICTED
|
|
65
|
+
NOROLE_OUT=$(QUALIA_HOME="$VAULT" env -u QUALIA_ROLE $NODE "$RECALL" zebrafish --scope vault 2>&1)
|
|
66
|
+
assert_absent "RESTRICTED (fail-closed) blocked from OWNER_ONLY" "$NOROLE_OUT" "_meta/access.md"
|
|
67
|
+
|
|
68
|
+
# ── JSON shape ──
|
|
69
|
+
JSON=$(QUALIA_ROLE=OWNER RUN $NODE "$RECALL" zebrafish --json 2>&1)
|
|
70
|
+
echo "$JSON" | $NODE -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{const j=JSON.parse(s);if(j.role&&j.scope==='all'&&typeof j.total==='number'&&Array.isArray(j.knowledge)&&Array.isArray(j.vault))process.exit(0);process.exit(1)})" \
|
|
71
|
+
&& { echo " ✓ --json valid shape (role/scope/total/knowledge/vault)"; PASS=$((PASS+1)); } \
|
|
72
|
+
|| { echo " ✗ --json shape"; FAIL=$((FAIL+1)); }
|
|
73
|
+
|
|
74
|
+
# ── Scope isolation ──
|
|
75
|
+
KONLY=$(QUALIA_ROLE=OWNER RUN $NODE "$RECALL" zebrafish --scope knowledge 2>&1)
|
|
76
|
+
assert_contains "scope=knowledge includes knowledge" "$KONLY" "learned-patterns.md"
|
|
77
|
+
assert_absent "scope=knowledge excludes vault" "$KONLY" "concepts/notes.md"
|
|
78
|
+
VONLY=$(QUALIA_ROLE=OWNER RUN $NODE "$RECALL" zebrafish --scope vault 2>&1)
|
|
79
|
+
assert_absent "scope=vault excludes knowledge" "$VONLY" "learned-patterns.md"
|
|
80
|
+
|
|
81
|
+
# ── Graceful degradation ──
|
|
82
|
+
NOVAULT=$(QUALIA_HOME="$HOME_DIR" QUALIA_MEMORY_ROOT="/tmp/qualia-no-such-vault-$$" QUALIA_ROLE=OWNER $NODE "$RECALL" zebrafish 2>&1); RC=$?
|
|
83
|
+
assert_exit "missing vault → still exit 0" 0 $RC
|
|
84
|
+
assert_contains "missing vault → knowledge still searched" "$NOVAULT" "learned-patterns.md"
|
|
85
|
+
ZERO=$(QUALIA_ROLE=OWNER RUN $NODE "$RECALL" "noyzzqxmatch" 2>&1); assert_exit "zero hits → exit 0" 0 $?
|
|
86
|
+
assert_contains "zero hits → friendly message" "$ZERO" "no matches"
|
|
87
|
+
|
|
88
|
+
rm -rf "$HOME_DIR" "$VAULT"
|
|
89
|
+
echo ""
|
|
90
|
+
echo "=== Results: $PASS passed, $FAIL failed ==="
|
|
91
|
+
[ "$FAIL" -eq 0 ]
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# repo-map.test.sh — bin/repo-map.js (zero-dep symbol map for /qualia-map)
|
|
3
|
+
# Run: bash tests/repo-map.test.sh
|
|
4
|
+
|
|
5
|
+
PASS=0
|
|
6
|
+
FAIL=0
|
|
7
|
+
BIN_DIR="$(cd "$(dirname "$0")/../bin" && pwd)"
|
|
8
|
+
NODE="${NODE:-node}"
|
|
9
|
+
RM="$BIN_DIR/repo-map.js"
|
|
10
|
+
|
|
11
|
+
assert_exit() { if [ "$2" = "$3" ]; then echo " ✓ $1"; PASS=$((PASS+1)); else echo " ✗ $1 (expected exit $2, got $3)"; FAIL=$((FAIL+1)); fi; }
|
|
12
|
+
assert_contains() { if echo "$2" | grep -qF "$3"; then echo " ✓ $1"; PASS=$((PASS+1)); else echo " ✗ $1 (missing '$3')"; FAIL=$((FAIL+1)); fi; }
|
|
13
|
+
assert_absent() { if echo "$2" | grep -qF "$3"; then echo " ✗ $1 (unexpected '$3')"; FAIL=$((FAIL+1)); else echo " ✓ $1"; PASS=$((PASS+1)); fi; }
|
|
14
|
+
|
|
15
|
+
echo "repo-map.test.sh — bin/repo-map.js"
|
|
16
|
+
echo ""
|
|
17
|
+
|
|
18
|
+
$NODE -c "$RM" 2>/dev/null && { echo " ✓ syntax valid"; PASS=$((PASS+1)); } || { echo " ✗ syntax invalid"; FAIL=$((FAIL+1)); }
|
|
19
|
+
|
|
20
|
+
# ── Fixture repo ──
|
|
21
|
+
REPO=$(mktemp -d)
|
|
22
|
+
mkdir -p "$REPO/src" "$REPO/node_modules/pkg" "$REPO/api"
|
|
23
|
+
cat > "$REPO/src/widget.ts" <<'EOF'
|
|
24
|
+
export function makeWidget() {}
|
|
25
|
+
export class WidgetStore {}
|
|
26
|
+
export const WIDGET_LIMIT = 5;
|
|
27
|
+
export type WidgetId = string;
|
|
28
|
+
function internalHelper() {}
|
|
29
|
+
EOF
|
|
30
|
+
cat > "$REPO/api/handler.py" <<'EOF'
|
|
31
|
+
class Handler:
|
|
32
|
+
pass
|
|
33
|
+
def serve():
|
|
34
|
+
pass
|
|
35
|
+
async def serve_async():
|
|
36
|
+
pass
|
|
37
|
+
EOF
|
|
38
|
+
cat > "$REPO/node_modules/pkg/index.js" <<'EOF'
|
|
39
|
+
export function shouldBeIgnored() {}
|
|
40
|
+
EOF
|
|
41
|
+
|
|
42
|
+
# ── Symbol extraction ──
|
|
43
|
+
OUT=$($NODE "$RM" "$REPO" 2>&1); assert_exit "valid dir → exit 0" 0 $?
|
|
44
|
+
assert_contains "extracts JS export function" "$OUT" "makeWidget"
|
|
45
|
+
assert_contains "extracts JS export class" "$OUT" "WidgetStore"
|
|
46
|
+
assert_contains "extracts JS export const" "$OUT" "WIDGET_LIMIT"
|
|
47
|
+
assert_contains "extracts JS export type" "$OUT" "WidgetId"
|
|
48
|
+
assert_contains "extracts Python def" "$OUT" "serve"
|
|
49
|
+
assert_contains "extracts Python class" "$OUT" "Handler"
|
|
50
|
+
assert_absent "ignores node_modules" "$OUT" "shouldBeIgnored"
|
|
51
|
+
|
|
52
|
+
# ── JSON shape ──
|
|
53
|
+
JSON=$($NODE "$RM" "$REPO" --json 2>&1)
|
|
54
|
+
echo "$JSON" | $NODE -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{const j=JSON.parse(s);if(typeof j.total_files==='number'&&typeof j.total_symbols==='number'&&Array.isArray(j.files)&&j.files[0].symbols)process.exit(0);process.exit(1)})" \
|
|
55
|
+
&& { echo " ✓ --json valid shape (total_files/total_symbols/files[].symbols)"; PASS=$((PASS+1)); } \
|
|
56
|
+
|| { echo " ✗ --json shape"; FAIL=$((FAIL+1)); }
|
|
57
|
+
# densest file first (widget.ts has 5 symbols, handler.py has 3)
|
|
58
|
+
FIRST=$(echo "$JSON" | $NODE -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{console.log(JSON.parse(s).files[0].file)})")
|
|
59
|
+
[ "$FIRST" = "src/widget.ts" ] && { echo " ✓ ranks densest file first"; PASS=$((PASS+1)); } || { echo " ✗ ranking (got '$FIRST')"; FAIL=$((FAIL+1)); }
|
|
60
|
+
|
|
61
|
+
# ── Edge cases ──
|
|
62
|
+
$NODE "$RM" /no/such/dir-xyz >/dev/null 2>&1; assert_exit "missing dir → exit 2" 2 $?
|
|
63
|
+
EMPTY=$(mktemp -d)
|
|
64
|
+
EOUT=$($NODE "$RM" "$EMPTY" 2>&1); assert_exit "empty dir → exit 0" 0 $?
|
|
65
|
+
assert_contains "empty dir → 0 source files" "$EOUT" "0 source files"
|
|
66
|
+
|
|
67
|
+
rm -rf "$REPO" "$EMPTY"
|
|
68
|
+
echo ""
|
|
69
|
+
echo "=== Results: $PASS passed, $FAIL failed ==="
|
|
70
|
+
[ "$FAIL" -eq 0 ]
|
package/tests/run-all.sh
CHANGED
|
@@ -20,6 +20,18 @@ SUITES=(
|
|
|
20
20
|
"slop-detect"
|
|
21
21
|
"agent-status"
|
|
22
22
|
"analyze-gate"
|
|
23
|
+
"instructions"
|
|
24
|
+
"verify-panel"
|
|
25
|
+
"wave-plan"
|
|
26
|
+
"eval-runner"
|
|
27
|
+
"branch-hygiene"
|
|
28
|
+
"last-report"
|
|
29
|
+
"project-sync"
|
|
30
|
+
"erp-event"
|
|
31
|
+
"recall"
|
|
32
|
+
"repo-map"
|
|
33
|
+
"design-tokens"
|
|
34
|
+
"batch-plan"
|
|
23
35
|
)
|
|
24
36
|
|
|
25
37
|
FAILED=()
|