qualia-framework 4.0.3 → 4.1.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/bin/statusline.js CHANGED
@@ -153,7 +153,21 @@ try {
153
153
  } catch {}
154
154
  } catch {}
155
155
 
156
+ // ─── Pill-style badge helper ─────────────────────────────
157
+ // Renders text as an inline pill with a solid background color, similar to
158
+ // Claude Code's native worktree tag. Pads with a leading+trailing space so
159
+ // the background band has visual weight.
160
+ function pill(text, rgb) {
161
+ const [r, g, b] = rgb;
162
+ const bg = `\x1b[48;2;${r};${g};${b}m`;
163
+ const fg = `\x1b[38;2;240;250;255m`;
164
+ const bold = `\x1b[1m`;
165
+ return `${bg}${fg}${bold} ${text} ${RESET}`;
166
+ }
167
+
156
168
  // ─── Phase info from .planning/tracking.json ─────────────
169
+ // Rendered as a pill at the start of line 1 — teal for normal, red when blockers > 0.
170
+ // Every segment is optional — missing data is skipped, never rendered as a placeholder.
157
171
  let PHASE_INFO = "";
158
172
  try {
159
173
  const trackingPath = path.join(DIR, ".planning", "tracking.json");
@@ -162,16 +176,53 @@ try {
162
176
  const phase = Number(tracking.phase || 0) || 0;
163
177
  const total = Number(tracking.total_phases || 0) || 0;
164
178
  const status = String(tracking.status || "");
165
- if (total > 0) {
166
- const pdone = Math.floor((phase * 100) / total);
167
- const pfill = Math.max(0, Math.min(4, Math.floor(pdone / 25)));
168
- const pempt = 4 - pfill;
169
- const pbar = "●".repeat(pfill) + "○".repeat(pempt);
170
- PHASE_INFO = `${TEAL}${pbar}${RESET} ${WHITE}P${phase}/${total}${RESET} ${TEAL_GLOW}${status}${RESET}`;
179
+ const milestone = Number(tracking.milestone || 0) || 0;
180
+ const milestoneName = String(tracking.milestone_name || "");
181
+ const tasksDone = Number(tracking.tasks_done || 0) || 0;
182
+ const tasksTotal = Number(tracking.tasks_total || 0) || 0;
183
+ const blockers = Array.isArray(tracking.blockers) ? tracking.blockers.length : 0;
184
+
185
+ const parts = [];
186
+
187
+ if (milestone > 0) {
188
+ let mStr = `M${milestone}`;
189
+ if (milestoneName) {
190
+ const shortName = milestoneName.length > 14 ? milestoneName.slice(0, 13) + "…" : milestoneName;
191
+ mStr += `·${shortName}`;
192
+ }
193
+ parts.push(mStr);
194
+ }
195
+
196
+ if (total > 0) parts.push(`P${phase}/${total}`);
197
+ if (tasksTotal > 0) parts.push(`T${tasksDone}/${tasksTotal}`);
198
+ if (status) parts.push(status);
199
+
200
+ let badgeText = parts.join(" · ");
201
+ if (blockers > 0) badgeText += badgeText ? ` · !${blockers}` : `!${blockers}`;
202
+
203
+ if (badgeText) {
204
+ // Red pill when blockers present, teal otherwise
205
+ const bg = blockers > 0 ? [153, 27, 27] : [0, 130, 135];
206
+ PHASE_INFO = pill(`⬢ ${badgeText}`, bg);
171
207
  }
172
208
  }
173
209
  } catch {}
174
210
 
211
+ // ─── Framework-dev badge ────────────────────────────────
212
+ // When editing the Qualia framework itself (detected by presence of the
213
+ // skills/ dir + qualia-ui.js), show a FRAMEWORK DEV pill even though
214
+ // there's no tracking.json. Gives the same "you're in Qualia mode" signal
215
+ // during framework work.
216
+ let FRAMEWORK_BADGE = "";
217
+ try {
218
+ const isFramework =
219
+ fs.existsSync(path.join(DIR, "skills", "qualia-plan", "SKILL.md")) &&
220
+ fs.existsSync(path.join(DIR, "bin", "qualia-ui.js"));
221
+ if (isFramework) {
222
+ FRAMEWORK_BADGE = pill("⬢ FRAMEWORK DEV", [120, 60, 140]);
223
+ }
224
+ } catch {}
225
+
175
226
  // ─── Memory count ────────────────────────────────────────
176
227
  let MEMORY_COUNT = 0;
177
228
  try {
@@ -186,36 +237,22 @@ try {
186
237
  }
187
238
  } catch {}
188
239
 
189
- // ─── Hooks count ─────────────────────────────────────────
190
- let HOOKS_COUNT = 0;
240
+ // ─── Qualia identity: first name of the installed employee ─────────
241
+ // Read from ~/.claude/.qualia-config.json. Used as the "signature" at the
242
+ // end of line 2. Gracefully degrades to empty string if the config is
243
+ // missing (pre-install, broken install, or running outside a Qualia env).
244
+ let QUALIA_FIRST_NAME = "";
191
245
  try {
192
- const settingsPath = path.join(HOME, ".claude", "settings.json");
193
- if (fs.existsSync(settingsPath)) {
194
- const settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
195
- if (settings.hooks) {
196
- for (const event of Object.values(settings.hooks)) {
197
- if (Array.isArray(event)) {
198
- for (const matcher of event) {
199
- if (matcher.hooks && Array.isArray(matcher.hooks)) {
200
- HOOKS_COUNT += matcher.hooks.length;
201
- }
202
- }
203
- }
204
- }
246
+ const configPath = path.join(HOME, ".claude", ".qualia-config.json");
247
+ if (fs.existsSync(configPath)) {
248
+ const cfg = JSON.parse(fs.readFileSync(configPath, "utf8"));
249
+ const fullName = String(cfg.installed_by || "").trim();
250
+ if (fullName) {
251
+ QUALIA_FIRST_NAME = fullName.split(/\s+/)[0] || "";
205
252
  }
206
253
  }
207
254
  } catch {}
208
255
 
209
- // ─── Skills count ────────────────────────────────────────
210
- let SKILLS_COUNT = 0;
211
- try {
212
- const skillsDir = path.join(HOME, ".claude", "skills");
213
- if (fs.existsSync(skillsDir)) {
214
- const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
215
- SKILLS_COUNT = entries.filter(e => e.isDirectory() || e.name.endsWith(".md")).length;
216
- }
217
- } catch {}
218
-
219
256
  // ─── Duration ────────────────────────────────────────────
220
257
  let DUR = "0s";
221
258
  try {
@@ -232,11 +269,14 @@ try {
232
269
  COST_FMT = `$${COST.toFixed(2)}`;
233
270
  } catch {}
234
271
 
235
- // ─── Line 1: Project + Git + Agent + Worktree + Phase + Memory + Hooks ──
272
+ // ─── Line 1: Pill badge + Project + Git + Agent + Worktree + Memory + Identity ──
273
+ // Leading pill (phase info or framework-dev) — one of these at most, phase wins.
236
274
  let LINE1 = "";
237
275
  try {
238
276
  const dirBase = path.basename(DIR) || DIR;
239
- LINE1 = `${TEAL}⬢${RESET} ${WHITE}${dirBase}${RESET}`;
277
+ const leadingBadge = PHASE_INFO || FRAMEWORK_BADGE;
278
+ if (leadingBadge) LINE1 += `${leadingBadge} `;
279
+ LINE1 += `${TEAL}⬢${RESET} ${WHITE}${dirBase}${RESET}`;
240
280
  if (BRANCH) {
241
281
  if (CHANGES > 0) {
242
282
  LINE1 += ` ${DIM}on${RESET} ${TEAL_GLOW}${BRANCH}${RESET} ${YELLOW}~${CHANGES}${RESET}`;
@@ -246,14 +286,11 @@ try {
246
286
  }
247
287
  if (AGENT) LINE1 += ` ${DIM}│${RESET} ${TEAL}⚡${AGENT}${RESET}`;
248
288
  if (WORKTREE) LINE1 += ` ${DIM}│${RESET} ${TEAL_DIM}⎇ ${WORKTREE}${RESET}`;
249
- if (PHASE_INFO) LINE1 += ` ${DIM}│${RESET} ${PHASE_INFO}`;
250
- // Memory, hooks, skills context indicators with labels
251
- const contextParts = [];
252
- if (MEMORY_COUNT > 0) contextParts.push(`${DIM}mem${RESET} ${TEAL}${MEMORY_COUNT}${RESET}`);
253
- if (HOOKS_COUNT > 0) contextParts.push(`${DIM}hooks${RESET} ${TEAL_GLOW}${HOOKS_COUNT}${RESET}`);
254
- if (SKILLS_COUNT > 0) contextParts.push(`${DIM}skills${RESET} ${TEAL_DIM}${SKILLS_COUNT}${RESET}`);
255
- if (contextParts.length > 0) {
256
- LINE1 += ` ${DIM}│${RESET} ${contextParts.join(` ${DIM}·${RESET} `)}`;
289
+ if (MEMORY_COUNT > 0) {
290
+ LINE1 += ` ${DIM}│${RESET} ${DIM}mem${RESET} ${TEAL}${MEMORY_COUNT}${RESET}`;
291
+ }
292
+ if (QUALIA_FIRST_NAME) {
293
+ LINE1 += ` ${DIM}│${RESET} ${TEAL}⬢${RESET} ${TEAL_GLOW}Qualia member${RESET}${DIM}:${RESET} ${WHITE}${QUALIA_FIRST_NAME}${RESET}`;
257
294
  }
258
295
  } catch {
259
296
  LINE1 = `${TEAL}⬢${RESET} ${WHITE}qualia${RESET}`;
@@ -28,8 +28,16 @@ Upload a session report.
28
28
  ```
29
29
  Authorization: Bearer <api-key>
30
30
  Content-Type: application/json
31
+ Idempotency-Key: <uuid> # optional; 24h replay window — see below
31
32
  ```
32
33
 
34
+ **Idempotency-Key behavior (v3.6+):**
35
+ When present, must be a valid UUID. Replays of the same key within 24h return
36
+ the original `report_id` with `Idempotent-Replay: true` response header and
37
+ 200 status — no new row is created. Invalid UUID format returns 400.
38
+ Independent of `client_report_id` UPSERT (both can be used together; see
39
+ below).
40
+
33
41
  **Request Body:**
34
42
  ```json
35
43
  {
@@ -38,6 +46,7 @@ Content-Type: application/json
38
46
  "team_id": "qualia-solutions",
39
47
  "git_remote": "github.com/QualiasolutionsCY/acme-portal",
40
48
  "client": "Client Name",
49
+ "client_report_id": "QS-REPORT-03",
41
50
  "milestone": 2,
42
51
  "milestone_name": "Core Product",
43
52
  "milestones": [
@@ -87,11 +96,27 @@ accept both shapes: if object, use `gap_cycles[String(phase)] || 0`.
87
96
  ```json
88
97
  {
89
98
  "ok": true,
90
- "report_id": "rpt_abc123def456",
99
+ "report_id": "QS-REPORT-03",
91
100
  "message": "Report received"
92
101
  }
93
102
  ```
94
103
 
104
+ `report_id` semantics:
105
+ - **v4.0.4+ payloads** (`client_report_id` present): ERP echoes the
106
+ `client_report_id` string back as `report_id` for display consistency.
107
+ Example: request sends `client_report_id: "QS-REPORT-03"` → response
108
+ returns `report_id: "QS-REPORT-03"`.
109
+ - **Legacy payloads** (no `client_report_id`): ERP returns its internal UUID
110
+ (e.g. `"a5304d8b-a5ac-4e22-b0c0-fed5f50299bb"`) as `report_id`.
111
+
112
+ **Idempotent UPSERT on retry (v4.0.4+):**
113
+ When BOTH `project_id` and `client_report_id` are present, the ERP treats
114
+ `(project_id, client_report_id)` as a unique key and UPSERTs. Retries after
115
+ a transient failure produce the same row and return the same `report_id`
116
+ — no duplicate. This is stronger than the 24h Idempotency-Key window (which
117
+ is exact-replay only) because `client_report_id` uniqueness is enforced
118
+ permanently.
119
+
95
120
  **Response (401 Unauthorized):**
96
121
  ```json
97
122
  {
@@ -171,7 +196,15 @@ Authorization: Bearer <api-key>
171
196
  - When the API key file is missing or empty, the upload is skipped with a warning.
172
197
  - Network failures are non-blocking — the report is saved locally regardless.
173
198
  - The ERP reads `tracking.json` directly from git for real-time status (no API call needed for passive monitoring).
174
- - Reports are append-only — no update or delete endpoints exist.
199
+ - Reports are append-only — no PUT/PATCH/DELETE endpoints exist for
200
+ external callers. Internal idempotent UPSERT on `(project_id,
201
+ client_report_id)` retries is the one exception (see "Idempotent UPSERT
202
+ on retry" above).
203
+ - **`dry_run` retention (v4.0.4+):** The ERP deletes rows where
204
+ `dry_run = true AND submitted_at < now() - 7 days` via a daily cron at
205
+ 03:00 UTC. Production report views (list, project tree, email digests)
206
+ exclude `dry_run = true` rows at read time by default. Admins can opt in
207
+ via `includeDryRun: true` on the server-action readers for diagnostics.
175
208
  - `tracking.json` includes `milestone` and `lifetime` fields (added in v3.4). These survive across milestone resets and `state.js init` calls. For aggregate reporting, use `lifetime.total_phases` + current `total_phases` for the grand total across all milestones.
176
209
  - Backward compatibility: if `lifetime` is absent in tracking.json, treat all counters as 0 and `milestone` as 1.
177
210
 
@@ -195,6 +228,8 @@ Authorization: Bearer <api-key>
195
228
  | last_pushed_at | string | optional (v3.6+) | ISO 8601 — distinct from `last_updated` (which fires on local writes too). |
196
229
  | build_count | number | optional (v3.6+) | Lifetime build counter. |
197
230
  | deploy_count | number | optional (v3.6+) | Lifetime deploy counter. |
231
+ | client_report_id | string | recommended (v4.0.4+) | Client-side sequential identifier: `QS-REPORT-01`, `QS-REPORT-02`, … per-project. Stable across retries. Preferred dedupe key over the ERP-generated `report_id`; safe to adopt as the ERP's primary report key. |
232
+ | dry_run | boolean | optional (v4.0.4+) | `true` marks a synthetic ping (from `qualia-framework erp-ping`). Receivers should filter these out of production report views. |
198
233
 
199
234
  All other fields are optional but recommended for complete reporting.
200
235
 
@@ -0,0 +1,128 @@
1
+ # Qualia Framework — Command Quality & Build Workflow Deep Research
2
+ **Date:** 2026-04-21
3
+ **Scope:** design, debug, optimize, review + plan/build/verify workflow + 8 subagent prompts
4
+ **Method:** 4 parallel Opus agents, each auditing one dimension, synthesized by framework owner
5
+
6
+ ## Executive Summary
7
+
8
+ The framework's biggest accuracy leak is **evidence-free claims**: 3 of 4 diagnostic commands (design, debug, review) do not require file:line citations for findings, so the model hallucinates specifics under pressure. The biggest speed leak is **serial work that should be parallel**: qualia-design and qualia-review list `Agent` in allowed-tools but never spawn, so large codebases get processed in a single context window; the plan-checker revision loop serially re-spawns the planner for issues (frontmatter, wave assignment) that can be fixed mechanically.
9
+
10
+ The single highest-leverage change is a shared **Grounding Protocol** + **Rubric Library** referenced from every skill and agent — it eliminates ~60% of the determinism defects at once.
11
+
12
+ ---
13
+
14
+ ## Top 15 Improvements — Ranked by Impact × Ease
15
+
16
+ | # | Change | Impact | Effort | Where |
17
+ |---|--------|--------|--------|-------|
18
+ | 1 | Add shared Grounding Protocol (cite-or-say-INSUFFICIENT-EVIDENCE) to all agents | 🔥 Accuracy | 30 min | `rules/grounding.md` + import into 8 agent files |
19
+ | 2 | Add deterministic severity formula (CRITICAL=8/HIGH=4/MED=2/LOW=1; score = max(1, 5−⌊Σ/8⌋)) to qualia-review | 🔥 Accuracy | 45 min | `skills/qualia-review/SKILL.md:124` |
20
+ | 3 | Pre-inline PROJECT.md into verifier prompt (currently missing) | 🔥 Accuracy | 10 min | `skills/qualia-verify/SKILL.md:42` |
21
+ | 4 | Make qualia-build spawn wave tasks in parallel explicitly ("all Agent() calls in SAME response") | ⚡ Speed | 30 min | `skills/qualia-build/SKILL.md:65` |
22
+ | 5 | Convert qualia-debug from interactive (4 questions) to investigative (parse $ARGUMENTS, run diagnostic greps) | 🔥 Accuracy | 2 hrs | `skills/qualia-debug/SKILL.md:39-44` |
23
+ | 6 | Add structured Output Contract (DONE/BLOCKED/PARTIAL prefix) to builder.md | ⚡ Speed + 🔥 Accuracy | 20 min | `agents/builder.md:14` |
24
+ | 7 | Mechanical-fix bypass in plan-checker (skip planner re-spawn for frontmatter/wave issues) | ⚡ Speed | 4 hrs | `skills/qualia-plan/SKILL.md:129-153` |
25
+ | 8 | Make wave assignment deterministic: file-based dependency graph, topological sort (not "tasks with no dependencies") | 🔥 Accuracy | 3 hrs | `agents/planner.md:33` |
26
+ | 9 | Add Rule 8 to plan-checker: "Validation must test behavior, not file-existence only" (stops stubs passing) | 🔥 Accuracy | 30 min | `agents/plan-checker.md` after Rule 7 |
27
+ | 10 | Split qualia-design/review into parallel agent fan-out for large file sets (5+ files) | ⚡ Speed | 3 hrs | `skills/qualia-design/SKILL.md`, `skills/qualia-review/SKILL.md` |
28
+ | 11 | Add wave-context summary (adjacent task titles + files) to builder prompt — stops semantic drift across parallel tasks | 🔥 Accuracy | 1 hr | `skills/qualia-build/SKILL.md:82` |
29
+ | 12 | Fix `grep -qL` bug in qualia-review API auth check (backwards logic) | 🔥 Accuracy | 15 min | `skills/qualia-review/SKILL.md:59-61` |
30
+ | 13 | Add tool budgets: researcher (8 external calls), verifier (25 bash calls), debug (10 reads) | ⚡ Speed | 45 min | `agents/researcher.md`, `agents/verifier.md`, `skills/qualia-debug` |
31
+ | 14 | Standardize input contracts across 8 agents with `<variable>` typed blocks (only plan-checker does this today) | 🔥 Accuracy | 2 hrs | All 8 agent files |
32
+ | 15 | Drop full `next build` from qualia-review; read existing `.next/` or skip with warning | ⚡ Speed | 20 min | `skills/qualia-review/SKILL.md:98` |
33
+
34
+ **Total effort for #1–#15:** ~20 hours of focused work → framework-wide accuracy and speed step-change.
35
+
36
+ ---
37
+
38
+ ## Per-Command Scores (before changes)
39
+
40
+ | Command | Score | Weakest Dimension |
41
+ |---------|-------|-------------------|
42
+ | qualia-debug | 4/10 | Interactive-by-default (4 mandatory questions), no output file, cheat sheets instead of diagnostic commands |
43
+ | qualia-design | 6/10 | No critique output contract, `Agent` listed but never spawned, tsc-only verification |
44
+ | qualia-review | 7/10 | Serial bash scans, latent `grep -qL` bug, no parallelism |
45
+ | qualia-optimize | 8/10 | Strongest — uses agent fan-out, severity labels, OPTIMIZE.md output. Loses points on inline `find`/`grep` in Step 6 + no `--fix` dry-run |
46
+
47
+ ## Per-Agent Scores (before changes)
48
+
49
+ | Agent | Overall | Biggest Gap |
50
+ |-------|---------|-------------|
51
+ | plan-checker | 9.5/10 | No tool budget |
52
+ | verifier | 9.0/10 | No frontend gate on design verification (runs 40 greps on backend phases) |
53
+ | planner | 8.5/10 | Prose input contract, no failure-mode handling |
54
+ | builder | 8.5/10 | No structured output contract |
55
+ | researcher | 8.5/10 | Unbounded WebSearch loops |
56
+ | qa-browser | 8.5/10 | Probes for dev server URL instead of receiving it; no fallback when Playwright unavailable |
57
+ | roadmapper | 8.5/10 | `full_detail` is a ghost parameter — referenced but not declared |
58
+ | research-synthesizer | 8.0/10 | No evidence requirement on milestone suggestions |
59
+
60
+ ---
61
+
62
+ ## Rubrics to Ship as `rules/rubrics.md`
63
+
64
+ **Severity (with deterministic category score):**
65
+ ```
66
+ CRITICAL = 8 | HIGH = 4 | MEDIUM = 2 | LOW = 1
67
+ weighted_sum = Σ(count_i × weight_i)
68
+ category_score = max(1, 5 − ⌊weighted_sum / 8⌋)
69
+ ```
70
+
71
+ **Design Quality (1–5 per dimension, any <3 = mandatory fix):**
72
+ Typography / Color / Spacing / States / Responsiveness / Accessibility — each with objective criteria (see `skills/qualia-design` comment thread for full matrix).
73
+
74
+ **Task-Done:**
75
+ - Compiles (`tsc --noEmit` = 0)
76
+ - No stubs (`grep -c "TODO|FIXME|placeholder" touched_files` = 0)
77
+ - Wired (every export imported somewhere)
78
+ - Each acceptance criterion has a passing validation command
79
+ - Committed (git log matches task title)
80
+
81
+ **Evidence Citation Format:**
82
+ ```
83
+ file:line — "quoted code" — {assessment}
84
+ ```
85
+ Claims missing this format are rejected. If evidence cannot be found: `INSUFFICIENT EVIDENCE: searched {files} with {commands}`.
86
+
87
+ ---
88
+
89
+ ## Grounding Protocol (paste into every agent)
90
+
91
+ ```markdown
92
+ ## Grounding Protocol (MANDATORY)
93
+ 1. Every factual claim requires `file:line — "quoted code"`. No exception.
94
+ 2. No hedging: "seems / probably / might" → verified or INSUFFICIENT EVIDENCE.
95
+ 3. Findings without file:line are discarded.
96
+ 4. Scores without evidence on the next line = 0.
97
+ 5. Severity requires quoting the matching Severity Rubric criterion.
98
+ 6. Output shape is a contract — missing sections = protocol violation.
99
+ 7. Stop at tool budget. Return what you found, not what you wish.
100
+ 8. Precondition: verify every @file exists before work; HALT if missing.
101
+ ```
102
+
103
+ ---
104
+
105
+ ## 3 Architectural Changes (bigger, keep for later)
106
+
107
+ 1. **Pre-Build Context Packet** — assemble one JSON with PROJECT.md + DESIGN.md + plan + wave-context before spawning builders. Eliminates per-builder file reads.
108
+ 2. **Intra-Wave Verification** — run each task's Validation contracts immediately after its builder completes, before next wave starts. Catches failure at task granularity, not phase.
109
+ 3. **Plan Cache** — cache parsed project identity in `.planning/.project-cache.json`; invalidate on PROJECT.md change. Saves ~30% planner context on multi-phase `--auto` runs.
110
+
111
+ ---
112
+
113
+ ## Missing Agents Worth Adding (ranked)
114
+
115
+ 1. **`migrator.md`** — generates + validates Supabase migrations. Current gap: builder writes raw SQL ad-hoc, migration guard catches only obvious patterns.
116
+ 2. **`dependency-auditor.md`** — pre-build peer-dependency / vulnerability check. Current gap: builder hits `npm install` conflicts mid-phase and wastes context debugging.
117
+ 3. **`rollback.md`** — on verify FAIL, bisect to last-good commit instead of always patching forward. Current gap: gap-closure plans build on broken code.
118
+
119
+ ---
120
+
121
+ ## Anti-Patterns to Kill
122
+
123
+ - `find` inside skills (use Glob) — qualia-optimize:302, qualia-review multiple places
124
+ - `Agent` in allowed-tools but never spawned — qualia-design, qualia-debug, qualia-review
125
+ - Interactive question gates in one-shot commands — qualia-debug
126
+ - Full `next build` as part of a "scan" — qualia-review:98
127
+ - Vague "investigate the codebase" with no tool budget — qualia-debug, researcher
128
+ - "seems / probably / might" language anywhere in agent output