qualia-framework 6.9.0 → 6.9.2

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/CHANGELOG.md CHANGED
@@ -8,6 +8,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8
8
  > Note: git tags for historical versions were not retained; commit references are approximate
9
9
  > and dates reflect commit history rather than npm publish timestamps.
10
10
 
11
+ ## [6.9.2] - 2026-06-20 (docs — visual field manual)
12
+
13
+ ### Added — `docs/qualia-manual.html`
14
+ - A self-contained, single-file introductory **Field Manual** in the Qualia house style (dark + teal `#00FFD1`), shipped in the npm package (`files`). It is the human-friendly visual companion to `docs/EMPLOYEE-QUICKSTART.md`: what the framework is, the plan→build→verify→ship→report loop, employee-mode install, the full command map grouped by purpose, a first-project walkthrough, the five hard rules, the credentials-and-who-issues-them table, and the `/qualia` "when you're lost" escape hatch. Content is grounded in the existing quickstart + skill set — nothing invented. Interactive but dependency-free: sticky scroll-spy nav, click-to-copy command chips, reveal-on-scroll, mobile-responsive, skip-link for a11y. `docs/EMPLOYEE-QUICKSTART.md` now links to it.
15
+
16
+ ## [6.9.1] - 2026-06-16 (fix — stale update banner)
17
+
18
+ ### Fixed — session-start showed a false "update available" banner after catching up
19
+ - `hooks/session-start.js` rendered the cached `.qualia-update-available.json` notice on every session without checking it against the installed version. Because `auto-update.js` only clears that file on its next (throttled) run, the window between an install and that run showed a stale "Current 6.8.1 → Latest 6.9.0" banner even though 6.9.0 was already installed. `maybeRenderUpdateBanner` now validates the notice against `.qualia-config.json` and self-clears (skips render) when the installed version is at or past the advertised `latest`. Regression covered by two new cases in `tests/hooks.test.sh` (stale self-clears; genuine notice preserved).
20
+
11
21
  ## [6.9.0] - 2026-06-13 (employee on-ramps — stack presets, employee-mode install, shared knowledge pull)
12
22
 
13
23
  Implements Part A of the 2026-06-13 restructure plan: turn the framework from an owner-only tool into something a new employee can take idea→deployed without tribal knowledge. **Additive only** — no skill, agent, hook, or rule was rewritten; these are on-ramps built *around* the existing loop.
package/bin/install.js CHANGED
@@ -1140,6 +1140,10 @@ Client-specific preferences, design choices, and requirements. Loaded by \`/qual
1140
1140
  enabled: !employeeMode,
1141
1141
  url: "https://portal.qualiasolutions.net",
1142
1142
  api_key_file: ".erp-api-key",
1143
+ // Performance-audit telemetry. command_usage (counts) is always on.
1144
+ // capturePrompts records real prompt text for the prompt-quality judge —
1145
+ // written explicitly (not implied) so engineers can see and flip it.
1146
+ capturePrompts: true,
1143
1147
  },
1144
1148
  };
1145
1149
  // mode 0o600: this file holds the role bit (OWNER vs EMPLOYEE) which the
@@ -1275,6 +1279,8 @@ Client-specific preferences, design choices, and requirements. Loaded by \`/qual
1275
1279
  "fawzi-approval-guard.js",
1276
1280
  // v5.0 — insights-driven destructive-op + wrong-account guards
1277
1281
  "vercel-account-guard.js", "env-empty-guard.js", "supabase-destructive-guard.js",
1282
+ // performance-audit telemetry capture (UserPromptSubmit)
1283
+ "usage-capture.js",
1278
1284
  ]);
1279
1285
  const isQualiaHookCmd = (cmd) => {
1280
1286
  if (typeof cmd !== "string") return false;
@@ -1338,6 +1344,16 @@ Client-specific preferences, design choices, and requirements. Loaded by \`/qual
1338
1344
  ],
1339
1345
  },
1340
1346
  ],
1347
+ // Performance-audit telemetry — record qualia-command usage + (opt-in)
1348
+ // prompt samples per session for the ERP clock-out payload.
1349
+ UserPromptSubmit: [
1350
+ {
1351
+ matcher: ".*",
1352
+ hooks: [
1353
+ { type: "command", command: nodeCmd("usage-capture.js"), timeout: 5 },
1354
+ ],
1355
+ },
1356
+ ],
1341
1357
  };
1342
1358
 
1343
1359
  // Merge user hooks: strip Qualia-owned commands, preserve everything else.
@@ -1519,6 +1535,13 @@ function printSummary({ member, target, claudeInstalled }) {
1519
1535
  console.log(` ${DIM}End of day?${RESET} ${TEAL}/qualia-report${RESET} ${DIM}(shift submission)${RESET}`);
1520
1536
  console.log(` ${DIM}Stuck?${RESET} ${TEAL}/qualia${RESET}`);
1521
1537
  console.log("");
1538
+ console.log(
1539
+ ` ${DIM}Telemetry${RESET} ${DIM}your /qualia command usage + prompt samples feed the team${RESET}`
1540
+ );
1541
+ console.log(
1542
+ ` ${DIM} performance audit. Opt out:${RESET} ${TEAL}erp.capturePrompts=false${RESET} ${DIM}in ~/.claude/.qualia-config.json${RESET}`
1543
+ );
1544
+ console.log("");
1522
1545
  console.log(` ${DIM2}${RULE}${RESET}`);
1523
1546
  console.log(` ${TEAL}${BOLD}Welcome to the future with Qualia.${RESET}`);
1524
1547
  console.log(` ${DIM2}${RULE}${RESET}`);
@@ -1626,6 +1649,8 @@ async function installCodex(member, target, employeeMode = false) {
1626
1649
  enabled: !employeeMode,
1627
1650
  url: "https://portal.qualiasolutions.net",
1628
1651
  api_key_file: ".erp-api-key",
1652
+ // See ~/.claude config above — explicit so engineers can see/flip it.
1653
+ capturePrompts: true,
1629
1654
  },
1630
1655
  };
1631
1656
  atomicWrite(path.join(CODEX_DIR, ".qualia-config.json"), JSON.stringify(codexConfig, null, 2) + "\n", 0o600);
@@ -89,6 +89,16 @@ function buildPayload(options = {}) {
89
89
  const latestHarnessEval = harnessEval.latestEval(cwd);
90
90
  const workPacket = readLocalWorkPacket(cwd);
91
91
 
92
+ // Performance-audit telemetry captured during the session by the
93
+ // usage-capture UserPromptSubmit hook. Cleared by /qualia-report after upload.
94
+ const usagePath = options.usagePath || path.join(cwd, ".planning", ".session-usage.json");
95
+ const usage = readJson(usagePath, {});
96
+ const commandUsage =
97
+ usage && typeof usage.command_usage === "object" && usage.command_usage
98
+ ? usage.command_usage
99
+ : {};
100
+ const promptSamples = Array.isArray(usage.prompt_samples) ? usage.prompt_samples : [];
101
+
92
102
  return {
93
103
  project: tracking.project || path.basename(cwd),
94
104
  project_id: projectKey,
@@ -118,6 +128,8 @@ function buildPayload(options = {}) {
118
128
  gap_cycles: (tracking.gap_cycles || {})[String(phase)] || 0,
119
129
  build_count: tracking.build_count || 0,
120
130
  deploy_count: tracking.deploy_count || 0,
131
+ command_usage: commandUsage,
132
+ prompt_samples: promptSamples,
121
133
  deployed_url: tracking.deployed_url || "",
122
134
  ...(tracking.session_started_at ? { session_started_at: tracking.session_started_at } : {}),
123
135
  ...(tracking.last_pushed_at ? { last_pushed_at: tracking.last_pushed_at } : {}),
@@ -2,6 +2,8 @@
2
2
 
3
3
  A five-minute path from a fresh machine to a shipped, reported day of work. This is the route for a **new employee who does not have a team install code yet**. You can install and do real work in employee mode today; the OWNER (Fawzi) issues the keys that unlock ERP reporting and any direct provider integrations.
4
4
 
5
+ > Prefer a visual tour? Open **[`docs/qualia-manual.html`](./qualia-manual.html)** — the same path as an interactive Field Manual (command map, first-project walkthrough, copy-to-clipboard commands).
6
+
5
7
  Who issues credentials: **the OWNER (Fawzi) issues every key** — team install codes, the ERP API key, OpenRouter / Supabase / Vercel / Retell / ElevenLabs / Telnyx credentials. If a step says "ask Fawzi", that is who to ask. Never share or reuse another person's key.
6
8
 
7
9
  ---
@@ -377,6 +377,8 @@ Snapshot shape:
377
377
  | last_pushed_at | string | optional (v3.6+) | ISO 8601 — distinct from `last_updated` (which fires on local writes too). |
378
378
  | build_count | number | optional (v3.6+) | Lifetime build counter. |
379
379
  | deploy_count | number | optional (v3.6+) | Lifetime deploy counter. |
380
+ | command_usage | object | optional (v6.9+) | Per-session qualia slash-command histogram, e.g. `{ "qualia-build": 3, "qualia-fix": 1 }`. Captured by the `usage-capture` UserPromptSubmit hook into `.planning/.session-usage.json` and cleared at clock-out. Drives the ERP performance audit's command-usage signal. Defaults to `{}`. |
381
+ | prompt_samples | string[] | optional (v6.9+) | Sampled real engineer prompts for the session (opt-in via `erp.capturePrompts`, default on; internal-only). Fed to the ERP prompt-quality judge. Capped at 60 entries / 8000 chars each. Defaults to `[]`. |
380
382
  | 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. |
381
383
  | 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. |
382
384
 
@@ -393,4 +395,11 @@ All other fields are optional but recommended for complete reporting.
393
395
  - API keys are per-user, not per-project
394
396
  - Keys expire after 90 days (re-issue via Fawzi)
395
397
  - All traffic is HTTPS-only
396
- - No PII beyond team member names is transmitted
398
+ - **Free-form content:** beyond team member names, the only free-form content
399
+ transmitted is `prompt_samples` — the engineer's real prompts — and only when
400
+ `erp.capturePrompts` is enabled (default on). This is disclosed to engineers
401
+ at install, in their own `.qualia-config.json`, and via a once-per-shift
402
+ runtime notice; they may opt out at any time. Treat `prompt_samples` as
403
+ data that **may contain PII**: store it access-controlled, keep it out of
404
+ third-party sub-processors that aren't covered by the team's data agreement,
405
+ and honor deletion requests. Every other field is non-PII project metadata.
@@ -0,0 +1,396 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Qualia Framework · Field Manual</title>
7
+ <style>
8
+ :root{
9
+ --bg:#0a0e12; --panel:#11161d; --panel2:#161d26; --border:#1f2937;
10
+ --text:#d7dee8; --dim:#8b98a8; --faint:#5b6675;
11
+ --teal:#00FFD1; --teal-dim:#0b8f7d; --amber:#f5b942; --red:#ff5c5c; --green:#4ade80;
12
+ --mono:'SF Mono',ui-monospace,Consolas,monospace;
13
+ --sans:-apple-system,'Segoe UI',Roboto,sans-serif;
14
+ --shadow:0 1px 2px rgba(0,0,0,.4),0 8px 30px rgba(0,0,0,.25);
15
+ --nav-w:240px;
16
+ }
17
+ *{box-sizing:border-box;margin:0;padding:0}
18
+ html{scroll-behavior:smooth}
19
+ body{
20
+ background:
21
+ radial-gradient(900px 500px at 85% -5%,rgba(0,255,209,.06),transparent 60%),
22
+ radial-gradient(700px 400px at 0% 0%,rgba(0,255,209,.04),transparent 55%),
23
+ var(--bg);
24
+ color:var(--text);font:15.5px/1.65 var(--sans);
25
+ -webkit-font-smoothing:antialiased;
26
+ }
27
+ a{color:var(--teal);text-decoration:none}
28
+ a:hover{text-decoration:underline}
29
+
30
+ /* skip link (a11y) */
31
+ .skip{position:absolute;left:-999px;top:0;background:var(--teal);color:#04110e;padding:10px 16px;border-radius:0 0 8px 0;font-weight:700;z-index:100}
32
+ .skip:focus{left:0}
33
+
34
+ /* layout */
35
+ .shell{display:grid;grid-template-columns:var(--nav-w) 1fr;max-width:1180px;margin:0 auto;gap:0}
36
+ nav.side{
37
+ position:sticky;top:0;align-self:start;height:100vh;overflow-y:auto;
38
+ padding:30px 18px 40px;border-right:1px solid var(--border);
39
+ }
40
+ nav.side .brand{display:flex;align-items:center;gap:9px;margin-bottom:6px}
41
+ nav.side .hex{color:var(--teal);font-size:20px;line-height:1}
42
+ nav.side .brand b{font-size:15px;letter-spacing:.5px}
43
+ nav.side .brand small{display:block;color:var(--faint);font-size:10.5px;letter-spacing:2px;text-transform:uppercase;margin-top:2px}
44
+ nav.side ol{list-style:none;margin-top:24px;counter-reset:s}
45
+ nav.side li{margin:1px 0}
46
+ nav.side a{
47
+ display:flex;gap:10px;align-items:baseline;color:var(--dim);font-size:13.5px;
48
+ padding:7px 10px;border-radius:7px;border-left:2px solid transparent;transition:.15s;
49
+ }
50
+ nav.side a .n{counter-increment:s;color:var(--faint);font-family:var(--mono);font-size:11px;min-width:14px}
51
+ nav.side a:hover{color:var(--text);background:var(--panel);text-decoration:none}
52
+ nav.side a.active{color:var(--teal);background:var(--panel2);border-left-color:var(--teal)}
53
+ nav.side a.active .n{color:var(--teal)}
54
+
55
+ main{padding:54px 48px 120px;min-width:0}
56
+
57
+ /* hero */
58
+ .hero{margin-bottom:14px}
59
+ .eyebrow{display:inline-flex;align-items:center;gap:8px;font-family:var(--mono);font-size:11.5px;color:var(--teal);letter-spacing:1.5px;text-transform:uppercase;border:1px solid var(--teal-dim);background:rgba(0,255,209,.05);padding:4px 12px;border-radius:30px;margin-bottom:20px}
60
+ .eyebrow .dot{width:6px;height:6px;border-radius:50%;background:var(--teal);box-shadow:0 0 10px var(--teal)}
61
+ h1{font-size:clamp(30px,5vw,46px);line-height:1.05;letter-spacing:-1.2px;font-weight:800}
62
+ h1 .g{color:var(--teal)}
63
+ .lede{color:var(--dim);font-size:clamp(15px,2vw,18px);max-width:62ch;margin-top:16px;line-height:1.55}
64
+
65
+ /* sections */
66
+ section{padding-top:64px;scroll-margin-top:24px}
67
+ h2{font-size:24px;letter-spacing:-.4px;font-weight:700;display:flex;align-items:center;gap:12px;margin-bottom:6px}
68
+ h2 .bar{width:26px;height:3px;border-radius:3px;background:var(--teal)}
69
+ .sec-sub{color:var(--dim);margin-bottom:22px;max-width:64ch}
70
+ h3{font-size:16px;font-weight:650;color:#fff;margin:26px 0 10px}
71
+ p{margin:10px 0}
72
+ .dim{color:var(--dim)}
73
+
74
+ /* panels */
75
+ .panel{background:var(--panel);border:1px solid var(--border);border-radius:14px;padding:22px 24px;margin:16px 0;box-shadow:var(--shadow)}
76
+ .grid{display:grid;gap:16px}
77
+ .g2{grid-template-columns:1fr 1fr}
78
+ .g3{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}
79
+
80
+ /* command chip w/ copy */
81
+ code{font-family:var(--mono);font-size:.88em;background:var(--panel2);padding:2px 7px;border-radius:5px;color:var(--teal);border:1px solid var(--border)}
82
+ .cmd{display:inline-flex;align-items:center;gap:8px;font-family:var(--mono);font-size:13px;background:var(--panel2);border:1px solid var(--border);border-radius:8px;padding:6px 8px 6px 12px;color:#cfe;cursor:pointer;transition:.15s;user-select:none}
83
+ .cmd:hover{border-color:var(--teal-dim);background:#13202a}
84
+ .cmd .ico{font-size:11px;color:var(--faint);border-left:1px solid var(--border);padding-left:8px}
85
+ .cmd.copied{border-color:var(--teal);color:var(--teal)}
86
+ .cmd.copied .ico{color:var(--teal)}
87
+
88
+ pre{background:#0c1218;border:1px solid var(--border);border-left:3px solid var(--teal-dim);border-radius:0 10px 10px 0;padding:16px 18px;margin:14px 0;overflow-x:auto;font-family:var(--mono);font-size:13px;line-height:1.7;color:#b8c4d4}
89
+ pre .c{color:var(--faint)}
90
+ pre .t{color:var(--teal)}
91
+
92
+ /* lifecycle flow */
93
+ .flow{display:flex;flex-wrap:wrap;gap:10px;align-items:stretch;margin:20px 0}
94
+ .step{flex:1 1 130px;background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:14px 14px;position:relative;transition:.18s;min-width:120px}
95
+ .step:hover{border-color:var(--teal-dim);transform:translateY(-3px)}
96
+ .step .k{font-family:var(--mono);font-size:11px;color:var(--teal);letter-spacing:.5px}
97
+ .step .cmdname{font-weight:700;font-size:14px;margin:6px 0 4px}
98
+ .step .d{font-size:12px;color:var(--dim);line-height:1.45}
99
+ .flow .arrow{display:flex;align-items:center;color:var(--faint);font-size:18px}
100
+
101
+ /* command reference cards */
102
+ .cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:14px;margin-top:14px}
103
+ .card{background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:16px 18px;transition:.18s}
104
+ .card:hover{border-color:var(--teal-dim);box-shadow:var(--shadow)}
105
+ .card h4{font-family:var(--mono);font-size:14px;color:var(--teal);margin-bottom:6px}
106
+ .card p{font-size:13px;color:var(--dim);margin:0}
107
+ .group-label{font-family:var(--mono);font-size:11px;letter-spacing:1.5px;text-transform:uppercase;color:var(--faint);margin:26px 0 4px}
108
+
109
+ /* rules */
110
+ .rule{display:flex;gap:14px;padding:14px 0;border-bottom:1px solid var(--border)}
111
+ .rule:last-child{border-bottom:none}
112
+ .rule .num{flex:0 0 30px;height:30px;border-radius:9px;background:rgba(0,255,209,.08);border:1px solid var(--teal-dim);color:var(--teal);display:flex;align-items:center;justify-content:center;font-weight:700;font-size:13px;font-family:var(--mono)}
113
+ .rule b{color:#fff}
114
+ .rule .why{color:var(--dim);font-size:13.5px;font-style:italic}
115
+
116
+ /* table */
117
+ table{width:100%;border-collapse:collapse;margin:14px 0;font-size:13.5px}
118
+ th{text-align:left;color:var(--faint);font-weight:600;padding:9px 12px;border-bottom:1px solid var(--border);text-transform:uppercase;font-size:10.5px;letter-spacing:.8px}
119
+ td{padding:10px 12px;border-bottom:1px solid #131a22;vertical-align:top}
120
+ tr:hover td{background:rgba(255,255,255,.012)}
121
+
122
+ /* callout */
123
+ .note{border-left:3px solid var(--amber);background:rgba(245,185,66,.05);border-radius:0 10px 10px 0;padding:14px 18px;margin:16px 0;font-size:14px}
124
+ .note.teal{border-left-color:var(--teal);background:rgba(0,255,209,.05)}
125
+ .note b{color:#fff}
126
+
127
+ /* walkthrough */
128
+ .walk{counter-reset:w;margin-top:10px}
129
+ .walk .w{display:flex;gap:16px;padding:16px 0;border-bottom:1px dashed var(--border)}
130
+ .walk .w:last-child{border-bottom:none}
131
+ .walk .wn{counter-increment:w;flex:0 0 36px;height:36px;border-radius:50%;background:var(--panel2);border:1px solid var(--teal-dim);color:var(--teal);display:flex;align-items:center;justify-content:center;font-weight:700;font-family:var(--mono)}
132
+ .walk .wn::before{content:counter(w)}
133
+ .walk .wbody{flex:1}
134
+ .walk .wbody h4{font-size:15px;color:#fff;margin-bottom:3px}
135
+
136
+ footer{margin-top:80px;padding-top:24px;border-top:1px solid var(--border);color:var(--faint);font-size:12.5px}
137
+
138
+ /* reveal animation */
139
+ .reveal{opacity:0;transform:translateY(14px);transition:opacity .5s ease,transform .5s ease}
140
+ .reveal.in{opacity:1;transform:none}
141
+ @media(prefers-reduced-motion:reduce){.reveal{opacity:1;transform:none;transition:none}html{scroll-behavior:auto}}
142
+
143
+ /* mobile */
144
+ .menu-btn{display:none}
145
+ @media(max-width:860px){
146
+ .shell{grid-template-columns:1fr}
147
+ nav.side{position:fixed;inset:0 auto 0 0;width:260px;background:var(--bg);z-index:50;transform:translateX(-100%);transition:transform .25s;border-right:1px solid var(--border)}
148
+ nav.side.open{transform:none;box-shadow:0 0 60px rgba(0,0,0,.6)}
149
+ main{padding:70px 22px 90px}
150
+ .menu-btn{display:inline-flex;position:fixed;top:14px;left:14px;z-index:60;align-items:center;gap:8px;background:var(--panel);border:1px solid var(--border);color:var(--teal);font-family:var(--mono);font-size:13px;padding:8px 14px;border-radius:10px;cursor:pointer}
151
+ .g2,.g3{grid-template-columns:1fr}
152
+ }
153
+ </style>
154
+ </head>
155
+ <body>
156
+ <a class="skip" href="#what">Skip to content</a>
157
+ <button class="menu-btn" aria-label="Toggle navigation" onclick="document.querySelector('nav.side').classList.toggle('open')">☰ Menu</button>
158
+
159
+ <div class="shell">
160
+ <nav class="side" aria-label="Manual sections">
161
+ <div class="brand">
162
+ <span class="hex">⬢</span>
163
+ <div><b>QUALIA</b><small>Field Manual</small></div>
164
+ </div>
165
+ <ol>
166
+ <li><a href="#what" class="active"><span class="n"></span> What it is</a></li>
167
+ <li><a href="#loop"><span class="n"></span> The loop</a></li>
168
+ <li><a href="#install"><span class="n"></span> Install</a></li>
169
+ <li><a href="#commands"><span class="n"></span> Command map</a></li>
170
+ <li><a href="#first"><span class="n"></span> Your first project</a></li>
171
+ <li><a href="#rules"><span class="n"></span> The hard rules</a></li>
172
+ <li><a href="#keys"><span class="n"></span> Credentials</a></li>
173
+ <li><a href="#lost"><span class="n"></span> When you're lost</a></li>
174
+ </ol>
175
+ </nav>
176
+
177
+ <main id="top">
178
+ <header class="hero reveal">
179
+ <span class="eyebrow"><span class="dot"></span> Getting started · v6.9</span>
180
+ <h1>The Qualia <span class="g">Framework</span>,<br>in one sitting.</h1>
181
+ <p class="lede">A set of commands that takes you from an <b style="color:#fff">idea</b> to a <b style="color:#fff">shipped, reported</b> piece of work, without needing to memorise how Qualia builds software. You type one command, it tells you the next. This manual is the human-friendly tour of that path.</p>
182
+ </header>
183
+
184
+ <!-- WHAT -->
185
+ <section id="what" class="reveal">
186
+ <h2><span class="bar"></span> What it is</h2>
187
+ <p class="sec-sub">Qualia is an opinionated workflow that lives inside Claude Code (and Codex). It ships a lifecycle of <code>/qualia-*</code> slash commands, plus the guardrails (hooks, rules, and a shared brain) that keep every project consistent and safe.</p>
188
+ <div class="grid g3">
189
+ <div class="panel"><h3>🧭 It routes you</h3><p class="dim">Never wonder what's next. <code>/qualia</code> reads your project's state and hands you the exact next command. The framework keeps the map; you keep moving.</p></div>
190
+ <div class="panel"><h3>🛟 It guards you</h3><p class="dim">Deterministic hooks block the dangerous stuff (pushing to <code>main</code>, leaking secrets, destructive DB ops) before it happens. Rules aren't suggestions; they're enforced.</p></div>
191
+ <div class="panel"><h3>🧠 It remembers</h3><p class="dim">Lessons from every project flow into a shared knowledge base. A fix one person discovered shows up in everyone's install, so the team gets smarter over time.</p></div>
192
+ </div>
193
+ <div class="note teal"><b>The one-line mental model:</b> you describe <em>what</em> you want; the framework owns <em>how</em> Qualia builds it: the stack, the phases, the quality bar, the deploy steps.</div>
194
+ </section>
195
+
196
+ <!-- LOOP -->
197
+ <section id="loop" class="reveal">
198
+ <h2><span class="bar"></span> The loop</h2>
199
+ <p class="sec-sub">Every project walks the same path. You don't have to remember it (<code>/qualia</code> always points to the right step), but seeing it once makes everything click.</p>
200
+ <div class="flow">
201
+ <div class="step"><div class="k">START</div><div class="cmdname">/qualia-new</div><div class="d">Kickoff interview → pick a project preset → planning files written.</div></div>
202
+ <div class="arrow">→</div>
203
+ <div class="step"><div class="k">PLAN</div><div class="cmdname">/qualia-plan</div><div class="d">Break the current phase into small, wave-grouped tasks.</div></div>
204
+ <div class="arrow">→</div>
205
+ <div class="step"><div class="k">BUILD</div><div class="cmdname">/qualia-build</div><div class="d">Fresh builder agents execute the tasks, one atomic commit each.</div></div>
206
+ <div class="arrow">→</div>
207
+ <div class="step"><div class="k">VERIFY</div><div class="cmdname">/qualia-verify</div><div class="d">Goal-backward check that it actually works, not just that code ran.</div></div>
208
+ <div class="arrow">→</div>
209
+ <div class="step"><div class="k">SHIP</div><div class="cmdname">/qualia-ship</div><div class="d">Quality gates → commit → deploy to Vercel → verify live.</div></div>
210
+ <div class="arrow">→</div>
211
+ <div class="step"><div class="k">REPORT</div><div class="cmdname">/qualia-report</div><div class="d">Clock out, then generate your shift report and file it.</div></div>
212
+ </div>
213
+ <p class="dim">Plan → Build → Verify repeats per phase until the milestone is done. <code>/qualia-polish</code> slots in before shipping when the work is visual.</p>
214
+ </section>
215
+
216
+ <!-- INSTALL -->
217
+ <section id="install" class="reveal">
218
+ <h2><span class="bar"></span> Install in employee mode</h2>
219
+ <p class="sec-sub">You can install and do real work <b>today</b>, with no credentials. Employee mode gives you the full framework at the safest privilege level: feature branches only, no pushes to <code>main</code>.</p>
220
+ <pre><span class="c"># one command, works on a fresh machine</span>
221
+ npx qualia-framework@latest install</pre>
222
+ <p>At the prompt <code>Install code or "EMPLOYEE":</code></p>
223
+ <table>
224
+ <tr><th>If you…</th><th>Type</th><th>You get</th></tr>
225
+ <tr><td>Have a team code</td><td><code>QS-NAME-##</code></td><td>Install as that team member (ERP reporting on, with the key)</td></tr>
226
+ <tr><td>Have no code yet</td><td><code>EMPLOYEE</code></td><td>Full framework, EMPLOYEE role, ERP reporting off until a code is set</td></tr>
227
+ </table>
228
+ <div class="note"><b>Nothing is missing in employee mode.</b> Skills, agents, hooks, and knowledge are installed identically. The only differences: you can't push to <code>main</code> (the <code>branch-guard</code> hook blocks it), and <code>/qualia-report</code> saves a <em>local</em> report instead of uploading to the ERP. Ask Fawzi for a <code>QS-NAME-##</code> code when you're ready to upgrade.</div>
229
+ <h3>Then confirm it's healthy</h3>
230
+ <p>Run the doctor before real work. It checks the install, hooks, project state, and the ERP queue, and prints PASS/FAIL with the exact fix for anything wrong.</p>
231
+ <p><span class="cmd" data-copy="/qualia-doctor">/qualia-doctor<span class="ico">copy</span></span> <span class="dim">in employee mode it reports ERP as disabled; that's expected, not an error.</span></p>
232
+ </section>
233
+
234
+ <!-- COMMANDS -->
235
+ <section id="commands" class="reveal">
236
+ <h2><span class="bar"></span> The command map</h2>
237
+ <p class="sec-sub">You won't use all of these on day one. The lifecycle six (the loop above) cover most work; the rest are there when you need them. Click any command to copy it.</p>
238
+
239
+ <div class="group-label">◆ Orient · where am I, what now</div>
240
+ <div class="cards">
241
+ <div class="card"><h4 data-copy="/qualia">/qualia</h4><p>The router. Reads your state, tells you the single next command. When in doubt, run this.</p></div>
242
+ <div class="card"><h4 data-copy="/qualia-idk">/qualia-idk</h4><p>Deep "I don't know what's going on" diagnostic. Plain-language guidance + a paste-ready command sequence.</p></div>
243
+ <div class="card"><h4 data-copy="/qualia-map">/qualia-map</h4><p>Inherited an existing codebase? Map it first (architecture, stack, conventions) before <code>/qualia-new</code>.</p></div>
244
+ </div>
245
+
246
+ <div class="group-label">◆ Build · idea to working code</div>
247
+ <div class="cards">
248
+ <div class="card"><h4 data-copy="/qualia-new">/qualia-new</h4><p>Start a project. Interview, research, planning substrate, pick a preset.</p></div>
249
+ <div class="card"><h4 data-copy="/qualia-scope">/qualia-scope</h4><p>Adversarial intake that grills the requirements before you plan, so v1 is the right v1.</p></div>
250
+ <div class="card"><h4 data-copy="/qualia-plan">/qualia-plan</h4><p>Break the current phase into wave-grouped, verifiable tasks.</p></div>
251
+ <div class="card"><h4 data-copy="/qualia-build">/qualia-build</h4><p>Execute the plan with fresh builder agents and atomic per-task commits.</p></div>
252
+ <div class="card"><h4 data-copy="/qualia-feature">/qualia-feature</h4><p>One small thing (1–5 files), auto-scoped. Skips the full phase machinery.</p></div>
253
+ <div class="card"><h4 data-copy="/qualia-fix">/qualia-fix</h4><p>Something's broken. Root-cause investigation, then a targeted repair.</p></div>
254
+ </div>
255
+
256
+ <div class="group-label">◆ Check & ship · make it real</div>
257
+ <div class="cards">
258
+ <div class="card"><h4 data-copy="/qualia-verify">/qualia-verify</h4><p>Goal-backward verification against acceptance criteria + design rubric.</p></div>
259
+ <div class="card"><h4 data-copy="/qualia-test">/qualia-test</h4><p>Write or run tests; <code>--tdd</code> drives a feature test-first.</p></div>
260
+ <div class="card"><h4 data-copy="/qualia-polish">/qualia-polish</h4><p>Visual quality pass: component, route, whole app, or a vibe pivot.</p></div>
261
+ <div class="card"><h4 data-copy="/qualia-review">/qualia-review</h4><p>Read-only production audit, severity-scored.</p></div>
262
+ <div class="card"><h4 data-copy="/qualia-ship">/qualia-ship</h4><p>Full deploy pipeline: gates → commit → deploy → verify live.</p></div>
263
+ </div>
264
+
265
+ <div class="group-label">◆ Maintain · close the loop</div>
266
+ <div class="cards">
267
+ <div class="card"><h4 data-copy="/qualia-report">/qualia-report</h4><p>Clock out. Generate and file your shift report (uploads to ERP when configured).</p></div>
268
+ <div class="card"><h4 data-copy="/qualia-learn">/qualia-learn</h4><p>Save a lesson, pattern, or client preference to the shared brain.</p></div>
269
+ <div class="card"><h4 data-copy="/qualia-milestone">/qualia-milestone</h4><p>Close the current milestone and open the next from the journey map.</p></div>
270
+ <div class="card"><h4 data-copy="/qualia-doctor">/qualia-doctor</h4><p>Health check the framework when something feels off.</p></div>
271
+ </div>
272
+ </section>
273
+
274
+ <!-- FIRST PROJECT -->
275
+ <section id="first" class="reveal">
276
+ <h2><span class="bar"></span> Your first project</h2>
277
+ <p class="sec-sub">A landing page, start to finish. The same shape works for anything bigger: just more Plan → Build → Verify loops.</p>
278
+ <div class="walk">
279
+ <div class="w"><span class="wn"></span><div class="wbody">
280
+ <h4>Install &amp; check</h4>
281
+ <p class="dim">Run <code>npx qualia-framework@latest install</code> (type <code>EMPLOYEE</code>), then <span class="cmd" data-copy="/qualia-doctor">/qualia-doctor<span class="ico">copy</span></span>. Green across the board? Go.</p>
282
+ </div></div>
283
+ <div class="w"><span class="wn"></span><div class="wbody">
284
+ <h4>Start the project</h4>
285
+ <p class="dim"><span class="cmd" data-copy="/qualia-new">/qualia-new<span class="ico">copy</span></span>: answer the short interview, and when asked, pick the <b>landing-page</b> preset. You now have a running skeleton and a phase plan, not a blank page.</p>
286
+ </div></div>
287
+ <div class="w"><span class="wn"></span><div class="wbody">
288
+ <h4>Plan → Build → Verify</h4>
289
+ <p class="dim">Walk the phase: <span class="cmd" data-copy="/qualia-plan">/qualia-plan<span class="ico">copy</span></span> <span class="cmd" data-copy="/qualia-build">/qualia-build<span class="ico">copy</span></span> <span class="cmd" data-copy="/qualia-verify">/qualia-verify<span class="ico">copy</span></span>. Repeat until verify passes. Stuck between steps? <code>/qualia</code>.</p>
290
+ </div></div>
291
+ <div class="w"><span class="wn"></span><div class="wbody">
292
+ <h4>Polish the look</h4>
293
+ <p class="dim">Landing pages live or die on design. <span class="cmd" data-copy="/qualia-polish">/qualia-polish<span class="ico">copy</span></span> runs a visual quality pass against the design rubric.</p>
294
+ </div></div>
295
+ <div class="w"><span class="wn"></span><div class="wbody">
296
+ <h4>Ship it</h4>
297
+ <p class="dim"><span class="cmd" data-copy="/qualia-ship">/qualia-ship<span class="ico">copy</span></span>: gates, commit, deploy to Vercel, verify live. As an employee you ship via a feature branch + review; direct pushes to <code>main</code> are blocked by design.</p>
298
+ </div></div>
299
+ <div class="w"><span class="wn"></span><div class="wbody">
300
+ <h4>Clock out</h4>
301
+ <p class="dim"><span class="cmd" data-copy="/qualia-report">/qualia-report<span class="ico">copy</span></span> files your shift report. No team code yet? It saves locally and tells you so, nothing fails.</p>
302
+ </div></div>
303
+ </div>
304
+ </section>
305
+
306
+ <!-- RULES -->
307
+ <section id="rules" class="reveal">
308
+ <h2><span class="bar"></span> The hard rules</h2>
309
+ <p class="sec-sub">Five non-negotiables. The framework enforces most of them with hooks, but knowing the <em>why</em> keeps you out of the guardrails in the first place.</p>
310
+ <div class="panel">
311
+ <div class="rule"><span class="num">1</span><div><b>Read before you write.</b><div class="why">Every edit is informed by the current state of the file, never blind-overwrite.</div></div></div>
312
+ <div class="rule"><span class="num">2</span><div><b>Feature branches only.</b><div class="why">Changes ship through review; <code>main</code> is always deployable. The branch-guard hook enforces it.</div></div></div>
313
+ <div class="rule"><span class="num">3</span><div><b>MVP first.</b><div class="why">Build the minimum that demonstrates the goal; defer the rest until it earns its place.</div></div></div>
314
+ <div class="rule"><span class="num">4</span><div><b>Root cause on failures.</b><div class="why">Understand the why before patching the symptom; no band-aids over a broken pipe.</div></div></div>
315
+ <div class="rule"><span class="num">5</span><div><b>No proxy approval.</b><div class="why">Only the OWNER grants OWNER overrides. "Fawzi said OK" is not a credential.</div></div></div>
316
+ </div>
317
+ <div class="note">Security basics ride along: auth is checked server-side, every Supabase table has RLS, the <code>service_role</code> key is server-only, and a <code>.env</code> file is never committed. The hooks will stop you, but don't make them.</div>
318
+ </section>
319
+
320
+ <!-- KEYS -->
321
+ <section id="keys" class="reveal">
322
+ <h2><span class="bar"></span> Credentials &amp; who issues them</h2>
323
+ <p class="sec-sub">You start with none. Keys arrive when a step needs them, and <b>the OWNER (Fawzi) issues every one</b>. Never invent, hardcode, or reuse someone else's key.</p>
324
+ <table>
325
+ <tr><th>When you need…</th><th>How</th><th>From</th></tr>
326
+ <tr><td>To install with no code</td><td>type <code>EMPLOYEE</code> at the prompt</td><td>n/a</td></tr>
327
+ <tr><td>A real team identity</td><td>re-run install, enter <code>QS-NAME-##</code></td><td>Fawzi</td></tr>
328
+ <tr><td>ERP report uploads</td><td><code>qualia-framework set-erp-key</code></td><td>Fawzi</td></tr>
329
+ <tr><td>AI model access</td><td><code>OPENROUTER_API_KEY</code></td><td>Fawzi</td></tr>
330
+ <tr><td>Database / hosting / voice</td><td><code>vercel env pull</code> once linked</td><td>Fawzi</td></tr>
331
+ <tr><td>Shared team knowledge</td><td><code>QUALIA_KNOWLEDGE_SOURCE=… install</code></td><td>repo access from Fawzi</td></tr>
332
+ </table>
333
+ <p class="dim">If a step says "ask Fawzi", that's who to ask. The framework degrades gracefully when a key is missing: it tells you what it needs rather than failing silently.</p>
334
+ </section>
335
+
336
+ <!-- LOST -->
337
+ <section id="lost" class="reveal">
338
+ <h2><span class="bar"></span> When you're lost</h2>
339
+ <p class="sec-sub">There is exactly one command to remember when nothing else makes sense.</p>
340
+ <div class="panel" style="text-align:center;padding:34px">
341
+ <span class="cmd" data-copy="/qualia" style="font-size:18px;padding:12px 16px 12px 22px">/qualia<span class="ico" style="font-size:13px">copy</span></span>
342
+ <p class="dim" style="margin-top:16px;max-width:52ch;margin-inline:auto">It reads where your project is and tells you the precise next move. If you only ever memorise one thing from this manual, memorise this. For a deeper "something feels off" read, use <code>/qualia-idk</code>.</p>
343
+ </div>
344
+ </section>
345
+
346
+ <footer>
347
+ <p>Qualia Framework · Field Manual · Qualia Solutions, Nicosia 🇨🇾 · a web/AI agency.<br>
348
+ The framework is the source of truth; if this manual and a command ever disagree, the command wins. Built to be read once and kept nearby.</p>
349
+ </footer>
350
+ </main>
351
+ </div>
352
+
353
+ <script>
354
+ (function(){
355
+ // ── copy-to-clipboard on any [data-copy] (command chips & cards) ──
356
+ function flash(el, label){
357
+ var prev = el.querySelector('.ico');
358
+ el.classList.add('copied');
359
+ if(prev){ var t=prev.textContent; prev.textContent='copied ✓'; setTimeout(function(){prev.textContent=t;el.classList.remove('copied');},1100); }
360
+ else { setTimeout(function(){el.classList.remove('copied');},1100); }
361
+ }
362
+ document.querySelectorAll('[data-copy]').forEach(function(el){
363
+ el.style.cursor='pointer';
364
+ el.setAttribute('title','Click to copy');
365
+ el.addEventListener('click',function(){
366
+ var txt = el.getAttribute('data-copy');
367
+ navigator.clipboard && navigator.clipboard.writeText(txt).then(function(){flash(el);}).catch(function(){flash(el);});
368
+ });
369
+ });
370
+
371
+ // ── scroll-spy: highlight the active nav link ──
372
+ var links = Array.prototype.slice.call(document.querySelectorAll('nav.side a'));
373
+ var map = {};
374
+ links.forEach(function(a){ map[a.getAttribute('href').slice(1)] = a; });
375
+ var spy = new IntersectionObserver(function(entries){
376
+ entries.forEach(function(e){
377
+ if(e.isIntersecting){
378
+ links.forEach(function(a){a.classList.remove('active');});
379
+ var a = map[e.target.id]; if(a) a.classList.add('active');
380
+ }
381
+ });
382
+ },{rootMargin:'-45% 0px -50% 0px',threshold:0});
383
+ document.querySelectorAll('section[id]').forEach(function(s){spy.observe(s);});
384
+
385
+ // close mobile nav on link tap
386
+ links.forEach(function(a){a.addEventListener('click',function(){document.querySelector('nav.side').classList.remove('open');});});
387
+
388
+ // ── reveal on scroll ──
389
+ var rev = new IntersectionObserver(function(entries){
390
+ entries.forEach(function(e){ if(e.isIntersecting){ e.target.classList.add('in'); rev.unobserve(e.target);} });
391
+ },{rootMargin:'0px 0px -10% 0px',threshold:.08});
392
+ document.querySelectorAll('.reveal').forEach(function(el){rev.observe(el);});
393
+ })();
394
+ </script>
395
+ </body>
396
+ </html>
@@ -167,17 +167,37 @@ function maybeDrainErpQueue() {
167
167
  } catch {}
168
168
  }
169
169
 
170
+ function cmpVersions(a, b) {
171
+ // Returns >0 if a>b, <0 if a<b, 0 if equal. Tolerates missing/non-numeric
172
+ // segments by treating them as 0. Pure semver-major.minor.patch compare.
173
+ const pa = String(a || "0").split(".").map((n) => parseInt(n, 10) || 0);
174
+ const pb = String(b || "0").split(".").map((n) => parseInt(n, 10) || 0);
175
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
176
+ const d = (pa[i] || 0) - (pb[i] || 0);
177
+ if (d !== 0) return d;
178
+ }
179
+ return 0;
180
+ }
181
+
170
182
  function maybeRenderUpdateBanner() {
171
183
  // EMPLOYEE-only sticky banner. auto-update.js writes NOTIF_FILE when a new
172
184
  // version is detected; we render it every session until the user actually
173
- // runs `npx qualia-framework@latest install`. The file is cleared by
174
- // auto-update.js once the install completes or the version catches up.
185
+ // runs `npx qualia-framework@latest install`. auto-update.js clears the file
186
+ // when it next runs and finds the version caught up — but it is throttled, so
187
+ // between an install and that next run the notif can be stale. We therefore
188
+ // self-validate here against the installed version (.qualia-config.json) and
189
+ // clear+skip a stale notice rather than show a false "update available".
175
190
  if (!fs.existsSync(NOTIF_FILE) || !fs.existsSync(UI)) return;
176
191
  try {
177
192
  const notif = JSON.parse(fs.readFileSync(NOTIF_FILE, "utf8"));
178
- if (notif && notif.current && notif.latest) {
179
- runUi("update", notif.current, notif.latest);
193
+ if (!notif || !notif.current || !notif.latest) return;
194
+ const installed = readConfig().version;
195
+ if (installed && cmpVersions(installed, notif.latest) >= 0) {
196
+ // Already at or past the advertised latest — the notice is stale.
197
+ try { fs.unlinkSync(NOTIF_FILE); } catch {}
198
+ return;
180
199
  }
200
+ runUi("update", notif.current, notif.latest);
181
201
  } catch {}
182
202
  }
183
203
 
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * UserPromptSubmit hook — per-session framework-usage capture.
4
+ *
5
+ * Records, into `.planning/.session-usage.json` of the active project:
6
+ * - command_usage : a histogram of qualia slash-commands the engineer fired
7
+ * - prompt_samples: the engineer's real prompts (opt-in), for the ERP
8
+ * prompt-quality judge
9
+ *
10
+ * `report-payload.js` folds both into the /qualia-report clock-out payload, and
11
+ * `/qualia-report` clears the file after a successful upload. This is what feeds
12
+ * the ERP performance audit's "framework usage & prompting" pillar (command
13
+ * volume + diversity, and LLM-judged prompt quality).
14
+ *
15
+ * Privacy: prompt capture is opt-in via `erp.capturePrompts` in
16
+ * ~/.claude/.qualia-config.json (default ON for the internal team; set to false
17
+ * to record command counts only). The file is a .planning dotfile and is
18
+ * deleted at clock-out — it must never be committed.
19
+ *
20
+ * Never blocks: any error exits 0 with no stdout (adds no context to the prompt).
21
+ */
22
+ const fs = require('fs');
23
+ const os = require('os');
24
+ const path = require('path');
25
+
26
+ const MAX_SAMPLES = 60;
27
+ const MAX_SAMPLE_LEN = 8000;
28
+
29
+ function readJson(file, fallback) {
30
+ try {
31
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
32
+ } catch {
33
+ return fallback;
34
+ }
35
+ }
36
+
37
+ function capturePromptsEnabled(home) {
38
+ const cfg =
39
+ readJson(path.join(home, '.claude', '.qualia-config.json'), null) ||
40
+ readJson(path.join(home, '.codex', '.qualia-config.json'), {});
41
+ return !cfg || !cfg.erp || cfg.erp.capturePrompts !== false;
42
+ }
43
+
44
+ function main() {
45
+ let raw = '';
46
+ try {
47
+ raw = fs.readFileSync(0, 'utf8');
48
+ } catch {
49
+ process.exit(0);
50
+ }
51
+ let input = {};
52
+ try {
53
+ input = JSON.parse(raw);
54
+ } catch {
55
+ process.exit(0);
56
+ }
57
+
58
+ const prompt = typeof input.prompt === 'string' ? input.prompt : '';
59
+ const cwd = input.cwd || input.cwd_path || process.cwd();
60
+ if (!prompt.trim()) process.exit(0);
61
+
62
+ // Only record inside a Qualia project (has .planning/).
63
+ const planningDir = path.join(cwd, '.planning');
64
+ if (!fs.existsSync(planningDir)) process.exit(0);
65
+
66
+ const usageFile = path.join(planningDir, '.session-usage.json');
67
+ const usage = readJson(usageFile, { command_usage: {}, prompt_samples: [] });
68
+ if (!usage.command_usage || typeof usage.command_usage !== 'object') usage.command_usage = {};
69
+ if (!Array.isArray(usage.prompt_samples)) usage.prompt_samples = [];
70
+
71
+ // Count a qualia slash-command if one appears as a token in the prompt.
72
+ const m = prompt.match(/(?:^|\s)\/(qualia[a-z0-9-]*)/i);
73
+ if (m) {
74
+ const cmd = m[1].toLowerCase();
75
+ usage.command_usage[cmd] = (usage.command_usage[cmd] || 0) + 1;
76
+ }
77
+
78
+ // Sample the real prompt for the prompt-quality judge (opt-in, disclosed).
79
+ if (capturePromptsEnabled(os.homedir())) {
80
+ // Transparency: tell the engineer once per shift that prompts are recorded
81
+ // and how to turn it off. stderr so it's seen but never enters model
82
+ // context. The flag rides in the usage file, which is cleared at clock-out,
83
+ // so the notice reappears each new shift. Best-effort — never blocks.
84
+ if (!usage.notice_shown) {
85
+ try {
86
+ process.stderr.write(
87
+ '\x1b[38;2;80;90;100m[qualia] This session records your /qualia command usage and ' +
88
+ 'prompt samples for the team performance audit. Opt out anytime: set ' +
89
+ 'erp.capturePrompts=false in ~/.claude/.qualia-config.json\x1b[0m\n'
90
+ );
91
+ } catch { /* never block the prompt */ }
92
+ usage.notice_shown = true;
93
+ }
94
+ usage.prompt_samples.push(prompt.trim().slice(0, MAX_SAMPLE_LEN));
95
+ if (usage.prompt_samples.length > MAX_SAMPLES) {
96
+ usage.prompt_samples = usage.prompt_samples.slice(-MAX_SAMPLES);
97
+ }
98
+ }
99
+
100
+ try {
101
+ fs.writeFileSync(usageFile, JSON.stringify(usage));
102
+ } catch {
103
+ /* best-effort; never block the prompt */
104
+ }
105
+ process.exit(0);
106
+ }
107
+
108
+ main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qualia-framework",
3
- "version": "6.9.0",
3
+ "version": "6.9.2",
4
4
  "description": "Claude Code and Codex workflow framework by Qualia Solutions. Plan, build, verify, ship.",
5
5
  "bin": {
6
6
  "qualia-framework": "./bin/cli.js"
@@ -52,6 +52,7 @@
52
52
  "docs/release.md",
53
53
  "docs/changelog-v6.html",
54
54
  "docs/onboarding.html",
55
+ "docs/qualia-manual.html",
55
56
  "docs/EMPLOYEE-QUICKSTART.md",
56
57
  "CLAUDE.md",
57
58
  "AGENTS.md",
@@ -230,6 +230,9 @@ if [ "$ERP_ENABLED" = "true" ]; then
230
230
  if [ "$HTTP_CODE" = "200" ]; then
231
231
  ERP_REPORT_ID=$(echo "$BODY" | node -e "try{process.stdout.write(JSON.parse(require('fs').readFileSync(0,'utf8')).report_id||'')}catch{}")
232
232
  node ${QUALIA_BIN}/qualia-ui.js ok "Uploaded as $CLIENT_REPORT_ID (ERP: ${ERP_REPORT_ID:-none})"
233
+ # Reset per-session usage telemetry (command_usage + prompt_samples) now
234
+ # that it has been delivered, so the next shift starts from a clean slate.
235
+ rm -f .planning/.session-usage.json 2>/dev/null
233
236
  break
234
237
  fi
235
238
  if [ "$HTTP_CODE" = "401" ] || [ "$HTTP_CODE" = "422" ]; then
@@ -8,3 +8,6 @@ tracking.json
8
8
  .state.journal
9
9
  .backup/
10
10
  migration-manifest.json
11
+ # Performance-audit telemetry buffer. Holds the engineer's real prompt samples
12
+ # until clock-out — must NEVER be committed (raw prompt text, may contain PII).
13
+ .session-usage.json
package/tests/bin.test.sh CHANGED
@@ -487,14 +487,15 @@ else
487
487
  fail_case "CLAUDE.md role substitution"
488
488
  fi
489
489
 
490
- # 31. All 13 hooks installed (block-env-edit removed in v3.2.0;
490
+ # 31. All 14 hooks installed (block-env-edit removed in v3.2.0;
491
491
  # git-guardrails + stop-session-log added in v4.2.0;
492
492
  # vercel-account-guard + env-empty-guard + supabase-destructive-guard added in v5.0.0;
493
493
  # fawzi-approval-guard added in v6.2.11; pre-compact removed in v6.2.0 and
494
- # REINTRODUCED in v6.3.2 with sidecar-snapshot mechanism)
494
+ # REINTRODUCED in v6.3.2 with sidecar-snapshot mechanism;
495
+ # usage-capture added in v6.9.1 — UserPromptSubmit telemetry capture)
495
496
  HOOK_COUNT=$(ls "$TMP/.claude/hooks/"*.js 2>/dev/null | wc -l)
496
- if [ "$HOOK_COUNT" -eq 13 ]; then
497
- pass "13 hooks installed in hooks/ (incl. pre-compact v6.3.2)"
497
+ if [ "$HOOK_COUNT" -eq 14 ]; then
498
+ pass "14 hooks installed in hooks/ (incl. usage-capture v6.9.1)"
498
499
  else
499
500
  fail_case "hook count" "got $HOOK_COUNT"
500
501
  fi
@@ -420,6 +420,38 @@ else
420
420
  fi
421
421
  rm -rf "$TMP"
422
422
 
423
+ # --- session-start.js — stale update banner self-clears (regression: v6.9.1) ---
424
+ # A notif file whose advertised `latest` is already installed must NOT render a
425
+ # false "update available" banner; it must be deleted. A genuinely-newer notif
426
+ # must be preserved.
427
+ QH=$(mktemp -d)
428
+ mkdir -p "$QH/bin"
429
+ echo 'process.exit(0)' > "$QH/bin/qualia-ui.js" # dummy UI so render path is reachable
430
+ echo '{"code":"QS-FAWZI-11","version":"6.9.0"}' > "$QH/.qualia-config.json"
431
+
432
+ # Case 1: installed (6.9.0) >= notif.latest (6.9.0) → stale, must self-clear
433
+ echo '{"current":"6.8.1","latest":"6.9.0","detected_at":"t"}' > "$QH/.qualia-update-available.json"
434
+ (cd "$QH" && QUALIA_HOME="$QH" $NODE "$HOOKS_DIR/session-start.js" >/dev/null 2>&1)
435
+ if [ ! -f "$QH/.qualia-update-available.json" ]; then
436
+ echo " ✓ stale update notice self-clears when version caught up"
437
+ PASS=$((PASS + 1))
438
+ else
439
+ echo " ✗ stale update notice was NOT cleared"
440
+ FAIL=$((FAIL + 1))
441
+ fi
442
+
443
+ # Case 2: installed (6.9.0) < notif.latest (9.9.9) → genuine, must persist
444
+ echo '{"current":"6.9.0","latest":"9.9.9","detected_at":"t"}' > "$QH/.qualia-update-available.json"
445
+ (cd "$QH" && QUALIA_HOME="$QH" $NODE "$HOOKS_DIR/session-start.js" >/dev/null 2>&1)
446
+ if [ -f "$QH/.qualia-update-available.json" ]; then
447
+ echo " ✓ genuine update notice is preserved"
448
+ PASS=$((PASS + 1))
449
+ else
450
+ echo " ✗ genuine update notice was wrongly cleared"
451
+ FAIL=$((FAIL + 1))
452
+ fi
453
+ rm -rf "$QH"
454
+
423
455
  # pre-compact.js removed in v6.2.0 — state.js journal provides crash safety.
424
456
 
425
457
  # --- auto-update.js ---
@@ -124,10 +124,11 @@ else
124
124
  fi
125
125
 
126
126
  if [ -d "$HOME_DIR/.claude/hooks" ] \
127
- && [ "$(find "$HOME_DIR/.claude/hooks" -maxdepth 1 -name '*.js' | wc -l | tr -d ' ')" = "13" ] \
127
+ && [ "$(find "$HOME_DIR/.claude/hooks" -maxdepth 1 -name '*.js' | wc -l | tr -d ' ')" = "14" ] \
128
128
  && [ -f "$HOME_DIR/.claude/hooks/fawzi-approval-guard.js" ] \
129
- && [ -f "$HOME_DIR/.claude/hooks/pre-compact.js" ]; then
130
- pass "packaged install has 13 hooks including pre-compact (v6.3.2 sidecar snapshot)"
129
+ && [ -f "$HOME_DIR/.claude/hooks/pre-compact.js" ] \
130
+ && [ -f "$HOME_DIR/.claude/hooks/usage-capture.js" ]; then
131
+ pass "packaged install has 14 hooks including usage-capture (v6.9.1 telemetry)"
131
132
  else
132
133
  fail_case "packaged hook set mismatch"
133
134
  fi