qualia-framework 4.4.0 → 5.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.
Files changed (70) hide show
  1. package/AGENTS.md +24 -0
  2. package/CLAUDE.md +12 -63
  3. package/README.md +24 -18
  4. package/agents/builder.md +13 -33
  5. package/agents/plan-checker.md +18 -0
  6. package/agents/planner.md +17 -0
  7. package/agents/verifier.md +70 -0
  8. package/agents/visual-evaluator.md +132 -0
  9. package/bin/cli.js +64 -23
  10. package/bin/install.js +375 -29
  11. package/bin/qualia-ui.js +208 -1
  12. package/bin/slop-detect.mjs +362 -0
  13. package/bin/state.js +218 -2
  14. package/docs/erp-contract.md +5 -0
  15. package/docs/install-redesign-builder-prompt.md +290 -0
  16. package/docs/install-redesign-pilot.md +234 -0
  17. package/docs/playwright-loop-builder-prompt.md +185 -0
  18. package/docs/playwright-loop-design-notes.md +108 -0
  19. package/docs/playwright-loop-pilot-results.md +170 -0
  20. package/docs/playwright-loop-review-2026-05-03.md +65 -0
  21. package/docs/playwright-loop-tester-prompt.md +213 -0
  22. package/docs/reviews/matt-pocock-skills-analysis.md +300 -0
  23. package/guide.md +9 -5
  24. package/hooks/env-empty-guard.js +74 -0
  25. package/hooks/pre-compact.js +19 -9
  26. package/hooks/pre-deploy-gate.js +8 -2
  27. package/hooks/pre-push.js +26 -12
  28. package/hooks/supabase-destructive-guard.js +62 -0
  29. package/hooks/vercel-account-guard.js +91 -0
  30. package/package.json +2 -1
  31. package/rules/design-brand.md +114 -0
  32. package/rules/design-laws.md +148 -0
  33. package/rules/design-product.md +114 -0
  34. package/rules/design-rubric.md +157 -0
  35. package/rules/grounding.md +4 -0
  36. package/skills/qualia-build/SKILL.md +40 -46
  37. package/skills/qualia-discuss/SKILL.md +51 -68
  38. package/skills/qualia-handoff/SKILL.md +1 -0
  39. package/skills/qualia-issues/SKILL.md +151 -0
  40. package/skills/qualia-map/SKILL.md +78 -35
  41. package/skills/qualia-new/REFERENCE.md +139 -0
  42. package/skills/qualia-new/SKILL.md +85 -124
  43. package/skills/qualia-optimize/REFERENCE.md +202 -0
  44. package/skills/qualia-optimize/SKILL.md +72 -237
  45. package/skills/qualia-plan/SKILL.md +58 -65
  46. package/skills/qualia-polish/SKILL.md +180 -136
  47. package/skills/qualia-polish-loop/REFERENCE.md +265 -0
  48. package/skills/qualia-polish-loop/SKILL.md +201 -0
  49. package/skills/qualia-polish-loop/fixtures/broken.html +117 -0
  50. package/skills/qualia-polish-loop/fixtures/clean.html +196 -0
  51. package/skills/qualia-polish-loop/scripts/loop.mjs +302 -0
  52. package/skills/qualia-polish-loop/scripts/playwright-capture.mjs +197 -0
  53. package/skills/qualia-polish-loop/scripts/score.mjs +176 -0
  54. package/skills/qualia-report/SKILL.md +141 -180
  55. package/skills/qualia-research/SKILL.md +28 -33
  56. package/skills/qualia-road/SKILL.md +103 -0
  57. package/skills/qualia-ship/SKILL.md +1 -0
  58. package/skills/qualia-task/SKILL.md +1 -1
  59. package/skills/qualia-test/SKILL.md +50 -2
  60. package/skills/qualia-triage/SKILL.md +152 -0
  61. package/skills/qualia-verify/SKILL.md +63 -104
  62. package/skills/qualia-zoom/SKILL.md +51 -0
  63. package/skills/zoho-workflow/SKILL.md +64 -0
  64. package/templates/CONTEXT.md +36 -0
  65. package/templates/DESIGN.md +229 -435
  66. package/templates/PRODUCT.md +95 -0
  67. package/templates/decisions/ADR-template.md +30 -0
  68. package/tests/bin.test.sh +451 -7
  69. package/tests/state.test.sh +58 -0
  70. package/skills/qualia-design/SKILL.md +0 -169
@@ -1,123 +1,160 @@
1
1
  ---
2
2
  name: qualia-report
3
- description: "Generate session report and commit to repo. Mandatory before clock-out."
3
+ description: "Generate session report, commit to git, push, and upload to the Qualia ERP — the mandatory clock-out flow. Use when the user says 'qualia-report', 'clock out', 'end of day', 'wrap up', 'session report', 'submit report', 'I'm done for today', or before stopping work. Handles empty days (no commits), missing API key, ERP outages, and dry-run preview gracefully."
4
4
  allowed-tools:
5
5
  - Bash
6
6
  - Read
7
7
  - Write
8
8
  - Edit
9
+ - AskUserQuestion
9
10
  ---
10
11
 
11
- # /qualia-report — Session Report
12
+ # /qualia-report — Daily Clock-Out Report
12
13
 
13
- Generate a concise report of what was done. Committed to git and uploaded to the ERP for clock-out.
14
+ The end-of-day flow. Generates a report, commits it, pushes, uploads to the ERP, and tells the employee they can stop. Designed so Hasan and Moayad never get stuck on it.
14
15
 
15
16
  ## Flags
16
-
17
17
  - `/qualia-report` — normal flow (generate, commit, push, upload to ERP)
18
- - `/qualia-report --dry-run` — generate + show payload, SKIP upload and SKIP commit. Useful for debugging or previewing before a real clock-out.
18
+ - `/qualia-report --dry-run` — preview the payload without committing/uploading
19
19
 
20
20
  ## Process
21
21
 
22
+ ### Step 0 — Pre-flight (graceful)
23
+
22
24
  ```bash
23
25
  node ~/.claude/bin/qualia-ui.js banner report
26
+
27
+ # Sanity checks — soft failures only, never block the report
28
+ test -d .git || node ~/.claude/bin/qualia-ui.js warn "Not a git repo — local commit will be skipped, ERP upload will still try"
29
+ test -f .planning/tracking.json || node ~/.claude/bin/qualia-ui.js warn "No tracking.json yet — ERP payload will use defaults"
30
+
31
+ DRY_RUN="${DRY_RUN:-false}"
32
+ echo "$ARGUMENTS" | grep -q -- '--dry-run' && DRY_RUN="true"
24
33
  ```
25
34
 
26
- ### 1. Gather Data
35
+ ### Step 1 Gather
27
36
 
28
37
  ```bash
29
38
  SINCE="8 hours ago"
30
- echo "---COMMITS---"
31
- git log --oneline --since="$SINCE" 2>/dev/null | head -20
32
- echo "---STATS---"
33
- echo "COUNT:$(git log --oneline --since="$SINCE" 2>/dev/null | wc -l)"
34
- echo "---PROJECT---"
35
- echo "DIR:$(basename $(pwd))"
36
- echo "BRANCH:$(git branch --show-current 2>/dev/null)"
37
- echo "---STATE---"
38
- node ~/.claude/bin/state.js check 2>/dev/null
39
+ COMMITS=$(git log --oneline --since="$SINCE" 2>/dev/null)
40
+ COUNT=$(echo "$COMMITS" | grep -c . 2>/dev/null || echo 0)
41
+ BRANCH=$(git branch --show-current 2>/dev/null || echo "no-branch")
42
+ PROJECT=$(basename "$(pwd)")
43
+ ```
44
+
45
+ ### Step 2 — Synthesize (with empty-day handling)
46
+
47
+ **If `COUNT > 0`** — build a structured summary using this template:
48
+
49
+ ```markdown
50
+ ## What Was Done
51
+ - {Verb} {object}. Why: {one-clause reason}. ← write 3–6 of these, one per related-commits group
52
+ verbs: Built, Fixed, Refactored, Added, Removed,
53
+ Migrated, Documented, Investigated, Reverted
54
+
55
+ ## Blockers
56
+ None. ← or list 1–N actual blockers (NOT "had to read docs" — that's normal)
57
+
58
+ ## Next Steps
59
+ 1. {Concrete next action — a command or a decision needed}
60
+ 2. ...
39
61
  ```
40
62
 
41
- ### 2. Synthesize
63
+ **If `COUNT == 0`** — ask the employee gracefully (don't force a fake report):
64
+
65
+ Use `AskUserQuestion`:
66
+ - header: "Empty day?"
67
+ - question: "No commits in the last 8 hours. What did you do today?"
68
+ - options:
69
+ - "Investigation / research only"
70
+ - "Meetings / calls (no code)"
71
+ - "Blocked — tell me on what"
72
+ - "Time off / partial day"
42
73
 
43
- Build a concise summary:
44
- - **What was done:** 3-6 bullet points. Start with verbs (Built, Fixed, Added). Group related commits.
45
- - **Blockers:** Only if something is actually blocked.
46
- - **Next:** 1-3 clear next actions.
74
+ Capture the answer as the report body. Empty days are still valid clock-outs — the ERP needs to see them.
47
75
 
48
- ### 3. Generate Report
76
+ ### Step 3 Write report file
49
77
 
50
- Write to `.planning/reports/report-{YYYY-MM-DD}.md`:
78
+ `.planning/reports/report-{YYYY-MM-DD}.md`:
51
79
 
52
80
  ```markdown
53
81
  # Session Report — {YYYY-MM-DD}
54
82
 
55
- **Project:** {name}
83
+ **Project:** {PROJECT}
56
84
  **Employee:** {git user.name}
57
- **Branch:** {branch}
58
- **Phase:** {N}{name} ({status})
85
+ **Branch:** {BRANCH}
86
+ **Phase:** {N — name (status), or "no active phase"}
59
87
  **Date:** {YYYY-MM-DD}
60
88
 
61
- ## What Was Done
62
- - {accomplishment 1}
63
- - {accomplishment 2}
64
- - {accomplishment 3}
65
-
66
- ## Blockers
67
- None. / - {blocker}
68
-
69
- ## Next Steps
70
- 1. {next action}
71
- 2. {next action}
89
+ {synthesis from Step 2}
72
90
 
73
- ## Commits
74
- {list from git log}
91
+ ## Commits ({COUNT})
92
+ {git log output, or "none today"}
75
93
  ```
76
94
 
77
- ### 4. Obtain Client Report ID (QS-REPORT-NN)
95
+ ### Step 4 Allocate client report ID
78
96
 
79
- Each session report gets a stable, sequential client-side identifier that travels with the report all the way to the ERP. The sequence is per-project, persisted in `tracking.json.report_seq`.
97
+ Per-project sequential ID (QS-REPORT-01, 02, ...). State.js owns the counter never edit by hand.
80
98
 
81
99
  ```bash
82
- # --dry-run: peek without incrementing
83
- # Wrap the pipe in try/catch so a state.js failure (missing tracking.json,
84
- # corrupt JSON) produces a clear error instead of silently becoming "".
85
- PEEK_FLAG=""
86
- [ "$DRY_RUN" = "true" ] && PEEK_FLAG="--peek"
87
- CLIENT_REPORT_ID=$(node ~/.claude/bin/state.js next-report-id $PEEK_FLAG 2>/dev/null | node -e "
88
- try {
89
- const raw = require('fs').readFileSync(0,'utf8');
90
- if (!raw.trim()) process.exit(2);
91
- const j = JSON.parse(raw);
92
- if (!j.report_id) process.exit(3);
93
- process.stdout.write(j.report_id);
94
- } catch (e) { process.exit(1); }
95
- ")
100
+ PEEK=""
101
+ [ "$DRY_RUN" = "true" ] && PEEK="--peek"
102
+ CLIENT_REPORT_ID=$(node ~/.claude/bin/state.js next-report-id $PEEK 2>/dev/null \
103
+ | node -e "try{const j=JSON.parse(require('fs').readFileSync(0,'utf8'));process.stdout.write(j.report_id||'')}catch{}")
96
104
 
97
105
  if [ -z "$CLIENT_REPORT_ID" ]; then
98
- node ~/.claude/bin/qualia-ui.js fail "Could not obtain report ID from state.js is .planning/tracking.json valid?"
106
+ node ~/.claude/bin/qualia-ui.js fail "Could not allocate report ID. Try: cat .planning/tracking.json (is it valid JSON?)"
99
107
  exit 1
100
108
  fi
101
109
  ```
102
110
 
103
- Example: first report on a fresh project `QS-REPORT-01`. Next → `QS-REPORT-02`. Etc.
104
-
105
- ### 5. Commit and Push (SKIP on --dry-run)
111
+ ### Step 5 Commit + push (skip on --dry-run)
106
112
 
107
113
  ```bash
108
- if [ "$DRY_RUN" != "true" ]; then
114
+ if [ "$DRY_RUN" != "true" ] && [ -d .git ]; then
109
115
  mkdir -p .planning/reports
110
116
  git add .planning/reports/report-{date}.md .planning/tracking.json
111
- git commit -m "report: {CLIENT_REPORT_ID} session {YYYY-MM-DD}"
112
- git push
117
+ git commit -m "report: $CLIENT_REPORT_ID session {YYYY-MM-DD}" || node ~/.claude/bin/qualia-ui.js warn "Nothing to commit (clean tree?)"
118
+ git push 2>&1 | tail -3
119
+ PUSH_EXIT=${PIPESTATUS[0]}
120
+ [ "$PUSH_EXIT" != "0" ] && node ~/.claude/bin/qualia-ui.js warn "git push failed — report committed locally but not pushed. ERP upload will still try."
113
121
  fi
114
122
  ```
115
123
 
116
- ### 6. Upload to ERP (SKIP on --dry-run)
124
+ ### Step 6 Upload to ERP
125
+
126
+ The full payload-builder + 3-attempt-retry logic lives unchanged from v4 — see the **ERP Upload** section below for the canonical implementation. Behavior summary:
127
+ - ERP disabled in config → skip silently, note local commit
128
+ - API key missing → warn with self-service fix instructions, skip upload
129
+ - 401/422 → permanent failure, no retry, tell employee to ask Fawzi
130
+ - Transient (timeout/5xx) → 3 attempts with 1s/3s/9s backoff
131
+ - Success → "Uploaded as $CLIENT_REPORT_ID (ERP: {uuid})"
117
132
 
118
- Read `~/.claude/.qualia-config.json` and check the `erp` object:
119
- - If `erp.enabled` is `false`, skip this step and print: "ERP upload skipped (disabled in config)."
120
- - If `erp.enabled` is `true` (default) or the `erp` field is missing (backward compatibility), proceed with the upload.
133
+ ### Step 7 State + closing
134
+
135
+ ```bash
136
+ if [ "$DRY_RUN" != "true" ]; then
137
+ node ~/.claude/bin/state.js transition --to activity --notes "Session report $CLIENT_REPORT_ID generated" 2>/dev/null
138
+ fi
139
+
140
+ node ~/.claude/bin/qualia-ui.js divider
141
+ node ~/.claude/bin/qualia-ui.js ok "Report $CLIENT_REPORT_ID complete."
142
+ node ~/.claude/bin/qualia-ui.js info "You can clock out now. See you tomorrow."
143
+ ```
144
+
145
+ ## Common errors (read this when something goes wrong)
146
+
147
+ | Symptom | Likely cause | Self-service fix |
148
+ |---|---|---|
149
+ | "Could not allocate report ID" | tracking.json missing/corrupt | `cat .planning/tracking.json` to inspect, or restore from `git checkout HEAD -- .planning/tracking.json` |
150
+ | "ERP API key missing" | `~/.claude/.erp-api-key` empty | `qualia-framework set-erp-key <key>` (ask Fawzi for the key) |
151
+ | "ERP auth failed (401)" | Key revoked or wrong | Ask Fawzi for a fresh key |
152
+ | "ERP upload failed after 3 attempts" | ERP down or network issue | Local commit is safe. Re-run `/qualia-report` later. |
153
+ | "git push failed" | Auth or network or upstream issue | `git push` manually, see the error, fix, re-run |
154
+
155
+ ## ERP Upload (canonical implementation)
156
+
157
+ The Step 6 payload + retry logic. Inlined to avoid a script-file fetch on every clock-out. The agent runs this verbatim.
121
158
 
122
159
  ```bash
123
160
  # Read ERP config
@@ -129,151 +166,75 @@ REPORT_FILE=".planning/reports/report-{date}.md"
129
166
  SUBMITTED_BY=$(git config user.name || echo "unknown")
130
167
  SUBMITTED_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)
131
168
 
132
- # Guard: ERP upload requires a non-empty API key. Without this check, curl
133
- # would POST with "Authorization: Bearer " (blank bearer) and the server
134
- # returns a generic 401 that is hard to diagnose.
169
+ # Guard: API key required for upload (otherwise curl posts an empty bearer)
135
170
  if [ "$ERP_ENABLED" = "true" ] && [ -z "$API_KEY" ] && [ "$DRY_RUN" != "true" ]; then
136
- node ~/.claude/bin/qualia-ui.js warn "ERP API key missing (~/.claude/.erp-api-key is empty or unreadable). Skipping upload."
137
- node ~/.claude/bin/qualia-ui.js info "Ask Fawzi for the ERP key, run 'qualia-framework set-erp-key <key>', then run 'qualia-framework erp-ping'."
171
+ node ~/.claude/bin/qualia-ui.js warn "ERP API key missing (~/.claude/.erp-api-key). Run: qualia-framework set-erp-key <key>"
138
172
  ERP_ENABLED="false"
139
173
  fi
140
174
 
141
- # Build structured JSON payload from tracking.json (matches ERP contract /api/v1/reports)
142
- # v4: include milestone_name, milestones[], team_id, project_id, git_remote,
143
- # session_started_at, last_pushed_at, build_count, deploy_count — the ERP
144
- # uses these to render the project tree (milestone → phases → unphased) correctly.
145
- # v4.0.4: client_report_id carries the QS-REPORT-NN identifier.
146
- # Build payload. Pass user-controlled values (SUBMITTED_BY, CLIENT_REPORT_ID,
147
- # SUBMITTED_AT, REPORT_FILE) via env vars instead of shell interpolation — a
148
- # single quote or backslash in git user.name would otherwise break the node -e
149
- # script silently. process.env.* is inert to shell metacharacters.
175
+ # Build payload (env-var-passed user values to dodge shell escaping)
150
176
  PAYLOAD=$(
151
- SUBMITTED_BY="$SUBMITTED_BY" \
152
- SUBMITTED_AT="$SUBMITTED_AT" \
153
- CLIENT_REPORT_ID="$CLIENT_REPORT_ID" \
154
- REPORT_FILE="$REPORT_FILE" \
177
+ SUBMITTED_BY="$SUBMITTED_BY" SUBMITTED_AT="$SUBMITTED_AT" \
178
+ CLIENT_REPORT_ID="$CLIENT_REPORT_ID" REPORT_FILE="$REPORT_FILE" \
155
179
  node -e "
156
- const fs = require('fs');
157
- const t = JSON.parse(fs.readFileSync('.planning/tracking.json', 'utf8'));
158
- const notes = fs.readFileSync(process.env.REPORT_FILE, 'utf8').substring(0, 60000);
159
- const commits = [];
160
- try {
161
- const { spawnSync } = require('child_process');
162
- const r = spawnSync('git', ['log', '--oneline', '--since=8 hours ago', '--format=%h'], { encoding: 'utf8', timeout: 3000 });
163
- if (r.stdout) commits.push(...r.stdout.trim().split('\n').filter(Boolean));
164
- } catch {}
180
+ const fs=require('fs'),path=require('path'),os=require('os');
181
+ const {spawnSync}=require('child_process');
182
+ const git=(a)=>{const r=spawnSync('git',a,{encoding:'utf8',timeout:3000});return r.status===0?r.stdout.trim():'';};
183
+ const repoSlug=(r)=>(r||'').replace(/^git@github\\.com:/,'github.com/').replace(/^https?:\\/\\//,'').replace(/\\.git$/,'').split('/').filter(Boolean).pop();
184
+ let config={};try{config=JSON.parse(fs.readFileSync(path.join(os.homedir(),'.claude/.qualia-config.json'),'utf8'));}catch{}
185
+ const t=JSON.parse(fs.readFileSync('.planning/tracking.json','utf8'));
186
+ const notes=fs.readFileSync(process.env.REPORT_FILE,'utf8').substring(0,60000);
187
+ const commits=[];try{const r=spawnSync('git',['log','--oneline','--since=8 hours ago','--format=%h'],{encoding:'utf8',timeout:3000});if(r.stdout)commits.push(...r.stdout.trim().split('\n').filter(Boolean));}catch{}
188
+ const gitRemote=t.git_remote||git(['config','--get','remote.origin.url']);
189
+ const projectKey=t.project_id||repoSlug(gitRemote)||require('path').basename(process.cwd());
165
190
  console.log(JSON.stringify({
166
- project: t.project || require('path').basename(process.cwd()),
167
- project_id: t.project_id || '',
168
- team_id: t.team_id || '',
169
- git_remote: t.git_remote || '',
170
- client: t.client || '',
171
- client_report_id: process.env.CLIENT_REPORT_ID,
172
- milestone: t.milestone || 1,
173
- milestone_name: t.milestone_name || '',
174
- milestones: Array.isArray(t.milestones) ? t.milestones : [],
175
- phase: t.phase,
176
- phase_name: t.phase_name,
177
- total_phases: t.total_phases,
178
- status: t.status,
179
- tasks_done: t.tasks_done || 0,
180
- tasks_total: t.tasks_total || 0,
181
- verification: t.verification || 'pending',
182
- gap_cycles: (t.gap_cycles || {})[String(t.phase)] || 0,
183
- build_count: t.build_count || 0,
184
- deploy_count: t.deploy_count || 0,
185
- deployed_url: t.deployed_url || '',
186
- session_started_at: t.session_started_at || '',
187
- last_pushed_at: t.last_pushed_at || '',
188
- lifetime: t.lifetime || {},
189
- commits: commits,
190
- notes: notes,
191
- submitted_by: process.env.SUBMITTED_BY || 'unknown',
192
- submitted_at: process.env.SUBMITTED_AT
191
+ project:t.project||require('path').basename(process.cwd()),
192
+ project_id:projectKey,team_id:t.team_id||'qualia-solutions',git_remote:gitRemote,
193
+ client:t.client||'',client_report_id:process.env.CLIENT_REPORT_ID,
194
+ framework_version:config.version||'',milestone:t.milestone||1,
195
+ milestone_name:t.milestone_name||'',milestones:Array.isArray(t.milestones)?t.milestones:[],
196
+ phase:t.phase,phase_name:t.phase_name,total_phases:t.total_phases,status:t.status,
197
+ tasks_done:t.tasks_done||0,tasks_total:t.tasks_total||0,verification:t.verification||'pending',
198
+ gap_cycles:(t.gap_cycles||{})[String(t.phase)]||0,build_count:t.build_count||0,
199
+ deploy_count:t.deploy_count||0,deployed_url:t.deployed_url||'',
200
+ session_started_at:t.session_started_at||'',last_pushed_at:t.last_pushed_at||'',
201
+ lifetime:t.lifetime||{},commits:commits,notes:notes,
202
+ submitted_by:process.env.SUBMITTED_BY||'unknown',submitted_at:process.env.SUBMITTED_AT
193
203
  }));
194
204
  "
195
205
  )
196
206
 
197
- # --dry-run: print payload and stop (no POST, no commit, no increment already handled in step 4)
207
+ # --dry-run: print and stop
198
208
  if [ "$DRY_RUN" = "true" ]; then
199
- echo "--- DRY RUN · payload ---"
200
- echo "$PAYLOAD" | node -e "const d=JSON.parse(require('fs').readFileSync(0,'utf8'));console.log(JSON.stringify(d,null,2))"
209
+ echo "--- DRY RUN · payload ---"; echo "$PAYLOAD" | node -e "console.log(JSON.stringify(JSON.parse(require('fs').readFileSync(0,'utf8')),null,2))"
201
210
  echo "--- DRY RUN · would POST to: $ERP_URL/api/v1/reports ---"
202
211
  echo "--- DRY RUN · client_report_id would be: $CLIENT_REPORT_ID ---"
203
212
  exit 0
204
213
  fi
205
214
 
206
- # Real upload — 3 attempts with exponential backoff (1s, 3s, 9s).
207
- # The local report file is already committed, so a failed upload doesn't
208
- # lose data — it just leaves the ERP view stale until the next push or
209
- # manual retry.
215
+ # Upload — 3 attempts with 1s/3s/9s backoff
210
216
  if [ "$ERP_ENABLED" = "true" ]; then
211
- MAX_ATTEMPTS=3
212
- ATTEMPT=1
213
- SUCCESS=false
214
- while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do
217
+ for ATTEMPT in 1 2 3; do
215
218
  RESPONSE=$(curl -sS -X POST "$ERP_URL/api/v1/reports" \
216
- -H "Authorization: Bearer $API_KEY" \
217
- -H "Content-Type: application/json" \
218
- -d "$PAYLOAD" \
219
- --max-time 10 \
220
- -w "\n__HTTP__%{http_code}" 2>&1)
219
+ -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
220
+ -d "$PAYLOAD" --max-time 10 -w "\n__HTTP__%{http_code}" 2>&1)
221
221
  HTTP_CODE=$(echo "$RESPONSE" | grep -o "__HTTP__[0-9]*" | sed 's/__HTTP__//')
222
222
  BODY=$(echo "$RESPONSE" | sed 's/__HTTP__[0-9]*//g')
223
223
 
224
224
  if [ "$HTTP_CODE" = "200" ]; then
225
- SUCCESS=true
226
- # Parse and display the ERP-returned report_id alongside our local QS-REPORT-NN
227
- ERP_REPORT_ID=$(echo "$BODY" | node -e "try{const d=JSON.parse(require('fs').readFileSync(0,'utf8'));process.stdout.write(d.report_id||'')}catch{}")
225
+ ERP_REPORT_ID=$(echo "$BODY" | node -e "try{process.stdout.write(JSON.parse(require('fs').readFileSync(0,'utf8')).report_id||'')}catch{}")
228
226
  node ~/.claude/bin/qualia-ui.js ok "Uploaded as $CLIENT_REPORT_ID (ERP: ${ERP_REPORT_ID:-none})"
229
227
  break
230
228
  fi
231
-
232
- # 401 / 422 are permanent failures — no retry.
233
229
  if [ "$HTTP_CODE" = "401" ] || [ "$HTTP_CODE" = "422" ]; then
234
- if [ "$HTTP_CODE" = "401" ]; then
235
- node ~/.claude/bin/qualia-ui.js warn "ERP auth failed (HTTP 401) API key in ~/.claude/.erp-api-key is invalid or revoked. Ask Fawzi for a fresh key."
236
- else
237
- node ~/.claude/bin/qualia-ui.js warn "ERP rejected payload (HTTP 422) — schema validation failed. Response body:"
238
- fi
239
- echo "$BODY" | head -3
230
+ node ~/.claude/bin/qualia-ui.js warn "ERP rejected ($HTTP_CODE) — $([ "$HTTP_CODE" = "401" ] && echo "API key invalid, ask Fawzi" || echo "schema mismatch:")"
231
+ [ "$HTTP_CODE" = "422" ] && echo "$BODY" | head -3
240
232
  break
241
233
  fi
242
-
243
- # Transient failure — back off and retry.
244
- if [ $ATTEMPT -lt $MAX_ATTEMPTS ]; then
245
- SLEEP=$(( 1 * 3 ** (ATTEMPT - 1) ))
246
- node ~/.claude/bin/qualia-ui.js warn "ERP upload attempt $ATTEMPT failed (HTTP ${HTTP_CODE:-timeout}), retrying in ${SLEEP}s..."
247
- sleep $SLEEP
248
- fi
249
- ATTEMPT=$(( ATTEMPT + 1 ))
234
+ [ $ATTEMPT -lt 3 ] && { SLEEP=$((1 * 3 ** (ATTEMPT - 1))); node ~/.claude/bin/qualia-ui.js warn "Attempt $ATTEMPT failed (HTTP ${HTTP_CODE:-timeout}), retrying in ${SLEEP}s..."; sleep $SLEEP; }
250
235
  done
251
-
252
- if [ "$SUCCESS" != "true" ]; then
253
- node ~/.claude/bin/qualia-ui.js warn "ERP upload failed after $MAX_ATTEMPTS attempts. $CLIENT_REPORT_ID is committed locally; it will NOT appear in the ERP until you retry with 'curl' or re-run /qualia-report."
254
- fi
236
+ [ "$ATTEMPT" = "3" ] && [ "$HTTP_CODE" != "200" ] && node ~/.claude/bin/qualia-ui.js warn "ERP upload failed after 3 attempts. $CLIENT_REPORT_ID is committed locally; will appear in ERP after retry."
255
237
  fi
256
238
 
257
- if [ "$ERP_ENABLED" != "true" ]; then
258
- node ~/.claude/bin/qualia-ui.js info "ERP upload skipped (disabled in config). Report committed locally as $CLIENT_REPORT_ID."
259
- fi
239
+ [ "$ERP_ENABLED" != "true" ] && node ~/.claude/bin/qualia-ui.js info "ERP upload skipped (disabled). $CLIENT_REPORT_ID committed locally."
260
240
  ```
261
-
262
- Summary rules:
263
- - **Upload succeeds:** print "Uploaded as QS-REPORT-NN (ERP: {uuid})". Employee can clock out.
264
- - **401/422:** no retry. Print the error, tell the employee to ask Fawzi.
265
- - **Transient (timeout, 5xx, network):** retry 3x with 1s/3s/9s backoff.
266
- - **All retries fail:** tell employee the report is committed locally, ERP will be stale until retry.
267
- - **ERP disabled:** skip silently with a note, local commit still happens.
268
-
269
- ### 7. Update State (SKIP on --dry-run)
270
-
271
- ```bash
272
- if [ "$DRY_RUN" != "true" ]; then
273
- node ~/.claude/bin/state.js transition --to activity --notes "Session report $CLIENT_REPORT_ID generated"
274
- fi
275
- ```
276
-
277
- Do NOT manually edit STATE.md or tracking.json — state.js handles both.
278
-
279
- Employee cannot skip this. Run `/qualia-report` before clock-out.
@@ -12,14 +12,14 @@ allowed-tools:
12
12
 
13
13
  # /qualia-research — Per-Phase Deep Research
14
14
 
15
- Runs targeted research on a domain, library, or integration that a specific phase depends on. Distinct from `/qualia-new` research (which covers 4 dimensions project-wide) — this one is narrow and phase-scoped.
15
+ Targeted research on domain, library, or integration for a specific phase. Narrow and phase-scoped (distinct from `/qualia-new` project-wide research).
16
16
 
17
17
  ## When to Use
18
18
 
19
- - A phase touches a library you've never used
20
- - A phase integrates with a niche API (FHIR, legal forms, payment gateways)
21
- - SUMMARY.md marked this phase as a "Research flag"
22
- - You're about to plan and realize you don't know the current best practice
19
+ - Phase touches unfamiliar library
20
+ - Phase integrates niche API (FHIR, legal forms, payment gateways)
21
+ - SUMMARY.md flagged phase for research
22
+ - Unsure of current best practice before planning
23
23
 
24
24
  ## Usage
25
25
 
@@ -33,7 +33,7 @@ Runs targeted research on a domain, library, or integration that a specific phas
33
33
  node ~/.claude/bin/state.js check 2>/dev/null
34
34
  ```
35
35
 
36
- Use phase N from args, or current phase from STATE.md.
36
+ Phase N from args, or current phase from STATE.md.
37
37
 
38
38
  ### 2. Load Context
39
39
 
@@ -43,21 +43,19 @@ cat .planning/ROADMAP.md 2>/dev/null
43
43
  cat .planning/phase-{N}-context.md 2>/dev/null # if /qualia-discuss was run first
44
44
  ```
45
45
 
46
- Identify what this phase needs to know.
46
+ Identify what phase needs to know.
47
47
 
48
- ### 3. Ask the User What to Research
48
+ ### 3. Ask User
49
49
 
50
- Inline free text:
50
+ **"Researching Phase {N}: {phase name}. What to dig into? Library, domain, integration, pattern?"**
51
51
 
52
- **"I'm about to research Phase {N}: {phase name}. What specifically do you want me to dig into? Library, domain, integration, pattern?"**
52
+ Wait for answer. Answer defines research question.
53
53
 
54
- Wait for their answer. Their answer defines the research question.
55
-
56
- ### 4. Spawn the Researcher
54
+ ### 4. Spawn Researcher
57
55
 
58
56
  ```
59
57
  Agent(prompt="
60
- Read your role: @~/.claude/agents/researcher.md
58
+ Role: @~/.claude/agents/researcher.md
61
59
 
62
60
  <dimension>phase-specific</dimension>
63
61
 
@@ -67,27 +65,24 @@ Read your role: @~/.claude/agents/researcher.md
67
65
 
68
66
  <phase_context>
69
67
  Phase: {N}
70
- Goal: {phase goal from ROADMAP.md}
71
- Requirements: {REQ-IDs covered by this phase}
68
+ Goal: {goal from ROADMAP.md}
69
+ Reqs: {REQ-IDs for this phase}
72
70
  </phase_context>
73
71
 
74
72
  <project_context>
75
73
  {PROJECT.md summary}
76
74
  </project_context>
77
75
 
78
- <output_path>
79
- .planning/phase-{N}-research.md
80
- </output_path>
76
+ <output_path>.planning/phase-{N}-research.md</output_path>
81
77
 
82
- Research using Context7 first, then WebFetch, then WebSearch. Be specific and concrete.
83
- Include: recommendation, rationale, version numbers (if applicable), code examples,
84
- alternatives considered, what to avoid, sources.
78
+ Priority: Context7 WebFetch WebSearch.
79
+ Include: recommendation, rationale, versions, code examples, alternatives, pitfalls, sources.
85
80
  ", subagent_type="qualia-researcher", description="Phase {N} research")
86
81
  ```
87
82
 
88
- ### 5. Review Output
83
+ ### 5. Review
89
84
 
90
- Read `.planning/phase-{N}-research.md`. Present the key findings:
85
+ Read `.planning/phase-{N}-research.md`. Present findings:
91
86
 
92
87
  ```bash
93
88
  node ~/.claude/bin/qualia-ui.js divider
@@ -100,15 +95,15 @@ Show:
100
95
  - Top 3 key findings
101
96
  - Sources used
102
97
 
103
- ### 6. User Confirms or Asks More
98
+ ### 6. Confirm or Continue
104
99
 
105
100
  - header: "Enough?"
106
- - question: "Is this enough research, or should I dig deeper?"
101
+ - question: "Enough research, or dig deeper?"
107
102
  - options:
108
- - "Enough" — Move to planning
109
- - "Dig deeper" — I have more questions
103
+ - "Enough"
104
+ - "Dig deeper"
110
105
 
111
- If "Dig deeper" ask what they want, re-spawn the researcher with additional questions.
106
+ "Dig deeper" ask what, re-spawn researcher with additional questions.
112
107
 
113
108
  ### 7. Commit
114
109
 
@@ -125,7 +120,7 @@ node ~/.claude/bin/qualia-ui.js end "PHASE {N} RESEARCH DONE" "/qualia-plan {N}"
125
120
 
126
121
  ## Rules
127
122
 
128
- 1. **One research session per run.** Don't try to research phases 1 through 5 in one call.
129
- 2. **Must produce a file.** The research is worthless if it only lives in conversation context.
130
- 3. **Honor locked decisions from phase-{N}-context.md.** Don't research alternatives to something already locked.
131
- 4. **Context7 first.** Always try Context7 MCP before WebFetch — it's fastest and most current for known libraries.
123
+ 1. **One session per run.** Don't research phases 1-5 in one call.
124
+ 2. **Must produce a file.** Research in conversation only is worthless.
125
+ 3. **Honor locked decisions.** Don't research alternatives to locked choices.
126
+ 4. **Context7 first.** Try Context7 MCP before WebFetch.
@@ -0,0 +1,103 @@
1
+ ---
2
+ name: qualia-road
3
+ description: "Show the Qualia workflow map in the terminal — Project → Journey → Milestones → Phases → Tasks. Lists every command, when to use it, and how phases chain. Use when user asks 'how does Qualia work', 'what's the workflow', 'show me the road', 'what command does X', 'how do projects flow', or is new to the framework. (For an interactive HTML reference instead, use /qualia-help.)"
4
+ disable-model-invocation: true
5
+ allowed-tools:
6
+ - Read
7
+ ---
8
+
9
+ # The Qualia Road — Project Workflow Map
10
+
11
+ **Hierarchy:** Project → Journey → Milestones (2–5, Handoff always last) → Phases (2–5 tasks each) → Tasks (one commit, one verification contract).
12
+
13
+ ```
14
+ /qualia-new → kickoff + parallel research + JOURNEY.md (all milestones upfront)
15
+ add --auto to chain the whole road end-to-end
16
+
17
+ For each milestone, for each phase:
18
+ /qualia-plan → plan the phase (planner + plan-checker revision loop, fresh context)
19
+ /qualia-build → build it (builder subagents per task, wave-based parallel)
20
+ /qualia-verify → goal-backward check (verifier agent, fresh context)
21
+
22
+ /qualia-milestone → close milestone, archive artifacts, prep next (human gate)
23
+ ↓ (repeat for each milestone until Handoff)
24
+
25
+ Final milestone = Handoff:
26
+ /qualia-polish → final design pass (whole app)
27
+ (content + SEO) → Phase 2
28
+ (final QA) → Phase 3
29
+ /qualia-ship → deploy to production (quality gates → deploy → verify)
30
+ /qualia-handoff → 4 deliverables: credentials, doc, final update, report
31
+
32
+ Done.
33
+ ```
34
+
35
+ ## Design as a thread (v4.5.0+)
36
+ Every road agent loads `PRODUCT.md + DESIGN.md + design-laws.md` substrate. Builders run `slop-detect` on every frontend commit. Verifiers score 8 design dimensions per phase.
37
+
38
+ ## /qualia-polish is scope-adaptive
39
+ ```
40
+ /qualia-polish src/components/Button.tsx ~30s component touch-up
41
+ /qualia-polish app/dashboard ~3m section pass
42
+ /qualia-polish ~12m whole app, fan-out
43
+ /qualia-polish --redesign ~30m ground-up redesign
44
+ /qualia-polish --critique read-only scored audit
45
+ /qualia-polish --quick ~1m gates only
46
+ ```
47
+
48
+ ## /qualia-polish-loop -- autonomous visual QA (v5.1+)
49
+ ```
50
+ /qualia-polish-loop http://localhost:3000 screenshot + eval + fix loop
51
+ /qualia-polish-loop {url} --max 4 cap iterations
52
+ /qualia-polish-loop {url} --ref design.png anchor to reference image
53
+ ```
54
+ Screenshots at 3 viewports (375/768/1440), scores 8 design dimensions using vision, fixes issues, re-screenshots, loops until all dims >= 3 or kill-switch triggers. Per-iteration git commits for clean revert.
55
+
56
+ ## Alignment substrate (v5.0+)
57
+ Before high-stakes phases, run alignment skills against `.planning/CONTEXT.md` (domain glossary) and `.planning/decisions/` (ADRs):
58
+
59
+ ```
60
+ /qualia-discuss → relentless one-question interview, updates CONTEXT.md inline
61
+ /qualia-zoom → map an unfamiliar code area using glossary terms
62
+ /qualia-optimize --deepen → find shallow modules, propose Ousterhout-style refactors
63
+ /qualia-test --tdd → vertical-slice red→green→refactor for one feature
64
+ /qualia-issues → break a phase plan into independent GH issues
65
+ /qualia-triage → label + route open issues (ready-for-agent vs human)
66
+ /qualia-map → adapt Qualia to an existing brownfield repo's conventions (5th onboarding agent)
67
+ ```
68
+
69
+ ## Auxiliary commands
70
+ ```
71
+ Lost? → /qualia (state router — tells you the next command)
72
+ Stuck/weird? → /qualia-idk (diagnostic — spawns plan-view + code-view agents in parallel)
73
+ Quick fix? → /qualia-quick (skip planning for small tasks)
74
+ Paused? → /qualia-resume (restore from .continue-here.md or STATE.md)
75
+ End of day? → /qualia-report (mandatory before clock-out; writes ERP payload)
76
+ Debug bug? → /qualia-debug (feedback-loop-first investigation)
77
+ Unsure plan? → /qualia-discuss (capture decisions before planning)
78
+ ```
79
+
80
+ ## Human gates
81
+ Journey approval after `/qualia-new`, then one at each milestone boundary via `/qualia-milestone`. `--auto` runs everything between gates automatically.
82
+
83
+ ## Context isolation
84
+ Every task runs in a fresh subagent context. Task 50 gets the same quality as Task 1.
85
+ - Planner gets: PROJECT.md + CONTEXT.md + phase requirements
86
+ - Builder gets: single task from plan + PROJECT.md + CONTEXT.md
87
+ - Verifier gets: success criteria + codebase access
88
+
89
+ No accumulated garbage. No context rot.
90
+
91
+ ## Quality gates (always active via hooks)
92
+ - **Frontend guard** — Read `.planning/DESIGN.md` before any frontend changes
93
+ - **Deploy guard** — tsc + lint + build + tests must pass before deploy
94
+ - **Migration guard** — Catches dangerous SQL (DROP without IF EXISTS, DELETE without WHERE, CREATE TABLE without RLS)
95
+ - **Slop-detect** — Em-dash and AI-tells check on every UI commit
96
+ - **Intent verification** — Confirm before modifying 3+ files (OWNER role: just do it)
97
+
98
+ ## Tracking
99
+ `.planning/tracking.json` is updated on every push. The ERP reads it via git.
100
+ Never edit tracking.json manually — hooks update it from STATE.md.
101
+
102
+ ## Compaction — ALWAYS preserve
103
+ Project path/name, branch, current phase, modified files, decisions, test results, in-progress work, errors, tracking.json state.