qualia-framework 6.1.0 → 6.2.9
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/README.md +39 -26
- package/agents/roadmapper.md +1 -1
- package/bin/cli.js +339 -200
- package/bin/codex-goal.js +92 -0
- package/bin/erp-retry.js +11 -3
- package/bin/install.js +483 -55
- package/bin/knowledge-flush.js +25 -13
- package/bin/knowledge.js +11 -1
- package/bin/project-snapshot.js +293 -0
- package/bin/qualia-ui.js +13 -2
- package/bin/report-payload.js +137 -0
- package/bin/state.js +8 -1
- package/bin/statusline.js +14 -2
- package/docs/changelog-v6.html +864 -0
- package/docs/ecosystem-operating-model.md +121 -0
- package/docs/erp-contract.md +74 -21
- package/docs/onboarding.html +1 -1
- package/docs/release.md +44 -0
- package/docs/reviews/v6.2.1-revival-audit.md +53 -0
- package/docs/reviews/v6.2.2-memory-erp-audit.md +41 -0
- package/docs/reviews/v6.2.3-erp-id-guard.md +15 -0
- package/guide.md +16 -4
- package/hooks/auto-update.js +14 -7
- package/hooks/branch-guard.js +10 -2
- package/hooks/env-empty-guard.js +10 -1
- package/hooks/git-guardrails.js +10 -1
- package/hooks/migration-guard.js +4 -1
- package/hooks/pre-deploy-gate.js +38 -1
- package/hooks/pre-push.js +56 -157
- package/hooks/session-start.js +22 -14
- package/hooks/stop-session-log.js +11 -3
- package/hooks/supabase-destructive-guard.js +11 -1
- package/hooks/vercel-account-guard.js +12 -3
- package/package.json +3 -2
- package/rules/codex-goal.md +46 -0
- package/skills/qualia-build/SKILL.md +4 -0
- package/skills/qualia-feature/SKILL.md +4 -0
- package/skills/qualia-map/SKILL.md +1 -1
- package/skills/qualia-milestone/SKILL.md +1 -1
- package/skills/qualia-optimize/SKILL.md +1 -1
- package/skills/qualia-plan/SKILL.md +4 -0
- package/skills/qualia-polish/SKILL.md +2 -2
- package/skills/qualia-report/SKILL.md +6 -43
- package/skills/qualia-road/SKILL.md +1 -1
- package/skills/qualia-verify/SKILL.md +1 -1
- package/templates/help.html +1 -1
- package/templates/knowledge/agents.md +3 -3
- package/templates/knowledge/index.md +1 -1
- package/templates/tracking.json +3 -0
- package/templates/work-packet.md +46 -0
- package/tests/bin.test.sh +411 -13
- package/tests/hooks.test.sh +1 -8
- package/tests/install-smoke.test.sh +137 -0
- package/tests/published-install-smoke.test.sh +126 -0
- package/tests/refs.test.sh +42 -0
- package/tests/run-all.sh +1 -0
- package/tests/runner.js +19 -33
- package/tests/state.test.sh +4 -1
- package/hooks/pre-compact.js +0 -127
package/tests/bin.test.sh
CHANGED
|
@@ -454,6 +454,7 @@ if [ "$EXIT" -eq 0 ] \
|
|
|
454
454
|
&& [ -f "$TMP/.claude/bin/state.js" ] \
|
|
455
455
|
&& [ -f "$TMP/.claude/bin/qualia-ui.js" ] \
|
|
456
456
|
&& [ -f "$TMP/.claude/bin/statusline.js" ] \
|
|
457
|
+
&& [ -f "$TMP/.claude/bin/project-snapshot.js" ] \
|
|
457
458
|
&& [ -f "$TMP/.claude/.qualia-config.json" ]; then
|
|
458
459
|
pass "QS-FAWZI-01 → installs skills, hooks, bin/, config"
|
|
459
460
|
else
|
|
@@ -477,12 +478,13 @@ else
|
|
|
477
478
|
fail_case "CLAUDE.md role substitution"
|
|
478
479
|
fi
|
|
479
480
|
|
|
480
|
-
# 31. All
|
|
481
|
+
# 31. All 11 hooks installed (block-env-edit removed in v3.2.0;
|
|
481
482
|
# git-guardrails + stop-session-log added in v4.2.0;
|
|
482
|
-
# vercel-account-guard + env-empty-guard + supabase-destructive-guard added in v5.0.0
|
|
483
|
+
# vercel-account-guard + env-empty-guard + supabase-destructive-guard added in v5.0.0;
|
|
484
|
+
# pre-compact removed in v6.2.0)
|
|
483
485
|
HOOK_COUNT=$(ls "$TMP/.claude/hooks/"*.js 2>/dev/null | wc -l)
|
|
484
|
-
if [ "$HOOK_COUNT" -eq
|
|
485
|
-
pass "
|
|
486
|
+
if [ "$HOOK_COUNT" -eq 11 ]; then
|
|
487
|
+
pass "11 hooks installed in hooks/"
|
|
486
488
|
else
|
|
487
489
|
fail_case "hook count" "got $HOOK_COUNT"
|
|
488
490
|
fi
|
|
@@ -498,22 +500,23 @@ else
|
|
|
498
500
|
fail_case "settings.json contents"
|
|
499
501
|
fi
|
|
500
502
|
|
|
501
|
-
# 33. settings.json contains all
|
|
503
|
+
# 33. settings.json contains all 11 hooks wired correctly
|
|
504
|
+
# pre-compact.js was removed in v6.2.0 — verify it's NOT in settings.json.
|
|
502
505
|
if grep -q 'branch-guard.js' "$TMP/.claude/settings.json" \
|
|
503
506
|
&& grep -q 'migration-guard.js' "$TMP/.claude/settings.json" \
|
|
504
507
|
&& grep -q 'pre-push.js' "$TMP/.claude/settings.json" \
|
|
505
508
|
&& grep -q 'pre-deploy-gate.js' "$TMP/.claude/settings.json" \
|
|
506
509
|
&& grep -q 'auto-update.js' "$TMP/.claude/settings.json" \
|
|
507
510
|
&& grep -q 'session-start.js' "$TMP/.claude/settings.json" \
|
|
508
|
-
&& grep -q 'pre-compact.js' "$TMP/.claude/settings.json" \
|
|
509
511
|
&& grep -q 'git-guardrails.js' "$TMP/.claude/settings.json" \
|
|
510
512
|
&& grep -q 'stop-session-log.js' "$TMP/.claude/settings.json" \
|
|
511
513
|
&& grep -q 'vercel-account-guard.js' "$TMP/.claude/settings.json" \
|
|
512
514
|
&& grep -q 'env-empty-guard.js' "$TMP/.claude/settings.json" \
|
|
513
|
-
&& grep -q 'supabase-destructive-guard.js' "$TMP/.claude/settings.json"
|
|
514
|
-
|
|
515
|
+
&& grep -q 'supabase-destructive-guard.js' "$TMP/.claude/settings.json" \
|
|
516
|
+
&& ! grep -q 'pre-compact.js' "$TMP/.claude/settings.json"; then
|
|
517
|
+
pass "settings.json has all 11 hooks wired (no pre-compact)"
|
|
515
518
|
else
|
|
516
|
-
fail_case "settings.json
|
|
519
|
+
fail_case "settings.json hooks misconfigured (check for stale pre-compact entry)"
|
|
517
520
|
fi
|
|
518
521
|
|
|
519
522
|
# 34. Lowercase code works (resolveTeamCode normalizes)
|
|
@@ -1340,20 +1343,49 @@ else
|
|
|
1340
1343
|
fail_case "target=1 Claude-only" "exit=$EXIT codex_exists=$(test -d "$TMP/.codex" && echo yes || echo no)"
|
|
1341
1344
|
fi
|
|
1342
1345
|
|
|
1343
|
-
# 119. Target=2 (Codex only) writes
|
|
1346
|
+
# 119. Target=2 (Codex only) writes native Codex runtime files, skips ~/.claude/
|
|
1344
1347
|
TMP=$(mktmp)
|
|
1345
1348
|
printf 'QS-FAWZI-01\n2\n' | HOME="$TMP" $NODE "$INSTALL_JS" > "$TMP/log.txt" 2>&1
|
|
1346
1349
|
EXIT=$?
|
|
1347
1350
|
if [ "$EXIT" -eq 0 ] \
|
|
1348
1351
|
&& [ -f "$TMP/.codex/AGENTS.md" ] \
|
|
1352
|
+
&& [ -f "$TMP/.codex/.qualia-config.json" ] \
|
|
1353
|
+
&& [ -f "$TMP/.codex/config.toml" ] \
|
|
1354
|
+
&& [ -f "$TMP/.codex/hooks.json" ] \
|
|
1355
|
+
&& [ -f "$TMP/.codex/bin/statusline.js" ] \
|
|
1356
|
+
&& [ -f "$TMP/.codex/bin/project-snapshot.js" ] \
|
|
1357
|
+
&& [ -f "$TMP/.codex/agents/planner.toml" ] \
|
|
1358
|
+
&& [ -f "$TMP/.codex/skills/qualia-new/SKILL.md" ] \
|
|
1359
|
+
&& [ -f "$TMP/.codex/qualia-references/questioning.md" ] \
|
|
1349
1360
|
&& [ ! -d "$TMP/.claude" ] \
|
|
1350
1361
|
&& grep -q "Role: OWNER" "$TMP/.codex/AGENTS.md" \
|
|
1351
|
-
&& ! grep -q "{{ROLE}}" "$TMP/.codex/AGENTS.md"
|
|
1352
|
-
|
|
1362
|
+
&& ! grep -q "{{ROLE}}" "$TMP/.codex/AGENTS.md" \
|
|
1363
|
+
&& grep -q "pre-deploy-gate.js" "$TMP/.codex/hooks.json" \
|
|
1364
|
+
&& grep -q "developer_instructions" "$TMP/.codex/agents/planner.toml" \
|
|
1365
|
+
&& ! grep -R "\.claude/bin" "$TMP/.codex/skills" >/dev/null 2>&1; then
|
|
1366
|
+
pass "target=2 → Codex runtime files with Role: OWNER, ~/.claude/ skipped"
|
|
1353
1367
|
else
|
|
1354
1368
|
fail_case "target=2 Codex-only" "exit=$EXIT claude_exists=$(test -d "$TMP/.claude" && echo yes || echo no)"
|
|
1355
1369
|
fi
|
|
1356
1370
|
|
|
1371
|
+
EXIT=0; HOME="$TMP" $NODE "$CLI_JS" doctor > "$TMP/doctor.log" 2>&1 || EXIT=$?
|
|
1372
|
+
if [ "$EXIT" -eq 0 ] \
|
|
1373
|
+
&& grep -q "Codex hooks.json PreToolUse" "$TMP/doctor.log" \
|
|
1374
|
+
&& grep -q "All checks passed" "$TMP/doctor.log"; then
|
|
1375
|
+
pass "doctor passes for Codex-only install"
|
|
1376
|
+
else
|
|
1377
|
+
fail_case "doctor Codex-only" "exit=$EXIT log=$(tail -20 "$TMP/doctor.log" 2>/dev/null)"
|
|
1378
|
+
fi
|
|
1379
|
+
|
|
1380
|
+
EXIT=0; HOME="$TMP" $NODE "$CLI_JS" migrate > "$TMP/migrate.log" 2>&1 || EXIT=$?
|
|
1381
|
+
if [ "$EXIT" -eq 0 ] \
|
|
1382
|
+
&& grep -q "Codex install uses" "$TMP/migrate.log" \
|
|
1383
|
+
&& grep -q "hooks.json" "$TMP/migrate.log"; then
|
|
1384
|
+
pass "migrate is Codex-aware on Codex-only install"
|
|
1385
|
+
else
|
|
1386
|
+
fail_case "migrate Codex-only" "exit=$EXIT log=$(cat "$TMP/migrate.log" 2>/dev/null)"
|
|
1387
|
+
fi
|
|
1388
|
+
|
|
1357
1389
|
# 120. Target=3 (Both) populates both directories with the right artifacts
|
|
1358
1390
|
TMP=$(mktmp)
|
|
1359
1391
|
printf 'QS-FAWZI-01\n3\n' | HOME="$TMP" $NODE "$INSTALL_JS" > "$TMP/log.txt" 2>&1
|
|
@@ -1361,8 +1393,13 @@ EXIT=$?
|
|
|
1361
1393
|
if [ "$EXIT" -eq 0 ] \
|
|
1362
1394
|
&& [ -f "$TMP/.claude/.qualia-config.json" ] \
|
|
1363
1395
|
&& [ -f "$TMP/.codex/AGENTS.md" ] \
|
|
1396
|
+
&& [ -f "$TMP/.codex/config.toml" ] \
|
|
1397
|
+
&& [ -f "$TMP/.codex/hooks.json" ] \
|
|
1398
|
+
&& [ -f "$TMP/.codex/bin/knowledge.js" ] \
|
|
1399
|
+
&& [ -f "$TMP/.codex/agents/builder.toml" ] \
|
|
1400
|
+
&& [ -f "$TMP/.codex/qualia-references/questioning.md" ] \
|
|
1364
1401
|
&& grep -q "Role: OWNER" "$TMP/.codex/AGENTS.md"; then
|
|
1365
|
-
pass "target=3 → both ~/.claude/ and
|
|
1402
|
+
pass "target=3 → both ~/.claude/ and Codex runtime populated"
|
|
1366
1403
|
else
|
|
1367
1404
|
fail_case "target=3 Both" "exit=$EXIT"
|
|
1368
1405
|
fi
|
|
@@ -1410,6 +1447,80 @@ else
|
|
|
1410
1447
|
fail_case "Codex backup over-zealous" "bak_count=$BAK_COUNT"
|
|
1411
1448
|
fi
|
|
1412
1449
|
|
|
1450
|
+
# 123b. Codex hooks.json merge preserves non-Qualia hook commands.
|
|
1451
|
+
TMP=$(mktmp)
|
|
1452
|
+
mkdir -p "$TMP/.codex"
|
|
1453
|
+
cat > "$TMP/.codex/hooks.json" <<'JSON'
|
|
1454
|
+
{
|
|
1455
|
+
"hooks": {
|
|
1456
|
+
"SessionStart": [
|
|
1457
|
+
{
|
|
1458
|
+
"matcher": ".*",
|
|
1459
|
+
"hooks": [
|
|
1460
|
+
{ "type": "command", "command": "node /tmp/custom-session-hook.js", "timeout": 1 }
|
|
1461
|
+
]
|
|
1462
|
+
}
|
|
1463
|
+
]
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
JSON
|
|
1467
|
+
printf 'QS-FAWZI-01\n2\n' | HOME="$TMP" $NODE "$INSTALL_JS" > "$TMP/log.txt" 2>&1
|
|
1468
|
+
if grep -q "custom-session-hook.js" "$TMP/.codex/hooks.json" \
|
|
1469
|
+
&& grep -q "session-start.js" "$TMP/.codex/hooks.json"; then
|
|
1470
|
+
pass "Codex hooks.json merge preserves non-Qualia hooks"
|
|
1471
|
+
else
|
|
1472
|
+
fail_case "Codex hooks.json merge dropped custom hooks"
|
|
1473
|
+
fi
|
|
1474
|
+
|
|
1475
|
+
# 123c. Codex uninstall removes Qualia runtime files while preserving user-owned
|
|
1476
|
+
# AGENTS.md, config.toml, knowledge, and non-Qualia hooks.json entries.
|
|
1477
|
+
TMP=$(mktmp)
|
|
1478
|
+
mkdir -p "$TMP/.codex"
|
|
1479
|
+
cat > "$TMP/.codex/hooks.json" <<'JSON'
|
|
1480
|
+
{
|
|
1481
|
+
"hooks": {
|
|
1482
|
+
"SessionStart": [
|
|
1483
|
+
{
|
|
1484
|
+
"matcher": ".*",
|
|
1485
|
+
"hooks": [
|
|
1486
|
+
{ "type": "command", "command": "node /tmp/custom-session-hook.js", "timeout": 1 }
|
|
1487
|
+
]
|
|
1488
|
+
}
|
|
1489
|
+
]
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
JSON
|
|
1493
|
+
printf 'QS-FAWZI-01\n2\n' | HOME="$TMP" $NODE "$INSTALL_JS" > "$TMP/log.txt" 2>&1
|
|
1494
|
+
EXIT=0; HOME="$TMP" $NODE "$CLI_JS" uninstall --yes > "$TMP/uninstall.log" 2>&1 || EXIT=$?
|
|
1495
|
+
if [ "$EXIT" -eq 0 ] \
|
|
1496
|
+
&& [ -f "$TMP/.codex/AGENTS.md" ] \
|
|
1497
|
+
&& [ -f "$TMP/.codex/config.toml" ] \
|
|
1498
|
+
&& [ -d "$TMP/.codex/knowledge" ] \
|
|
1499
|
+
&& [ ! -f "$TMP/.codex/.qualia-config.json" ] \
|
|
1500
|
+
&& [ ! -f "$TMP/.codex/bin/statusline.js" ] \
|
|
1501
|
+
&& [ ! -f "$TMP/.codex/hooks/session-start.js" ] \
|
|
1502
|
+
&& [ ! -f "$TMP/.codex/agents/planner.toml" ] \
|
|
1503
|
+
&& [ ! -d "$TMP/.codex/skills/qualia-new" ] \
|
|
1504
|
+
&& grep -q "custom-session-hook.js" "$TMP/.codex/hooks.json" \
|
|
1505
|
+
&& ! grep -q "session-start.js" "$TMP/.codex/hooks.json"; then
|
|
1506
|
+
pass "Codex uninstall removes Qualia runtime and preserves user-owned files"
|
|
1507
|
+
else
|
|
1508
|
+
fail_case "Codex uninstall cleanup" "exit=$EXIT log=$(tail -20 "$TMP/uninstall.log" 2>/dev/null)"
|
|
1509
|
+
fi
|
|
1510
|
+
|
|
1511
|
+
# 123d. Team management writes to Codex-only installs without creating ~/.claude.
|
|
1512
|
+
TMP=$(mktmp)
|
|
1513
|
+
printf 'QS-FAWZI-01\n2\n' | HOME="$TMP" $NODE "$INSTALL_JS" > "$TMP/log.txt" 2>&1
|
|
1514
|
+
EXIT=0; HOME="$TMP" $NODE "$CLI_JS" team add --code QS-TEST-99 --name "Test User" > "$TMP/team.log" 2>&1 || EXIT=$?
|
|
1515
|
+
if [ "$EXIT" -eq 0 ] \
|
|
1516
|
+
&& [ -f "$TMP/.codex/.qualia-team.json" ] \
|
|
1517
|
+
&& [ ! -d "$TMP/.claude" ] \
|
|
1518
|
+
&& grep -q "QS-TEST-99" "$TMP/.codex/.qualia-team.json"; then
|
|
1519
|
+
pass "team add writes to Codex-only install home"
|
|
1520
|
+
else
|
|
1521
|
+
fail_case "Codex team management" "exit=$EXIT log=$(cat "$TMP/team.log" 2>/dev/null)"
|
|
1522
|
+
fi
|
|
1523
|
+
|
|
1413
1524
|
# 124. Non-TTY install log is free of cursor-control / line-clear escape
|
|
1414
1525
|
# sequences (\r, \x1b[?25l/h hide-cursor, \x1b[2K clear-line). Spinner +
|
|
1415
1526
|
# overwrite primitives must degrade cleanly when output is piped.
|
|
@@ -1643,6 +1754,293 @@ else
|
|
|
1643
1754
|
fail_case "CONTEXT template missing framework-task vs ERP-workflow distinction"
|
|
1644
1755
|
fi
|
|
1645
1756
|
|
|
1757
|
+
# 147. report-payload omits slug-like ERP UUID fields before POST
|
|
1758
|
+
TMP_PAYLOAD=$(mktmp)
|
|
1759
|
+
OUT=$(FRAMEWORK_DIR="$FRAMEWORK_DIR" TMP_PAYLOAD="$TMP_PAYLOAD" $NODE <<'NODE' 2>&1
|
|
1760
|
+
const assert = require("assert/strict");
|
|
1761
|
+
const fs = require("fs");
|
|
1762
|
+
const path = require("path");
|
|
1763
|
+
const { buildPayload } = require(path.join(process.env.FRAMEWORK_DIR, "bin", "report-payload.js"));
|
|
1764
|
+
|
|
1765
|
+
const root = process.env.TMP_PAYLOAD;
|
|
1766
|
+
const cwd = path.join(root, "project");
|
|
1767
|
+
const home = path.join(root, "home");
|
|
1768
|
+
fs.mkdirSync(path.join(cwd, ".planning"), { recursive: true });
|
|
1769
|
+
fs.mkdirSync(path.join(home, ".claude"), { recursive: true });
|
|
1770
|
+
fs.writeFileSync(path.join(home, ".claude", ".qualia-config.json"), JSON.stringify({ version: "test-version" }));
|
|
1771
|
+
fs.writeFileSync(path.join(cwd, ".planning", "tracking.json"), JSON.stringify({
|
|
1772
|
+
project: "acme-portal",
|
|
1773
|
+
project_id: "qs-acme-portal",
|
|
1774
|
+
team_id: "qualia-solutions",
|
|
1775
|
+
git_remote: "github.com/QualiasolutionsCY/acme-portal",
|
|
1776
|
+
erp_project_id: "project-slug",
|
|
1777
|
+
client_id: "acme",
|
|
1778
|
+
workspace_id: "qualia-solutions",
|
|
1779
|
+
phase: 2,
|
|
1780
|
+
phase_name: "ERP reporting",
|
|
1781
|
+
total_phases: 4,
|
|
1782
|
+
status: "built",
|
|
1783
|
+
gap_cycles: { "2": 1 },
|
|
1784
|
+
session_started_at: "2026-05-21T00:00:00Z"
|
|
1785
|
+
}));
|
|
1786
|
+
const reportFile = path.join(root, "report.md");
|
|
1787
|
+
fs.writeFileSync(reportFile, "Shift notes");
|
|
1788
|
+
|
|
1789
|
+
const payload = buildPayload({
|
|
1790
|
+
cwd,
|
|
1791
|
+
home,
|
|
1792
|
+
reportFile,
|
|
1793
|
+
env: {
|
|
1794
|
+
CLIENT_REPORT_ID: "QS-REPORT-01",
|
|
1795
|
+
SUBMITTED_BY: "Fawzi Goussous",
|
|
1796
|
+
SUBMITTED_AT: "2026-05-21T01:30:00Z",
|
|
1797
|
+
},
|
|
1798
|
+
});
|
|
1799
|
+
assert.equal(payload.project_id, "qs-acme-portal");
|
|
1800
|
+
assert.equal(payload.team_id, "qualia-solutions");
|
|
1801
|
+
assert.equal(payload.framework_version, "test-version");
|
|
1802
|
+
assert.equal(payload.gap_cycles, 1);
|
|
1803
|
+
assert.equal(payload.session_duration_minutes, 90);
|
|
1804
|
+
assert.equal(payload.notes, "Shift notes");
|
|
1805
|
+
assert.equal(Object.prototype.hasOwnProperty.call(payload, "erp_project_id"), false);
|
|
1806
|
+
assert.equal(Object.prototype.hasOwnProperty.call(payload, "client_id"), false);
|
|
1807
|
+
assert.equal(Object.prototype.hasOwnProperty.call(payload, "workspace_id"), false);
|
|
1808
|
+
NODE
|
|
1809
|
+
)
|
|
1810
|
+
EXIT=$?
|
|
1811
|
+
if [ "$EXIT" -eq 0 ]; then
|
|
1812
|
+
pass "report-payload omits slug-like ERP UUID fields"
|
|
1813
|
+
else
|
|
1814
|
+
fail_case "report-payload slug guard failed" "$OUT"
|
|
1815
|
+
fi
|
|
1816
|
+
|
|
1817
|
+
# 148. report-payload sends canonical ERP UUID fields when present
|
|
1818
|
+
TMP_PAYLOAD=$(mktmp)
|
|
1819
|
+
OUT=$(FRAMEWORK_DIR="$FRAMEWORK_DIR" TMP_PAYLOAD="$TMP_PAYLOAD" $NODE <<'NODE' 2>&1
|
|
1820
|
+
const assert = require("assert/strict");
|
|
1821
|
+
const fs = require("fs");
|
|
1822
|
+
const path = require("path");
|
|
1823
|
+
const { buildPayload } = require(path.join(process.env.FRAMEWORK_DIR, "bin", "report-payload.js"));
|
|
1824
|
+
|
|
1825
|
+
const root = process.env.TMP_PAYLOAD;
|
|
1826
|
+
const cwd = path.join(root, "project");
|
|
1827
|
+
const home = path.join(root, "home");
|
|
1828
|
+
fs.mkdirSync(path.join(cwd, ".planning"), { recursive: true });
|
|
1829
|
+
fs.mkdirSync(path.join(home, ".claude"), { recursive: true });
|
|
1830
|
+
fs.writeFileSync(path.join(home, ".claude", ".qualia-config.json"), JSON.stringify({ version: "test-version" }));
|
|
1831
|
+
fs.writeFileSync(path.join(cwd, ".planning", "tracking.json"), JSON.stringify({
|
|
1832
|
+
project: "acme-portal",
|
|
1833
|
+
project_id: "qs-acme-portal",
|
|
1834
|
+
team_id: "qualia-solutions",
|
|
1835
|
+
erp_project_id: "7b5d3b4e-2b8a-4de4-91a1-9b2f3182f5ef",
|
|
1836
|
+
client_id: "5f5a8d8e-8c58-4c30-9b76-13a08f0d0d8a",
|
|
1837
|
+
workspace_id: "2af02a2d-6f1f-4d43-a6cb-6a1e7e09ac43",
|
|
1838
|
+
phase: 1,
|
|
1839
|
+
status: "verified"
|
|
1840
|
+
}));
|
|
1841
|
+
const payload = buildPayload({
|
|
1842
|
+
cwd,
|
|
1843
|
+
home,
|
|
1844
|
+
reportFile: path.join(root, "missing-report.md"),
|
|
1845
|
+
env: {
|
|
1846
|
+
CLIENT_REPORT_ID: "QS-REPORT-02",
|
|
1847
|
+
SUBMITTED_BY: "Fawzi Goussous",
|
|
1848
|
+
SUBMITTED_AT: "2026-05-21T01:30:00Z",
|
|
1849
|
+
},
|
|
1850
|
+
});
|
|
1851
|
+
assert.equal(payload.erp_project_id, "7b5d3b4e-2b8a-4de4-91a1-9b2f3182f5ef");
|
|
1852
|
+
assert.equal(payload.client_id, "5f5a8d8e-8c58-4c30-9b76-13a08f0d0d8a");
|
|
1853
|
+
assert.equal(payload.workspace_id, "2af02a2d-6f1f-4d43-a6cb-6a1e7e09ac43");
|
|
1854
|
+
assert.equal(payload.client_report_id, "QS-REPORT-02");
|
|
1855
|
+
assert.equal(payload.notes, "");
|
|
1856
|
+
NODE
|
|
1857
|
+
)
|
|
1858
|
+
EXIT=$?
|
|
1859
|
+
if [ "$EXIT" -eq 0 ]; then
|
|
1860
|
+
pass "report-payload sends canonical ERP UUID fields"
|
|
1861
|
+
else
|
|
1862
|
+
fail_case "report-payload UUID pass-through failed" "$OUT"
|
|
1863
|
+
fi
|
|
1864
|
+
|
|
1865
|
+
# 149. Installer ships the payload builder used by /qualia-report
|
|
1866
|
+
TMP_INSTALL=$(mktmp)
|
|
1867
|
+
echo "QS-FAWZI-01" | HOME="$TMP_INSTALL" $NODE "$INSTALL_JS" > "$TMP_INSTALL/out.log" 2>&1
|
|
1868
|
+
EXIT=$?
|
|
1869
|
+
if [ "$EXIT" -eq 0 ] \
|
|
1870
|
+
&& [ -f "$TMP_INSTALL/.claude/bin/report-payload.js" ] \
|
|
1871
|
+
&& [ -f "$TMP_INSTALL/.claude/bin/project-snapshot.js" ] \
|
|
1872
|
+
&& grep -q "report-payload.js" "$TMP_INSTALL/.claude/skills/qualia-report/SKILL.md"; then
|
|
1873
|
+
pass "installer ships ERP report/snapshot helpers"
|
|
1874
|
+
else
|
|
1875
|
+
fail_case "installer missing ERP report/snapshot helpers" "exit=$EXIT"
|
|
1876
|
+
fi
|
|
1877
|
+
|
|
1878
|
+
# 150. project-snapshot exports a 0-to-100 admin progress object
|
|
1879
|
+
TMP_SNAPSHOT=$(mktmp)
|
|
1880
|
+
OUT=$(FRAMEWORK_DIR="$FRAMEWORK_DIR" TMP_SNAPSHOT="$TMP_SNAPSHOT" $NODE <<'NODE' 2>&1
|
|
1881
|
+
const assert = require("assert/strict");
|
|
1882
|
+
const fs = require("fs");
|
|
1883
|
+
const path = require("path");
|
|
1884
|
+
const { buildSnapshot, writeSnapshot } = require(path.join(process.env.FRAMEWORK_DIR, "bin", "project-snapshot.js"));
|
|
1885
|
+
|
|
1886
|
+
const root = process.env.TMP_SNAPSHOT;
|
|
1887
|
+
const cwd = path.join(root, "project");
|
|
1888
|
+
const home = path.join(root, "home");
|
|
1889
|
+
fs.mkdirSync(path.join(cwd, ".planning"), { recursive: true });
|
|
1890
|
+
fs.mkdirSync(path.join(home, ".claude"), { recursive: true });
|
|
1891
|
+
fs.writeFileSync(path.join(home, ".claude", ".qualia-config.json"), JSON.stringify({ version: "test-version" }));
|
|
1892
|
+
fs.writeFileSync(path.join(cwd, ".planning", "JOURNEY.md"), [
|
|
1893
|
+
"# Journey",
|
|
1894
|
+
"## Milestone 1 · Foundation",
|
|
1895
|
+
"## Milestone 2 · Product",
|
|
1896
|
+
"## Milestone 3 · Handoff",
|
|
1897
|
+
].join("\n"));
|
|
1898
|
+
fs.writeFileSync(path.join(cwd, ".planning", "tracking.json"), JSON.stringify({
|
|
1899
|
+
project: "acme-portal",
|
|
1900
|
+
project_id: "qs-acme-portal",
|
|
1901
|
+
erp_project_id: "7b5d3b4e-2b8a-4de4-91a1-9b2f3182f5ef",
|
|
1902
|
+
client_id: "5f5a8d8e-8c58-4c30-9b76-13a08f0d0d8a",
|
|
1903
|
+
team_id: "qualia-solutions",
|
|
1904
|
+
git_remote: "github.com/QualiasolutionsCY/acme-portal",
|
|
1905
|
+
client: "Acme",
|
|
1906
|
+
milestone: 2,
|
|
1907
|
+
milestone_name: "Product",
|
|
1908
|
+
phase: 2,
|
|
1909
|
+
phase_name: "Dashboard",
|
|
1910
|
+
total_phases: 4,
|
|
1911
|
+
status: "built",
|
|
1912
|
+
tasks_done: 3,
|
|
1913
|
+
tasks_total: 5,
|
|
1914
|
+
verification: "pending",
|
|
1915
|
+
gap_cycles: { "2": 1 },
|
|
1916
|
+
build_count: 4,
|
|
1917
|
+
deploy_count: 1,
|
|
1918
|
+
milestones: [{ num: 1, name: "Foundation", closed_at: "2026-05-01T00:00:00Z" }],
|
|
1919
|
+
lifetime: {
|
|
1920
|
+
tasks_completed: 12,
|
|
1921
|
+
phases_completed: 4,
|
|
1922
|
+
milestones_completed: 1,
|
|
1923
|
+
total_phases: 4,
|
|
1924
|
+
last_closed_milestone: 1
|
|
1925
|
+
}
|
|
1926
|
+
}));
|
|
1927
|
+
|
|
1928
|
+
const snapshot = buildSnapshot({ cwd, home, now: "2026-05-21T00:00:00.000Z" });
|
|
1929
|
+
assert.equal(snapshot.snapshot_version, 1);
|
|
1930
|
+
assert.equal(snapshot.framework_version, "test-version");
|
|
1931
|
+
assert.equal(snapshot.identifiers.project_id, "qs-acme-portal");
|
|
1932
|
+
assert.equal(snapshot.identifiers.erp_project_id, "7b5d3b4e-2b8a-4de4-91a1-9b2f3182f5ef");
|
|
1933
|
+
assert.equal(snapshot.project.progress_percent, 42);
|
|
1934
|
+
assert.equal(snapshot.current.gap_cycles, 1);
|
|
1935
|
+
assert.equal(snapshot.journey.total_milestones, 3);
|
|
1936
|
+
assert.deepEqual(snapshot.journey.milestones.map((m) => m.status), ["closed", "active", "pending"]);
|
|
1937
|
+
assert.equal(snapshot.lifetime.tasks_completed, 12);
|
|
1938
|
+
|
|
1939
|
+
const file = writeSnapshot(snapshot, { cwd });
|
|
1940
|
+
assert.ok(file.endsWith(".planning/snapshots/project-snapshot-2026-05-21T00-00-00-000Z.json"));
|
|
1941
|
+
assert.equal(JSON.parse(fs.readFileSync(file, "utf8")).project.progress_percent, 42);
|
|
1942
|
+
NODE
|
|
1943
|
+
)
|
|
1944
|
+
EXIT=$?
|
|
1945
|
+
if [ "$EXIT" -eq 0 ]; then
|
|
1946
|
+
pass "project-snapshot exports ERP/admin progress object"
|
|
1947
|
+
else
|
|
1948
|
+
fail_case "project-snapshot progress export failed" "$OUT"
|
|
1949
|
+
fi
|
|
1950
|
+
|
|
1951
|
+
# 151. project-snapshot uploads the progress object to ERP intake
|
|
1952
|
+
TMP_SNAPSHOT_UPLOAD=$(mktmp)
|
|
1953
|
+
OUT=$(FRAMEWORK_DIR="$FRAMEWORK_DIR" TMP_SNAPSHOT_UPLOAD="$TMP_SNAPSHOT_UPLOAD" $NODE <<'NODE' 2>&1
|
|
1954
|
+
const assert = require("assert/strict");
|
|
1955
|
+
const { EventEmitter } = require("events");
|
|
1956
|
+
const fs = require("fs");
|
|
1957
|
+
const http = require("http");
|
|
1958
|
+
const path = require("path");
|
|
1959
|
+
const { buildSnapshot, uploadSnapshot } = require(path.join(process.env.FRAMEWORK_DIR, "bin", "project-snapshot.js"));
|
|
1960
|
+
|
|
1961
|
+
const root = process.env.TMP_SNAPSHOT_UPLOAD;
|
|
1962
|
+
const cwd = path.join(root, "project");
|
|
1963
|
+
const home = path.join(root, "home");
|
|
1964
|
+
fs.mkdirSync(path.join(cwd, ".planning"), { recursive: true });
|
|
1965
|
+
fs.mkdirSync(path.join(home, ".claude"), { recursive: true });
|
|
1966
|
+
fs.writeFileSync(path.join(home, ".claude", ".qualia-config.json"), JSON.stringify({ version: "test-version" }));
|
|
1967
|
+
fs.writeFileSync(path.join(cwd, ".planning", "JOURNEY.md"), [
|
|
1968
|
+
"# Journey",
|
|
1969
|
+
"## Milestone 1 · Foundation",
|
|
1970
|
+
"## Milestone 2 · Product",
|
|
1971
|
+
"## Milestone 3 · Handoff",
|
|
1972
|
+
].join("\n"));
|
|
1973
|
+
fs.writeFileSync(path.join(cwd, ".planning", "tracking.json"), JSON.stringify({
|
|
1974
|
+
project: "acme-portal",
|
|
1975
|
+
project_id: "qs-acme-portal",
|
|
1976
|
+
erp_project_id: "7b5d3b4e-2b8a-4de4-91a1-9b2f3182f5ef",
|
|
1977
|
+
client_id: "5f5a8d8e-8c58-4c30-9b76-13a08f0d0d8a",
|
|
1978
|
+
milestone: 2,
|
|
1979
|
+
phase: 2,
|
|
1980
|
+
total_phases: 4,
|
|
1981
|
+
milestones: [{ num: 1, name: "Foundation" }]
|
|
1982
|
+
}));
|
|
1983
|
+
|
|
1984
|
+
let seen = null;
|
|
1985
|
+
const originalRequest = http.request;
|
|
1986
|
+
http.request = (endpoint, options, callback) => {
|
|
1987
|
+
let raw = "";
|
|
1988
|
+
const req = new EventEmitter();
|
|
1989
|
+
req.write = (chunk) => { raw += chunk; };
|
|
1990
|
+
req.end = () => {
|
|
1991
|
+
seen = {
|
|
1992
|
+
method: options.method,
|
|
1993
|
+
url: endpoint.pathname,
|
|
1994
|
+
authorization: options.headers.Authorization,
|
|
1995
|
+
body: JSON.parse(raw)
|
|
1996
|
+
};
|
|
1997
|
+
const res = new EventEmitter();
|
|
1998
|
+
res.statusCode = 200;
|
|
1999
|
+
res.setEncoding = () => {};
|
|
2000
|
+
callback(res);
|
|
2001
|
+
process.nextTick(() => {
|
|
2002
|
+
res.emit("data", JSON.stringify({
|
|
2003
|
+
ok: true,
|
|
2004
|
+
project_id: "erp-project",
|
|
2005
|
+
progress_percent: seen.body.project.progress_percent
|
|
2006
|
+
}));
|
|
2007
|
+
res.emit("end");
|
|
2008
|
+
});
|
|
2009
|
+
};
|
|
2010
|
+
req.destroy = (error) => req.emit("error", error);
|
|
2011
|
+
return req;
|
|
2012
|
+
};
|
|
2013
|
+
|
|
2014
|
+
(async () => {
|
|
2015
|
+
try {
|
|
2016
|
+
const snapshot = buildSnapshot({ cwd, home, now: "2026-05-21T00:00:00.000Z" });
|
|
2017
|
+
const response = await uploadSnapshot(snapshot, {
|
|
2018
|
+
erp: { enabled: true, url: "http://erp.local", key: "qlt_test" }
|
|
2019
|
+
});
|
|
2020
|
+
assert.equal(response.status, 200);
|
|
2021
|
+
assert.equal(response.body.ok, true);
|
|
2022
|
+
assert.equal(seen.method, "POST");
|
|
2023
|
+
assert.equal(seen.url, "/api/v1/project-snapshots");
|
|
2024
|
+
assert.equal(seen.authorization, "Bearer qlt_test");
|
|
2025
|
+
assert.equal(seen.body.snapshot_version, 1);
|
|
2026
|
+
assert.equal(seen.body.project.progress_percent, 42);
|
|
2027
|
+
assert.equal(seen.body.identifiers.erp_project_id, "7b5d3b4e-2b8a-4de4-91a1-9b2f3182f5ef");
|
|
2028
|
+
} finally {
|
|
2029
|
+
http.request = originalRequest;
|
|
2030
|
+
}
|
|
2031
|
+
})().catch((error) => {
|
|
2032
|
+
console.error(error);
|
|
2033
|
+
process.exit(1);
|
|
2034
|
+
});
|
|
2035
|
+
NODE
|
|
2036
|
+
)
|
|
2037
|
+
EXIT=$?
|
|
2038
|
+
if [ "$EXIT" -eq 0 ]; then
|
|
2039
|
+
pass "project-snapshot uploads ERP/admin progress object"
|
|
2040
|
+
else
|
|
2041
|
+
fail_case "project-snapshot upload failed" "$OUT"
|
|
2042
|
+
fi
|
|
2043
|
+
|
|
1646
2044
|
echo ""
|
|
1647
2045
|
echo "=== Results: $PASS passed, $FAIL failed ==="
|
|
1648
2046
|
[ "$FAIL" -eq 0 ] && exit 0 || exit 1
|
package/tests/hooks.test.sh
CHANGED
|
@@ -340,14 +340,7 @@ EOF
|
|
|
340
340
|
assert_exit "exits 0 with STATE.md" 0 $?
|
|
341
341
|
rm -rf "$TMP"
|
|
342
342
|
|
|
343
|
-
#
|
|
344
|
-
echo ""
|
|
345
|
-
echo "pre-compact:"
|
|
346
|
-
|
|
347
|
-
TMP=$(mktemp -d)
|
|
348
|
-
(cd "$TMP" && $NODE "$HOOKS_DIR/pre-compact.js" >/dev/null 2>&1)
|
|
349
|
-
assert_exit "exits 0 with no STATE.md" 0 $?
|
|
350
|
-
rm -rf "$TMP"
|
|
343
|
+
# pre-compact.js removed in v6.2.0 — state.js journal provides crash safety.
|
|
351
344
|
|
|
352
345
|
# --- auto-update.js ---
|
|
353
346
|
echo ""
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# install-smoke.test.sh — package-level install smoke test.
|
|
3
|
+
# Builds the npm tarball, extracts it, and runs the packaged installer into an
|
|
4
|
+
# isolated HOME for the Claude + Codex target. This catches files that pass
|
|
5
|
+
# source-tree tests but are missing from the published package.
|
|
6
|
+
|
|
7
|
+
set -u
|
|
8
|
+
|
|
9
|
+
PASS=0
|
|
10
|
+
FAIL=0
|
|
11
|
+
|
|
12
|
+
pass() {
|
|
13
|
+
echo " ✓ $1"
|
|
14
|
+
PASS=$((PASS + 1))
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
fail_case() {
|
|
18
|
+
echo " ✗ $1"
|
|
19
|
+
[ -n "${2:-}" ] && echo " $2"
|
|
20
|
+
FAIL=$((FAIL + 1))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
24
|
+
NODE="${NODE:-node}"
|
|
25
|
+
TMP="$(mktemp -d 2>/dev/null || mktemp -d -t qualia-install-smoke)"
|
|
26
|
+
HOME_DIR="$TMP/home"
|
|
27
|
+
PACK_DIR="$TMP/pack"
|
|
28
|
+
CACHE_DIR="$TMP/npm-cache"
|
|
29
|
+
LOGS_DIR="$TMP/npm-logs"
|
|
30
|
+
USERCONFIG="$TMP/npmrc"
|
|
31
|
+
mkdir -p "$HOME_DIR" "$PACK_DIR" "$CACHE_DIR" "$LOGS_DIR"
|
|
32
|
+
: >"$USERCONFIG"
|
|
33
|
+
|
|
34
|
+
cleanup() {
|
|
35
|
+
rm -rf "$TMP"
|
|
36
|
+
}
|
|
37
|
+
trap cleanup EXIT
|
|
38
|
+
|
|
39
|
+
echo "install-smoke.test.sh — packaged Claude + Codex install"
|
|
40
|
+
echo ""
|
|
41
|
+
|
|
42
|
+
PACK_OUT="$TMP/npm-pack.out"
|
|
43
|
+
PACK_ERR="$TMP/npm-pack.err"
|
|
44
|
+
(cd "$ROOT" && env \
|
|
45
|
+
NPM_CONFIG_CACHE="$CACHE_DIR" \
|
|
46
|
+
NPM_CONFIG_LOGS_DIR="$LOGS_DIR" \
|
|
47
|
+
NPM_CONFIG_USERCONFIG="$USERCONFIG" \
|
|
48
|
+
npm pack --silent --pack-destination "$PACK_DIR" >"$PACK_OUT" 2>"$PACK_ERR")
|
|
49
|
+
PACK_EXIT=$?
|
|
50
|
+
TARBALL=$(tail -1 "$PACK_OUT" 2>/dev/null | tr -d '[:space:]')
|
|
51
|
+
TARBALL_PATH="$PACK_DIR/$TARBALL"
|
|
52
|
+
|
|
53
|
+
if [ -f "$TARBALL_PATH" ]; then
|
|
54
|
+
pass "npm pack produced $TARBALL"
|
|
55
|
+
else
|
|
56
|
+
fail_case "npm pack tarball missing" "exit=$PACK_EXIT expected=$TARBALL_PATH stderr=$(cat "$PACK_ERR" 2>/dev/null)"
|
|
57
|
+
echo ""
|
|
58
|
+
echo "Results: $PASS passed, $FAIL failed"
|
|
59
|
+
exit 1
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
tar -xzf "$TARBALL_PATH" -C "$TMP" 2>"$TMP/tar.err"
|
|
63
|
+
if [ -f "$TMP/package/bin/install.js" ] \
|
|
64
|
+
&& [ -f "$TMP/package/bin/report-payload.js" ] \
|
|
65
|
+
&& [ -f "$TMP/package/bin/project-snapshot.js" ] \
|
|
66
|
+
&& [ -f "$TMP/package/AGENTS.md" ] \
|
|
67
|
+
&& [ -f "$TMP/package/CLAUDE.md" ]; then
|
|
68
|
+
pass "tarball contains installer + Claude/Codex instruction roots"
|
|
69
|
+
else
|
|
70
|
+
fail_case "tarball missing install surfaces" "$(cat "$TMP/tar.err" 2>/dev/null)"
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
printf 'QS-FAWZI-01\n3\n' | HOME="$HOME_DIR" "$NODE" "$TMP/package/bin/install.js" >"$TMP/install.log" 2>&1
|
|
74
|
+
EXIT=$?
|
|
75
|
+
if [ "$EXIT" -eq 0 ]; then
|
|
76
|
+
pass "packaged installer exits 0 for target=Both"
|
|
77
|
+
else
|
|
78
|
+
fail_case "packaged installer failed" "exit=$EXIT log=$(tail -20 "$TMP/install.log" 2>/dev/null)"
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
if [ -f "$HOME_DIR/.claude/CLAUDE.md" ] \
|
|
82
|
+
&& grep -q "Role: OWNER" "$HOME_DIR/.claude/CLAUDE.md" \
|
|
83
|
+
&& ! grep -q "{{ROLE}}" "$HOME_DIR/.claude/CLAUDE.md"; then
|
|
84
|
+
pass "Claude CLAUDE.md installed with OWNER role"
|
|
85
|
+
else
|
|
86
|
+
fail_case "Claude CLAUDE.md role substitution failed"
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
if [ -f "$HOME_DIR/.codex/AGENTS.md" ] \
|
|
90
|
+
&& [ -f "$HOME_DIR/.codex/hooks.json" ] \
|
|
91
|
+
&& [ -f "$HOME_DIR/.codex/config.toml" ] \
|
|
92
|
+
&& [ -f "$HOME_DIR/.codex/bin/statusline.js" ] \
|
|
93
|
+
&& [ -f "$HOME_DIR/.codex/bin/project-snapshot.js" ] \
|
|
94
|
+
&& [ -f "$HOME_DIR/.codex/agents/planner.toml" ] \
|
|
95
|
+
&& [ -f "$HOME_DIR/.codex/skills/qualia-new/SKILL.md" ] \
|
|
96
|
+
&& [ -f "$HOME_DIR/.codex/qualia-references/questioning.md" ] \
|
|
97
|
+
&& grep -q "Role: OWNER" "$HOME_DIR/.codex/AGENTS.md" \
|
|
98
|
+
&& ! grep -q "{{ROLE}}" "$HOME_DIR/.codex/AGENTS.md" \
|
|
99
|
+
&& grep -q "pre-deploy-gate.js" "$HOME_DIR/.codex/hooks.json" \
|
|
100
|
+
&& ! grep -R "\.claude/bin" "$HOME_DIR/.codex/skills" >/dev/null 2>&1; then
|
|
101
|
+
pass "Codex runtime installed with OWNER role"
|
|
102
|
+
else
|
|
103
|
+
fail_case "Codex runtime install failed"
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
if [ -d "$HOME_DIR/.claude/hooks" ] \
|
|
107
|
+
&& [ "$(find "$HOME_DIR/.claude/hooks" -maxdepth 1 -name '*.js' | wc -l | tr -d ' ')" = "11" ] \
|
|
108
|
+
&& [ ! -f "$HOME_DIR/.claude/hooks/pre-compact.js" ]; then
|
|
109
|
+
pass "packaged install has 11 hooks and no pre-compact"
|
|
110
|
+
else
|
|
111
|
+
fail_case "packaged hook set mismatch"
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
if [ -f "$HOME_DIR/.claude/bin/report-payload.js" ] \
|
|
115
|
+
&& [ -f "$HOME_DIR/.claude/bin/project-snapshot.js" ] \
|
|
116
|
+
&& grep -q "report-payload.js" "$HOME_DIR/.claude/skills/qualia-report/SKILL.md"; then
|
|
117
|
+
pass "packaged install includes ERP report/snapshot helpers"
|
|
118
|
+
else
|
|
119
|
+
fail_case "packaged install missing ERP report/snapshot helpers"
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
PKG_VERSION=$("$NODE" -e "console.log(require(process.argv[1]).version)" "$TMP/package/package.json")
|
|
123
|
+
CONFIG_VERSION=$("$NODE" -e "console.log(require(process.argv[1]).version)" "$HOME_DIR/.claude/.qualia-config.json" 2>/dev/null || echo "")
|
|
124
|
+
if [ "$PKG_VERSION" = "$CONFIG_VERSION" ]; then
|
|
125
|
+
pass "installed config version matches package ($PKG_VERSION)"
|
|
126
|
+
else
|
|
127
|
+
fail_case "installed config version mismatch" "package=$PKG_VERSION config=$CONFIG_VERSION"
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
echo ""
|
|
131
|
+
echo "Results: $PASS passed, $FAIL failed"
|
|
132
|
+
|
|
133
|
+
if [ "$FAIL" -gt 0 ]; then
|
|
134
|
+
exit 1
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
exit 0
|