qualia-framework 4.0.3 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -8
- package/agents/builder.md +12 -2
- package/agents/plan-checker.md +26 -4
- package/agents/planner.md +33 -4
- package/agents/qa-browser.md +5 -1
- package/agents/research-synthesizer.md +2 -0
- package/agents/researcher.md +4 -0
- package/agents/roadmapper.md +11 -5
- package/agents/verifier.md +22 -3
- package/bin/cli.js +121 -4
- package/bin/install.js +13 -2
- package/bin/state.js +52 -2
- package/bin/statusline.js +78 -41
- package/docs/erp-contract.md +37 -2
- package/docs/research/2026-04-21-command-quality-deep-research.md +128 -0
- package/docs/research/2026-04-21-industry-best-practices.md +255 -0
- package/package.json +1 -1
- package/rules/grounding.md +110 -0
- package/skills/qualia-build/SKILL.md +20 -9
- package/skills/qualia-debug/SKILL.md +141 -49
- package/skills/qualia-design/SKILL.md +52 -5
- package/skills/qualia-new/SKILL.md +18 -3
- package/skills/qualia-plan/SKILL.md +11 -8
- package/skills/qualia-report/SKILL.md +141 -60
- package/skills/qualia-review/SKILL.md +36 -16
- package/skills/qualia-skill-new/SKILL.md +1 -1
- package/skills/qualia-verify/SKILL.md +5 -1
- package/templates/tracking.json +1 -0
- package/tests/runner.js +98 -0
package/README.md
CHANGED
|
@@ -9,19 +9,21 @@ It is not an application framework like Rails or Next.js. It doesn't generate co
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npx qualia-framework install
|
|
12
|
+
npx qualia-framework@latest install
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Enter your team code when prompted. Get your code from Fawzi.
|
|
16
16
|
|
|
17
|
+
> **Why `@latest`?** npx caches packages at `~/.npm/_npx/` and has no time-based TTL — `npx qualia-framework install` (without `@latest`) will silently run whatever version you happened to fetch the first time, even if a newer one shipped. Always pin `@latest` when installing or upgrading. If a stale cache still bites you: `npx clear-npx-cache` then re-run.
|
|
18
|
+
|
|
17
19
|
**Other commands:**
|
|
18
20
|
```bash
|
|
19
|
-
npx qualia-framework version # Check installed version + updates
|
|
20
|
-
npx qualia-framework update # Update to latest (remembers your code)
|
|
21
|
-
npx qualia-framework uninstall # Clean removal from ~/.claude/
|
|
22
|
-
npx qualia-framework team list # Show team members
|
|
23
|
-
npx qualia-framework team add # Add a team member
|
|
24
|
-
npx qualia-framework traces # View recent hook telemetry
|
|
21
|
+
npx qualia-framework@latest version # Check installed version + updates
|
|
22
|
+
npx qualia-framework@latest update # Update to latest (remembers your code)
|
|
23
|
+
npx qualia-framework@latest uninstall # Clean removal from ~/.claude/
|
|
24
|
+
npx qualia-framework@latest team list # Show team members
|
|
25
|
+
npx qualia-framework@latest team add # Add a team member
|
|
26
|
+
npx qualia-framework@latest traces # View recent hook telemetry
|
|
25
27
|
```
|
|
26
28
|
|
|
27
29
|
## Usage
|
|
@@ -177,7 +179,7 @@ Plans are grouped into waves for parallel execution. No fancy DAG solver — the
|
|
|
177
179
|
## Architecture
|
|
178
180
|
|
|
179
181
|
```
|
|
180
|
-
npx qualia-framework install
|
|
182
|
+
npx qualia-framework@latest install
|
|
181
183
|
|
|
|
182
184
|
v
|
|
183
185
|
~/.claude/
|
package/agents/builder.md
CHANGED
|
@@ -11,8 +11,18 @@ You execute ONE task from a phase plan. You run in a fresh context — you have
|
|
|
11
11
|
## Input
|
|
12
12
|
You receive: one task block from the plan + PROJECT.md context.
|
|
13
13
|
|
|
14
|
-
## Output
|
|
15
|
-
|
|
14
|
+
## Output Contract
|
|
15
|
+
|
|
16
|
+
Return EXACTLY one of these three prefixed lines as the first line of your final message:
|
|
17
|
+
|
|
18
|
+
- `DONE — Task {N}: {commit_hash}` — followed by a list of files changed (one per line), then any trivial/minor deviation notes. Use this ONLY if every Validation command passed AND every Acceptance Criterion is observably met.
|
|
19
|
+
- `BLOCKED — {reason}` — followed by a JSON block documenting the block:
|
|
20
|
+
```json
|
|
21
|
+
{"type": "major_deviation|dependency_missing|wave_ordering", "task": {N}, "file": "path/to/file", "planned": "...", "actual": "...", "impact": "..."}
|
|
22
|
+
```
|
|
23
|
+
- `PARTIAL — {what completed}; remaining: {what's left}` — only if context limit forces early stop. Commit what works.
|
|
24
|
+
|
|
25
|
+
Never return without one of these three prefixes. The orchestrator parses the prefix to route next steps.
|
|
16
26
|
|
|
17
27
|
## How to Execute
|
|
18
28
|
|
package/agents/plan-checker.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: qualia-plan-checker
|
|
3
|
-
description: Validates a phase plan before execution. Checks task specificity, wave assignment, verification contracts, and coverage of success criteria. Spawned by qualia-plan in a revision loop (max
|
|
3
|
+
description: Validates a phase plan before execution. Checks task specificity, wave assignment, verification contracts, and coverage of success criteria. Spawned by qualia-plan in a revision loop (max 2 iterations).
|
|
4
4
|
tools: Read, Bash, Grep
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Plan Checker
|
|
8
8
|
|
|
9
|
-
You validate phase plans before they go to the builder. You do NOT write plans — you evaluate them. If a plan has issues, return a structured list; the planner will revise and you'll check again (max
|
|
9
|
+
You validate phase plans before they go to the builder. You do NOT write plans — you evaluate them. If a plan has issues, return a structured list; the planner will revise and you'll check again (max 2 revision cycles).
|
|
10
10
|
|
|
11
11
|
## Input
|
|
12
12
|
|
|
@@ -105,6 +105,28 @@ If `.planning/phase-{N}-context.md` exists, read its "Locked Decisions" section.
|
|
|
105
105
|
|
|
106
106
|
**FAIL if:** plan contradicts a locked decision (e.g., context says "use library X" but plan uses library Y).
|
|
107
107
|
|
|
108
|
+
### Rule 8: Validation commands test behavior, not just existence
|
|
109
|
+
|
|
110
|
+
Each task's `**Validation:**` list must contain at least one `grep-match` or `command-exit` check — a command that proves the code DOES something. A task whose ONLY validation is `test -f {file}` will pass even if the file contains only `// TODO`.
|
|
111
|
+
|
|
112
|
+
**FAIL if:** any task has only `file-exists`-type validations. Require at least one of:
|
|
113
|
+
- `grep -c "{specific_call}" {file}` returning non-zero
|
|
114
|
+
- `npx tsc --noEmit` exiting 0
|
|
115
|
+
- `curl -s {endpoint}` returning expected content
|
|
116
|
+
- Any command whose success/failure depends on the code doing something, not just being there
|
|
117
|
+
|
|
118
|
+
**Pass examples:**
|
|
119
|
+
- `grep -c "signInWithPassword" src/lib/auth.ts` → ≥ 1
|
|
120
|
+
- `npx tsc --noEmit 2>&1 | grep -c "error"` → 0
|
|
121
|
+
|
|
122
|
+
**Fail examples:**
|
|
123
|
+
- `test -f src/lib/auth.ts && echo EXISTS` (only) — file exists, but could be empty or stubbed
|
|
124
|
+
- `ls src/components/Chat.tsx` (only) — same problem
|
|
125
|
+
|
|
126
|
+
## Tool Budget
|
|
127
|
+
|
|
128
|
+
Read the plan file once. Grep the codebase only to validate Rule 7 (locked decisions). Do NOT speculatively check whether files listed in the plan already exist — that's the builder's job. Max 10 tool calls per invocation.
|
|
129
|
+
|
|
108
130
|
## Output Format
|
|
109
131
|
|
|
110
132
|
### If all rules pass:
|
|
@@ -145,12 +167,12 @@ The planner uses your output to revise the plan. Be specific enough that the rev
|
|
|
145
167
|
|
|
146
168
|
## Revision Limits
|
|
147
169
|
|
|
148
|
-
You will be called up to
|
|
170
|
+
You will be called up to 2 times per plan. If the plan still fails after 2 revisions, report:
|
|
149
171
|
|
|
150
172
|
```
|
|
151
173
|
## BLOCKED
|
|
152
174
|
|
|
153
|
-
Plan failed validation after
|
|
175
|
+
Plan failed validation after 2 revision cycles. Issues remaining:
|
|
154
176
|
|
|
155
177
|
{list}
|
|
156
178
|
|
package/agents/planner.md
CHANGED
|
@@ -9,7 +9,15 @@ tools: Read, Write, Bash, Glob, Grep, WebFetch
|
|
|
9
9
|
You create phase plans. Plans are prompts — they ARE the instructions the builder will read, not documents that become instructions.
|
|
10
10
|
|
|
11
11
|
## Input
|
|
12
|
-
|
|
12
|
+
|
|
13
|
+
- `<project_context>` — inlined `.planning/PROJECT.md` contents
|
|
14
|
+
- `<current_state>` — inlined `.planning/STATE.md` contents
|
|
15
|
+
- `<phase_details>` — phase goal + success criteria + REQ-IDs from ROADMAP.md
|
|
16
|
+
- `<locked_decisions>` (optional) — Locked Decisions from `.planning/phase-{N}-context.md` if it exists
|
|
17
|
+
- `<research_findings>` (optional) — inlined `.planning/phase-{N}-research.md` if present
|
|
18
|
+
- `<relevant_learnings>` (optional) — applicable patterns from `~/.claude/knowledge/learned-patterns.md`
|
|
19
|
+
- `<revision_mode>` (optional, boolean) — when `true`, also receives `<current_plan>` and `<checker_feedback>`; revise in place, don't rewrite
|
|
20
|
+
- `<gaps_mode>` (optional, boolean) — when `true`, also receives `<verification_path>`; create gap-closure tasks only
|
|
13
21
|
|
|
14
22
|
## Output
|
|
15
23
|
Write `.planning/phase-{N}-plan.md` — a plan file with 2-5 tasks.
|
|
@@ -29,11 +37,32 @@ Start from the phase goal. Work backwards:
|
|
|
29
37
|
|
|
30
38
|
Each truth → one task. 2-5 tasks per phase. Each task must fit in one context window.
|
|
31
39
|
|
|
32
|
-
### 3. Assign Waves
|
|
33
|
-
|
|
34
|
-
|
|
40
|
+
### 3. Assign Waves (file-based dependency graph — deterministic)
|
|
41
|
+
|
|
42
|
+
Wave assignment is NOT a vibes call. Use this mechanical algorithm:
|
|
43
|
+
|
|
44
|
+
1. **Build adjacency list.** For each task T, define:
|
|
45
|
+
- `writes(T)` = the set of file paths in `**Files:**` that T creates or modifies
|
|
46
|
+
- `reads(T)` = file paths T consumes from `**Context:**` (@references) + any paths declared in `**Depends on:**`
|
|
47
|
+
2. **Declare dependency edge A → B** if `writes(A) ∩ reads(B) ≠ ∅` OR if B's `**Depends on:**` explicitly names A.
|
|
48
|
+
3. **Topological-sort into waves.** Wave 1 = all tasks with in-degree 0 (no incoming edges). Wave 2 = tasks whose only dependencies are in Wave 1. Continue until all tasks placed.
|
|
49
|
+
4. **Parallel-safety check.** No two tasks in the same wave may share a file in their `writes()` sets. If they do, serialize them into consecutive waves.
|
|
50
|
+
|
|
51
|
+
Two tasks that both *read* the same file (same entry in `Context:`) are fine in the same wave — only *write conflicts* force serialization.
|
|
52
|
+
|
|
35
53
|
- Most phases need 1-2 waves. If you need 3+, your tasks are too granular.
|
|
36
54
|
|
|
55
|
+
**Worked example:**
|
|
56
|
+
|
|
57
|
+
| Task | Files (writes) | Context/Depends-on (reads) | Edges | Wave |
|
|
58
|
+
|------|----------------|----------------------------|-------|------|
|
|
59
|
+
| T1 — Create auth lib | `src/lib/auth.ts` | `@.planning/PROJECT.md` | none | 1 |
|
|
60
|
+
| T2 — Create login page | `src/app/login/page.tsx` | `@src/lib/auth.ts` (reads T1's write) | T1 → T2 | 2 |
|
|
61
|
+
| T3 — Create signup page | `src/app/signup/page.tsx` | `@src/lib/auth.ts` (reads T1's write) | T1 → T3 | 2 |
|
|
62
|
+
| T4 — Add RLS policies | `supabase/migrations/001.sql` | `@.planning/PROJECT.md` | none | 1 |
|
|
63
|
+
|
|
64
|
+
T1 and T4 → Wave 1 (no shared writes, both reading PROJECT.md is fine). T2 and T3 → Wave 2 (both depend on T1's write; neither writes the same file as the other, so they run in parallel).
|
|
65
|
+
|
|
37
66
|
### 4. Write the Plan (Story-File Format)
|
|
38
67
|
|
|
39
68
|
Plans are STORY FILES, not task lists. Every task is a self-contained package that embeds *why*, *what*, and *how to verify* — so the builder can execute without re-reading PRDs and the verifier has explicit acceptance targets.
|
package/agents/qa-browser.md
CHANGED
|
@@ -11,7 +11,11 @@ You verify that the **running app actually looks and behaves right** — not jus
|
|
|
11
11
|
**Critical mindset:** You are the user. You don't trust the code — you drive the app and see what happens. If it breaks at 375px, it's broken. If the console screams, it's broken. If clicking the primary CTA does nothing, it's broken.
|
|
12
12
|
|
|
13
13
|
## Input
|
|
14
|
-
|
|
14
|
+
|
|
15
|
+
- `<plan_path>` — path to `.planning/phase-{N}-plan.md`
|
|
16
|
+
- `<dev_server_url>` — e.g. `http://localhost:3000`. If omitted, probe ports 3000–3001 as fallback; if no server answers within 10s, write `BLOCKED: dev server not reachable` and exit.
|
|
17
|
+
- `<phase_number>` — integer, used for the verification filename
|
|
18
|
+
- Access to Playwright MCP browser tools
|
|
15
19
|
|
|
16
20
|
## Output
|
|
17
21
|
Append a `## Browser QA` section to `.planning/phase-{N}-verification.md` with PASS/FAIL per check.
|
|
@@ -48,6 +48,8 @@ Don't duplicate full documents. Summarize the 3-5 most important items from each
|
|
|
48
48
|
|
|
49
49
|
This is the most important section. Suggest the **full milestone arc**, not just a v1 phase list.
|
|
50
50
|
|
|
51
|
+
**Evidence requirement:** Every milestone suggestion MUST cite at least one research finding from STACK.md, FEATURES.md, ARCHITECTURE.md, or PITFALLS.md as justification. Format the citation as `[DIMENSION.md: <specific finding or item>]` — e.g., `[FEATURES.md: table-stakes AUTH-*]` or `[PITFALLS.md: risk P3, stall risk for downstream milestones]`. Milestones without a citable finding are speculative — mark them explicitly with `[speculative — no source]` and the roadmapper will scrutinize.
|
|
52
|
+
|
|
51
53
|
Based on:
|
|
52
54
|
- FEATURES.md split (table stakes = v1 across milestones, differentiators = later milestones or post-handoff)
|
|
53
55
|
- ARCHITECTURE.md build order → what depends on what, which foundation must land in Milestone 1 to support final-milestone requirements
|
package/agents/researcher.md
CHANGED
|
@@ -17,6 +17,10 @@ You receive from the orchestrator:
|
|
|
17
17
|
- `<milestone_context>` — greenfield or subsequent
|
|
18
18
|
- `<output_path>` — absolute path where you write your research file
|
|
19
19
|
|
|
20
|
+
## Tool Budget
|
|
21
|
+
|
|
22
|
+
Maximum 8 external calls total per invocation: 3 Context7 queries + 3 WebFetch calls + 2 WebSearch queries. If you exhaust this budget, write what you have and mark remaining sections as `confidence: LOW`. Research is time-boxed, not exhaustive — a 10-minute deep dive with concrete sources beats a 30-minute wander.
|
|
23
|
+
|
|
20
24
|
## Output
|
|
21
25
|
|
|
22
26
|
Write exactly ONE file to `<output_path>`, using the template matching your dimension:
|
package/agents/roadmapper.md
CHANGED
|
@@ -17,6 +17,7 @@ You receive:
|
|
|
17
17
|
- `.planning/research/SUMMARY.md` — research synthesis (optional — may not exist if research was skipped)
|
|
18
18
|
- `.planning/config.json` — project config (`depth`, `template_type`)
|
|
19
19
|
- User's confirmed feature scope (from the scoping conversation in qualia-new)
|
|
20
|
+
- `<full_detail>` — boolean (default `false`). When `true`, write full phase detail for EVERY milestone in ROADMAP.md, not just M1. Passed by the orchestrator when the user runs `/qualia-new --full-plan`.
|
|
20
21
|
|
|
21
22
|
## Output
|
|
22
23
|
|
|
@@ -85,14 +86,18 @@ Use the research SUMMARY.md as your starting point. Don't force-fit the template
|
|
|
85
86
|
- **Phases** — 2-5 phases. For Milestone 1, include full detail (goal + success criteria). For M2..M{N-1}, names + one-line goals are enough (progressive detail — full detail gets written when that milestone opens). For Handoff, use the fixed 4-phase template.
|
|
86
87
|
- **Requirements covered** — list the REQ-IDs this milestone delivers
|
|
87
88
|
|
|
88
|
-
### 4. Build ROADMAP.md —
|
|
89
|
+
### 4. Build ROADMAP.md — Milestone 1's phases (progressive detail by default)
|
|
89
90
|
|
|
90
|
-
|
|
91
|
+
Check the `<full_detail>` flag in your prompt:
|
|
91
92
|
|
|
92
|
-
|
|
93
|
+
**`full_detail=false` (default):** Only Milestone 1 gets full phase detail in ROADMAP.md. Future milestones stay as sketches in JOURNEY.md until they open. This matches progressive-detail planning and is the recommended default.
|
|
94
|
+
|
|
95
|
+
**`full_detail=true`:** Write full phase detail for EVERY milestone (M1..Handoff) in ROADMAP.md, sectioned by milestone. Use when the client wants a fully-committed plan at kickoff. Trade-off: M2+ detail often needs revision as M1 ships — flag this in your summary.
|
|
96
|
+
|
|
97
|
+
For each phase in the milestone(s) you're detailing:
|
|
93
98
|
- **Name** + **goal** (one line)
|
|
94
99
|
- **Success criteria** — 2-5 observable user-facing behaviors
|
|
95
|
-
- **Requirements covered** — REQ-IDs from REQUIREMENTS.md
|
|
100
|
+
- **Requirements covered** — REQ-IDs from REQUIREMENTS.md section for that milestone
|
|
96
101
|
|
|
97
102
|
### 5. Validate Coverage
|
|
98
103
|
|
|
@@ -104,7 +109,8 @@ Before writing, verify:
|
|
|
104
109
|
- [ ] Final milestone is literally named "Handoff" with the 4 standard phases
|
|
105
110
|
- [ ] No milestone depends on a later milestone
|
|
106
111
|
- [ ] Milestone 1 has full phase-level detail (goals + success criteria) ready for `/qualia-plan 1`
|
|
107
|
-
- [ ] M2..M{N-1} have phase names + one-line goals (sketch, not full detail)
|
|
112
|
+
- [ ] If `full_detail=false` (default): M2..M{N-1} have phase names + one-line goals (sketch, not full detail)
|
|
113
|
+
- [ ] If `full_detail=true`: every milestone in ROADMAP.md has full phase detail; flag this mode explicitly in your summary output
|
|
108
114
|
|
|
109
115
|
If any check fails, fix it. The orchestrator trusts your output.
|
|
110
116
|
|
package/agents/verifier.md
CHANGED
|
@@ -11,10 +11,17 @@ You verify that a phase achieved its GOAL, not just completed its TASKS.
|
|
|
11
11
|
**Critical mindset:** Do NOT trust claims about what was built. Summaries document what Claude SAID it did. You verify what ACTUALLY EXISTS in the code. These often differ.
|
|
12
12
|
|
|
13
13
|
## Input
|
|
14
|
-
|
|
14
|
+
|
|
15
|
+
- `<plan_path>` — path to `.planning/phase-{N}-plan.md`
|
|
16
|
+
- `<project_context>` — inlined `.planning/PROJECT.md` contents (for Quality scoring against project conventions)
|
|
17
|
+
- `<previous_verification>` (optional) — inlined `.planning/phase-{N}-verification.md` from a prior run
|
|
15
18
|
|
|
16
19
|
## Output
|
|
17
|
-
Write `.planning/phase-{N}-verification.md` — PASS or FAIL with evidence.
|
|
20
|
+
Write `.planning/phase-{N}-verification.md` — PASS or FAIL with evidence. Apply the Grounding Protocol: every finding needs `file:line — "quoted"` evidence, no hedging, scores with rubric criterion citations.
|
|
21
|
+
|
|
22
|
+
## Tool Budget
|
|
23
|
+
|
|
24
|
+
Maximum 25 Bash/Grep calls per invocation. Prefer one multi-pattern grep over many single-pattern greps. If you exhaust the budget, write what you found and mark unchecked criteria as `INSUFFICIENT EVIDENCE` — do not fabricate.
|
|
18
25
|
|
|
19
26
|
## Goal-Backward Verification
|
|
20
27
|
|
|
@@ -260,7 +267,19 @@ Phase goal: "Working real-time chat interface with message history."
|
|
|
260
267
|
|
|
261
268
|
## Design Verification (for phases with frontend work)
|
|
262
269
|
|
|
263
|
-
|
|
270
|
+
**Gate (run first):** Only execute this section if the phase touched frontend.
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
# Is this a frontend phase?
|
|
274
|
+
FRONTEND=false
|
|
275
|
+
if grep -qE "\.tsx|\.jsx|\.css|\.scss|app/|components/|pages/|Persona:\s*(frontend|ux)" .planning/phase-{N}-plan.md 2>/dev/null; then
|
|
276
|
+
FRONTEND=true
|
|
277
|
+
fi
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
If `FRONTEND=false`, write `Design Verification: N/A (no frontend tasks in phase)` in the report and skip the rest of this section. This saves ~40 greps on backend-only phases.
|
|
281
|
+
|
|
282
|
+
If `FRONTEND=true`, proceed. Add a **Design Quality** section to the report.
|
|
264
283
|
|
|
265
284
|
First, read the project's DESIGN.md:
|
|
266
285
|
```bash
|
package/bin/cli.js
CHANGED
|
@@ -144,8 +144,17 @@ const QUALIA_LEGACY_HOOK_FILES = [
|
|
|
144
144
|
"block-env-edit.js", // removed in v3.2.0
|
|
145
145
|
];
|
|
146
146
|
|
|
147
|
-
//
|
|
148
|
-
const QUALIA_AGENT_FILES = [
|
|
147
|
+
// 8 Qualia agents — only these are removed.
|
|
148
|
+
const QUALIA_AGENT_FILES = [
|
|
149
|
+
"planner.md",
|
|
150
|
+
"builder.md",
|
|
151
|
+
"verifier.md",
|
|
152
|
+
"qa-browser.md",
|
|
153
|
+
"plan-checker.md",
|
|
154
|
+
"researcher.md",
|
|
155
|
+
"research-synthesizer.md",
|
|
156
|
+
"roadmapper.md",
|
|
157
|
+
];
|
|
149
158
|
|
|
150
159
|
// 3 Qualia bin scripts.
|
|
151
160
|
const QUALIA_BIN_FILES = ["state.js", "qualia-ui.js", "statusline.js"];
|
|
@@ -557,7 +566,7 @@ function cmdMigrate() {
|
|
|
557
566
|
|
|
558
567
|
// Check PreToolUse hooks — ensure all critical hooks are present
|
|
559
568
|
const requiredBashHooks = ["auto-update.js", "branch-guard.js", "pre-push.js", "pre-deploy-gate.js"];
|
|
560
|
-
const requiredEditHooks = ["
|
|
569
|
+
const requiredEditHooks = ["migration-guard.js"];
|
|
561
570
|
|
|
562
571
|
if (!settings.hooks.PreToolUse) settings.hooks.PreToolUse = [];
|
|
563
572
|
|
|
@@ -649,7 +658,7 @@ function cmdMigrate() {
|
|
|
649
658
|
if (!settings.mcpServers["next-devtools"]) {
|
|
650
659
|
settings.mcpServers["next-devtools"] = {
|
|
651
660
|
command: "npx",
|
|
652
|
-
args: ["next-devtools-mcp@
|
|
661
|
+
args: ["next-devtools-mcp@latest"],
|
|
653
662
|
disabled: false,
|
|
654
663
|
};
|
|
655
664
|
changes++;
|
|
@@ -760,6 +769,109 @@ function cmdAnalytics() {
|
|
|
760
769
|
console.log("");
|
|
761
770
|
}
|
|
762
771
|
|
|
772
|
+
// ─── ERP Ping ───────────────────────────────────────────
|
|
773
|
+
// Synthetic POST to ERP /api/v1/reports to verify connectivity, auth key
|
|
774
|
+
// validity, and endpoint health. Uses a distinct dry_run=true flag in the
|
|
775
|
+
// payload so receivers can filter these out of real report views.
|
|
776
|
+
|
|
777
|
+
function cmdErpPing() {
|
|
778
|
+
banner();
|
|
779
|
+
console.log("");
|
|
780
|
+
|
|
781
|
+
const cfg = readConfig();
|
|
782
|
+
const erpUrl = (cfg.erp && cfg.erp.url) || "https://portal.qualiasolutions.net";
|
|
783
|
+
const erpEnabled = !(cfg.erp && cfg.erp.enabled === false);
|
|
784
|
+
const keyFile = path.join(CLAUDE_DIR, ".erp-api-key");
|
|
785
|
+
|
|
786
|
+
console.log(` ${DIM}URL:${RESET} ${WHITE}${erpUrl}${RESET}`);
|
|
787
|
+
console.log(` ${DIM}Enabled:${RESET} ${erpEnabled ? `${GREEN}yes${RESET}` : `${YELLOW}no (erp.enabled=false)${RESET}`}`);
|
|
788
|
+
|
|
789
|
+
let apiKey = "";
|
|
790
|
+
try {
|
|
791
|
+
apiKey = fs.readFileSync(keyFile, "utf8").trim();
|
|
792
|
+
} catch {}
|
|
793
|
+
if (!apiKey) {
|
|
794
|
+
console.log(` ${DIM}Key:${RESET} ${RED}missing${RESET} ${DIM}(${keyFile})${RESET}`);
|
|
795
|
+
console.log("");
|
|
796
|
+
console.log(` ${RED}✗ Cannot ping — no API key. Ask Fawzi for one.${RESET}`);
|
|
797
|
+
console.log("");
|
|
798
|
+
process.exit(1);
|
|
799
|
+
}
|
|
800
|
+
console.log(` ${DIM}Key:${RESET} ${GREEN}present${RESET} ${DIM}(${apiKey.length} bytes)${RESET}`);
|
|
801
|
+
console.log("");
|
|
802
|
+
|
|
803
|
+
if (!erpEnabled) {
|
|
804
|
+
console.log(` ${YELLOW}ERP is disabled in config. Enable with:${RESET}`);
|
|
805
|
+
console.log(` ${DIM} qualia-framework erp-ping --enable${RESET}`);
|
|
806
|
+
console.log("");
|
|
807
|
+
process.exit(1);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
const payload = JSON.stringify({
|
|
811
|
+
project: "qualia-framework-erp-ping",
|
|
812
|
+
project_id: "ping",
|
|
813
|
+
team_id: "qualia-solutions",
|
|
814
|
+
client_report_id: "QS-PING-00",
|
|
815
|
+
phase: 0,
|
|
816
|
+
phase_name: "ping",
|
|
817
|
+
status: "setup",
|
|
818
|
+
milestone: 0,
|
|
819
|
+
milestone_name: "ping",
|
|
820
|
+
submitted_by: cfg.installed_by || "ping",
|
|
821
|
+
submitted_at: new Date().toISOString(),
|
|
822
|
+
notes: "ERP PING — synthetic connectivity test, safe to ignore",
|
|
823
|
+
dry_run: true,
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
const started = Date.now();
|
|
827
|
+
const r = spawnSync("curl", [
|
|
828
|
+
"-sS", "-X", "POST",
|
|
829
|
+
"-H", `Authorization: Bearer ${apiKey}`,
|
|
830
|
+
"-H", "Content-Type: application/json",
|
|
831
|
+
"-d", payload,
|
|
832
|
+
"--max-time", "10",
|
|
833
|
+
"-w", "\n__HTTP__%{http_code}",
|
|
834
|
+
`${erpUrl}/api/v1/reports`,
|
|
835
|
+
], { encoding: "utf8", timeout: 12000 });
|
|
836
|
+
|
|
837
|
+
const duration = Date.now() - started;
|
|
838
|
+
const raw = (r.stdout || "") + (r.stderr || "");
|
|
839
|
+
const httpMatch = raw.match(/__HTTP__(\d+)/);
|
|
840
|
+
const httpCode = httpMatch ? httpMatch[1] : "—";
|
|
841
|
+
const body = raw.replace(/\n?__HTTP__\d+/, "").trim();
|
|
842
|
+
|
|
843
|
+
console.log(` ${DIM}Response:${RESET} ${WHITE}HTTP ${httpCode}${RESET} ${DIM}(${duration}ms)${RESET}`);
|
|
844
|
+
if (body) {
|
|
845
|
+
try {
|
|
846
|
+
const j = JSON.parse(body);
|
|
847
|
+
if (j.ok && j.report_id) {
|
|
848
|
+
console.log(` ${DIM}report_id:${RESET} ${GREEN}${j.report_id}${RESET}`);
|
|
849
|
+
}
|
|
850
|
+
if (!j.ok && j.error) {
|
|
851
|
+
console.log(` ${DIM}error:${RESET} ${RED}${j.error}${RESET} ${DIM}${j.message || ""}${RESET}`);
|
|
852
|
+
}
|
|
853
|
+
} catch {
|
|
854
|
+
console.log(` ${DIM}body:${RESET} ${WHITE}${body.slice(0, 200)}${RESET}`);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
console.log("");
|
|
858
|
+
|
|
859
|
+
if (httpCode === "200") {
|
|
860
|
+
console.log(` ${GREEN}✓ ERP reachable, key valid, endpoint healthy.${RESET}`);
|
|
861
|
+
console.log("");
|
|
862
|
+
process.exit(0);
|
|
863
|
+
}
|
|
864
|
+
if (httpCode === "401") {
|
|
865
|
+
console.log(` ${RED}✗ API key rejected. Ask Fawzi for a fresh key.${RESET}`);
|
|
866
|
+
} else if (httpCode === "—") {
|
|
867
|
+
console.log(` ${RED}✗ No response — DNS, TLS, or network issue.${RESET}`);
|
|
868
|
+
} else {
|
|
869
|
+
console.log(` ${YELLOW}! Unexpected response. Check ERP status.${RESET}`);
|
|
870
|
+
}
|
|
871
|
+
console.log("");
|
|
872
|
+
process.exit(1);
|
|
873
|
+
}
|
|
874
|
+
|
|
763
875
|
function cmdHelp() {
|
|
764
876
|
banner();
|
|
765
877
|
console.log("");
|
|
@@ -772,6 +884,7 @@ function cmdHelp() {
|
|
|
772
884
|
console.log(` qualia-framework ${TEAL}team${RESET} Manage team members (${DIM}list|add|remove${RESET})`);
|
|
773
885
|
console.log(` qualia-framework ${TEAL}traces${RESET} View recent hook telemetry`);
|
|
774
886
|
console.log(` qualia-framework ${TEAL}analytics${RESET} Show outcome scoring & gap cycle stats`);
|
|
887
|
+
console.log(` qualia-framework ${TEAL}erp-ping${RESET} Verify ERP connectivity + API key`);
|
|
775
888
|
console.log("");
|
|
776
889
|
console.log(` ${WHITE}After install:${RESET}`);
|
|
777
890
|
console.log(` ${TG}/qualia${RESET} What should I do next?`);
|
|
@@ -824,6 +937,10 @@ switch (cmd) {
|
|
|
824
937
|
case "stats":
|
|
825
938
|
cmdAnalytics();
|
|
826
939
|
break;
|
|
940
|
+
case "erp-ping":
|
|
941
|
+
case "ping":
|
|
942
|
+
cmdErpPing();
|
|
943
|
+
break;
|
|
827
944
|
default:
|
|
828
945
|
cmdHelp();
|
|
829
946
|
}
|
package/bin/install.js
CHANGED
|
@@ -517,7 +517,18 @@ Client-specific preferences, design choices, and requirements. Loaded by \`/qual
|
|
|
517
517
|
ok(".erp-api-key (from $QUALIA_ERP_KEY)");
|
|
518
518
|
} else if (fs.existsSync(erpKeyFile)) {
|
|
519
519
|
try { fs.chmodSync(erpKeyFile, 0o600); } catch {}
|
|
520
|
-
|
|
520
|
+
// Sanity check: warn on a clearly-empty/placeholder key. Genuine tokens
|
|
521
|
+
// from the ERP are ≥ 20 bytes; under 10 is almost certainly a mistake.
|
|
522
|
+
try {
|
|
523
|
+
const existingKey = fs.readFileSync(erpKeyFile, "utf8").trim();
|
|
524
|
+
if (existingKey.length < 10) {
|
|
525
|
+
warn(`.erp-api-key exists but looks truncated (${existingKey.length} bytes) — verify with 'qualia-framework erp-ping'`);
|
|
526
|
+
} else {
|
|
527
|
+
ok(".erp-api-key (existing — preserved)");
|
|
528
|
+
}
|
|
529
|
+
} catch {
|
|
530
|
+
ok(".erp-api-key (existing — preserved)");
|
|
531
|
+
}
|
|
521
532
|
} else {
|
|
522
533
|
// Disable ERP in the config we just wrote.
|
|
523
534
|
try {
|
|
@@ -673,7 +684,7 @@ Client-specific preferences, design choices, and requirements. Loaded by \`/qual
|
|
|
673
684
|
if (!settings.mcpServers["next-devtools"]) {
|
|
674
685
|
settings.mcpServers["next-devtools"] = {
|
|
675
686
|
command: "npx",
|
|
676
|
-
args: ["next-devtools-mcp@
|
|
687
|
+
args: ["next-devtools-mcp@latest"],
|
|
677
688
|
disabled: false,
|
|
678
689
|
};
|
|
679
690
|
ok("MCP: next-devtools (runtime error visibility for Next.js projects)");
|
package/bin/state.js
CHANGED
|
@@ -194,6 +194,7 @@ function ensureLifetime(t) {
|
|
|
194
194
|
if (typeof t.milestone !== "number") t.milestone = 1;
|
|
195
195
|
if (typeof t.milestone_name !== "string") t.milestone_name = "";
|
|
196
196
|
if (!Array.isArray(t.milestones)) t.milestones = [];
|
|
197
|
+
if (typeof t.report_seq !== "number") t.report_seq = 0;
|
|
197
198
|
if (!t.lifetime || typeof t.lifetime !== "object") {
|
|
198
199
|
t.lifetime = {
|
|
199
200
|
tasks_completed: 0,
|
|
@@ -205,6 +206,26 @@ function ensureLifetime(t) {
|
|
|
205
206
|
return t;
|
|
206
207
|
}
|
|
207
208
|
|
|
209
|
+
// Parse JOURNEY.md and extract the human name of the Nth milestone.
|
|
210
|
+
// Matches headers like:
|
|
211
|
+
// ## Milestone 2 · Core Features
|
|
212
|
+
// ## Milestone 2 · Core Features [CURRENT]
|
|
213
|
+
// ## Milestone 5 · Handoff [FINAL]
|
|
214
|
+
// Returns "" if JOURNEY.md is absent or the milestone isn't in it.
|
|
215
|
+
function readNextMilestoneNameFromJourney(milestoneNum) {
|
|
216
|
+
try {
|
|
217
|
+
const journeyPath = path.join(PLANNING, "JOURNEY.md");
|
|
218
|
+
if (!fs.existsSync(journeyPath)) return "";
|
|
219
|
+
const content = fs.readFileSync(journeyPath, "utf8");
|
|
220
|
+
const re = new RegExp(`^##\\s+Milestone\\s+${milestoneNum}\\s*[·•-]\\s*([^\\n\\[]+)`, "m");
|
|
221
|
+
const m = content.match(re);
|
|
222
|
+
if (m && m[1]) return m[1].trim();
|
|
223
|
+
return "";
|
|
224
|
+
} catch {
|
|
225
|
+
return "";
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
208
229
|
function readState() {
|
|
209
230
|
try {
|
|
210
231
|
return fs.readFileSync(STATE_FILE, "utf8");
|
|
@@ -1141,7 +1162,12 @@ function cmdCloseMilestone(opts) {
|
|
|
1141
1162
|
t.lifetime.total_phases += (parseInt(t.total_phases) || 0);
|
|
1142
1163
|
t.lifetime.last_closed_milestone = closedMilestone;
|
|
1143
1164
|
t.milestone = closedMilestone + 1;
|
|
1144
|
-
|
|
1165
|
+
// Try to pre-populate next milestone's name from JOURNEY.md so the ERP
|
|
1166
|
+
// tree view doesn't show a blank between close-milestone and the next
|
|
1167
|
+
// state.js init --force (which happens in /qualia-milestone step 7).
|
|
1168
|
+
// If JOURNEY.md is missing or unparseable, fall through with blank —
|
|
1169
|
+
// /qualia-milestone will still set it via init --force.
|
|
1170
|
+
t.milestone_name = readNextMilestoneNameFromJourney(t.milestone);
|
|
1145
1171
|
t.last_updated = new Date().toISOString();
|
|
1146
1172
|
|
|
1147
1173
|
writeTracking(t);
|
|
@@ -1238,6 +1264,27 @@ function cmdBackfillLifetime(opts) {
|
|
|
1238
1264
|
});
|
|
1239
1265
|
}
|
|
1240
1266
|
|
|
1267
|
+
// ─── Next Report ID ──────────────────────────────────────
|
|
1268
|
+
// Increments report_seq and returns the next QS-REPORT-NN id. Per-project
|
|
1269
|
+
// counter (lives in tracking.json). /qualia-report calls this to tag each
|
|
1270
|
+
// session report with a stable, human-readable client ID before POSTing
|
|
1271
|
+
// to the ERP. If --peek is passed, the next id is returned WITHOUT
|
|
1272
|
+
// incrementing — useful for --dry-run previews.
|
|
1273
|
+
function cmdNextReportId(opts) {
|
|
1274
|
+
const t = readTracking();
|
|
1275
|
+
if (!t) return output(fail("NO_PROJECT", "No .planning/ found."));
|
|
1276
|
+
ensureLifetime(t);
|
|
1277
|
+
const peek = !!opts.peek;
|
|
1278
|
+
const next = (parseInt(t.report_seq) || 0) + 1;
|
|
1279
|
+
const id = `QS-REPORT-${String(next).padStart(2, "0")}`;
|
|
1280
|
+
if (!peek) {
|
|
1281
|
+
t.report_seq = next;
|
|
1282
|
+
t.last_updated = new Date().toISOString();
|
|
1283
|
+
writeTracking(t);
|
|
1284
|
+
}
|
|
1285
|
+
output({ ok: true, action: "next-report-id", report_id: id, report_seq: next, peeked: peek });
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1241
1288
|
// ─── Output ──────────────────────────────────────────────
|
|
1242
1289
|
function output(obj) {
|
|
1243
1290
|
console.log(JSON.stringify(obj, null, 2));
|
|
@@ -1289,11 +1336,14 @@ try {
|
|
|
1289
1336
|
case "backfill-lifetime":
|
|
1290
1337
|
cmdBackfillLifetime(opts);
|
|
1291
1338
|
break;
|
|
1339
|
+
case "next-report-id":
|
|
1340
|
+
cmdNextReportId(opts);
|
|
1341
|
+
break;
|
|
1292
1342
|
default:
|
|
1293
1343
|
output(
|
|
1294
1344
|
fail(
|
|
1295
1345
|
"UNKNOWN_COMMAND",
|
|
1296
|
-
`Usage: state.js <check|transition|init|fix|validate-plan|close-milestone|backfill-lifetime> [--options]`
|
|
1346
|
+
`Usage: state.js <check|transition|init|fix|validate-plan|close-milestone|backfill-lifetime|next-report-id> [--options]`
|
|
1297
1347
|
)
|
|
1298
1348
|
);
|
|
1299
1349
|
}
|