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.
- package/AGENTS.md +24 -0
- package/CLAUDE.md +12 -63
- package/README.md +24 -18
- package/agents/builder.md +13 -33
- package/agents/plan-checker.md +18 -0
- package/agents/planner.md +17 -0
- package/agents/verifier.md +70 -0
- package/agents/visual-evaluator.md +132 -0
- package/bin/cli.js +64 -23
- package/bin/install.js +375 -29
- package/bin/qualia-ui.js +208 -1
- package/bin/slop-detect.mjs +362 -0
- package/bin/state.js +218 -2
- package/docs/erp-contract.md +5 -0
- package/docs/install-redesign-builder-prompt.md +290 -0
- package/docs/install-redesign-pilot.md +234 -0
- package/docs/playwright-loop-builder-prompt.md +185 -0
- package/docs/playwright-loop-design-notes.md +108 -0
- package/docs/playwright-loop-pilot-results.md +170 -0
- package/docs/playwright-loop-review-2026-05-03.md +65 -0
- package/docs/playwright-loop-tester-prompt.md +213 -0
- package/docs/reviews/matt-pocock-skills-analysis.md +300 -0
- package/guide.md +9 -5
- package/hooks/env-empty-guard.js +74 -0
- package/hooks/pre-compact.js +19 -9
- package/hooks/pre-deploy-gate.js +8 -2
- package/hooks/pre-push.js +26 -12
- package/hooks/supabase-destructive-guard.js +62 -0
- package/hooks/vercel-account-guard.js +91 -0
- package/package.json +2 -1
- package/rules/design-brand.md +114 -0
- package/rules/design-laws.md +148 -0
- package/rules/design-product.md +114 -0
- package/rules/design-rubric.md +157 -0
- package/rules/grounding.md +4 -0
- package/skills/qualia-build/SKILL.md +40 -46
- package/skills/qualia-discuss/SKILL.md +51 -68
- package/skills/qualia-handoff/SKILL.md +1 -0
- package/skills/qualia-issues/SKILL.md +151 -0
- package/skills/qualia-map/SKILL.md +78 -35
- package/skills/qualia-new/REFERENCE.md +139 -0
- package/skills/qualia-new/SKILL.md +85 -124
- package/skills/qualia-optimize/REFERENCE.md +202 -0
- package/skills/qualia-optimize/SKILL.md +72 -237
- package/skills/qualia-plan/SKILL.md +58 -65
- package/skills/qualia-polish/SKILL.md +180 -136
- package/skills/qualia-polish-loop/REFERENCE.md +265 -0
- package/skills/qualia-polish-loop/SKILL.md +201 -0
- package/skills/qualia-polish-loop/fixtures/broken.html +117 -0
- package/skills/qualia-polish-loop/fixtures/clean.html +196 -0
- package/skills/qualia-polish-loop/scripts/loop.mjs +302 -0
- package/skills/qualia-polish-loop/scripts/playwright-capture.mjs +197 -0
- package/skills/qualia-polish-loop/scripts/score.mjs +176 -0
- package/skills/qualia-report/SKILL.md +141 -180
- package/skills/qualia-research/SKILL.md +28 -33
- package/skills/qualia-road/SKILL.md +103 -0
- package/skills/qualia-ship/SKILL.md +1 -0
- package/skills/qualia-task/SKILL.md +1 -1
- package/skills/qualia-test/SKILL.md +50 -2
- package/skills/qualia-triage/SKILL.md +152 -0
- package/skills/qualia-verify/SKILL.md +63 -104
- package/skills/qualia-zoom/SKILL.md +51 -0
- package/skills/zoho-workflow/SKILL.md +64 -0
- package/templates/CONTEXT.md +36 -0
- package/templates/DESIGN.md +229 -435
- package/templates/PRODUCT.md +95 -0
- package/templates/decisions/ADR-template.md +30 -0
- package/tests/bin.test.sh +451 -7
- package/tests/state.test.sh +58 -0
- 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
|
|
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 —
|
|
12
|
+
# /qualia-report — Daily Clock-Out Report
|
|
12
13
|
|
|
13
|
-
|
|
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` —
|
|
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
|
|
35
|
+
### Step 1 — Gather
|
|
27
36
|
|
|
28
37
|
```bash
|
|
29
38
|
SINCE="8 hours ago"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
echo "
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
76
|
+
### Step 3 — Write report file
|
|
49
77
|
|
|
50
|
-
|
|
78
|
+
`.planning/reports/report-{YYYY-MM-DD}.md`:
|
|
51
79
|
|
|
52
80
|
```markdown
|
|
53
81
|
# Session Report — {YYYY-MM-DD}
|
|
54
82
|
|
|
55
|
-
**Project:** {
|
|
83
|
+
**Project:** {PROJECT}
|
|
56
84
|
**Employee:** {git user.name}
|
|
57
|
-
**Branch:** {
|
|
58
|
-
**Phase:** {N
|
|
85
|
+
**Branch:** {BRANCH}
|
|
86
|
+
**Phase:** {N — name (status), or "no active phase"}
|
|
59
87
|
**Date:** {YYYY-MM-DD}
|
|
60
88
|
|
|
61
|
-
|
|
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
|
-
{
|
|
91
|
+
## Commits ({COUNT})
|
|
92
|
+
{git log output, or "none today"}
|
|
75
93
|
```
|
|
76
94
|
|
|
77
|
-
### 4
|
|
95
|
+
### Step 4 — Allocate client report ID
|
|
78
96
|
|
|
79
|
-
|
|
97
|
+
Per-project sequential ID (QS-REPORT-01, 02, ...). State.js owns the counter — never edit by hand.
|
|
80
98
|
|
|
81
99
|
```bash
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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:
|
|
167
|
-
project_id:
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
235
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
20
|
-
-
|
|
21
|
-
- SUMMARY.md
|
|
22
|
-
-
|
|
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
|
-
|
|
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
|
|
46
|
+
Identify what phase needs to know.
|
|
47
47
|
|
|
48
|
-
### 3. Ask
|
|
48
|
+
### 3. Ask User
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
**"Researching Phase {N}: {phase name}. What to dig into? Library, domain, integration, pattern?"**
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
Wait for answer. Answer defines research question.
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
### 4. Spawn the Researcher
|
|
54
|
+
### 4. Spawn Researcher
|
|
57
55
|
|
|
58
56
|
```
|
|
59
57
|
Agent(prompt="
|
|
60
|
-
|
|
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: {
|
|
71
|
-
|
|
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
|
-
|
|
83
|
-
Include: recommendation, rationale,
|
|
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
|
|
83
|
+
### 5. Review
|
|
89
84
|
|
|
90
|
-
Read `.planning/phase-{N}-research.md`. Present
|
|
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.
|
|
98
|
+
### 6. Confirm or Continue
|
|
104
99
|
|
|
105
100
|
- header: "Enough?"
|
|
106
|
-
- question: "
|
|
101
|
+
- question: "Enough research, or dig deeper?"
|
|
107
102
|
- options:
|
|
108
|
-
- "Enough"
|
|
109
|
-
- "Dig deeper"
|
|
103
|
+
- "Enough"
|
|
104
|
+
- "Dig deeper"
|
|
110
105
|
|
|
111
|
-
|
|
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
|
|
129
|
-
2. **Must produce a file.**
|
|
130
|
-
3. **Honor locked decisions
|
|
131
|
-
4. **Context7 first.**
|
|
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.
|