qualia-framework 6.3.0 → 6.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/AGENTS.md +8 -8
  2. package/CLAUDE.md +6 -5
  3. package/README.md +17 -39
  4. package/bin/cli.js +64 -16
  5. package/bin/command-surface.js +6 -1
  6. package/bin/install.js +26 -11
  7. package/bin/learning-candidates.js +217 -0
  8. package/bin/prune-deprecated.js +64 -0
  9. package/bin/qualia-ui.js +1 -0
  10. package/bin/runtime-manifest.js +4 -0
  11. package/bin/security-scan.js +409 -0
  12. package/bin/state.js +106 -1
  13. package/bin/status-snapshot.js +363 -0
  14. package/guide.md +18 -33
  15. package/hooks/pre-compact.js +232 -0
  16. package/package.json +8 -2
  17. package/references/archetypes/ai-agent.md +89 -0
  18. package/references/archetypes/voice-agent.md +60 -0
  19. package/references/archetypes/web-app.md +67 -0
  20. package/references/archetypes/website.md +78 -0
  21. package/rules/constitution.md +42 -0
  22. package/skills/qualia/SKILL.md +3 -1
  23. package/skills/qualia-build/SKILL.md +1 -1
  24. package/skills/qualia-discuss/SKILL.md +1 -1
  25. package/skills/qualia-doctor/SKILL.md +1 -1
  26. package/skills/qualia-feature/SKILL.md +1 -1
  27. package/skills/qualia-fix/SKILL.md +1 -1
  28. package/skills/qualia-idk/SKILL.md +245 -0
  29. package/skills/qualia-learn/SKILL.md +1 -1
  30. package/skills/qualia-map/SKILL.md +1 -1
  31. package/skills/qualia-milestone/SKILL.md +1 -1
  32. package/skills/qualia-new/SKILL.md +1 -1
  33. package/skills/qualia-optimize/SKILL.md +1 -1
  34. package/skills/qualia-plan/SKILL.md +1 -1
  35. package/skills/qualia-polish/SKILL.md +1 -1
  36. package/skills/qualia-postmortem/SKILL.md +1 -1
  37. package/skills/qualia-report/SKILL.md +1 -1
  38. package/skills/qualia-research/SKILL.md +1 -1
  39. package/skills/qualia-review/SKILL.md +1 -1
  40. package/skills/qualia-road/SKILL.md +1 -1
  41. package/skills/qualia-scope/SKILL.md +123 -0
  42. package/skills/qualia-secure/SKILL.md +105 -0
  43. package/skills/qualia-test/SKILL.md +1 -1
  44. package/skills/qualia-verify/SKILL.md +1 -1
  45. package/skills/zoho-workflow/SKILL.md +1 -1
  46. package/tests/bin.test.sh +9 -9
  47. package/tests/install-smoke.test.sh +3 -3
  48. package/tests/lib.test.sh +17 -10
  49. package/tests/published-install-smoke.test.sh +3 -3
  50. package/tests/refs.test.sh +29 -22
  51. package/tests/runner.js +3 -3
  52. package/tests/state.test.sh +38 -7
  53. package/docs/archive/CHANGELOG-pre-v4.md +0 -855
  54. package/docs/archive/v4.0.0-review.md +0 -288
  55. package/docs/ecosystem-operating-model.md +0 -121
  56. package/docs/research/2026-04-21-command-quality-deep-research.md +0 -128
  57. package/docs/research/2026-04-21-industry-best-practices.md +0 -255
  58. package/docs/research/2026-05-11-deep-research.md +0 -189
  59. package/docs/reviews/matt-pocock-skills-analysis.md +0 -300
  60. package/docs/reviews/v4.1.0-audit.html +0 -1488
  61. package/docs/reviews/v4.1.0-audit.md +0 -263
  62. package/docs/reviews/v6.2.1-revival-audit.md +0 -53
  63. package/docs/reviews/v6.2.2-memory-erp-audit.md +0 -41
  64. package/docs/reviews/v6.2.3-erp-id-guard.md +0 -15
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: zoho-workflow
3
- description: "Zoho Invoice and Mail operations via ERP-first routing. Creates invoices from templates, sends cover emails, manages contacts, reads inbox, and handles payment reminders. Use when the user says 'invoice this client', 'send an email', 'check inbox', 'create a Zoho contact', 'send payment reminder', or mentions invoicing, billing, or Zoho."
3
+ description: "Zoho Invoice + Mail ops via ERP-first routing. Invoices from templates, cover emails, contacts, inbox, payment reminders. Triggers: 'invoice this client', 'send an email', 'check inbox', 'create a Zoho contact', 'payment reminder'."
4
4
  tags: [zoho, invoice, email, billing, crm]
5
5
  ---
6
6
 
package/tests/bin.test.sh CHANGED
@@ -487,13 +487,14 @@ else
487
487
  fail_case "CLAUDE.md role substitution"
488
488
  fi
489
489
 
490
- # 31. All 12 hooks installed (block-env-edit removed in v3.2.0;
490
+ # 31. All 13 hooks installed (block-env-edit removed in v3.2.0;
491
491
  # git-guardrails + stop-session-log added in v4.2.0;
492
492
  # vercel-account-guard + env-empty-guard + supabase-destructive-guard added in v5.0.0;
493
- # pre-compact removed in v6.2.0; fawzi-approval-guard added in v6.2.11)
493
+ # fawzi-approval-guard added in v6.2.11; pre-compact removed in v6.2.0 and
494
+ # REINTRODUCED in v6.3.2 with sidecar-snapshot mechanism)
494
495
  HOOK_COUNT=$(ls "$TMP/.claude/hooks/"*.js 2>/dev/null | wc -l)
495
- if [ "$HOOK_COUNT" -eq 12 ]; then
496
- pass "12 hooks installed in hooks/"
496
+ if [ "$HOOK_COUNT" -eq 13 ]; then
497
+ pass "13 hooks installed in hooks/ (incl. pre-compact v6.3.2)"
497
498
  else
498
499
  fail_case "hook count" "got $HOOK_COUNT"
499
500
  fi
@@ -509,8 +510,7 @@ else
509
510
  fail_case "settings.json contents"
510
511
  fi
511
512
 
512
- # 33. settings.json contains all 12 hooks wired correctly
513
- # pre-compact.js was removed in v6.2.0 — verify it's NOT in settings.json.
513
+ # 33. settings.json contains all 13 hooks wired correctly (v6.3.2 reintroduces pre-compact).
514
514
  if grep -q 'branch-guard.js' "$TMP/.claude/settings.json" \
515
515
  && grep -q 'migration-guard.js' "$TMP/.claude/settings.json" \
516
516
  && grep -q 'pre-push.js' "$TMP/.claude/settings.json" \
@@ -523,10 +523,10 @@ if grep -q 'branch-guard.js' "$TMP/.claude/settings.json" \
523
523
  && grep -q 'fawzi-approval-guard.js' "$TMP/.claude/settings.json" \
524
524
  && grep -q 'env-empty-guard.js' "$TMP/.claude/settings.json" \
525
525
  && grep -q 'supabase-destructive-guard.js' "$TMP/.claude/settings.json" \
526
- && ! grep -q 'pre-compact.js' "$TMP/.claude/settings.json"; then
527
- pass "settings.json has all 12 hooks wired (no pre-compact)"
526
+ && grep -q 'pre-compact.js' "$TMP/.claude/settings.json"; then
527
+ pass "settings.json has all 13 hooks wired (incl. pre-compact)"
528
528
  else
529
- fail_case "settings.json hooks misconfigured (check for stale pre-compact entry)"
529
+ fail_case "settings.json hooks misconfigured"
530
530
  fi
531
531
 
532
532
  # 34. Lowercase code works (resolveTeamCode normalizes)
@@ -124,10 +124,10 @@ else
124
124
  fi
125
125
 
126
126
  if [ -d "$HOME_DIR/.claude/hooks" ] \
127
- && [ "$(find "$HOME_DIR/.claude/hooks" -maxdepth 1 -name '*.js' | wc -l | tr -d ' ')" = "12" ] \
127
+ && [ "$(find "$HOME_DIR/.claude/hooks" -maxdepth 1 -name '*.js' | wc -l | tr -d ' ')" = "13" ] \
128
128
  && [ -f "$HOME_DIR/.claude/hooks/fawzi-approval-guard.js" ] \
129
- && [ ! -f "$HOME_DIR/.claude/hooks/pre-compact.js" ]; then
130
- pass "packaged install has 12 hooks and no pre-compact"
129
+ && [ -f "$HOME_DIR/.claude/hooks/pre-compact.js" ]; then
130
+ pass "packaged install has 13 hooks including pre-compact (v6.3.2 sidecar snapshot)"
131
131
  else
132
132
  fail_case "packaged hook set mismatch"
133
133
  fi
package/tests/lib.test.sh CHANGED
@@ -369,9 +369,16 @@ CS="$FRAMEWORK_DIR/bin/command-surface.js"
369
369
  $NODE --check "$CS" >/dev/null 2>&1 && ok "command-surface.js parses" || fail "command-surface.js parse"
370
370
  RES=$($NODE -e '
371
371
  const cs = require("'"$CS"'");
372
- console.log(cs.ACTIVE_SKILLS.length === 23 && cs.RETIRED_SKILLS.includes("qualia-debug") && cs.RETIRED_SKILLS.includes("qualia-vibe") ? "SURFACE-OK" : JSON.stringify(cs));
372
+ const active = cs.ACTIVE_SKILLS;
373
+ const retired = cs.RETIRED_SKILLS;
374
+ const ok =
375
+ Array.isArray(active) && active.length > 0 &&
376
+ active.includes("qualia-scope") && active.includes("qualia-idk") && active.includes("qualia-secure") &&
377
+ !active.includes("qualia-debug") && retired.includes("qualia-debug") &&
378
+ !active.includes("qualia-vibe") && retired.includes("qualia-vibe");
379
+ console.log(ok ? "SURFACE-OK" : JSON.stringify(cs));
373
380
  ')
374
- [ "$RES" = "SURFACE-OK" ] && ok "command-surface defines 23 active skills and retired folds" || fail "command-surface manifest: $RES"
381
+ [ "$RES" = "SURFACE-OK" ] && ok "command-surface manifest: active surface excludes retired folds (qualia-debug/qualia-vibe), count derived from ACTIVE_SKILLS" || fail "command-surface manifest: $RES"
375
382
  RES=$($NODE -e '
376
383
  const fs = require("fs");
377
384
  const path = require("path");
@@ -382,12 +389,12 @@ const dirs = fs.readdirSync(path.join(root, "skills"), { withFileTypes: true })
382
389
  .map((d) => d.name)
383
390
  .sort();
384
391
  const active = [...ACTIVE_SKILLS].sort();
385
- const extra = dirs.filter((d) => !active.includes(d));
392
+ const known = new Set([...ACTIVE_SKILLS, ...RETIRED_SKILLS]);
393
+ const orphans = dirs.filter((d) => !known.has(d));
386
394
  const missing = active.filter((d) => !dirs.includes(d));
387
- const retiredPresent = RETIRED_SKILLS.filter((d) => dirs.includes(d));
388
- console.log(!extra.length && !missing.length && !retiredPresent.length ? "SKILL-DIRS-OK" : JSON.stringify({ extra, missing, retiredPresent }));
395
+ console.log(!orphans.length && !missing.length ? "SKILL-DIRS-OK" : JSON.stringify({ orphans, missing }));
389
396
  ')
390
- [ "$RES" = "SKILL-DIRS-OK" ] && ok "skills directory matches active command surface only" || fail "skill directory surface: $RES"
397
+ [ "$RES" = "SKILL-DIRS-OK" ] && ok "every active skill has a folder; no orphan skill folders" || fail "skill directory surface: $RES"
391
398
 
392
399
  TMP=$(mktmp)
393
400
  RES=$(cd "$TMP" && $NODE -e '
@@ -500,7 +507,7 @@ TMP=$(mktmp)
500
507
  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"
501
508
  echo '{"installed_by":"Test","role":"OWNER","version":"6.3.0","erp":{"enabled":false}}' > "$TMP/home/.claude/.qualia-config.json"
502
509
  touch "$TMP/home/.claude/CLAUDE.md" "$TMP/home/.claude/settings.json"
503
- 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 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; do
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 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; do
504
511
  touch "$TMP/home/.claude/bin/$f"
505
512
  done
506
513
  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
@@ -510,7 +517,7 @@ touch "$TMP/home/.claude/knowledge/index.md" "$TMP/home/.claude/knowledge/agents
510
517
  for f in design-laws.md design-rubric.md design-brand.md design-product.md design-reference.md frontend.md graphics.md; do
511
518
  touch "$TMP/home/.claude/qualia-design/$f"
512
519
  done
513
- for s in qualia qualia-new qualia-discuss qualia-map qualia-research qualia-plan qualia-build qualia-verify qualia-fix qualia-feature qualia-review qualia-optimize qualia-polish qualia-test qualia-milestone qualia-ship qualia-handoff qualia-report qualia-doctor qualia-road qualia-learn qualia-postmortem zoho-workflow; do
520
+ for s in $($NODE -e 'console.log(require(process.argv[1]).ACTIVE_SKILLS.join(" "))' "$CS"); do
514
521
  mkdir -p "$TMP/home/.claude/skills/$s"
515
522
  touch "$TMP/home/.claude/skills/$s/SKILL.md"
516
523
  done
@@ -615,7 +622,7 @@ TMP=$(mktmp)
615
622
  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"
616
623
  echo '{"installed_by":"Test","role":"OWNER","erp":{"enabled":false}}' > "$TMP/.claude/.qualia-config.json"
617
624
  touch "$TMP/.claude/CLAUDE.md" "$TMP/.claude/settings.json"
618
- 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 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; do
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 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; do
619
626
  touch "$TMP/.claude/bin/$f"
620
627
  done
621
628
  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
@@ -625,7 +632,7 @@ touch "$TMP/.claude/knowledge/index.md" "$TMP/.claude/knowledge/agents.md"
625
632
  for f in design-laws.md design-rubric.md design-brand.md design-product.md design-reference.md frontend.md graphics.md; do
626
633
  touch "$TMP/.claude/qualia-design/$f"
627
634
  done
628
- for s in qualia qualia-new qualia-discuss qualia-map qualia-research qualia-plan qualia-build qualia-verify qualia-fix qualia-feature qualia-review qualia-optimize qualia-polish qualia-test qualia-milestone qualia-ship qualia-handoff qualia-report qualia-doctor qualia-road qualia-learn qualia-postmortem zoho-workflow; do
635
+ for s in $($NODE -e 'console.log(require(process.argv[1]).ACTIVE_SKILLS.join(" "))' "$CS"); do
629
636
  mkdir -p "$TMP/.claude/skills/$s"
630
637
  touch "$TMP/.claude/skills/$s/SKILL.md"
631
638
  done
@@ -102,10 +102,10 @@ else
102
102
  fi
103
103
 
104
104
  if [ -d "$HOME_DIR/.claude/hooks" ] \
105
- && [ "$(find "$HOME_DIR/.claude/hooks" -maxdepth 1 -name '*.js' | wc -l | tr -d ' ')" = "12" ] \
105
+ && [ "$(find "$HOME_DIR/.claude/hooks" -maxdepth 1 -name '*.js' | wc -l | tr -d ' ')" = "13" ] \
106
106
  && [ -f "$HOME_DIR/.claude/hooks/fawzi-approval-guard.js" ] \
107
- && [ ! -f "$HOME_DIR/.claude/hooks/pre-compact.js" ]; then
108
- pass "public install has 12 hooks and no pre-compact"
107
+ && [ -f "$HOME_DIR/.claude/hooks/pre-compact.js" ]; then
108
+ pass "public install has 13 hooks including pre-compact (v6.3.2 sidecar snapshot)"
109
109
  else
110
110
  fail_case "public install hook set mismatch"
111
111
  fi
@@ -79,27 +79,33 @@ SCAN_FILES=$(
79
79
  # 1. `/qualia-foo` — backticked, the canonical command-doc style
80
80
  # 2. <dt>/qualia-foo</dt> — HTML help/onboarding pages
81
81
  # We capture name + file:line so we can show context per failure.
82
- declare -A SEEN_REFS
83
- declare -A REF_LOCATIONS
84
- declare -A ACTIVE_SKILL_MAP
85
-
86
- while IFS= read -r skill; do
87
- [ -n "$skill" ] && ACTIVE_SKILL_MAP["/$skill"]=1
88
- done < <("$NODE" -e 'const {ACTIVE_SKILLS}=require(process.argv[1]); for (const s of ACTIVE_SKILLS) console.log(s)' "$FRAMEWORK_ROOT/bin/command-surface.js")
82
+ #
83
+ # Storage uses a temp file (one line per "ref<TAB>location") instead of
84
+ # bash 4 associative arrays — macOS ships bash 3.2 (frozen at that version
85
+ # for licensing reasons) which lacks `declare -A`. Temp file is portable
86
+ # and behaves correctly under `set -u`.
87
+ REF_STORE=$(mktemp)
88
+ ACTIVE_SKILLS_LIST=$("$NODE" -e 'const {ACTIVE_SKILLS}=require(process.argv[1]); console.log(ACTIVE_SKILLS.map(s => "/" + s).join(" "))' "$FRAMEWORK_ROOT/bin/command-surface.js")
89
+ trap 'rm -f "$REF_STORE"' EXIT
90
+
91
+ # is_active "/qualia-foo" → returns 0 if in ACTIVE_SKILLS_LIST.
92
+ is_active() {
93
+ case " $ACTIVE_SKILLS_LIST " in
94
+ *" $1 "*) return 0 ;;
95
+ *) return 1 ;;
96
+ esac
97
+ }
89
98
 
90
99
  while IFS= read -r file; do
91
100
  # Pattern A: backtick-quoted commands. Allow trailing flag/word but only capture base name.
92
101
  while IFS=: read -r path lineno line; do
93
102
  [ -z "$line" ] && continue
94
- # Skip migration-context lines.
95
103
  if echo "$line" | grep -qE "$MIGRATION_CONTEXT_REGEX"; then
96
104
  continue
97
105
  fi
98
- # Extract every backticked /qualia-foo on this line.
99
106
  matches=$(echo "$line" | grep -oE '`/qualia(-[a-z]+){0,3}`' | sed 's/^`//; s/`$//')
100
107
  for ref in $matches; do
101
- SEEN_REFS["$ref"]=1
102
- REF_LOCATIONS["$ref"]="${REF_LOCATIONS[$ref]:+${REF_LOCATIONS[$ref]}, }$(basename "$path"):$lineno"
108
+ printf '%s\t%s:%s\n' "$ref" "$(basename "$path")" "$lineno" >> "$REF_STORE"
103
109
  done
104
110
  done < <(grep -nE '`/qualia(-[a-z]+){0,3}`' "$file" 2>/dev/null)
105
111
 
@@ -111,36 +117,37 @@ while IFS= read -r file; do
111
117
  fi
112
118
  matches=$(echo "$line" | grep -oE '<dt>/qualia(-[a-z]+){0,3}( [^<]*)?</dt>' | sed -E 's|<dt>(/qualia(-[a-z]+){0,3}).*|\1|')
113
119
  for ref in $matches; do
114
- SEEN_REFS["$ref"]=1
115
- REF_LOCATIONS["$ref"]="${REF_LOCATIONS[$ref]:+${REF_LOCATIONS[$ref]}, }$(basename "$path"):$lineno"
120
+ printf '%s\t%s:%s\n' "$ref" "$(basename "$path")" "$lineno" >> "$REF_STORE"
116
121
  done
117
122
  done < <(grep -nE '<dt>/qualia' "$file" 2>/dev/null)
118
123
  done <<<"$SCAN_FILES"
119
124
 
120
- if [ ${#SEEN_REFS[@]} -eq 0 ]; then
125
+ if [ ! -s "$REF_STORE" ]; then
121
126
  fail_case "scan" "no backticked /qualia-* references found across active surfaces (scanner broken?)"
122
127
  echo ""
123
128
  echo "Results: $PASS passed, $FAIL failed"
124
129
  exit 1
125
130
  fi
126
131
 
127
- # Sort refs for deterministic output.
128
- for ref in $(printf '%s\n' "${!SEEN_REFS[@]}" | sort); do
132
+ # Iterate unique refs, deterministic order.
133
+ for ref in $(cut -f1 "$REF_STORE" | sort -u); do
129
134
  name="${ref#/}"
130
135
  skill_dir="$SKILLS_DIR/$name"
131
- locations="${REF_LOCATIONS[$ref]}"
132
- if [ "${ACTIVE_SKILL_MAP[$ref]:-}" = "1" ] && [ -d "$skill_dir" ] && [ -f "$skill_dir/SKILL.md" ]; then
136
+ # Comma-join all locations for this ref.
137
+ locations=$(awk -v r="$ref" -F'\t' '$1==r {print $2}' "$REF_STORE" | paste -sd, -)
138
+ if is_active "$ref" && [ -d "$skill_dir" ] && [ -f "$skill_dir/SKILL.md" ]; then
133
139
  pass "$ref → active skills/$name/SKILL.md"
134
140
  continue
135
141
  fi
136
142
  fail_case "$ref" "not an active shipped command or missing skills/$name/SKILL.md — referenced by: $locations"
137
143
  done
138
144
 
139
- PACKAGE_VERSION="$("$NODE" -e 'console.log(require(process.argv[1]).version)' "$FRAMEWORK_ROOT/package.json" 2>/dev/null || echo "")"
140
- if [ -n "$PACKAGE_VERSION" ] && grep -q "# Qualia Framework v$PACKAGE_VERSION" "$FRAMEWORK_ROOT/README.md"; then
141
- pass "README title matches package.json version ($PACKAGE_VERSION)"
145
+ # README title is version-less by design — version lives in package.json + CHANGELOG.md,
146
+ # not in the README h1, so the orientation block above the fold stays version-stable.
147
+ if grep -qE '^# Qualia Framework( |$)' "$FRAMEWORK_ROOT/README.md"; then
148
+ pass "README h1 is version-less (version lives in CHANGELOG.md)"
142
149
  else
143
- fail_case "README version drift" "README.md title must match package.json version $PACKAGE_VERSION"
150
+ fail_case "README h1 shape" "README.md must open with '# Qualia Framework' (no version suffix)"
144
151
  fi
145
152
 
146
153
  forbidden_surface_patterns=(
package/tests/runner.js CHANGED
@@ -2578,14 +2578,14 @@ describe("install.js", () => {
2578
2578
  }
2579
2579
  });
2580
2580
 
2581
- it("12 hooks installed (v6.2.11: proxy approval guard added; v6.2.0: pre-compact removed)", () => {
2581
+ it("13 hooks installed (v6.3.2: pre-compact reintroduced with sidecar-snapshot mechanism)", () => {
2582
2582
  const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "qualia-install-"));
2583
2583
  try {
2584
2584
  runInstall("QS-FAWZI-11", tmpHome);
2585
2585
  const hooks = fs.readdirSync(path.join(tmpHome, ".claude", "hooks")).filter(f => f.endsWith(".js"));
2586
- assert.equal(hooks.length, 12, `expected 12 hooks, got ${hooks.length}: ${hooks.join(", ")}`);
2586
+ assert.equal(hooks.length, 13, `expected 13 hooks, got ${hooks.length}: ${hooks.join(", ")}`);
2587
2587
  assert.ok(hooks.includes("fawzi-approval-guard.js"), "fawzi-approval-guard.js must be installed");
2588
- assert.ok(!hooks.includes("pre-compact.js"), "pre-compact.js must NOT be installed (removed in v6.2.0)");
2588
+ assert.ok(hooks.includes("pre-compact.js"), "pre-compact.js must be installed (v6.3.2 sidecar-snapshot mechanism)");
2589
2589
  } finally {
2590
2590
  fs.rmSync(tmpHome, { recursive: true, force: true });
2591
2591
  }
@@ -4,9 +4,40 @@
4
4
 
5
5
  PASS=0
6
6
  FAIL=0
7
- # Resolve STATE_JS to an ABSOLUTE path so `cd` inside subshells doesn't break it.
8
- STATE_JS="$(cd "$(dirname "$0")/../bin" && pwd)/state.js"
9
- FRAMEWORK_DIR="$(cd "$(dirname "$0")/.." && pwd)"
7
+ # Windows portability coerce shell-native MSYS paths (/d/a/..., /tmp/...) to
8
+ # Windows-native form (D:/a/..., C:/Users/.../Temp/...) before they get
9
+ # interpolated into Node `$NODE -e` snippets. Without coercion, Node receives
10
+ # the MSYS string literally and resolves it against the current drive, yielding
11
+ # non-existent paths like `D:\tmp\tmp.XXX\.planning\tracking.json`.
12
+ #
13
+ # Mechanism: `cygpath -m` is the canonical MSYS path converter and is present
14
+ # on Git Bash / MSYS2 (Windows GitHub runners use Git Bash for `shell: bash`).
15
+ # `pwd -W` doesn't work because bash's builtin pwd doesn't accept -W, and
16
+ # /usr/bin/pwd's -W support varies by MSYS distribution.
17
+ # On Linux/macOS, cygpath doesn't exist — fall through to plain pwd.
18
+ _pwd_native() {
19
+ local p
20
+ p=$(pwd)
21
+ if command -v cygpath >/dev/null 2>&1; then
22
+ cygpath -m "$p" 2>/dev/null || echo "$p"
23
+ else
24
+ echo "$p"
25
+ fi
26
+ }
27
+ _mktemp_native() {
28
+ local d
29
+ d=$(mktemp -d) || return 1
30
+ if command -v cygpath >/dev/null 2>&1; then
31
+ cygpath -m "$d" 2>/dev/null || echo "$d"
32
+ else
33
+ echo "$d"
34
+ fi
35
+ }
36
+
37
+ # Resolve STATE_JS to an ABSOLUTE Windows-native path so `cd` inside subshells
38
+ # doesn't break it.
39
+ STATE_JS="$(cd "$(dirname "$0")/../bin" && _pwd_native)/state.js"
40
+ FRAMEWORK_DIR="$(cd "$(dirname "$0")/.." && _pwd_native)"
10
41
  STATE_LEDGER_JS="$FRAMEWORK_DIR/bin/state-ledger.js"
11
42
  NODE="${NODE:-node}"
12
43
 
@@ -23,7 +54,7 @@ trap cleanup EXIT
23
54
  # Prints the absolute path to the new tmp dir (does NOT cd).
24
55
  make_project() {
25
56
  local TMP
26
- TMP=$(mktemp -d)
57
+ TMP=$(_mktemp_native)
27
58
  TMP_DIRS+=("$TMP")
28
59
  (
29
60
  cd "$TMP" || exit 1
@@ -129,7 +160,7 @@ fi
129
160
  echo "basic I/O:"
130
161
 
131
162
  # 1. cmdInit produces valid tracking.json + STATE.md
132
- TMP=$(mktemp -d); TMP_DIRS+=("$TMP")
163
+ TMP=$(_mktemp_native); TMP_DIRS+=("$TMP")
133
164
  (
134
165
  cd "$TMP" || exit 1
135
166
  $NODE "$STATE_JS" init \
@@ -190,7 +221,7 @@ else
190
221
  fi
191
222
 
192
223
  # 3. cmdCheck with no project → ok:false NO_PROJECT, exit 1
193
- TMP2=$(mktemp -d); TMP_DIRS+=("$TMP2")
224
+ TMP2=$(_mktemp_native); TMP_DIRS+=("$TMP2")
194
225
  OUT=$(cd "$TMP2" && $NODE "$STATE_JS" check 2>&1)
195
226
  CHECK_EXIT=$?
196
227
  if [ "$CHECK_EXIT" -eq 1 ] \
@@ -1014,7 +1045,7 @@ else
1014
1045
  fi
1015
1046
 
1016
1047
  # 49. First-time init (no existing tracking.json) sets lifetime to zeros
1017
- TMP=$(mktemp -d); TMP_DIRS+=("$TMP")
1048
+ TMP=$(_mktemp_native); TMP_DIRS+=("$TMP")
1018
1049
  (cd "$TMP" && $NODE "$STATE_JS" init \
1019
1050
  --project "FreshProject" \
1020
1051
  --phases '[{"name":"P1","goal":"G1"}]' \