qualia-framework 6.5.0 → 6.7.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 +3 -3
- package/agents/roadmapper.md +1 -1
- package/bin/auto-report.js +156 -0
- package/bin/command-surface.js +3 -1
- package/bin/erp-retry.js +4 -2
- package/bin/report-payload.js +5 -0
- package/bin/state.js +727 -1
- package/guide.md +1 -1
- package/hooks/stop-session-log.js +15 -0
- package/package.json +1 -1
- package/rules/codex-goal.md +1 -1
- package/rules/one-opinion.md +1 -1
- package/skills/qualia/SKILL.md +2 -0
- package/skills/qualia-discuss/SKILL.md +2 -0
- package/skills/qualia-idk/SKILL.md +2 -2
- package/skills/qualia-milestone/SKILL.md +1 -1
- package/skills/qualia-new/SKILL.md +16 -12
- package/skills/qualia-plan/SKILL.md +2 -2
- package/skills/qualia-research/SKILL.md +1 -1
- package/skills/qualia-road/SKILL.md +2 -2
- package/skills/qualia-scope/SKILL.md +86 -4
- package/skills/qualia-ship/SKILL.md +8 -1
- package/templates/CONTEXT.md +1 -1
- package/templates/phase-context.md +1 -1
- package/templates/planning.gitignore +10 -0
- package/templates/project-discovery.md +1 -1
- package/templates/projects/ai-agent.md +2 -2
- package/templates/projects/mobile-app.md +2 -2
- package/templates/projects/voice-agent.md +1 -1
- package/templates/projects/website.md +1 -1
- package/tests/auto-report.test.sh +158 -0
- package/tests/bin.test.sh +8 -8
- package/tests/run-all.sh +1 -0
- package/tests/state.test.sh +176 -0
package/README.md
CHANGED
|
@@ -75,7 +75,7 @@ Two human gates per project. One halt case (gap-cycle limit exceeded on a failin
|
|
|
75
75
|
### Phase-specific depth (optional)
|
|
76
76
|
|
|
77
77
|
```
|
|
78
|
-
/qualia-
|
|
78
|
+
/qualia-scope N # Capture decisions before planning a complex phase (locks constraints for the planner)
|
|
79
79
|
/qualia-research N # Deep-research a niche phase (Context7/WebFetch/WebSearch)
|
|
80
80
|
/qualia-map # Map existing codebase (brownfield projects — run before /qualia-new)
|
|
81
81
|
```
|
|
@@ -137,7 +137,7 @@ Project
|
|
|
137
137
|
|
|
138
138
|
## What's Inside (v6.3.0)
|
|
139
139
|
|
|
140
|
-
- **
|
|
140
|
+
- **25 installed skills**, focused into Road (new / plan / build / verify / milestone / polish / ship / handoff / report), depth (scope, research, map), navigation (qualia router + road), quality (fix, review, optimize with `--deepen` parallel-interface design, feature, test), design (`qualia-polish --loop` and `--vibe`), health/reporting (doctor, learn, postmortem), and Zoho workflow support. Retired helper commands are pruned on install rather than exposed as default slash commands.
|
|
141
141
|
- **9 agents** (each runs in fresh context): planner, builder, verifier, qa-browser, researcher, research-synthesizer, roadmapper, plan-checker, visual-evaluator
|
|
142
142
|
- **12 hooks** (pure Node.js, cross-platform): session-start, auto-update, git-guardrails, branch-guard, pre-push tracking stamp, migration-guard, pre-deploy-gate, stop-session-log, fawzi-approval-guard, vercel-account-guard, env-empty-guard, supabase-destructive-guard
|
|
143
143
|
- **10 installed rules** (`rules/`): grounding, security, infrastructure, deployment, speed, architecture, trust-boundary, codex-goal, one-opinion, and always-on command-output transparency.
|
|
@@ -214,7 +214,7 @@ npx qualia-framework@latest install
|
|
|
214
214
|
|
|
|
215
215
|
v
|
|
216
216
|
~/.claude/ and/or ~/.codex/
|
|
217
|
-
├── skills/
|
|
217
|
+
├── skills/ 25 installed skills (each may ship SKILL.md + REFERENCE.md + scripts/ + fixtures/)
|
|
218
218
|
├── agents/ 9 agent definitions (Claude .md, Codex .toml)
|
|
219
219
|
├── hooks/ 11 Node.js hooks — cross-platform (no bash dependency)
|
|
220
220
|
├── bin/ state.js + qualia-ui.js + statusline.js + knowledge.js + knowledge-flush.js + slop-detect.mjs + planning-hygiene.js + plan-contract.js + agent-runs.js + ERP/report helpers
|
package/agents/roadmapper.md
CHANGED
|
@@ -23,7 +23,7 @@ You do NOT run research — that's already done upstream.
|
|
|
23
23
|
Per `rules/grounding.md`. JOURNEY.md / REQUIREMENTS.md / ROADMAP.md become canon for every downstream agent. Anything you write becomes the source of truth — fabrications calcify into requirements:
|
|
24
24
|
|
|
25
25
|
1. **Every milestone scope ties back to PROJECT.md or research.** Format inline: `{milestone scope sentence} — [PROJECT.md §Goals]` or `[research/SUMMARY.md §Architecture]`. Sections without attribution will be rejected by the user during the journey-approval gate.
|
|
26
|
-
2. **Every REQ-ID has a source line.** REQ-001 cites the line in PROJECT.md or research where the requirement originated. No `/qualia-
|
|
26
|
+
2. **Every REQ-ID has a source line.** REQ-001 cites the line in PROJECT.md or research where the requirement originated. No `/qualia-scope` loop should be needed to ask "where did REQ-007 come from?" later.
|
|
27
27
|
3. **Don't invent features that weren't asked for.** If PROJECT.md mentions "user dashboard" and you derive 8 dashboard sub-requirements, cite which user need each one addresses. If you can't, drop it.
|
|
28
28
|
4. **No hedging in milestone names or success criteria.** "M2 might include payments" → either it does (commit to it with citation) or it goes in `Out of Scope`. Roadmaps with hedge language produce hedge plans.
|
|
29
29
|
5. **Tool-use is mandatory before challenging an assumption.** Before saying "the tech stack should be Next.js," `Read` PROJECT.md to confirm whether the user already chose. The user's preferences override your taste.
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// ~/.claude/bin/auto-report.js — B1 auto-capture (framework side).
|
|
3
|
+
//
|
|
4
|
+
// Fires at SHIP TIME: when a Qualia project's tracking.json reaches
|
|
5
|
+
// status `shipped`, POST a session report to the ERP tagged `source: "auto"`,
|
|
6
|
+
// so the ERP reflects real shipped work without anyone running /qualia-report.
|
|
7
|
+
//
|
|
8
|
+
// Design (mirrors the constraints learned the hard way):
|
|
9
|
+
// • Ship-time, NOT per-turn. The Stop hook fires every turn; this guards on
|
|
10
|
+
// status===shipped + a per-shipped-unit dedupe marker, so it POSTs exactly
|
|
11
|
+
// ONCE per shipped (milestone, phase) — never a per-turn spam stream.
|
|
12
|
+
// • Fail-soft. Never throws, never blocks. On any upload failure it enqueues
|
|
13
|
+
// to the existing erp-retry queue (drained by session-start) and exits 0.
|
|
14
|
+
// • One ERP-upload seam. Reuses erp-retry's postOnce/enqueue/config/key
|
|
15
|
+
// readers and report-payload's buildPayload — no duplicated contract.
|
|
16
|
+
// • No double-posting. The dedupe marker means re-running on the same shipped
|
|
17
|
+
// unit is a no-op; the ERP also UPSERTs on (project_id, client_report_id).
|
|
18
|
+
//
|
|
19
|
+
// Invoked fire-and-forget (detached) by hooks/stop-session-log.js, or directly:
|
|
20
|
+
// node auto-report.js # run the guarded auto-report for cwd
|
|
21
|
+
// SOURCE handled internally as "auto"; set DRY_RUN=1 to mark the report dry.
|
|
22
|
+
|
|
23
|
+
const fs = require("fs");
|
|
24
|
+
const os = require("os");
|
|
25
|
+
const path = require("path");
|
|
26
|
+
const crypto = require("crypto");
|
|
27
|
+
const { spawnSync } = require("child_process");
|
|
28
|
+
const { buildPayload } = require("./report-payload.js");
|
|
29
|
+
const { enqueue, postOnce, readApiKey, readConfig } = require("./erp-retry.js");
|
|
30
|
+
|
|
31
|
+
function qualiaHome(home = os.homedir()) {
|
|
32
|
+
if (process.env.QUALIA_HOME) return process.env.QUALIA_HOME;
|
|
33
|
+
const parent = path.basename(path.dirname(__dirname));
|
|
34
|
+
if (parent === ".codex" || parent === ".claude") return path.dirname(__dirname);
|
|
35
|
+
return path.join(home, ".claude");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function readJson(file) {
|
|
39
|
+
try {
|
|
40
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function markerFile(home, projectKey) {
|
|
47
|
+
const safe = String(projectKey || "project").replace(/[^a-zA-Z0-9._-]+/g, "-").slice(0, 80);
|
|
48
|
+
return path.join(qualiaHome(home), `.qualia-auto-report-${safe}.json`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function erpUrl(cfg) {
|
|
52
|
+
const base = (cfg && cfg.erp && cfg.erp.url) || "https://portal.qualiasolutions.net";
|
|
53
|
+
return base.replace(/\/+$/, "") + "/api/v1/reports";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function allocateReportId(cwd) {
|
|
57
|
+
// Sequential QS-REPORT-NN via state.js (the same allocator /qualia-report uses).
|
|
58
|
+
try {
|
|
59
|
+
const r = spawnSync("node", [path.join(__dirname, "state.js"), "next-report-id"], {
|
|
60
|
+
cwd,
|
|
61
|
+
encoding: "utf8",
|
|
62
|
+
timeout: 4000,
|
|
63
|
+
});
|
|
64
|
+
if (r.status === 0 && r.stdout) {
|
|
65
|
+
const parsed = JSON.parse(r.stdout);
|
|
66
|
+
if (parsed && parsed.report_id) return parsed.report_id;
|
|
67
|
+
}
|
|
68
|
+
} catch {}
|
|
69
|
+
return "";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// The single decision + action. Returns a small status object; never throws.
|
|
73
|
+
async function maybeAutoReport({ cwd = process.cwd(), home = os.homedir(), env = process.env } = {}) {
|
|
74
|
+
try {
|
|
75
|
+
// Guard 1 — ERP configured. No key / disabled → silent no-op.
|
|
76
|
+
const cfg = readConfig();
|
|
77
|
+
if (cfg && cfg.erp && cfg.erp.enabled === false) return { skipped: "erp-disabled" };
|
|
78
|
+
const apiKey = readApiKey();
|
|
79
|
+
if (!apiKey) return { skipped: "no-key" };
|
|
80
|
+
|
|
81
|
+
// Guard 2 — Qualia project at SHIP time only.
|
|
82
|
+
const tracking = readJson(path.join(cwd, ".planning", "tracking.json"));
|
|
83
|
+
if (!tracking) return { skipped: "no-project" };
|
|
84
|
+
if (String(tracking.status) !== "shipped") return { skipped: "not-shipped" };
|
|
85
|
+
|
|
86
|
+
// Guard 3 — dedupe: one report per shipped (milestone, phase).
|
|
87
|
+
const projectKey =
|
|
88
|
+
tracking.project_id ||
|
|
89
|
+
tracking.project ||
|
|
90
|
+
path.basename(cwd);
|
|
91
|
+
const unit = `${tracking.milestone || 1}:${tracking.phase || 0}:shipped`;
|
|
92
|
+
const mFile = markerFile(home, projectKey);
|
|
93
|
+
const marker = readJson(mFile) || {};
|
|
94
|
+
if (marker.last === unit) return { skipped: "already-reported", unit };
|
|
95
|
+
|
|
96
|
+
// Allocate a sequential client_report_id (the ERP dedupe key).
|
|
97
|
+
const clientReportId = allocateReportId(cwd);
|
|
98
|
+
const idempotencyKey = crypto.randomUUID();
|
|
99
|
+
const payload = buildPayload({
|
|
100
|
+
cwd,
|
|
101
|
+
home,
|
|
102
|
+
env: { ...env, SOURCE: "auto", CLIENT_REPORT_ID: clientReportId },
|
|
103
|
+
});
|
|
104
|
+
const body = JSON.stringify(payload);
|
|
105
|
+
const url = erpUrl(cfg);
|
|
106
|
+
|
|
107
|
+
const result = await postOnce(
|
|
108
|
+
{ url, payload: body, idempotency_key: idempotencyKey },
|
|
109
|
+
apiKey,
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const writeMarker = (extra) => {
|
|
113
|
+
try {
|
|
114
|
+
fs.writeFileSync(
|
|
115
|
+
mFile,
|
|
116
|
+
JSON.stringify({ last: unit, client_report_id: clientReportId, at: new Date().toISOString(), ...extra }, null, 2),
|
|
117
|
+
{ mode: 0o600 },
|
|
118
|
+
);
|
|
119
|
+
} catch {}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
if (result.code === "200") {
|
|
123
|
+
writeMarker({ posted: true });
|
|
124
|
+
return { posted: clientReportId, unit };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Any non-200 → enqueue for the retry queue (session-start drains it).
|
|
128
|
+
// Mark the unit so we don't re-allocate a new id on the next turn; the
|
|
129
|
+
// queued item carries this client_report_id and the ERP dedupes on it.
|
|
130
|
+
try {
|
|
131
|
+
enqueue({
|
|
132
|
+
client_report_id: clientReportId,
|
|
133
|
+
idempotency_key: idempotencyKey,
|
|
134
|
+
url,
|
|
135
|
+
payload: body,
|
|
136
|
+
last_error: result.error ? `network: ${result.error}` : `HTTP ${result.code}`,
|
|
137
|
+
});
|
|
138
|
+
} catch {}
|
|
139
|
+
writeMarker({ queued: true, last_error: result.error || `HTTP ${result.code}` });
|
|
140
|
+
return { queued: clientReportId, unit, error: result.error || `HTTP ${result.code}` };
|
|
141
|
+
} catch (e) {
|
|
142
|
+
// Auto-capture must never break a session.
|
|
143
|
+
return { skipped: "error", error: e && e.message ? e.message : String(e) };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
module.exports = { maybeAutoReport };
|
|
148
|
+
|
|
149
|
+
if (require.main === module) {
|
|
150
|
+
maybeAutoReport()
|
|
151
|
+
.then((r) => {
|
|
152
|
+
if (process.env.QUALIA_DEBUG) process.stdout.write(JSON.stringify(r) + "\n");
|
|
153
|
+
process.exit(0);
|
|
154
|
+
})
|
|
155
|
+
.catch(() => process.exit(0));
|
|
156
|
+
}
|
package/bin/command-surface.js
CHANGED
|
@@ -9,7 +9,6 @@ const ACTIVE_SKILLS = [
|
|
|
9
9
|
"qualia",
|
|
10
10
|
"qualia-new",
|
|
11
11
|
"qualia-scope",
|
|
12
|
-
"qualia-discuss",
|
|
13
12
|
"qualia-map",
|
|
14
13
|
"qualia-research",
|
|
15
14
|
"qualia-plan",
|
|
@@ -57,6 +56,9 @@ const RETIRED_SKILLS = [
|
|
|
57
56
|
"qualia-hook-gen", // framework-authoring utility, not employee default
|
|
58
57
|
"qualia-skill-new", // framework-authoring utility, not employee default
|
|
59
58
|
"qualia-flush", // available as qualia-framework flush / automation
|
|
59
|
+
|
|
60
|
+
// A4 surface collapse (v6.7): folded where a surviving skill genuinely covers it.
|
|
61
|
+
"qualia-discuss", // folded into qualia-scope — PROJECT MODE (kickoff → project-discovery.md) + PHASE MODE (grill → phase-N-context.md) now live there via its mode router
|
|
60
62
|
];
|
|
61
63
|
|
|
62
64
|
function activeSkills() {
|
package/bin/erp-retry.js
CHANGED
|
@@ -274,8 +274,10 @@ function actionClear() {
|
|
|
274
274
|
log(`queue cleared (backup at ${bak})`);
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
-
// ─── Export for in-process use (qualia-report skill enqueues directly
|
|
278
|
-
|
|
277
|
+
// ─── Export for in-process use (qualia-report skill enqueues directly;
|
|
278
|
+
// auto-report.js reuses the POST + config/key readers so there is ONE
|
|
279
|
+
// ERP-upload seam, not two). ──
|
|
280
|
+
module.exports = { enqueue, readQueue, writeQueue, postOnce, readApiKey, readConfig };
|
|
279
281
|
|
|
280
282
|
// ─── CLI entrypoint ─────────────────────────────────────
|
|
281
283
|
if (require.main === module) {
|
package/bin/report-payload.js
CHANGED
|
@@ -136,6 +136,11 @@ function buildPayload(options = {}) {
|
|
|
136
136
|
notes,
|
|
137
137
|
submitted_by: env.SUBMITTED_BY || "unknown",
|
|
138
138
|
submitted_at: submittedAt,
|
|
139
|
+
// B1 — provenance. 'auto' = captured automatically at ship-time (auto-report.js);
|
|
140
|
+
// 'manual' = a deliberate /qualia-report. Defaults to 'manual' so the manual
|
|
141
|
+
// flow is unchanged; auto-report passes SOURCE=auto.
|
|
142
|
+
source: env.SOURCE === "auto" ? "auto" : "manual",
|
|
143
|
+
...(env.DRY_RUN === "1" ? { dry_run: true } : {}),
|
|
139
144
|
};
|
|
140
145
|
}
|
|
141
146
|
|