qualia-framework 6.2.10 → 6.4.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/AGENTS.md +8 -7
- package/CLAUDE.md +5 -4
- package/README.md +27 -56
- package/bin/cli.js +113 -18
- package/bin/command-surface.js +75 -0
- package/bin/harness-eval.js +296 -0
- package/bin/install.js +43 -31
- package/bin/knowledge-flush.js +21 -10
- package/bin/knowledge.js +1 -1
- package/bin/learning-candidates.js +217 -0
- package/bin/project-snapshot.js +20 -0
- package/bin/prune-deprecated.js +64 -0
- package/bin/report-payload.js +18 -0
- package/bin/runtime-manifest.js +7 -0
- package/bin/security-scan.js +409 -0
- package/bin/state.js +31 -0
- package/bin/status-snapshot.js +363 -0
- package/bin/trust-score.js +3 -11
- package/bin/work-packet.js +228 -0
- package/docs/erp-contract.md +81 -1
- package/docs/onboarding.html +0 -11
- package/guide.md +15 -38
- package/hooks/fawzi-approval-guard.js +143 -0
- package/hooks/pre-compact.js +232 -0
- package/hooks/pre-deploy-gate.js +74 -1
- package/hooks/session-start.js +29 -1
- package/package.json +1 -1
- package/qualia-design/frontend.md +2 -2
- package/rules/codex-goal.md +1 -1
- package/rules/one-opinion.md +2 -2
- package/rules/speed.md +0 -1
- package/skills/qualia/SKILL.md +4 -4
- package/skills/qualia-build/SKILL.md +1 -1
- package/skills/qualia-discuss/SKILL.md +1 -1
- package/skills/qualia-doctor/SKILL.md +1 -1
- package/skills/qualia-feature/SKILL.md +2 -2
- package/skills/qualia-fix/SKILL.md +4 -4
- package/skills/qualia-idk/SKILL.md +133 -54
- package/skills/qualia-learn/SKILL.md +2 -2
- package/skills/qualia-map/SKILL.md +1 -1
- package/skills/qualia-milestone/SKILL.md +1 -1
- package/skills/qualia-new/SKILL.md +1 -1
- package/skills/qualia-optimize/SKILL.md +1 -1
- package/skills/qualia-plan/SKILL.md +1 -1
- package/skills/qualia-polish/REFERENCE.md +1 -1
- package/skills/qualia-polish/SKILL.md +19 -4
- package/skills/{qualia-vibe/scripts/extract.mjs → qualia-polish/scripts/vibe-extract.mjs} +4 -4
- package/skills/{qualia-vibe/scripts/tokens.mjs → qualia-polish/scripts/vibe-tokens.mjs} +6 -6
- package/skills/qualia-postmortem/SKILL.md +1 -1
- package/skills/qualia-report/SKILL.md +1 -1
- package/skills/qualia-research/SKILL.md +1 -1
- package/skills/qualia-review/SKILL.md +1 -1
- package/skills/qualia-road/SKILL.md +15 -20
- package/skills/qualia-secure/SKILL.md +105 -0
- package/skills/qualia-ship/SKILL.md +12 -5
- package/skills/qualia-test/SKILL.md +1 -1
- package/skills/qualia-verify/SKILL.md +10 -2
- package/skills/zoho-workflow/SKILL.md +1 -1
- package/templates/help.html +1 -12
- package/tests/bin.test.sh +147 -75
- package/tests/hooks.test.sh +81 -1
- package/tests/install-smoke.test.sh +14 -4
- package/tests/lib.test.sh +145 -3
- package/tests/published-install-smoke.test.sh +5 -4
- package/tests/refs.test.sh +32 -20
- package/tests/runner.js +30 -29
- package/tests/state.test.sh +106 -7
- package/skills/qualia-debug/SKILL.md +0 -193
- package/skills/qualia-flush/SKILL.md +0 -198
- package/skills/qualia-help/SKILL.md +0 -74
- package/skills/qualia-hook-gen/SKILL.md +0 -206
- package/skills/qualia-issues/SKILL.md +0 -151
- package/skills/qualia-pause/SKILL.md +0 -68
- package/skills/qualia-resume/SKILL.md +0 -52
- package/skills/qualia-skill-new/SKILL.md +0 -173
- package/skills/qualia-triage/SKILL.md +0 -152
- package/skills/qualia-vibe/SKILL.md +0 -229
- package/skills/qualia-zoom/SKILL.md +0 -51
package/docs/erp-contract.md
CHANGED
|
@@ -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). |
|
package/docs/onboarding.html
CHANGED
|
@@ -497,7 +497,6 @@
|
|
|
497
497
|
<h4>Diagnose & 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,31 +1,11 @@
|
|
|
1
|
-
# Qualia Developer Guide
|
|
1
|
+
# Qualia Developer Guide
|
|
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
|
-
**
|
|
6
|
+
Surface: **23 active skills**. Use `/qualia-fix` for broken behavior, `/qualia-feature` for new single-feature work, `/qualia-discuss` in PROJECT MODE for kickoff capture, `/qualia-polish --loop` for the autonomous visual loop, and `/qualia-polish --vibe` for fast layout-preserving aesthetic pivots.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
**v6.2.4 carries forward.** The Framework -> ERP report payload now comes from the shipped and tested `report-payload.js` builder, so admin-visible report linking fields are covered by executable tests instead of only prompt prose.
|
|
11
|
-
|
|
12
|
-
**v6.2.3 carries forward.** Framework slugs stay in `project_id`/`team_id`; ERP foreign-key identifiers (`erp_project_id`, `client_id`, `workspace_id`) are sent only when UUID-shaped, so reports do not fail validation because a local slug leaked into an ERP UUID field.
|
|
13
|
-
|
|
14
|
-
**v6.2.2 carries forward.** Framework builds, Memory remembers, ERP operates. ERP work packets can seed Claude/Codex sessions, `/qualia-report` can carry ERP-native IDs, and release verification now has a public `@latest` install smoke.
|
|
15
|
-
|
|
16
|
-
**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
|
-
|
|
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).
|
|
19
|
-
|
|
20
|
-
**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
|
-
|
|
22
|
-
**v5.9.2 carries forward.** `pre-push.js` self-gates against `branch-guard.js` so a blocked push no longer leaves an orphan bot commit. `qualia-report` ERP payload omits empty ISO datetime fields (avoids 422 from the ERP validator).
|
|
23
|
-
|
|
24
|
-
**v5.9.1 carries forward.** `/qualia-new` opens with the Demo/Full/Quick project-shape gate via `AskUserQuestion`, then exactly one free-text pitch question, then a hard hand-off to `/qualia-discuss` for the structured interview (8 questions for demos, 14 for full projects).
|
|
25
|
-
|
|
26
|
-
**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
|
-
|
|
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.
|
|
8
|
+
For the identity statement see [`SOUL.md`](./SOUL.md). For every skill flag see [`FLAGS.md`](./FLAGS.md). When something breaks see [`TROUBLESHOOTING.md`](./TROUBLESHOOTING.md). For release history see [`CHANGELOG.md`](./CHANGELOG.md).
|
|
29
9
|
|
|
30
10
|
## The Road
|
|
31
11
|
|
|
@@ -81,21 +61,18 @@ Append `--auto` to `/qualia-new` and the framework chains every step:
|
|
|
81
61
|
| Single feature | `/qualia-feature` | Auto-scoped: inline for trivia, fresh spawn for 1-5 files |
|
|
82
62
|
| Finishing | `/qualia-polish` | Design and UX pass (scope-adaptive: component / route / app / redesign / critique / quick / loop) |
|
|
83
63
|
| | `/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 |
|
|
64
|
+
| Pivot the vibe | `/qualia-polish --vibe` | Fast aesthetic pivot (~3 min): swap tokens, keep layout. Propose ONE direction. |
|
|
65
|
+
| | `/qualia-polish --vibe --extract <URL>` | Reverse-engineer DESIGN.md from a reference site |
|
|
66
|
+
| | `/qualia-polish --vibe --sync` | Show / patch drift between code (CSS vars + Tailwind) and DESIGN.md |
|
|
87
67
|
| | `/qualia-ship` | Deploy to production |
|
|
88
68
|
| | `/qualia-handoff` | Deliver to client (4 mandatory deliverables) |
|
|
89
69
|
| 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
70
|
| Optimize | `/qualia-optimize --deepen` | Find shallow modules; v5.3+ spawns 3 parallel interface-design variants per candidate |
|
|
71
|
+
| Harness eval | `qualia-framework eval --run --write` | Run machine contract checks and write scored eval artifacts for ERP/reporting |
|
|
94
72
|
| 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
73
|
| Road view | `/qualia-road` | View and navigate journey/milestone/phase status |
|
|
97
|
-
| Lost
|
|
98
|
-
| Confused
|
|
74
|
+
| Lost — need next command | `/qualia` | Mechanical state-driven router |
|
|
75
|
+
| Confused — need to understand the situation | `/qualia-idk` | Three-scan diagnostic + paste-ready command sequence |
|
|
99
76
|
|
|
100
77
|
## Full Journey Hierarchy
|
|
101
78
|
|
|
@@ -119,14 +96,14 @@ Hard rules (enforced by `state.js` and the roadmapper):
|
|
|
119
96
|
2. **Read before write** — don't edit files you haven't read
|
|
120
97
|
3. **MVP first** — build what's asked, nothing extra
|
|
121
98
|
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
|
-
5. **`/qualia` is your friend** — lost?
|
|
123
|
-
6. **`/qualia-idk` is your deeper friend** —
|
|
99
|
+
5. **`/qualia` is your friend** — lost on "what's my next command?" The router reads state and returns the next move.
|
|
100
|
+
6. **`/qualia-idk` is your deeper friend** — confused about *the situation itself*. Reads conversation + planning + code, then returns guidance plus a paste-ready Qualia command sequence.
|
|
124
101
|
|
|
125
102
|
## When You're Stuck
|
|
126
103
|
|
|
127
104
|
```
|
|
128
|
-
/qualia ← "what
|
|
129
|
-
/qualia-idk ← "what
|
|
105
|
+
/qualia ← "what's my next command?" (state-driven, instant)
|
|
106
|
+
/qualia-idk ← "what is actually going on here?" (three-scan diagnostic, ~30-45s)
|
|
130
107
|
```
|
|
131
108
|
|
|
132
109
|
If neither helps, paste the error and ask Claude directly. If Claude can't fix it, tell Fawzi.
|
|
@@ -154,8 +131,8 @@ If neither helps, paste the error and ask Claude directly. If Claude can't fix i
|
|
|
154
131
|
| Starting a new client project | `/qualia-new` (or `/qualia-new --auto` to roll end-to-end) |
|
|
155
132
|
| Starting a quick throwaway | `/qualia-new --quick` |
|
|
156
133
|
| Brownfield project | `/qualia-map` first, then `/qualia-new` |
|
|
157
|
-
| Stuck
|
|
158
|
-
| Confused
|
|
134
|
+
| Stuck — "what command should I run?" | `/qualia` |
|
|
135
|
+
| Confused — "what's actually going on?" | `/qualia-idk` |
|
|
159
136
|
| Finished the last phase of a milestone | `/qualia-milestone` |
|
|
160
137
|
| About to ship | `/qualia-ship` |
|
|
161
138
|
| 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);
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// ~/.claude/hooks/pre-compact.js — snapshot in-flight session context to
|
|
3
|
+
// .planning/.compaction-snapshot.md so /qualia-resume (or the next session)
|
|
4
|
+
// can surface what was active when compaction wiped the conversation.
|
|
5
|
+
//
|
|
6
|
+
// PreCompact hook (fires before Claude Code compacts the context window).
|
|
7
|
+
//
|
|
8
|
+
// Why this hook returned in v6.3.2:
|
|
9
|
+
// v6.2.0 removed the original pre-compact.js because it created BOT COMMITS
|
|
10
|
+
// to stamp STATE.md / tracking.json. That was the wrong mechanism —
|
|
11
|
+
// state.js's atomic write + journal already gives crash safety. This v2
|
|
12
|
+
// hook does NOT touch git, does NOT touch tracking.json, does NOT write
|
|
13
|
+
// through state.js. It only writes to a sidecar file that survives
|
|
14
|
+
// compaction and tells the next session "here's what was in flight".
|
|
15
|
+
//
|
|
16
|
+
// Design constraints (same shape as stop-session-log.js):
|
|
17
|
+
// • NEVER block — exit 0 always, even on internal failure.
|
|
18
|
+
// • Fast — under 200ms, no network, no LLM call.
|
|
19
|
+
// • No PII / secrets — file paths, commit subjects, phase numbers only.
|
|
20
|
+
// • Idempotent — overwrites the sidecar on every fire; we only care
|
|
21
|
+
// about the LATEST pre-compaction state.
|
|
22
|
+
// • No git commits, no state.js mutations.
|
|
23
|
+
//
|
|
24
|
+
// Sidecar format (.planning/.compaction-snapshot.md):
|
|
25
|
+
// # Pre-compaction snapshot — {ISO timestamp}
|
|
26
|
+
// ## State
|
|
27
|
+
// Phase: 2 of 4 — built
|
|
28
|
+
// ## Work in flight
|
|
29
|
+
// - 3 modified files (uncommitted)
|
|
30
|
+
// - foo.ts, bar.ts, baz.test.ts
|
|
31
|
+
// ## Recent commits (last 5)
|
|
32
|
+
// - abc1234 feat: implement signin
|
|
33
|
+
// - def5678 test: signin happy path
|
|
34
|
+
// ## Active planning artifacts
|
|
35
|
+
// - .planning/phase-2-plan.md (modified 14m ago)
|
|
36
|
+
// - .planning/phase-2-verification.md (modified 3h ago)
|
|
37
|
+
|
|
38
|
+
const fs = require("fs");
|
|
39
|
+
const path = require("path");
|
|
40
|
+
const { spawnSync } = require("child_process");
|
|
41
|
+
|
|
42
|
+
const _traceStart = Date.now();
|
|
43
|
+
|
|
44
|
+
function qualiaHome() {
|
|
45
|
+
if (process.env.QUALIA_HOME) return process.env.QUALIA_HOME;
|
|
46
|
+
const parent = path.basename(path.dirname(__dirname));
|
|
47
|
+
if (parent === ".codex" || parent === ".claude") return path.dirname(__dirname);
|
|
48
|
+
return path.join(require("os").homedir(), ".claude");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function _trace(result, extra) {
|
|
52
|
+
try {
|
|
53
|
+
const traceDir = path.join(qualiaHome(), ".qualia-traces");
|
|
54
|
+
if (!fs.existsSync(traceDir)) fs.mkdirSync(traceDir, { recursive: true });
|
|
55
|
+
fs.appendFileSync(
|
|
56
|
+
path.join(traceDir, `${new Date().toISOString().split("T")[0]}.jsonl`),
|
|
57
|
+
JSON.stringify({
|
|
58
|
+
hook: "pre-compact",
|
|
59
|
+
result,
|
|
60
|
+
timestamp: new Date().toISOString(),
|
|
61
|
+
duration_ms: Date.now() - _traceStart,
|
|
62
|
+
...extra,
|
|
63
|
+
}) + "\n",
|
|
64
|
+
);
|
|
65
|
+
} catch {}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function git(args, opts = {}) {
|
|
69
|
+
try {
|
|
70
|
+
const r = spawnSync("git", args, {
|
|
71
|
+
encoding: "utf8",
|
|
72
|
+
timeout: 2000,
|
|
73
|
+
shell: process.platform === "win32",
|
|
74
|
+
...opts,
|
|
75
|
+
});
|
|
76
|
+
if (r.status !== 0) return "";
|
|
77
|
+
return (r.stdout || "").trim();
|
|
78
|
+
} catch {
|
|
79
|
+
return "";
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function readJson(p) {
|
|
84
|
+
try {
|
|
85
|
+
return JSON.parse(fs.readFileSync(p, "utf8"));
|
|
86
|
+
} catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function safeStatM(p) {
|
|
92
|
+
try { return fs.statSync(p).mtimeMs; } catch { return 0; }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function humanAge(ms) {
|
|
96
|
+
if (ms < 60_000) return `${Math.round(ms / 1000)}s ago`;
|
|
97
|
+
if (ms < 3600_000) return `${Math.round(ms / 60_000)}m ago`;
|
|
98
|
+
if (ms < 86_400_000) return `${Math.round(ms / 3600_000)}h ago`;
|
|
99
|
+
return `${Math.round(ms / 86_400_000)}d ago`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const cwd = process.cwd();
|
|
104
|
+
const repoRoot = git(["rev-parse", "--show-toplevel"], { cwd }) || cwd;
|
|
105
|
+
const planningDir = path.join(repoRoot, ".planning");
|
|
106
|
+
|
|
107
|
+
// Skip if not a Qualia project (no .planning/ dir) — nothing to snapshot.
|
|
108
|
+
if (!fs.existsSync(planningDir)) {
|
|
109
|
+
_trace("skip", { reason: "no-planning-dir" });
|
|
110
|
+
process.exit(0);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const now = Date.now();
|
|
114
|
+
const tracking = readJson(path.join(planningDir, "tracking.json")) || {};
|
|
115
|
+
const stateText = (() => {
|
|
116
|
+
try { return fs.readFileSync(path.join(planningDir, "STATE.md"), "utf8"); }
|
|
117
|
+
catch { return ""; }
|
|
118
|
+
})();
|
|
119
|
+
|
|
120
|
+
// State header line (one line from STATE.md, or fall back to tracking.json).
|
|
121
|
+
const stateLine = (() => {
|
|
122
|
+
const phaseLine = stateText.split("\n").find((l) => /^Phase:/i.test(l));
|
|
123
|
+
const statusLine = stateText.split("\n").find((l) => /^Status:/i.test(l));
|
|
124
|
+
if (phaseLine || statusLine) return [phaseLine, statusLine].filter(Boolean).join(" — ");
|
|
125
|
+
const p = tracking.phase || "?";
|
|
126
|
+
const total = tracking.total_phases || tracking.phase_total || "?";
|
|
127
|
+
const status = tracking.status || "?";
|
|
128
|
+
return `Phase: ${p} of ${total} — ${status}`;
|
|
129
|
+
})();
|
|
130
|
+
|
|
131
|
+
// Work in flight: uncommitted modified files.
|
|
132
|
+
const modified = (() => {
|
|
133
|
+
const out = git(["diff", "--name-only", "HEAD"], { cwd: repoRoot });
|
|
134
|
+
return out ? out.split("\n").filter(Boolean) : [];
|
|
135
|
+
})();
|
|
136
|
+
const staged = (() => {
|
|
137
|
+
const out = git(["diff", "--name-only", "--cached"], { cwd: repoRoot });
|
|
138
|
+
return out ? out.split("\n").filter(Boolean) : [];
|
|
139
|
+
})();
|
|
140
|
+
const inFlight = [...new Set([...modified, ...staged])].slice(0, 10);
|
|
141
|
+
|
|
142
|
+
// Recent commits — last 5, single-line.
|
|
143
|
+
const recentCommits = (() => {
|
|
144
|
+
const out = git(["log", "--pretty=%h %s", "-5"], { cwd: repoRoot });
|
|
145
|
+
return out ? out.split("\n").filter(Boolean) : [];
|
|
146
|
+
})();
|
|
147
|
+
|
|
148
|
+
// Active planning artifacts: phase-*-* files modified in last 24h.
|
|
149
|
+
const activePlanning = (() => {
|
|
150
|
+
try {
|
|
151
|
+
const files = fs.readdirSync(planningDir).filter((f) => /^phase-\d+/.test(f) && f.endsWith(".md"));
|
|
152
|
+
const day = 24 * 3600_000;
|
|
153
|
+
return files
|
|
154
|
+
.map((f) => ({ name: f, age: now - safeStatM(path.join(planningDir, f)) }))
|
|
155
|
+
.filter((x) => x.age < day)
|
|
156
|
+
.sort((a, b) => a.age - b.age)
|
|
157
|
+
.slice(0, 5);
|
|
158
|
+
} catch { return []; }
|
|
159
|
+
})();
|
|
160
|
+
|
|
161
|
+
// Open deviations / verification FAILs in current phase.
|
|
162
|
+
const phaseNum = tracking.phase || 0;
|
|
163
|
+
const verifyFails = (() => {
|
|
164
|
+
if (!phaseNum) return [];
|
|
165
|
+
const vPath = path.join(planningDir, `phase-${phaseNum}-verification.md`);
|
|
166
|
+
if (!fs.existsSync(vPath)) return [];
|
|
167
|
+
try {
|
|
168
|
+
const v = fs.readFileSync(vPath, "utf8");
|
|
169
|
+
const fails = (v.match(/^[^\n]*\bFAIL\b[^\n]*$/gm) || []).slice(0, 5);
|
|
170
|
+
const insufficient = (v.match(/^[^\n]*INSUFFICIENT EVIDENCE[^\n]*$/gm) || []).slice(0, 3);
|
|
171
|
+
return [...fails, ...insufficient];
|
|
172
|
+
} catch { return []; }
|
|
173
|
+
})();
|
|
174
|
+
|
|
175
|
+
// Compose snapshot. Markdown so the next session can render it as-is.
|
|
176
|
+
const ts = new Date().toISOString();
|
|
177
|
+
const lines = [
|
|
178
|
+
`# Pre-compaction snapshot — ${ts}`,
|
|
179
|
+
"",
|
|
180
|
+
"_Written by `hooks/pre-compact.js` immediately before Claude Code compacted the context window. `/qualia-resume` and session-start surface this so the next session sees what was active._",
|
|
181
|
+
"",
|
|
182
|
+
"## State",
|
|
183
|
+
stateLine || "(no state info)",
|
|
184
|
+
"",
|
|
185
|
+
];
|
|
186
|
+
|
|
187
|
+
lines.push("## Work in flight");
|
|
188
|
+
if (inFlight.length === 0) {
|
|
189
|
+
lines.push("(no uncommitted changes)");
|
|
190
|
+
} else {
|
|
191
|
+
lines.push(`${inFlight.length} file(s):`);
|
|
192
|
+
for (const f of inFlight) lines.push(`- ${f}`);
|
|
193
|
+
}
|
|
194
|
+
lines.push("");
|
|
195
|
+
|
|
196
|
+
lines.push("## Recent commits");
|
|
197
|
+
if (recentCommits.length === 0) {
|
|
198
|
+
lines.push("(no commit history)");
|
|
199
|
+
} else {
|
|
200
|
+
for (const c of recentCommits) lines.push(`- ${c}`);
|
|
201
|
+
}
|
|
202
|
+
lines.push("");
|
|
203
|
+
|
|
204
|
+
lines.push("## Active planning artifacts (modified in last 24h)");
|
|
205
|
+
if (activePlanning.length === 0) {
|
|
206
|
+
lines.push("(none)");
|
|
207
|
+
} else {
|
|
208
|
+
for (const p of activePlanning) lines.push(`- ${p.name} — ${humanAge(p.age)}`);
|
|
209
|
+
}
|
|
210
|
+
lines.push("");
|
|
211
|
+
|
|
212
|
+
if (verifyFails.length > 0) {
|
|
213
|
+
lines.push("## Open verification issues");
|
|
214
|
+
for (const f of verifyFails) lines.push(`- ${f.trim()}`);
|
|
215
|
+
lines.push("");
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
lines.push("---");
|
|
219
|
+
lines.push("_Sidecar file. Not committed to git. Read by `/qualia-resume` and `session-start` to restore in-flight context after compaction._");
|
|
220
|
+
|
|
221
|
+
fs.writeFileSync(path.join(planningDir, ".compaction-snapshot.md"), lines.join("\n") + "\n");
|
|
222
|
+
_trace("snapshot", {
|
|
223
|
+
in_flight: inFlight.length,
|
|
224
|
+
commits: recentCommits.length,
|
|
225
|
+
active_planning: activePlanning.length,
|
|
226
|
+
verify_fails: verifyFails.length,
|
|
227
|
+
});
|
|
228
|
+
process.exit(0);
|
|
229
|
+
} catch (err) {
|
|
230
|
+
_trace("error", { error: err && err.message ? err.message : String(err) });
|
|
231
|
+
process.exit(0);
|
|
232
|
+
}
|