qualia-framework 4.1.1 → 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.
Files changed (43) hide show
  1. package/README.md +15 -11
  2. package/agents/builder.md +28 -0
  3. package/agents/research-synthesizer.md +7 -0
  4. package/bin/agent-runs.js +233 -0
  5. package/bin/cli.js +355 -16
  6. package/bin/install.js +87 -6
  7. package/bin/knowledge-flush.js +164 -0
  8. package/bin/knowledge.js +317 -0
  9. package/bin/plan-contract.js +220 -0
  10. package/bin/state.js +15 -9
  11. package/docs/agent-runs.md +273 -0
  12. package/docs/journey-demo.html +1008 -0
  13. package/docs/plan-contract.md +321 -0
  14. package/docs/reviews/v4.1.0-audit.html +1488 -0
  15. package/docs/reviews/v4.1.0-audit.md +263 -0
  16. package/hooks/auto-update.js +3 -7
  17. package/hooks/git-guardrails.js +167 -0
  18. package/hooks/pre-compact.js +22 -11
  19. package/hooks/pre-deploy-gate.js +16 -2
  20. package/hooks/pre-push.js +22 -2
  21. package/hooks/stop-session-log.js +180 -0
  22. package/package.json +8 -2
  23. package/skills/qualia-build/SKILL.md +5 -5
  24. package/skills/qualia-debug/SKILL.md +1 -1
  25. package/skills/qualia-design/SKILL.md +15 -0
  26. package/skills/qualia-flush/SKILL.md +200 -0
  27. package/skills/qualia-learn/SKILL.md +47 -37
  28. package/skills/qualia-new/SKILL.md +1 -1
  29. package/skills/qualia-plan/SKILL.md +3 -2
  30. package/skills/qualia-postmortem/SKILL.md +238 -0
  31. package/skills/qualia-quick/SKILL.md +1 -1
  32. package/skills/qualia-report/SKILL.md +1 -1
  33. package/skills/qualia-review/SKILL.md +3 -2
  34. package/skills/qualia-ship/SKILL.md +12 -10
  35. package/skills/qualia-verify/SKILL.md +60 -0
  36. package/templates/help.html +13 -7
  37. package/templates/knowledge/agents.md +71 -0
  38. package/templates/knowledge/index.md +47 -0
  39. package/tests/bin.test.sh +322 -12
  40. package/tests/hooks.test.sh +131 -20
  41. package/tests/lib.test.sh +217 -0
  42. package/tests/runner.js +103 -77
  43. package/tests/state.test.sh +4 -3
@@ -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 ]