qualia-framework 6.2.10 → 6.3.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.
Files changed (58) hide show
  1. package/AGENTS.md +1 -0
  2. package/CLAUDE.md +1 -0
  3. package/README.md +16 -23
  4. package/bin/cli.js +49 -2
  5. package/bin/command-surface.js +71 -0
  6. package/bin/harness-eval.js +296 -0
  7. package/bin/install.js +17 -20
  8. package/bin/knowledge-flush.js +21 -10
  9. package/bin/knowledge.js +1 -1
  10. package/bin/project-snapshot.js +20 -0
  11. package/bin/report-payload.js +18 -0
  12. package/bin/runtime-manifest.js +3 -0
  13. package/bin/state.js +31 -0
  14. package/bin/trust-score.js +3 -11
  15. package/bin/work-packet.js +228 -0
  16. package/docs/erp-contract.md +81 -1
  17. package/docs/onboarding.html +0 -11
  18. package/guide.md +14 -15
  19. package/hooks/fawzi-approval-guard.js +143 -0
  20. package/hooks/pre-deploy-gate.js +74 -1
  21. package/hooks/session-start.js +29 -1
  22. package/package.json +1 -1
  23. package/qualia-design/frontend.md +2 -2
  24. package/rules/codex-goal.md +1 -1
  25. package/rules/one-opinion.md +2 -2
  26. package/rules/speed.md +0 -1
  27. package/skills/qualia/SKILL.md +4 -4
  28. package/skills/qualia-feature/SKILL.md +1 -1
  29. package/skills/qualia-fix/SKILL.md +4 -4
  30. package/skills/qualia-learn/SKILL.md +1 -1
  31. package/skills/qualia-polish/REFERENCE.md +1 -1
  32. package/skills/qualia-polish/SKILL.md +19 -4
  33. package/skills/{qualia-vibe/scripts/extract.mjs → qualia-polish/scripts/vibe-extract.mjs} +4 -4
  34. package/skills/{qualia-vibe/scripts/tokens.mjs → qualia-polish/scripts/vibe-tokens.mjs} +6 -6
  35. package/skills/qualia-road/SKILL.md +15 -20
  36. package/skills/qualia-ship/SKILL.md +12 -5
  37. package/skills/qualia-verify/SKILL.md +9 -1
  38. package/templates/help.html +1 -12
  39. package/tests/bin.test.sh +144 -72
  40. package/tests/hooks.test.sh +81 -1
  41. package/tests/install-smoke.test.sh +13 -3
  42. package/tests/lib.test.sh +145 -3
  43. package/tests/published-install-smoke.test.sh +4 -3
  44. package/tests/refs.test.sh +9 -4
  45. package/tests/runner.js +29 -28
  46. package/tests/state.test.sh +68 -0
  47. package/skills/qualia-debug/SKILL.md +0 -193
  48. package/skills/qualia-flush/SKILL.md +0 -198
  49. package/skills/qualia-help/SKILL.md +0 -74
  50. package/skills/qualia-hook-gen/SKILL.md +0 -206
  51. package/skills/qualia-idk/SKILL.md +0 -166
  52. package/skills/qualia-issues/SKILL.md +0 -151
  53. package/skills/qualia-pause/SKILL.md +0 -68
  54. package/skills/qualia-resume/SKILL.md +0 -52
  55. package/skills/qualia-skill-new/SKILL.md +0 -173
  56. package/skills/qualia-triage/SKILL.md +0 -152
  57. package/skills/qualia-vibe/SKILL.md +0 -229
  58. package/skills/qualia-zoom/SKILL.md +0 -51
@@ -31,6 +31,29 @@ The API key is read from `.erp-api-key` in the same install home (file mode 0600
31
31
 
32
32
  ## Endpoints
33
33
 
34
+ ### POST /api/v1/policy-events
35
+
36
+ Silently record framework policy violations that should not interrupt the
37
+ employee's active session, such as proxy owner-approval claims ("Fawzi said OK").
38
+
39
+ **Request Body:**
40
+ ```json
41
+ {
42
+ "type": "proxy_owner_approval_claim",
43
+ "actor_code": "QS-HASAN-02",
44
+ "actor_name": "Hasan",
45
+ "actor_role": "EMPLOYEE",
46
+ "count": 3,
47
+ "sample": "Fawzi said ok",
48
+ "project": "client-project",
49
+ "cwd": "/path/to/client-project",
50
+ "recorded_at": "2026-05-23T10:00:00.000Z"
51
+ }
52
+ ```
53
+
54
+ The ERP should increment or store by `(type, actor_code)` so Fawzi can see how
55
+ many times each employee attempted to use proxy approval.
56
+
34
57
  ### POST /api/v1/reports
35
58
 
36
59
  Upload a session report.
@@ -59,6 +82,9 @@ below).
59
82
  "team_id": "qualia-solutions",
60
83
  "workspace_id": "2af02a2d-6f1f-4d43-a6cb-6a1e7e09ac43",
61
84
  "git_remote": "github.com/QualiasolutionsCY/acme-portal",
85
+ "work_packet_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
86
+ "assignment_id": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
87
+ "assignment_deadline": "2026-06-01",
62
88
  "client": "Client Name",
63
89
  "client_report_id": "QS-REPORT-03",
64
90
  "milestone": 2,
@@ -90,6 +116,13 @@ below).
90
116
  "total_phases": 8,
91
117
  "last_closed_milestone": 1
92
118
  },
119
+ "harness_eval": {
120
+ "status": "PASS",
121
+ "score": 92,
122
+ "phase": 2,
123
+ "generated_at": "2026-05-23T10:15:00.000Z",
124
+ "artifact": ".planning/evals/harness-eval-2026-05-23T10-15-00-000Z.json"
125
+ },
93
126
  "session_started_at": "2026-04-12T13:45:00Z",
94
127
  "session_duration_minutes": 45,
95
128
  "last_pushed_at": "2026-04-12T14:25:00Z",
@@ -173,6 +206,37 @@ Authorization: Bearer <api-key>
173
206
  }
174
207
  ```
175
208
 
209
+ ## Work Packet Pull
210
+
211
+ `qualia-framework work-packet pull --project <erp_project_id>` reads the active
212
+ ERP mission packet and writes it to:
213
+
214
+ ```text
215
+ .planning/work-packet.json
216
+ ```
217
+
218
+ The Framework treats this as session context, not as a new source of business
219
+ truth. ERP owns the employee, deadline, assignment, review state, and owner
220
+ approval. Framework owns build, verification, snapshot, and report proof.
221
+
222
+ Local packet shape:
223
+
224
+ ```json
225
+ {
226
+ "id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
227
+ "project_id": "7b5d3b4e-2b8a-4de4-91a1-9b2f3182f5ef",
228
+ "assignment_id": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
229
+ "deadline_date": "2026-06-01",
230
+ "next_command": "/qualia-verify",
231
+ "mission_url": "https://portal.qualiasolutions.net/projects/7b5d3b4e-2b8a-4de4-91a1-9b2f3182f5ef/mission"
232
+ }
233
+ ```
234
+
235
+ Session start reads this file and shows the ERP deadline + next Framework
236
+ command. `/qualia-report` sends `work_packet_id`, `assignment_id`, and
237
+ `assignment_deadline`; project snapshots include the same fields under
238
+ `identifiers`.
239
+
176
240
  ## Project Snapshot Export
177
241
 
178
242
  `qualia-framework project-snapshot --write` creates a local project-level rollup at:
@@ -206,7 +270,10 @@ Snapshot shape:
206
270
  "team_id": "qualia-solutions",
207
271
  "git_remote": "github.com/QualiasolutionsCY/acme-portal",
208
272
  "erp_project_id": "7b5d3b4e-2b8a-4de4-91a1-9b2f3182f5ef",
209
- "client_id": "5f5a8d8e-8c58-4c30-9b76-13a08f0d0d8a"
273
+ "client_id": "5f5a8d8e-8c58-4c30-9b76-13a08f0d0d8a",
274
+ "work_packet_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
275
+ "assignment_id": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
276
+ "assignment_deadline": "2026-06-01"
210
277
  },
211
278
  "project": {
212
279
  "name": "acme-portal",
@@ -226,6 +293,15 @@ Snapshot shape:
226
293
  "verification": "pending",
227
294
  "gap_cycles": 1
228
295
  },
296
+ "quality": {
297
+ "harness_eval": {
298
+ "status": "PASS",
299
+ "score": 92,
300
+ "phase": 2,
301
+ "generated_at": "2026-05-23T10:15:00.000Z",
302
+ "artifact": ".planning/evals/harness-eval-2026-05-23T10-15-00-000Z.json"
303
+ }
304
+ },
229
305
  "journey": {
230
306
  "total_milestones": 3,
231
307
  "milestones": [
@@ -287,11 +363,15 @@ Snapshot shape:
287
363
  | milestone_name | string | recommended (v4+) | Human name of the current milestone — from JOURNEY.md / tracking.json |
288
364
  | milestones | array | recommended (v4+) | Array of closed milestone summaries: `{num, name, closed_at, phases_completed, tasks_completed}`. Renders the journey tree on the ERP. |
289
365
  | lifetime | object | recommended | Cumulative counters — tasks_completed, phases_completed, milestones_completed, total_phases, last_closed_milestone |
366
+ | harness_eval | object | recommended (v6.3+) | Latest deterministic harness eval summary: status, score, phase, generated_at, and local artifact path. |
290
367
  | project_id | string | recommended (v3.6+) | Stable per-project identifier — preferred dedupe key over `project` slug. Survives directory renames. |
291
368
  | team_id | string | recommended (v3.6+) | Installation's team identifier. Composite `(team_id, project_id)` is the canonical project key. |
292
369
  | erp_project_id | UUID string | optional | ERP's canonical project UUID when known. Strongest direct link for admin dashboards; `/qualia-report` only sends UUID-shaped values. |
293
370
  | client_id | UUID string | optional | ERP/client canonical UUID when known. Lets reports attach to the client without guessing from a display name; slug-like values are omitted to avoid ERP validation failures. |
294
371
  | workspace_id | UUID string | optional | Workspace/tenant scope for multi-company or multi-team installs; only UUID-shaped values are sent. |
372
+ | work_packet_id | UUID string | optional (v6.3+) | ERP `project_work_packets.id` read from `.planning/work-packet.json`. Links the report to the exact employee mission packet. |
373
+ | assignment_id | UUID string | optional (v6.3+) | ERP `project_assignments.id` read from `.planning/work-packet.json`. Links the report to the assignment/deadline owner. |
374
+ | assignment_deadline | date string | optional (v6.3+) | ERP-owned assignment deadline (`YYYY-MM-DD`) active when the report was submitted. |
295
375
  | git_remote | string | optional (v3.6+) | e.g. `github.com/QualiasolutionsCY/foo`. Lets the ERP correlate tracking with the source repo. |
296
376
  | session_started_at | string | optional (v3.6+) | ISO 8601 — when the current Claude Code session began. |
297
377
  | last_pushed_at | string | optional (v3.6+) | ISO 8601 — distinct from `last_updated` (which fires on local writes too). |
@@ -497,7 +497,6 @@
497
497
  <h4>Diagnose &amp; fix</h4>
498
498
  <dl>
499
499
  <dt>/qualia-fix</dt><dd>Repair lane. Finds root cause, applies a minimal fix, verifies it, writes a fix report.</dd>
500
- <dt>/qualia-debug</dt><dd>Investigative debugging. Use when the failure is unclear and you need evidence before repair.</dd>
501
500
  <dt>/qualia-review</dt><dd>Read-only production audit with severity-scored findings. Run before a big deploy.</dd>
502
501
  <dt>/qualia-optimize</dt><dd>Deep improvement map for performance, design, alignment, and architecture. Spawns parallel specialists.</dd>
503
502
  <dt>/qualia-postmortem</dt><dd>Self-healing layer. After a failed verify, identifies which agent or rule should have caught it and proposes a delta.</dd>
@@ -529,11 +528,7 @@
529
528
  <h4>Navigate the session</h4>
530
529
  <dl>
531
530
  <dt>/qualia</dt><dd>State router. Tells you the exact next command.</dd>
532
- <dt>/qualia-idk</dt><dd>"Something feels off." Diagnoses confusion across .planning + codebase.</dd>
533
- <dt>/qualia-pause</dt><dd>Save context for handoff to a future session.</dd>
534
- <dt>/qualia-resume</dt><dd>Restore context and routing from a paused session.</dd>
535
531
  <dt>/qualia-road</dt><dd>Terminal map of the workflow. Headless-friendly.</dd>
536
- <dt>/qualia-help</dt><dd>Open this kind of reference in the browser.</dd>
537
532
  </dl>
538
533
  </div>
539
534
  </div>
@@ -559,7 +554,6 @@
559
554
  <h4>Code archaeology</h4>
560
555
  <dl>
561
556
  <dt>/qualia-map</dt><dd>Brownfield onboarding. Map an existing codebase before adding to it.</dd>
562
- <dt>/qualia-zoom</dt><dd>Map a small, unfamiliar code area in domain-glossary terms.</dd>
563
557
  </dl>
564
558
  </div>
565
559
  </div>
@@ -574,21 +568,16 @@
574
568
  <h4>Knowledge</h4>
575
569
  <dl>
576
570
  <dt>/qualia-learn</dt><dd>Save a learning, pattern, fix, or client preference. Persists across projects and sessions.</dd>
577
- <dt>/qualia-flush</dt><dd>Promote daily-log raw entries to the curated knowledge tier.</dd>
578
571
  </dl>
579
572
  </div>
580
573
  <div class="kit-group">
581
574
  <h4>Issue queue</h4>
582
575
  <dl>
583
- <dt>/qualia-issues</dt><dd>Break a phase plan into independent vertical-slice GitHub issues.</dd>
584
- <dt>/qualia-triage</dt><dd>Route open issues into needs-info, ready-for-agent, ready-for-human.</dd>
585
576
  </dl>
586
577
  </div>
587
578
  <div class="kit-group">
588
579
  <h4>Extend the framework</h4>
589
580
  <dl>
590
- <dt>/qualia-skill-new</dt><dd>Author a new Qualia skill or agent.</dd>
591
- <dt>/qualia-hook-gen</dt><dd>Convert an instruction into a deterministic Claude Code hook.</dd>
592
581
  </dl>
593
582
  </div>
594
583
  <div class="kit-group">
package/guide.md CHANGED
@@ -1,9 +1,11 @@
1
- # Qualia Developer Guide (v6.2.7)
1
+ # Qualia Developer Guide (v6.3.0)
2
2
 
3
3
  > Follow the road. Type the commands. The framework handles the rest.
4
4
  > `--auto` chains the whole road end-to-end with only two human checkpoints per project.
5
5
 
6
- **v6.2.7 is the Codex runtime compatibility patch.** Codex installs now get native `~/.codex/hooks.json`, TOML agents, bin scripts, rules, skills, templates, knowledge, guide, and role config in addition to `AGENTS.md`.
6
+ **v6.3.0 is the harness hardening patch.** The default command surface is 23 active skills, retired helper sources are removed and pruned from older installs, `/qualia-polish --vibe` absorbs the old vibe command, `qualia-framework eval --run --write` produces scored eval artifacts, and PASS transitions now require clean machine evidence when a phase contract exists.
7
+
8
+ **v6.2.7 carries forward.** Codex installs now get native `~/.codex/hooks.json`, TOML agents, bin scripts, rules, skills, templates, knowledge, guide, and role config in addition to `AGENTS.md`.
7
9
 
8
10
  **v6.2.5 carries forward.** `qualia-framework project-snapshot --write` creates a single `.planning/snapshots/project-snapshot-*.json` artifact with project identifiers, current milestone/phase, closed milestones, lifetime counters, and a project progress percentage for explicit ERP/admin import.
9
11
 
@@ -15,7 +17,7 @@
15
17
 
16
18
  **v6.2.1 carries forward.** Active docs match the v6.2 no-bot-commit model, the explicit `/qualia-report` ERP contract, the current skill surface, and fail-closed `INSUFFICIENT EVIDENCE` behavior. `tests/refs.test.sh` guards those claims.
17
19
 
18
- **v6.1.0 ships the design-pivot path you were missing.** New `/qualia-vibe` is fast aesthetic pivot (~3 min): swap design tokens, keep layout. Default proposes ONE direction per `rules/one-opinion.md` (the EventMaster discipline — never give the user a menu). Sub-modes: `--variants N` for the opt-in menu, `--extract URL` reverse-engineers DESIGN.md from a reference site, `--sync` shows code↔DESIGN.md drift and can patch DESIGN.md from code. Slop-detect grew banned fonts (Montserrat/Poppins/Lato/Open Sans) and a `--watch` flag for proactive single-file mode. Several design-surface bugs from v6.0 audit are fixed too (viewport mismatch, slop-detect path resolution in the polish loop, dead `/qualia-design` references, the bounce-easing token that contradicted design-laws).
20
+ **v6.1.0 ships the design-pivot path you were missing.** New `/qualia-polish --vibe` is fast aesthetic pivot (~3 min): swap design tokens, keep layout. Default proposes ONE direction per `rules/one-opinion.md` (the EventMaster discipline — never give the user a menu). Sub-modes: `--variants N` for the opt-in menu, `--extract URL` reverse-engineers DESIGN.md from a reference site, `--sync` shows code↔DESIGN.md drift and can patch DESIGN.md from code. Slop-detect grew banned fonts (Montserrat/Poppins/Lato/Open Sans) and a `--watch` flag for proactive single-file mode. Several design-surface bugs from v6.0 audit are fixed too (viewport mismatch, slop-detect path resolution in the polish loop, dead `/qualia-design` references, the bounce-easing token that contradicted design-laws).
19
21
 
20
22
  **v6.2.0 removes hook-created bot commits.** `pre-push.js` stamps local `tracking.json` telemetry but no longer commits it. `pre-compact.js` is gone because `state.js` already gives stronger crash safety with atomic writes plus a journal. ERP sync remains explicit through `/qualia-report` POSTs, not passive git scraping.
21
23
 
@@ -25,7 +27,7 @@
25
27
 
26
28
  **v5.9.0 carries forward.** `tests/refs.test.sh` catches dead command references in user-facing surfaces on every release. `bin/erp-retry.js` is a real persistent retry queue for ERP report uploads. Four structured agents (verifier, plan-checker, roadmapper, qa-browser) run on Sonnet for ~40% per-phase cost cut, while builder/planner/researcher/visual-evaluator stay on Opus where the architectural and vision reasoning lives. The verifier downgrades to FAIL on any `INSUFFICIENT EVIDENCE` line.
27
29
 
28
- **Surface is 35 installed skills.** Use `/qualia-fix` for broken existing behavior, `/qualia-feature` for new single-feature work, and `/qualia-discuss` in PROJECT MODE for kickoff capture; `/qualia-polish --loop` for the autonomous visual loop; `/qualia-vibe` for fast layout-preserving aesthetic pivots.
30
+ **Surface is 23 installed skills.** Use `/qualia-fix` for broken existing behavior, `/qualia-feature` for new single-feature work, and `/qualia-discuss` in PROJECT MODE for kickoff capture; `/qualia-polish --loop` for the autonomous visual loop; `/qualia-polish --vibe` for fast layout-preserving aesthetic pivots.
29
31
 
30
32
  ## The Road
31
33
 
@@ -81,21 +83,18 @@ Append `--auto` to `/qualia-new` and the framework chains every step:
81
83
  | Single feature | `/qualia-feature` | Auto-scoped: inline for trivia, fresh spawn for 1-5 files |
82
84
  | Finishing | `/qualia-polish` | Design and UX pass (scope-adaptive: component / route / app / redesign / critique / quick / loop) |
83
85
  | | `/qualia-polish --loop` | Autonomous visual-polish loop: screenshot, vision-eval, fix, repeat |
84
- | Pivot the vibe | `/qualia-vibe` | Fast aesthetic pivot (~3 min): swap tokens, keep layout. Propose ONE direction. |
85
- | | `/qualia-vibe --extract <URL>` | Reverse-engineer DESIGN.md from a reference site |
86
- | | `/qualia-vibe --sync` | Show / patch drift between code (CSS vars + Tailwind) and DESIGN.md |
86
+ | Pivot the vibe | `/qualia-polish --vibe` | Fast aesthetic pivot (~3 min): swap tokens, keep layout. Propose ONE direction. |
87
+ | | `/qualia-polish --vibe --extract <URL>` | Reverse-engineer DESIGN.md from a reference site |
88
+ | | `/qualia-polish --vibe --sync` | Show / patch drift between code (CSS vars + Tailwind) and DESIGN.md |
87
89
  | | `/qualia-ship` | Deploy to production |
88
90
  | | `/qualia-handoff` | Deliver to client (4 mandatory deliverables) |
89
91
  | Reporting | `/qualia-report` | Log what you did (mandatory before clock-out) |
90
- | Zooming in | `/qualia-zoom` | Focus on a single file or function with full context |
91
- | Issues | `/qualia-issues` | Break a phase plan into vertical-slice GitHub issues |
92
- | Triage | `/qualia-triage` | Triage open issues through the ready-for-agent state machine |
93
92
  | Optimize | `/qualia-optimize --deepen` | Find shallow modules; v5.3+ spawns 3 parallel interface-design variants per candidate |
93
+ | Harness eval | `qualia-framework eval --run --write` | Run machine contract checks and write scored eval artifacts for ERP/reporting |
94
94
  | Planning hygiene | `qualia-framework planning-hygiene scan` | Detect loose `.planning/` reports/assets before they turn into folder bloat |
95
- | Hook gen | `/qualia-hook-gen` | Convert a CLAUDE.md/rules instruction into a deterministic hook (v5.3+) |
96
95
  | Road view | `/qualia-road` | View and navigate journey/milestone/phase status |
97
96
  | Lost? | `/qualia` | Mechanical next-command router |
98
- | Confused? | `/qualia-idk` | Diagnostic — scans planning + code, explains what's going on |
97
+ | Confused? | `/qualia` | Diagnostic — scans planning + code, explains what's going on |
99
98
 
100
99
  ## Full Journey Hierarchy
101
100
 
@@ -120,13 +119,13 @@ Hard rules (enforced by `state.js` and the roadmapper):
120
119
  3. **MVP first** — build what's asked, nothing extra
121
120
  4. **Every task has a `Why`** (story-file format) — if you can't explain why a task matters in one sentence, it probably shouldn't exist
122
121
  5. **`/qualia` is your friend** — lost? type it
123
- 6. **`/qualia-idk` is your deeper friend** — not lost on "what command", but confused about the *situation*? Type `idk`.
122
+ 6. **`/qualia` is your deeper friend** — not lost on "what command", but confused about the *situation*? Type `idk`.
124
123
 
125
124
  ## When You're Stuck
126
125
 
127
126
  ```
128
127
  /qualia ← "what command should I run next?" (state-driven, instant)
129
- /qualia-idk ← "what's actually going on here?" (diagnostic, scans planning + code, ~30s)
128
+ /qualia ← "what's actually going on here?" (diagnostic, scans planning + code, ~30s)
130
129
  ```
131
130
 
132
131
  If neither helps, paste the error and ask Claude directly. If Claude can't fix it, tell Fawzi.
@@ -155,7 +154,7 @@ If neither helps, paste the error and ask Claude directly. If Claude can't fix i
155
154
  | Starting a quick throwaway | `/qualia-new --quick` |
156
155
  | Brownfield project | `/qualia-map` first, then `/qualia-new` |
157
156
  | Stuck picking next command | `/qualia` |
158
- | Confused about the situation | `/qualia-idk` |
157
+ | Confused about the situation | `/qualia` |
159
158
  | Finished the last phase of a milestone | `/qualia-milestone` |
160
159
  | About to ship | `/qualia-ship` |
161
160
  | Client is ready to take over | `/qualia-handoff` |
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env node
2
+ // Silently records EMPLOYEE attempts to use "Fawzi said OK" style proxy
3
+ // approval. This hook never blocks and never prints: the policy text teaches
4
+ // the agent not to do it; this hook records when it still appears in tool input.
5
+
6
+ const fs = require("fs");
7
+ const path = require("path");
8
+ const os = require("os");
9
+ const crypto = require("crypto");
10
+
11
+ function qualiaHome() {
12
+ if (process.env.QUALIA_HOME) return process.env.QUALIA_HOME;
13
+ const parent = path.basename(path.dirname(__dirname));
14
+ if (parent === ".codex" || parent === ".claude") return path.dirname(__dirname);
15
+ return path.join(os.homedir(), ".claude");
16
+ }
17
+
18
+ const QUALIA_HOME = qualiaHome();
19
+ const CONFIG = path.join(QUALIA_HOME, ".qualia-config.json");
20
+ const EVENT_FILE = path.join(QUALIA_HOME, ".approval-policy-events.json");
21
+
22
+ function readJson(file, fallback) {
23
+ try {
24
+ return JSON.parse(fs.readFileSync(file, "utf8"));
25
+ } catch {
26
+ return fallback;
27
+ }
28
+ }
29
+
30
+ function writeJson(file, data) {
31
+ try {
32
+ const dir = path.dirname(file);
33
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
34
+ const tmp = `${file}.tmp.${process.pid}`;
35
+ fs.writeFileSync(tmp, JSON.stringify(data, null, 2) + "\n", { mode: 0o600 });
36
+ try { fs.chmodSync(tmp, 0o600); } catch {}
37
+ fs.renameSync(tmp, file);
38
+ } catch {}
39
+ }
40
+
41
+ function collectStrings(value, out = []) {
42
+ if (typeof value === "string") out.push(value);
43
+ else if (Array.isArray(value)) value.forEach((v) => collectStrings(v, out));
44
+ else if (value && typeof value === "object") {
45
+ for (const v of Object.values(value)) collectStrings(v, out);
46
+ }
47
+ return out;
48
+ }
49
+
50
+ function hookInputText() {
51
+ try {
52
+ if (process.stdin.isTTY) return "";
53
+ const raw = fs.readFileSync(0, "utf8");
54
+ if (!raw) return "";
55
+ try {
56
+ const parsed = JSON.parse(raw);
57
+ return collectStrings(parsed.tool_input || parsed).join("\n");
58
+ } catch {
59
+ return raw;
60
+ }
61
+ } catch {
62
+ return "";
63
+ }
64
+ }
65
+
66
+ function approvalClaim(text) {
67
+ const normalized = String(text || "").replace(/\s+/g, " ").trim();
68
+ if (!normalized) return "";
69
+ const patterns = [
70
+ /\bfawzi\b.{0,80}\b(said|says|told|approved|approves|okayed|ok'd|allowed|allows|authorized|authorizes|confirmed)\b.{0,80}\b(ok|okay|fine|yes|ship|deploy|go ahead|do it|allowed|approved)?/i,
71
+ /\bfawzi\s+is\s+(here|with\s+me|beside\s+me)\b.{0,80}\b(ok|okay|fine|yes|approved|approves|allow|allows|ship|deploy|go ahead|do it)\b/i,
72
+ /\b(owner|boss)\b.{0,40}\b(said|says|approved|approves|okayed|ok'd)\b.{0,60}\b(ok|okay|fine|yes|ship|deploy|go ahead|do it)\b/i,
73
+ ];
74
+ for (const re of patterns) {
75
+ const m = normalized.match(re);
76
+ if (m) return m[0].slice(0, 220);
77
+ }
78
+ return "";
79
+ }
80
+
81
+ function recordLocal(config, sample) {
82
+ const data = readJson(EVENT_FILE, { counts: {}, events: [] });
83
+ if (!data.counts || typeof data.counts !== "object") data.counts = {};
84
+ if (!Array.isArray(data.events)) data.events = [];
85
+
86
+ const key = config.code || config.installed_by || "unknown";
87
+ const prev = data.counts[key] || {};
88
+ const count = (prev.total || 0) + 1;
89
+ const event = {
90
+ type: "proxy_owner_approval_claim",
91
+ actor_code: config.code || "",
92
+ actor_name: config.installed_by || "",
93
+ actor_role: config.role || "",
94
+ count,
95
+ sample,
96
+ project: path.basename(process.cwd()),
97
+ cwd: process.cwd(),
98
+ recorded_at: new Date().toISOString(),
99
+ };
100
+
101
+ data.counts[key] = {
102
+ actor_code: event.actor_code,
103
+ actor_name: event.actor_name,
104
+ actor_role: event.actor_role,
105
+ total: count,
106
+ last_seen_at: event.recorded_at,
107
+ };
108
+ data.events.push(event);
109
+ data.events = data.events.slice(-200);
110
+ writeJson(EVENT_FILE, data);
111
+ return event;
112
+ }
113
+
114
+ function enqueueErp(config, event) {
115
+ try {
116
+ const retryPath = path.join(QUALIA_HOME, "bin", "erp-retry.js");
117
+ if (!fs.existsSync(retryPath)) return;
118
+ if (config.erp && config.erp.enabled === false) return;
119
+ const erpUrl = (config.erp && config.erp.url) || "https://portal.qualiasolutions.net";
120
+ const { enqueue } = require(retryPath);
121
+ enqueue({
122
+ client_report_id: `QS-POLICY-${(event.actor_code || "UNKNOWN").replace(/[^A-Z0-9-]/gi, "")}-${event.count}`,
123
+ idempotency_key: crypto.randomUUID ? crypto.randomUUID() : "",
124
+ url: `${erpUrl.replace(/\/$/, "")}/api/v1/policy-events`,
125
+ payload: JSON.stringify(event),
126
+ last_error: "",
127
+ });
128
+ } catch {}
129
+ }
130
+
131
+ try {
132
+ const config = readJson(CONFIG, {});
133
+ if ((config.role || "").toUpperCase() === "OWNER") process.exit(0);
134
+ if ((config.role || "").toUpperCase() !== "EMPLOYEE") process.exit(0);
135
+
136
+ const sample = approvalClaim(hookInputText());
137
+ if (!sample) process.exit(0);
138
+
139
+ const event = recordLocal(config, sample);
140
+ enqueueErp(config, event);
141
+ } catch {}
142
+
143
+ process.exit(0);
@@ -21,6 +21,8 @@ function qualiaHome() {
21
21
  }
22
22
 
23
23
  const QUALIA_HOME = qualiaHome();
24
+ const CONFIG = path.join(QUALIA_HOME, ".qualia-config.json");
25
+ let HOOK_COMMAND = null;
24
26
 
25
27
  // Self-filter on the proposed bash command — only act when the user is
26
28
  // actually trying to deploy. Claude Code's `if: "Bash(vercel --prod*)"` does
@@ -44,7 +46,8 @@ const QUALIA_HOME = qualiaHome();
44
46
  }
45
47
  } catch {}
46
48
  if (command === null) return; // malformed or empty stdin — run full gate
47
- if (!/^\s*(npx\s+)?vercel\s+(--prod|deploy\s+--prod)\b/.test(command)) {
49
+ HOOK_COMMAND = command;
50
+ if (!/^\s*(?:[A-Za-z_][A-Za-z0-9_]*=\S+\s+)*(npx\s+)?vercel\s+(--prod|deploy\s+--prod)\b/.test(command)) {
48
51
  process.exit(0);
49
52
  }
50
53
  })();
@@ -107,6 +110,76 @@ function hasScript(name) {
107
110
  }
108
111
  }
109
112
 
113
+ function readConfig() {
114
+ try {
115
+ return JSON.parse(fs.readFileSync(CONFIG, "utf8"));
116
+ } catch {
117
+ return {};
118
+ }
119
+ }
120
+
121
+ function readTrackingState() {
122
+ try {
123
+ const tracking = JSON.parse(fs.readFileSync(path.join(process.cwd(), ".planning", "tracking.json"), "utf8"));
124
+ return {
125
+ status: tracking.status || "",
126
+ verification: tracking.verification || "",
127
+ next_command: tracking.next_command || "",
128
+ };
129
+ } catch {
130
+ return null;
131
+ }
132
+ }
133
+
134
+ function readState() {
135
+ const stateJs = path.join(QUALIA_HOME, "bin", "state.js");
136
+ if (fs.existsSync(stateJs)) {
137
+ try {
138
+ const r = spawnSync(process.execPath, [stateJs, "check"], {
139
+ encoding: "utf8",
140
+ timeout: 3000,
141
+ stdio: ["ignore", "pipe", "ignore"],
142
+ });
143
+ if (r.status === 0 && r.stdout) return JSON.parse(r.stdout);
144
+ } catch {}
145
+ }
146
+ return readTrackingState();
147
+ }
148
+
149
+ function blockDeploy(reason, nextCommand) {
150
+ console.error(`BLOCKED: ${reason}`);
151
+ if (nextCommand) console.error(`Run: ${nextCommand}`);
152
+ _trace("pre-deploy-gate", "block", { reason, next_command: nextCommand || "" });
153
+ process.exit(2);
154
+ }
155
+
156
+ function enforceShipPolicy() {
157
+ if (!HOOK_COMMAND) return;
158
+
159
+ const config = readConfig();
160
+ const role = String(config.role || "").toUpperCase();
161
+ const force = process.env.QUALIA_SHIP_FORCE === "1" || /\bQUALIA_SHIP_FORCE=1\b/.test(HOOK_COMMAND);
162
+ const state = readState();
163
+ const status = state && state.status ? String(state.status) : "";
164
+ const verification = state && state.verification ? String(state.verification) : "";
165
+ const nextCommand = (state && state.next_command) || "/qualia";
166
+
167
+ if (force && role !== "OWNER") {
168
+ blockDeploy("QUALIA_SHIP_FORCE is OWNER-only.", nextCommand);
169
+ }
170
+
171
+ // If this is not a Qualia-managed project, keep the legacy behavior: run the
172
+ // quality/security gates but do not invent state.
173
+ if (!state || !status) return;
174
+
175
+ const shippable = status === "polished" || (status === "verified" && verification === "pass");
176
+ if (!shippable && !force) {
177
+ blockDeploy(`Ship refused from state '${status}' (verification: ${verification || "none"}).`, nextCommand);
178
+ }
179
+ }
180
+
181
+ enforceShipPolicy();
182
+
110
183
  // Directories that should never be walked (build outputs, deps, caches).
111
184
  const EXCLUDED_DIRS = new Set([
112
185
  "node_modules",
@@ -25,6 +25,9 @@ function qualiaHome() {
25
25
  if (process.env.QUALIA_HOME) return process.env.QUALIA_HOME;
26
26
  const parent = path.basename(path.dirname(__dirname));
27
27
  if (parent === ".codex" || parent === ".claude") return path.dirname(__dirname);
28
+ if (fs.existsSync(path.join(path.dirname(__dirname), "bin", "qualia-ui.js"))) {
29
+ return path.dirname(__dirname);
30
+ }
28
31
  return path.join(HOME, ".claude");
29
32
  }
30
33
 
@@ -36,6 +39,7 @@ const NOTIF_FILE = path.join(QUALIA_HOME, ".qualia-update-available.json");
36
39
  const HEALTH_FILE = path.join(QUALIA_HOME, ".qualia-install-health.json");
37
40
  const ERP_RETRY = path.join(QUALIA_HOME, "bin", "erp-retry.js");
38
41
  const ERP_QUEUE = path.join(QUALIA_HOME, ".erp-retry-queue.json");
42
+ const WORK_PACKET_BIN = path.join(QUALIA_HOME, "bin", "work-packet.js");
39
43
 
40
44
  // Critical files referenced by skills via @-import. If any are missing, skills
41
45
  // silently get empty context and produce ungrounded output. We spot-check these
@@ -97,6 +101,29 @@ function getNextCommand() {
97
101
  }
98
102
  }
99
103
 
104
+ function readWorkPacket() {
105
+ try {
106
+ if (!fs.existsSync(WORK_PACKET_BIN)) return null;
107
+ const mod = require(WORK_PACKET_BIN);
108
+ if (!mod || typeof mod.readLocalWorkPacket !== "function") return null;
109
+ return mod.readLocalWorkPacket(process.cwd());
110
+ } catch {
111
+ return null;
112
+ }
113
+ }
114
+
115
+ function renderWorkPacketContext() {
116
+ const packet = readWorkPacket();
117
+ if (!packet) return;
118
+ const project = packet.project && packet.project.name ? packet.project.name : "ERP mission";
119
+ const deadline = packet.deadline_date || "no deadline";
120
+ const next = packet.next_command || "/qualia";
121
+ const employee = packet.employee && packet.employee.name ? ` · ${packet.employee.name}` : "";
122
+ const text = `${project}: due ${deadline} · next ${next}${employee}`;
123
+ if (fs.existsSync(UI)) runUi("info", text);
124
+ else console.log(`QUALIA: ${text}`);
125
+ }
126
+
100
127
  function readConfig() {
101
128
  try {
102
129
  return JSON.parse(fs.readFileSync(path.join(QUALIA_HOME, ".qualia-config.json"), "utf8"));
@@ -178,6 +205,7 @@ try {
178
205
  fallbackText();
179
206
  } else if (fs.existsSync(STATE_FILE)) {
180
207
  runUi("banner", "router");
208
+ renderWorkPacketContext();
181
209
  const next = getNextCommand();
182
210
  if (next) {
183
211
  console.log("");
@@ -185,7 +213,7 @@ try {
185
213
  }
186
214
  } else if (fs.existsSync(CONTINUE_HERE)) {
187
215
  runUi("banner", "resume");
188
- runUi("warn", "Previous session found — type /qualia-resume to pick up where you left off");
216
+ runUi("warn", "Previous session found — type /qualia to pick up where you left off");
189
217
  console.log("");
190
218
  } else {
191
219
  // No project — show a welcoming first-run experience
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qualia-framework",
3
- "version": "6.2.10",
3
+ "version": "6.3.0",
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"
@@ -115,8 +115,8 @@ For redesigns and full-app polish, also read:
115
115
 
116
116
  ## Qualia design commands
117
117
  - `/qualia-polish` — design pass, scope-adaptive (component / section / app / redesign / critique / quick / loop)
118
- - `/qualia-vibe` — fast aesthetic pivot (swap tokens, keep layout) + reverse-engineer from URL + code↔DESIGN.md sync
118
+ - `/qualia-polish --vibe` — fast aesthetic pivot (swap tokens, keep layout) + reverse-engineer from URL + code↔DESIGN.md sync
119
119
  - `/qualia-review` — scored production audit
120
120
 
121
121
  ### Recommended workflow
122
- 1. Build feature → 2. `/qualia-polish` to polish within the current vibe → 3. `/qualia-vibe` when the vibe itself needs to change → 4. `/qualia-polish --redesign` when the whole surface needs stronger visual concept/graphics → 5. `/qualia-polish --loop` for autonomous visual QA → ship.
122
+ 1. Build feature → 2. `/qualia-polish` to polish within the current vibe → 3. `/qualia-polish --vibe` when the vibe itself needs to change → 4. `/qualia-polish --redesign` when the whole surface needs stronger visual concept/graphics → 5. `/qualia-polish --loop` for autonomous visual QA → ship.
@@ -39,7 +39,7 @@ If the answer is `claude`, **skip this entire rule** — Claude Code has no equi
39
39
 
40
40
  - The user is on Claude Code (no `/goal` surface).
41
41
  - A goal is already active for this thread (Codex rejects `update_goal` when one exists — call `thread/goal/get` first if you're using the tool API directly).
42
- - The work is open-ended exploration with no clear objective (e.g. `/qualia-idk`, `/qualia-discuss`). Goals are for executing a defined scope.
42
+ - The work is open-ended exploration with no clear objective (e.g. `/qualia`, `/qualia-discuss`). Goals are for executing a defined scope.
43
43
 
44
44
  ## Why
45
45
 
@@ -1,6 +1,6 @@
1
1
  # One Opinion (design-decision discipline)
2
2
 
3
- Loaded on demand by design-adjacent skills: `/qualia-vibe`, `/qualia-polish`, `/qualia-new` (DESIGN.md creation step), `/qualia-discuss` PROJECT MODE. Not always-on — most skills don't need it.
3
+ Loaded on demand by design-adjacent skills: `/qualia-polish --vibe`, `/qualia-polish`, `/qualia-new` (DESIGN.md creation step), `/qualia-discuss` PROJECT MODE. Not always-on — most skills don't need it.
4
4
 
5
5
  ## The rule
6
6
 
@@ -34,7 +34,7 @@ Owners and clients have a strong sense of what they DON'T want and a weaker sens
34
34
  - The user explicitly asked for options ("show me 3 directions", "give me a menu").
35
35
  - The decision is technical, not aesthetic (e.g. "use Postgres or MongoDB?" — those have objectively different tradeoffs and should be discussed, not opinionated).
36
36
  - A locked decision in `.planning/decisions/*.md` already exists — surface it, don't re-decide.
37
- - The framework command explicitly supports a `--variants` mode (e.g. `/qualia-vibe --variants 3`), which is the user opting into the menu surface.
37
+ - The framework command explicitly supports a `--variants` mode (e.g. `/qualia-polish --vibe --variants 3`), which is the user opting into the menu surface.
38
38
 
39
39
  ## Output shape
40
40
 
package/rules/speed.md CHANGED
@@ -50,5 +50,4 @@ When a Qualia command exists for the situation, use it — don't reinvent:
50
50
  - `/qualia-feature` — single feature, auto-scoped: inline for trivia, fresh builder spawn for 1-5 file features
51
51
  - `/qualia-ship` — full deploy pipeline (quality gates → commit → deploy → verify)
52
52
  - `/qualia-review` — production audit
53
- - `/qualia-pause` — save context before clearing the conversation
54
53
  - `/qualia-learn` — save a lesson from a mistake