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.
- package/README.md +15 -11
- package/agents/builder.md +28 -0
- package/agents/research-synthesizer.md +7 -0
- package/bin/agent-runs.js +233 -0
- package/bin/cli.js +355 -16
- package/bin/install.js +87 -6
- package/bin/knowledge-flush.js +164 -0
- package/bin/knowledge.js +317 -0
- package/bin/plan-contract.js +220 -0
- package/bin/state.js +15 -9
- package/docs/agent-runs.md +273 -0
- package/docs/journey-demo.html +1008 -0
- package/docs/plan-contract.md +321 -0
- package/docs/reviews/v4.1.0-audit.html +1488 -0
- package/docs/reviews/v4.1.0-audit.md +263 -0
- package/hooks/auto-update.js +3 -7
- package/hooks/git-guardrails.js +167 -0
- package/hooks/pre-compact.js +22 -11
- package/hooks/pre-deploy-gate.js +16 -2
- package/hooks/pre-push.js +22 -2
- package/hooks/stop-session-log.js +180 -0
- package/package.json +8 -2
- package/skills/qualia-build/SKILL.md +5 -5
- package/skills/qualia-debug/SKILL.md +1 -1
- package/skills/qualia-design/SKILL.md +15 -0
- package/skills/qualia-flush/SKILL.md +200 -0
- package/skills/qualia-learn/SKILL.md +47 -37
- package/skills/qualia-new/SKILL.md +1 -1
- package/skills/qualia-plan/SKILL.md +3 -2
- package/skills/qualia-postmortem/SKILL.md +238 -0
- package/skills/qualia-quick/SKILL.md +1 -1
- package/skills/qualia-report/SKILL.md +1 -1
- package/skills/qualia-review/SKILL.md +3 -2
- package/skills/qualia-ship/SKILL.md +12 -10
- package/skills/qualia-verify/SKILL.md +60 -0
- package/templates/help.html +13 -7
- package/templates/knowledge/agents.md +71 -0
- package/templates/knowledge/index.md +47 -0
- package/tests/bin.test.sh +322 -12
- package/tests/hooks.test.sh +131 -20
- package/tests/lib.test.sh +217 -0
- package/tests/runner.js +103 -77
- 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 ]
|