qualia-framework 4.3.0 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -297,7 +297,7 @@
297
297
  <div class="header-content">
298
298
  <h1><span>Qualia</span> Framework</h1>
299
299
  <p>Plan, build, verify, ship. The AI-powered workflow for Qualia Solutions.</p>
300
- <div class="version">{{VERSION}} &middot; 26 skills</div>
300
+ <div class="version">{{VERSION}} &middot; 28 skills</div>
301
301
  </div>
302
302
  </div>
303
303
 
@@ -328,7 +328,7 @@
328
328
 
329
329
  <!-- Skills -->
330
330
  <section>
331
- <h2>Skills (26)</h2>
331
+ <h2>Skills (28)</h2>
332
332
 
333
333
  <!-- Road (core flow) -->
334
334
  <div class="cmd-group">
@@ -336,7 +336,7 @@
336
336
  <p class="cmd-group-note">The seven steps every project walks through.</p>
337
337
  <div class="commands">
338
338
  <div class="cmd"><span class="cmd-name">/qualia-new</span><span class="cmd-desc">Set up a new project from scratch &mdash; deep questioning, parallel research, REQUIREMENTS.md, ROADMAP.md, approval gate. Use when starting any new client project.</span></div>
339
- <div class="cmd"><span class="cmd-name">/qualia-plan</span><span class="cmd-desc">Plan the current phase &mdash; spawns planner, validates with plan-checker in a revision loop (max 3), optionally runs discuss/research first. Use when ready to plan a phase.</span></div>
339
+ <div class="cmd"><span class="cmd-name">/qualia-plan</span><span class="cmd-desc">Plan the current phase &mdash; spawns planner, validates with plan-checker in a revision loop (max 2), optionally runs discuss/research first. Use when ready to plan a phase.</span></div>
340
340
  <div class="cmd"><span class="cmd-name">/qualia-build</span><span class="cmd-desc">Execute the current phase &mdash; spawns builder subagents per task with wave-based parallelization. Fresh context per task.</span></div>
341
341
  <div class="cmd"><span class="cmd-name">/qualia-verify</span><span class="cmd-desc">Goal-backward verification &mdash; checks if the phase ACTUALLY works, not just if tasks completed. Spawns verifier agent.</span></div>
342
342
  <div class="cmd"><span class="cmd-name">/qualia-polish</span><span class="cmd-desc">Design and UX pass &mdash; anti-AI-slop, genuine craft, responsive, accessible. Run after all phases are verified.</span></div>
@@ -365,7 +365,6 @@
365
365
  <div class="cmd"><span class="cmd-name">/qualia-debug</span><span class="cmd-desc">Structured debugging &mdash; symptom gathering, diagnosis confirmation, root cause analysis. Trigger on 'debug', 'find bug', 'fix error', 'something is broken'.</span></div>
366
366
  <div class="cmd"><span class="cmd-name">/qualia-review</span><span class="cmd-desc">Production audit with scored diagnostics. Runs real commands, scores findings by severity. Trigger on 'review', 'audit', 'code review', 'security check'.</span></div>
367
367
  <div class="cmd"><span class="cmd-name">/qualia-optimize</span><span class="cmd-desc">Deep optimization pass &mdash; reads .planning/ AND codebase to find performance, design, UI, backend, and frontend issues. Spawns parallel specialist agents. Supports --perf, --ui, --backend, --alignment, --fix flags.</span></div>
368
- <div class="cmd"><span class="cmd-name">/qualia-polish</span><span class="cmd-desc">Design and UX pass &mdash; anti-AI-slop, genuine craft, responsive, accessible. Run after all phases are verified.</span></div>
369
368
  <div class="cmd"><span class="cmd-name">/qualia-test</span><span class="cmd-desc">Generate or run tests for client projects. Trigger on 'write tests', 'add tests', 'test this', 'test coverage'.</span></div>
370
369
  </div>
371
370
  </div>
@@ -387,6 +386,7 @@
387
386
  <p class="cmd-group-note">Persist learnings and log work.</p>
388
387
  <div class="commands">
389
388
  <div class="cmd"><span class="cmd-name">/qualia-learn</span><span class="cmd-desc">Save a learning, pattern, fix, or client preference to the knowledge base. Persists across projects and sessions.</span></div>
389
+ <div class="cmd"><span class="cmd-name">/qualia-flush</span><span class="cmd-desc">Promote raw daily logs into curated knowledge concepts. Use weekly or when session logs contain durable lessons.</span></div>
390
390
  <div class="cmd"><span class="cmd-name">/qualia-report</span><span class="cmd-desc">Generate session report and commit to repo. Mandatory before clock-out.</span></div>
391
391
  </div>
392
392
  </div>
@@ -418,6 +418,7 @@
418
418
  <p class="cmd-group-note">Extend the framework itself.</p>
419
419
  <div class="commands">
420
420
  <div class="cmd"><span class="cmd-name">/qualia-skill-new</span><span class="cmd-desc">Author a new Qualia skill or agent. Generates the SKILL.md, registers it in the right location, and optionally ships to the framework repo.</span></div>
421
+ <div class="cmd"><span class="cmd-name">/qualia-postmortem</span><span class="cmd-desc">Analyze a verification failure and turn the lesson into a framework improvement.</span></div>
421
422
  </div>
422
423
  </div>
423
424
  </section>
@@ -430,8 +431,13 @@
430
431
  <div class="cmd"><span class="cmd-name">qualia-framework install</span><span class="cmd-desc">Install or reinstall the framework.</span></div>
431
432
  <div class="cmd"><span class="cmd-name">qualia-framework update</span><span class="cmd-desc">Update to the latest version.</span></div>
432
433
  <div class="cmd"><span class="cmd-name">qualia-framework version</span><span class="cmd-desc">Show installed version + check for updates.</span></div>
434
+ <div class="cmd"><span class="cmd-name">qualia-framework uninstall</span><span class="cmd-desc">Clean removal from ~/.claude/ while preserving user-owned settings.</span></div>
433
435
  <div class="cmd"><span class="cmd-name">qualia-framework migrate</span><span class="cmd-desc">Upgrade legacy settings.json to the current hook layout.</span></div>
434
436
  <div class="cmd"><span class="cmd-name">qualia-framework analytics</span><span class="cmd-desc">Hook telemetry, verification pass rates, gap cycles.</span></div>
437
+ <div class="cmd"><span class="cmd-name">qualia-framework set-erp-key</span><span class="cmd-desc">Save and enable the ERP API key.</span></div>
438
+ <div class="cmd"><span class="cmd-name">qualia-framework erp-ping</span><span class="cmd-desc">Verify ERP connectivity and API key health.</span></div>
439
+ <div class="cmd"><span class="cmd-name">qualia-framework doctor</span><span class="cmd-desc">Health-check installed files, hooks, settings, and knowledge layer.</span></div>
440
+ <div class="cmd"><span class="cmd-name">qualia-framework flush</span><span class="cmd-desc">Run the non-interactive knowledge flush.</span></div>
435
441
  <div class="cmd"><span class="cmd-name">qualia-framework team</span><span class="cmd-desc">List, add, or remove team members.</span></div>
436
442
  <div class="cmd"><span class="cmd-name">qualia-framework traces</span><span class="cmd-desc">View recent hook activity.</span></div>
437
443
  </div>
@@ -470,11 +476,11 @@
470
476
  <section>
471
477
  <h2>Rules</h2>
472
478
  <ul class="rules">
473
- <li><span class="rule-icon">1</span> Feature branches only &mdash; never push to main</li>
479
+ <li><span class="rule-icon">1</span> Feature branches by default &mdash; OWNER overrides must be explicit</li>
474
480
  <li><span class="rule-icon">2</span> Read before write &mdash; understand files before editing</li>
475
481
  <li><span class="rule-icon">3</span> MVP first &mdash; build what's asked, nothing extra</li>
476
482
  <li><span class="rule-icon">4</span> /qualia-report before clock-out &mdash; mandatory, enforced by ERP</li>
477
- <li><span class="rule-icon">5</span> No .env edits &mdash; ask Fawzi for secrets</li>
483
+ <li><span class="rule-icon">5</span> Secrets through approved flows &mdash; use set-erp-key or ask Fawzi</li>
478
484
  <li><span class="rule-icon">6</span> Stuck 30+ minutes? Ask Fawzi</li>
479
485
  </ul>
480
486
  </section>
@@ -536,7 +542,7 @@
536
542
  <div class="footer">
537
543
  <strong>Welcome to the future with Qualia.</strong><br>
538
544
  Qualia Solutions &mdash; Nicosia, Cyprus
539
- <span class="footer-version">qualia-framework {{VERSION}} &middot; 26 skills</span>
545
+ <span class="footer-version">qualia-framework {{VERSION}} &middot; 28 skills</span>
540
546
  </div>
541
547
 
542
548
  </body>
package/tests/bin.test.sh CHANGED
@@ -638,8 +638,11 @@ else
638
638
  fail_case "knowledge idempotency" "exit=$EXIT"
639
639
  fi
640
640
 
641
- # 43. ERP API key file created and not overwritten on re-install
642
- if [ -f "$TMP/.claude/.erp-api-key" ]; then
641
+ # 43. ERP API key is opt-in and preserved on re-install
642
+ CONFIG_ENABLED=$($NODE -e "const c=require('$TMP/.claude/.qualia-config.json'); console.log(c.erp && c.erp.enabled === false ? 'disabled' : 'enabled')")
643
+ if [ ! -f "$TMP/.claude/.erp-api-key" ] && [ "$CONFIG_ENABLED" = "disabled" ]; then
644
+ echo " ✓ ERP disabled when no API key is provided"
645
+ PASS=$((PASS + 1))
643
646
  echo "custom-erp-key" > "$TMP/.claude/.erp-api-key"
644
647
  echo "QS-FAWZI-01" | HOME="$TMP" $NODE "$INSTALL_JS" > "$TMP/out3.log" 2>&1
645
648
  if grep -q "custom-erp-key" "$TMP/.claude/.erp-api-key"; then
@@ -648,7 +651,7 @@ if [ -f "$TMP/.claude/.erp-api-key" ]; then
648
651
  fail_case ".erp-api-key preservation"
649
652
  fi
650
653
  else
651
- fail_case ".erp-api-key missing after install"
654
+ fail_case "ERP opt-in default" "key_exists=$(test -f "$TMP/.claude/.erp-api-key" && echo yes || echo no) config=$CONFIG_ENABLED"
652
655
  fi
653
656
 
654
657
  # 44. Templates copied to qualia-templates/
@@ -34,28 +34,17 @@ for f in "$HOOKS_DIR"/*.js; do
34
34
  fi
35
35
  done
36
36
 
37
- # --- block-env-edit.js ---
37
+ # --- block-env-edit.js retired in v3.2.0 ---
38
38
  echo ""
39
39
  echo "block-env-edit:"
40
40
 
41
- echo '{"tool_input":{"file_path":".env.local"}}' | $NODE "$HOOKS_DIR/block-env-edit.js" > /dev/null 2>&1
42
- assert_exit "blocks .env.local" 2 $?
43
-
44
- echo '{"tool_input":{"file_path":".env.production"}}' | $NODE "$HOOKS_DIR/block-env-edit.js" > /dev/null 2>&1
45
- assert_exit "blocks .env.production" 2 $?
46
-
47
- echo '{"tool_input":{"file_path":".env"}}' | $NODE "$HOOKS_DIR/block-env-edit.js" > /dev/null 2>&1
48
- assert_exit "blocks .env" 2 $?
49
-
50
- # Windows-style path with backslashes (normalized by the hook)
51
- echo '{"tool_input":{"file_path":"C:\\project\\.env.local"}}' | $NODE "$HOOKS_DIR/block-env-edit.js" > /dev/null 2>&1
52
- assert_exit "blocks windows .env.local" 2 $?
53
-
54
- echo '{"tool_input":{"file_path":"src/app.tsx"}}' | $NODE "$HOOKS_DIR/block-env-edit.js" > /dev/null 2>&1
55
- assert_exit "allows src/app.tsx" 0 $?
56
-
57
- echo '{"tool_input":{"file_path":"components/Footer.tsx"}}' | $NODE "$HOOKS_DIR/block-env-edit.js" > /dev/null 2>&1
58
- assert_exit "allows components/Footer.tsx" 0 $?
41
+ if [ ! -f "$HOOKS_DIR/block-env-edit.js" ]; then
42
+ echo " retired hook is absent"
43
+ PASS=$((PASS + 1))
44
+ else
45
+ echo " retired hook still exists"
46
+ FAIL=$((FAIL + 1))
47
+ fi
59
48
 
60
49
  # --- migration-guard.js ---
61
50
  echo ""
@@ -329,7 +318,7 @@ mkdir -p "$TMP/app/admin"
329
318
  echo 'const key = "service_role"; export default function Page() { return <div>{key}</div>; }' > "$TMP/app/admin/page.tsx"
330
319
  OUT=$( (cd "$TMP" && $NODE "$HOOKS_DIR/pre-deploy-gate.js") 2>&1 )
331
320
  RC=$?
332
- assert_exit "regular page.tsx with service_role → blocked (exit 1)" 1 $RC
321
+ assert_exit "regular page.tsx with service_role → blocked (exit 2)" 2 $RC
333
322
  rm -rf "$TMP"
334
323
 
335
324
  # --- session-start.js — must exit 0 always ---
@@ -0,0 +1,217 @@
1
+ #!/bin/bash
2
+ # Qualia Framework — bin/plan-contract.js + bin/agent-runs.js
3
+ # Run: bash tests/lib.test.sh
4
+
5
+ PASS=0
6
+ FAIL=0
7
+ FRAMEWORK_DIR="$(cd "$(dirname "$0")/.." && pwd)"
8
+ NODE="${NODE:-node}"
9
+
10
+ TMP_DIRS=()
11
+ cleanup() {
12
+ for d in "${TMP_DIRS[@]}"; do
13
+ [ -d "$d" ] && rm -rf "$d"
14
+ done
15
+ }
16
+ trap cleanup EXIT
17
+
18
+ mktmp() {
19
+ local t
20
+ t=$(mktemp -d)
21
+ TMP_DIRS+=("$t")
22
+ echo "$t"
23
+ }
24
+
25
+ ok() { echo " ✓ $1"; PASS=$((PASS + 1)); }
26
+ fail() { echo " ✗ $1"; FAIL=$((FAIL + 1)); }
27
+
28
+ echo "lib.test.sh — plan-contract + agent-runs"
29
+
30
+ # ─── plan-contract ──────────────────────────────────────────
31
+
32
+ PC="$FRAMEWORK_DIR/bin/plan-contract.js"
33
+ $NODE --check "$PC" >/dev/null 2>&1 && ok "plan-contract.js parses" || fail "plan-contract.js parse"
34
+
35
+ OUT=$($NODE -e '
36
+ const pc = require("'"$PC"'");
37
+ const good = {
38
+ version: 1, phase: 1, goal: "x", why: "y",
39
+ generated_at: "2026-04-28T12:00:00Z", generated_by: "planner",
40
+ source_plan_hash: "sha256:abc", success_criteria: ["sc"],
41
+ tasks: [{
42
+ id: "T1", title: "t1", wave: 1, depends_on: [],
43
+ files_modify: [], files_create: [], files_delete: [],
44
+ acceptance_criteria: ["ac"], action: "do", context_files: [],
45
+ verification: [{ type: "file-exists", path: "a.ts" }]
46
+ }]
47
+ };
48
+ const errs = pc.validate(good);
49
+ console.log(errs.length === 0 ? "VALID" : "INVALID:" + errs.join(";"));
50
+ ')
51
+ [ "$OUT" = "VALID" ] && ok "validates well-formed contract" || fail "well-formed contract: $OUT"
52
+
53
+ OUT=$($NODE -e '
54
+ const pc = require("'"$PC"'");
55
+ const errs = pc.validate({ version: 2, phase: 0, tasks: [] });
56
+ console.log(errs.length > 0 ? "REJECTED" : "ACCEPTED");
57
+ ')
58
+ [ "$OUT" = "REJECTED" ] && ok "rejects malformed contract" || fail "malformed accepted"
59
+
60
+ OUT=$($NODE -e '
61
+ const pc = require("'"$PC"'");
62
+ const c = {
63
+ version: 1, phase: 1, goal: "x", why: "y",
64
+ generated_at: "t", generated_by: "planner", source_plan_hash: "h",
65
+ success_criteria: ["sc"],
66
+ tasks: [
67
+ { id: "T1", title: "a", wave: 1, depends_on: ["T2"], files_modify: [], files_create: [], files_delete: [],
68
+ acceptance_criteria: ["x"], action: "", context_files: [],
69
+ verification: [{ type: "file-exists", path: "a" }] },
70
+ { id: "T2", title: "b", wave: 1, depends_on: ["T1"], files_modify: [], files_create: [], files_delete: [],
71
+ acceptance_criteria: ["x"], action: "", context_files: [],
72
+ verification: [{ type: "file-exists", path: "b" }] },
73
+ ],
74
+ };
75
+ const errs = pc.validate(c);
76
+ const cycle = errs.some(e => /cycle/.test(e));
77
+ console.log(cycle ? "CYCLE-DETECTED" : "MISSED:" + errs.join(";"));
78
+ ')
79
+ [ "$OUT" = "CYCLE-DETECTED" ] && ok "detects depends_on cycle" || fail "cycle missed: $OUT"
80
+
81
+ OUT=$($NODE -e '
82
+ const pc = require("'"$PC"'");
83
+ const c = {
84
+ version: 1, phase: 1, goal: "x", why: "y",
85
+ generated_at: "t", generated_by: "planner", source_plan_hash: "h",
86
+ success_criteria: ["sc"],
87
+ tasks: [{ id: "T1", title: "a", wave: 1, depends_on: [], files_modify: [], files_create: [], files_delete: [],
88
+ acceptance_criteria: ["x"], action: "", context_files: [],
89
+ verification: [{ type: "command-exit", command: "node && rm", args: [], expected_exit: 0 }] }],
90
+ };
91
+ const errs = pc.validate(c);
92
+ const blocked = errs.some(e => /shell metacharacters/.test(e));
93
+ console.log(blocked ? "BLOCKED" : "ALLOWED");
94
+ ')
95
+ [ "$OUT" = "BLOCKED" ] && ok "blocks shell metacharacters in command-exit" || fail "shell metas allowed: $OUT"
96
+
97
+ OUT=$($NODE -e '
98
+ const pc = require("'"$PC"'");
99
+ const c = {
100
+ version: 1, phase: 1, goal: "x", why: "y",
101
+ generated_at: "t", generated_by: "planner", source_plan_hash: "h",
102
+ success_criteria: ["sc"],
103
+ tasks: [{ id: "T1", title: "a", wave: 1, depends_on: [], files_modify: [], files_create: [], files_delete: [],
104
+ acceptance_criteria: ["x"], action: "", context_files: [],
105
+ verification: [{ type: "behavioral", description: "feel", evidence_required: [] }] }],
106
+ };
107
+ const errs = pc.validate(c);
108
+ const blocked = errs.some(e => /evidence_required/.test(e));
109
+ console.log(blocked ? "BLOCKED" : "ALLOWED");
110
+ ')
111
+ [ "$OUT" = "BLOCKED" ] && ok "rejects behavioral check with empty evidence" || fail "empty evidence allowed: $OUT"
112
+
113
+ # Drift detection
114
+ TMP=$(mktmp)
115
+ $NODE -e '
116
+ const pc = require("'"$PC"'");
117
+ const fs = require("fs"), path = require("path");
118
+ const dir = "'"$TMP"'/.planning";
119
+ fs.mkdirSync(dir, { recursive: true });
120
+ fs.writeFileSync(path.join(dir, "phase-1-plan.md"), "plan v1");
121
+ const h = pc.hashPlan("plan v1");
122
+ fs.writeFileSync(path.join(dir, "phase-1-contract.json"), JSON.stringify({ source_plan_hash: h }));
123
+ const d1 = pc.checkDrift(path.join(dir, "phase-1-contract.json"), path.join(dir, "phase-1-plan.md"));
124
+ console.log(d1.drift ? "FAIL1" : "");
125
+ fs.appendFileSync(path.join(dir, "phase-1-plan.md"), "\nedit\n");
126
+ const d2 = pc.checkDrift(path.join(dir, "phase-1-contract.json"), path.join(dir, "phase-1-plan.md"));
127
+ console.log(d2.drift ? "DRIFT-DETECTED" : "FAIL2");
128
+ ' | tail -1 > /tmp/qfdrift.out
129
+ [ "$(cat /tmp/qfdrift.out)" = "DRIFT-DETECTED" ] && ok "drift detection" || fail "drift detection: $(cat /tmp/qfdrift.out)"
130
+
131
+ # ─── agent-runs ──────────────────────────────────────────
132
+
133
+ AR="$FRAMEWORK_DIR/bin/agent-runs.js"
134
+ $NODE --check "$AR" >/dev/null 2>&1 && ok "agent-runs.js parses" || fail "agent-runs.js parse"
135
+
136
+ TMP=$(mktmp)
137
+ mkdir -p "$TMP/.planning"
138
+ RES=$($NODE -e '
139
+ const ar = require("'"$AR"'");
140
+ const tok = ar.start({ agent_type: "builder", model: "claude-sonnet-4-6", phase: 1, task_id: "T1" });
141
+ const r = ar.finish(tok, { status: "success", commit_sha: "abc", cwd: "'"$TMP"'" });
142
+ console.log(r.written ? "WRITTEN" : "NOT");
143
+ ')
144
+ [ "$RES" = "WRITTEN" ] && ok "agent-runs writes a record" || fail "agent-runs write: $RES"
145
+
146
+ RES=$($NODE -e '
147
+ const ar = require("'"$AR"'");
148
+ const recs = ar.read("'"$TMP"'");
149
+ console.log(recs.length === 1 && recs[0].agent_type === "builder" ? "READ-OK" : "READ-FAIL");
150
+ ')
151
+ [ "$RES" = "READ-OK" ] && ok "agent-runs reads back the record" || fail "read failed: $RES"
152
+
153
+ # Telemetry off → no write
154
+ TMP2=$(mktmp)
155
+ mkdir -p "$TMP2/.planning"
156
+ RES=$(QUALIA_TELEMETRY=off $NODE -e '
157
+ const ar = require("'"$AR"'");
158
+ const tok = ar.start({ agent_type: "verifier", model: "claude-opus-4-7" });
159
+ const r = ar.finish(tok, { status: "success", cwd: "'"$TMP2"'" });
160
+ console.log(r.written ? "WRITTEN" : "SKIPPED");
161
+ ')
162
+ [ "$RES" = "SKIPPED" ] && ok "QUALIA_TELEMETRY=off disables writes" || fail "telemetry off: $RES"
163
+
164
+ # Read still works after opt-out (history not hidden) — synthesize one record then disable
165
+ TMP3=$(mktmp)
166
+ mkdir -p "$TMP3/.planning"
167
+ $NODE -e '
168
+ const ar = require("'"$AR"'");
169
+ const tok = ar.start({ agent_type: "builder", model: "x" });
170
+ ar.finish(tok, { status: "success", cwd: "'"$TMP3"'" });
171
+ ' >/dev/null
172
+ RES=$(QUALIA_TELEMETRY=off $NODE -e '
173
+ const ar = require("'"$AR"'");
174
+ console.log(ar.read("'"$TMP3"'").length);
175
+ ')
176
+ [ "$RES" = "1" ] && ok "QUALIA_TELEMETRY=off does NOT hide existing records" || fail "history hidden: $RES"
177
+
178
+ # Failure log file
179
+ TMP4=$(mktmp)
180
+ mkdir -p "$TMP4/.planning"
181
+ RES=$($NODE -e '
182
+ const ar = require("'"$AR"'");
183
+ const tok = ar.start({ agent_type: "verifier", model: "x" });
184
+ const r = ar.finish(tok, { status: "failure", failure_reason: "tsc-failed",
185
+ failure_detail: "x".repeat(800), full_stderr: "trace lines", cwd: "'"$TMP4"'" });
186
+ console.log(r.log_file ? "LOG:" + r.log_file : "NO-LOG");
187
+ ' )
188
+ echo "$RES" | grep -q '^LOG:' && ok "failure writes side log file" || fail "no log on failure: $RES"
189
+
190
+ # Truncation: must keep tail
191
+ RES=$($NODE -e '
192
+ const ar = require("'"$AR"'");
193
+ const recs = ar.read("'"$TMP4"'");
194
+ const fd = recs[0].failure_detail;
195
+ const allXs = /^x+$/.test(fd) && fd.length === 500;
196
+ console.log(allXs ? "TAIL-KEPT-500" : "WRONG-LEN-OR-CONTENT:" + fd.length);
197
+ ')
198
+ [ "$RES" = "TAIL-KEPT-500" ] && ok "failure_detail keeps tail at 500 chars" || fail "truncation: $RES"
199
+
200
+ # Prune
201
+ TMP5=$(mktmp)
202
+ mkdir -p "$TMP5/.planning"
203
+ $NODE -e '
204
+ const ar = require("'"$AR"'");
205
+ const t1 = ar.start({ agent_type: "builder", model: "x" });
206
+ ar.finish(t1, { status: "success", cwd: "'"$TMP5"'" });
207
+ ' >/dev/null
208
+ RES=$($NODE -e '
209
+ const ar = require("'"$AR"'");
210
+ const r = ar.prune("'"$TMP5"'", "2099-01-01T00:00:00Z");
211
+ console.log("removed:" + r.removed);
212
+ ')
213
+ [ "$RES" = "removed:1" ] && ok "prune --before removes old records" || fail "prune: $RES"
214
+
215
+ echo ""
216
+ echo "lib.test.sh: $PASS passed, $FAIL failed"
217
+ [ "$FAIL" -eq 0 ]