qualia-framework 6.2.7 → 6.2.10

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 (76) hide show
  1. package/README.md +18 -11
  2. package/agents/builder.md +7 -7
  3. package/agents/planner.md +39 -3
  4. package/agents/research-synthesizer.md +1 -1
  5. package/agents/researcher.md +3 -3
  6. package/agents/roadmapper.md +7 -7
  7. package/agents/verifier.md +18 -6
  8. package/agents/visual-evaluator.md +8 -7
  9. package/bin/cli.js +111 -14
  10. package/bin/codex-goal.js +92 -0
  11. package/bin/contract-runner.js +219 -0
  12. package/bin/host-adapters.js +66 -0
  13. package/bin/install.js +171 -124
  14. package/bin/plan-contract.js +99 -2
  15. package/bin/planning-hygiene.js +262 -0
  16. package/bin/runtime-manifest.js +32 -0
  17. package/bin/state-ledger.js +184 -0
  18. package/bin/state.js +299 -20
  19. package/bin/trust-score.js +276 -0
  20. package/docs/onboarding.html +5 -4
  21. package/guide.md +3 -2
  22. package/hooks/pre-deploy-gate.js +27 -0
  23. package/hooks/pre-push.js +19 -0
  24. package/package.json +1 -1
  25. package/qualia-design/design-rubric.md +17 -5
  26. package/qualia-design/frontend.md +5 -1
  27. package/qualia-design/graphics.md +47 -0
  28. package/rules/codex-goal.md +46 -0
  29. package/rules/command-output.md +35 -0
  30. package/skills/qualia/SKILL.md +10 -10
  31. package/skills/qualia-build/SKILL.md +24 -14
  32. package/skills/qualia-debug/SKILL.md +16 -8
  33. package/skills/qualia-discuss/SKILL.md +10 -10
  34. package/skills/qualia-doctor/SKILL.md +140 -0
  35. package/skills/qualia-feature/SKILL.md +27 -21
  36. package/skills/qualia-fix/SKILL.md +216 -0
  37. package/skills/qualia-flush/SKILL.md +9 -9
  38. package/skills/qualia-handoff/SKILL.md +9 -9
  39. package/skills/qualia-help/SKILL.md +3 -3
  40. package/skills/qualia-hook-gen/SKILL.md +1 -1
  41. package/skills/qualia-idk/SKILL.md +4 -4
  42. package/skills/qualia-issues/SKILL.md +2 -2
  43. package/skills/qualia-learn/SKILL.md +10 -10
  44. package/skills/qualia-map/SKILL.md +2 -2
  45. package/skills/qualia-milestone/SKILL.md +15 -15
  46. package/skills/qualia-new/REFERENCE.md +9 -9
  47. package/skills/qualia-new/SKILL.md +14 -14
  48. package/skills/qualia-optimize/REFERENCE.md +1 -1
  49. package/skills/qualia-optimize/SKILL.md +23 -16
  50. package/skills/qualia-pause/SKILL.md +2 -2
  51. package/skills/qualia-plan/SKILL.md +27 -13
  52. package/skills/qualia-polish/REFERENCE.md +14 -14
  53. package/skills/qualia-polish/SKILL.md +64 -19
  54. package/skills/qualia-polish/scripts/loop.mjs +3 -3
  55. package/skills/qualia-polish/scripts/score.mjs +9 -3
  56. package/skills/qualia-postmortem/SKILL.md +9 -9
  57. package/skills/qualia-report/SKILL.md +23 -23
  58. package/skills/qualia-research/SKILL.md +5 -5
  59. package/skills/qualia-resume/SKILL.md +4 -4
  60. package/skills/qualia-review/SKILL.md +28 -12
  61. package/skills/qualia-road/SKILL.md +18 -5
  62. package/skills/qualia-ship/SKILL.md +22 -22
  63. package/skills/qualia-skill-new/SKILL.md +13 -13
  64. package/skills/qualia-test/SKILL.md +5 -5
  65. package/skills/qualia-triage/SKILL.md +1 -1
  66. package/skills/qualia-verify/SKILL.md +37 -23
  67. package/skills/qualia-vibe/SKILL.md +13 -10
  68. package/skills/qualia-vibe/scripts/extract.mjs +1 -1
  69. package/skills/zoho-workflow/SKILL.md +1 -1
  70. package/templates/help.html +12 -10
  71. package/tests/bin.test.sh +35 -5
  72. package/tests/install-smoke.test.sh +23 -3
  73. package/tests/lib.test.sh +290 -0
  74. package/tests/runner.js +3 -0
  75. package/tests/skills.test.sh +4 -4
  76. package/tests/state.test.sh +65 -3
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env node
2
+ // codex-goal — suggest a Codex /goal objective + token budget from current
3
+ // .planning/STATE.md + ROADMAP.md state.
4
+ //
5
+ // Output is a single line meant to be pasted into a Codex session, or quoted
6
+ // in an agent prompt that will then issue update_goal itself. The framework
7
+ // does NOT write directly to ~/.codex/goals_1.sqlite — Codex owns the schema
8
+ // and the thread_id; the /goal slash command (and the update_goal tool the
9
+ // model can call) are the stable interfaces.
10
+ //
11
+ // Usage: node bin/codex-goal.js [phase|task|feature|quick]
12
+ // default scope = phase
13
+
14
+ const fs = require("fs");
15
+ const path = require("path");
16
+
17
+ const SCOPE = (process.argv[2] || "phase").toLowerCase();
18
+
19
+ // Token budgets are calibrated to Codex's per-thread context window and the
20
+ // typical Qualia work-unit size. Phase = full builder loop with verification.
21
+ // Task/feature = single fresh-context spawn. Quick = one-shot inline edit.
22
+ const TOKEN_BUDGETS = {
23
+ phase: 80000,
24
+ task: 30000,
25
+ feature: 30000,
26
+ quick: 10000,
27
+ };
28
+
29
+ const PLANNING = path.join(process.cwd(), ".planning");
30
+ const STATE_FILE = path.join(PLANNING, "STATE.md");
31
+ const ROADMAP_FILE = path.join(PLANNING, "ROADMAP.md");
32
+
33
+ function readSafe(p) {
34
+ try { return fs.readFileSync(p, "utf8"); } catch { return ""; }
35
+ }
36
+
37
+ function parseState(content) {
38
+ if (!content) return null;
39
+ const phaseMatch = content.match(/^Phase:\s*(\d+)\s+of\s+(\d+)\s*[—-]\s*(.+?)\r?$/m);
40
+ if (!phaseMatch) return null;
41
+ return {
42
+ phase: parseInt(phaseMatch[1], 10),
43
+ total: parseInt(phaseMatch[2], 10),
44
+ name: phaseMatch[3].trim(),
45
+ };
46
+ }
47
+
48
+ function readPhaseGoal(roadmap, phaseNum) {
49
+ if (!roadmap) return "";
50
+ // Match a heading like "## Phase 2 — Name" or "## Phase 2: Name", then
51
+ // grab the "Goal:" line that follows.
52
+ const heading = new RegExp(`##\\s*Phase\\s*${phaseNum}\\s*[—:\\-]\\s*[^\\n]+`, "i");
53
+ const idx = roadmap.search(heading);
54
+ if (idx === -1) return "";
55
+ const body = roadmap.slice(idx, idx + 2000);
56
+ const goalMatch = body.match(/^\s*\*?\*?Goal:?\*?\*?\s*(.+?)$/im);
57
+ return goalMatch ? goalMatch[1].trim() : "";
58
+ }
59
+
60
+ function clip(s, max) {
61
+ return s.length <= max ? s : s.slice(0, max - 1).trimEnd() + "…";
62
+ }
63
+
64
+ function main() {
65
+ const budget = TOKEN_BUDGETS[SCOPE];
66
+ if (budget == null) {
67
+ process.stderr.write(`unknown scope: ${SCOPE} (use phase|task|feature|quick)\n`);
68
+ process.exit(2);
69
+ }
70
+
71
+ const state = parseState(readSafe(STATE_FILE));
72
+ if (!state) {
73
+ // No .planning/STATE.md — fall back to a generic shape so the helper
74
+ // still emits something useful for ad-hoc invocations.
75
+ const objective = `${SCOPE[0].toUpperCase() + SCOPE.slice(1)} in ${path.basename(process.cwd())}`;
76
+ process.stdout.write(`/goal ${objective}\n`);
77
+ process.stdout.write(`# token_budget suggestion: ${budget}\n`);
78
+ return;
79
+ }
80
+
81
+ const phaseGoal = readPhaseGoal(readSafe(ROADMAP_FILE), state.phase);
82
+ const objectivePrefix = `Phase ${state.phase}/${state.total} — ${state.name}`;
83
+ const objective = clip(
84
+ phaseGoal ? `${objectivePrefix}: ${phaseGoal}` : objectivePrefix,
85
+ 280
86
+ );
87
+
88
+ process.stdout.write(`/goal ${objective}\n`);
89
+ process.stdout.write(`# token_budget suggestion: ${budget}\n`);
90
+ }
91
+
92
+ main();
@@ -0,0 +1,219 @@
1
+ #!/usr/bin/env node
2
+ // Execute Qualia phase contract checks and write evidence.
3
+ // No shell interpolation: command checks run through spawnSync(argv).
4
+
5
+ const fs = require("fs");
6
+ const path = require("path");
7
+ const { spawnSync } = require("child_process");
8
+ const pc = require("./plan-contract.js");
9
+
10
+ function parseArgs(argv) {
11
+ const args = { _: [] };
12
+ for (let i = 2; i < argv.length; i++) {
13
+ const a = argv[i];
14
+ if (a === "--json") args.json = true;
15
+ else if (a === "--no-write") args.no_write = true;
16
+ else if (a === "--cwd") args.cwd = argv[++i];
17
+ else if (a.startsWith("--cwd=")) args.cwd = a.slice("--cwd=".length);
18
+ else args._.push(a);
19
+ }
20
+ return args;
21
+ }
22
+
23
+ function usage() {
24
+ console.error([
25
+ "Usage:",
26
+ " contract-runner.js <contract.json> [--cwd DIR] [--json] [--no-write]",
27
+ "",
28
+ "Runs file-exists, grep-match, command-exit, and behavioral evidence checks.",
29
+ ].join("\n"));
30
+ }
31
+
32
+ function rel(root, p) {
33
+ return path.resolve(root, p);
34
+ }
35
+
36
+ function checkFileExists(root, check) {
37
+ const file = rel(root, check.path);
38
+ if (!fs.existsSync(file)) return { ok: false, detail: `missing file: ${check.path}` };
39
+ if (check.must_contain != null) {
40
+ const content = fs.readFileSync(file, "utf8");
41
+ if (!content.includes(check.must_contain)) {
42
+ return { ok: false, detail: `file does not contain required text: ${check.path}` };
43
+ }
44
+ }
45
+ return { ok: true };
46
+ }
47
+
48
+ function checkGrepMatch(root, check) {
49
+ const file = rel(root, check.path);
50
+ if (!fs.existsSync(file)) return { ok: false, detail: `missing file: ${check.path}` };
51
+ const content = fs.readFileSync(file, "utf8");
52
+ const re = new RegExp(check.pattern);
53
+ const present = re.test(content);
54
+ if (check.expect === "present" && !present) return { ok: false, detail: `pattern absent: ${check.pattern}` };
55
+ if (check.expect === "absent" && present) return { ok: false, detail: `pattern present: ${check.pattern}` };
56
+ return { ok: true };
57
+ }
58
+
59
+ function checkCommandExit(root, check) {
60
+ const started = Date.now();
61
+ const r = spawnSync(check.command, check.args || [], {
62
+ cwd: root,
63
+ encoding: "utf8",
64
+ timeout: check.timeout_ms || 30_000,
65
+ stdio: ["ignore", "pipe", "pipe"],
66
+ shell: false,
67
+ });
68
+ const status = typeof r.status === "number" ? r.status : 1;
69
+ if (status !== check.expected_exit) {
70
+ return {
71
+ ok: false,
72
+ detail: `exit ${status}, expected ${check.expected_exit}`,
73
+ duration_ms: Date.now() - started,
74
+ stdout: (r.stdout || "").slice(-1000),
75
+ stderr: (r.stderr || r.error?.message || "").slice(-1000),
76
+ };
77
+ }
78
+ if (check.expect_stdout_match != null) {
79
+ const re = new RegExp(check.expect_stdout_match);
80
+ if (!re.test(r.stdout || "")) {
81
+ return {
82
+ ok: false,
83
+ detail: `stdout did not match: ${check.expect_stdout_match}`,
84
+ duration_ms: Date.now() - started,
85
+ stdout: (r.stdout || "").slice(-1000),
86
+ stderr: (r.stderr || "").slice(-1000),
87
+ };
88
+ }
89
+ }
90
+ return {
91
+ ok: true,
92
+ duration_ms: Date.now() - started,
93
+ stdout: (r.stdout || "").slice(-1000),
94
+ stderr: (r.stderr || "").slice(-1000),
95
+ };
96
+ }
97
+
98
+ function checkBehavioral(root, check) {
99
+ for (const ev of check.evidence_required || []) {
100
+ const file = rel(root, ev.path);
101
+ if (!fs.existsSync(file)) {
102
+ return { ok: false, detail: `missing evidence file: ${ev.path}` };
103
+ }
104
+ if (ev.matcher != null) {
105
+ const content = fs.readFileSync(file, "utf8");
106
+ const re = new RegExp(ev.matcher);
107
+ if (!re.test(content)) {
108
+ return { ok: false, detail: `evidence matcher failed for ${ev.path}: ${ev.matcher}` };
109
+ }
110
+ }
111
+ }
112
+ return { ok: true };
113
+ }
114
+
115
+ function runCheck(root, check) {
116
+ try {
117
+ if (check.type === "file-exists") return checkFileExists(root, check);
118
+ if (check.type === "grep-match") return checkGrepMatch(root, check);
119
+ if (check.type === "command-exit") return checkCommandExit(root, check);
120
+ if (check.type === "behavioral") return checkBehavioral(root, check);
121
+ return { ok: false, detail: `unknown check type: ${check.type}` };
122
+ } catch (e) {
123
+ return { ok: false, detail: e.message };
124
+ }
125
+ }
126
+
127
+ function writeEvidence(root, contract, result) {
128
+ const dir = path.join(root, ".planning", "evidence");
129
+ fs.mkdirSync(dir, { recursive: true });
130
+ const phase = Number(contract.phase || 0) || "unknown";
131
+ const file = path.join(dir, `phase-${phase}-contract-run.json`);
132
+ fs.writeFileSync(file, JSON.stringify(result, null, 2) + "\n");
133
+ return path.relative(root, file);
134
+ }
135
+
136
+ function runContract(contract, opts = {}) {
137
+ const root = path.resolve(opts.cwd || process.cwd());
138
+ const errors = pc.validate(contract);
139
+ if (errors.length > 0) {
140
+ return {
141
+ ok: false,
142
+ error: "CONTRACT_INVALID",
143
+ errors,
144
+ checked: 0,
145
+ failed: errors.length,
146
+ results: [],
147
+ };
148
+ }
149
+
150
+ const results = [];
151
+ for (const task of contract.tasks || []) {
152
+ for (let i = 0; i < (task.verification || []).length; i++) {
153
+ const check = task.verification[i];
154
+ const r = runCheck(root, check);
155
+ results.push({
156
+ task_id: task.id,
157
+ task_title: task.title,
158
+ index: i,
159
+ type: check.type,
160
+ ok: !!r.ok,
161
+ detail: r.detail || "",
162
+ duration_ms: r.duration_ms,
163
+ stdout: r.stdout,
164
+ stderr: r.stderr,
165
+ });
166
+ }
167
+ }
168
+ const failed = results.filter((r) => !r.ok).length;
169
+ const payload = {
170
+ ok: failed === 0,
171
+ phase: contract.phase,
172
+ goal: contract.goal,
173
+ checked: results.length,
174
+ failed,
175
+ generated_at: new Date().toISOString(),
176
+ results,
177
+ };
178
+ if (!opts.no_write) payload.evidence_file = writeEvidence(root, contract, payload);
179
+ return payload;
180
+ }
181
+
182
+ function main(argv) {
183
+ const args = parseArgs(argv);
184
+ const contractPath = args._[0];
185
+ if (!contractPath || contractPath === "--help" || contractPath === "-h") {
186
+ usage();
187
+ return 2;
188
+ }
189
+ const loaded = pc.readContractFile(contractPath);
190
+ if (!loaded.ok) {
191
+ const payload = { ok: false, ...loaded, path: contractPath };
192
+ if (args.json) console.log(JSON.stringify(payload, null, 2));
193
+ else console.error(`${payload.error}: ${payload.message}`);
194
+ return 2;
195
+ }
196
+ const result = runContract(loaded.contract, {
197
+ cwd: args.cwd,
198
+ no_write: args.no_write,
199
+ });
200
+ if (args.json) {
201
+ console.log(JSON.stringify(result, null, 2));
202
+ } else if (result.ok) {
203
+ console.log(`PASS phase ${result.phase}: ${result.checked} check(s)`);
204
+ if (result.evidence_file) console.log(`Evidence: ${result.evidence_file}`);
205
+ } else {
206
+ console.error(`FAIL phase ${result.phase || "?"}: ${result.failed} of ${result.checked || result.failed} check(s) failed`);
207
+ for (const r of result.results || []) {
208
+ if (!r.ok) console.error(`- ${r.task_id} ${r.type}: ${r.detail}`);
209
+ }
210
+ if (result.error) for (const e of result.errors || []) console.error(`- ${e}`);
211
+ }
212
+ return result.ok ? 0 : 1;
213
+ }
214
+
215
+ module.exports = { runContract, runCheck };
216
+
217
+ if (require.main === module) {
218
+ process.exit(main(process.argv));
219
+ }
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env node
2
+ // Host adapter rendering for installed Qualia text surfaces.
3
+
4
+ const path = require("path");
5
+ const os = require("os");
6
+
7
+ const HOSTS = {
8
+ claude: {
9
+ name: "Claude Code",
10
+ home: path.join(os.homedir(), ".claude"),
11
+ },
12
+ codex: {
13
+ name: "OpenAI Codex",
14
+ home: path.join(os.homedir(), ".codex"),
15
+ },
16
+ };
17
+
18
+ function adapter(name) {
19
+ const host = HOSTS[name];
20
+ if (!host) throw new Error(`Unknown Qualia host adapter: ${name}`);
21
+ const home = host.home;
22
+ return {
23
+ ...host,
24
+ tokens: {
25
+ QUALIA_HOME: home,
26
+ QUALIA_BIN: `${home}/bin`,
27
+ QUALIA_AGENTS: `${home}/agents`,
28
+ QUALIA_SKILLS: `${home}/skills`,
29
+ QUALIA_RULES: `${home}/rules`,
30
+ QUALIA_TEMPLATES: `${home}/qualia-templates`,
31
+ QUALIA_KNOWLEDGE: `${home}/knowledge`,
32
+ QUALIA_REFERENCES: `${home}/qualia-references`,
33
+ QUALIA_DESIGN: `${home}/qualia-design`,
34
+ },
35
+ };
36
+ }
37
+
38
+ function renderText(content, hostName) {
39
+ const host = adapter(hostName);
40
+ let out = String(content);
41
+ for (const [token, value] of Object.entries(host.tokens)) {
42
+ out = out.replaceAll(`\${${token}}`, value);
43
+ }
44
+
45
+ // Backward-compatible rendering while source files migrate from hardcoded
46
+ // Claude paths to explicit ${QUALIA_*} tokens.
47
+ out = out
48
+ .replaceAll("~/.claude/", `${host.home}/`)
49
+ .replaceAll("$HOME/.claude/", `${host.home}/`)
50
+ .replaceAll("${HOME}/.claude/", `${host.home}/`)
51
+ .replaceAll("@~/.claude/", `@${host.home}/`)
52
+ .replaceAll(".claude/", `${path.basename(host.home)}/`);
53
+
54
+ if (hostName === "codex") {
55
+ out = out
56
+ .replaceAll("Claude Code", "Codex")
57
+ .replaceAll("Claude's", "Codex's");
58
+ }
59
+ return out;
60
+ }
61
+
62
+ module.exports = {
63
+ HOSTS,
64
+ adapter,
65
+ renderText,
66
+ };