qualia-framework 4.5.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 -75
- package/README.md +23 -16
- package/agents/builder.md +9 -21
- package/agents/planner.md +8 -0
- package/agents/verifier.md +8 -0
- package/agents/visual-evaluator.md +132 -0
- package/bin/cli.js +54 -18
- package/bin/install.js +369 -29
- package/bin/qualia-ui.js +208 -1
- package/bin/slop-detect.mjs +5 -0
- package/bin/state.js +34 -1
- 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 +4 -0
- package/rules/design-laws.md +4 -0
- package/rules/design-product.md +4 -0
- package/rules/design-rubric.md +4 -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 +45 -121
- 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-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 -200
- 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 +1 -1
- package/templates/CONTEXT.md +36 -0
- package/templates/decisions/ADR-template.md +30 -0
- package/tests/bin.test.sh +451 -7
- package/tests/state.test.sh +58 -0
|
@@ -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,171 +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
|
-
const
|
|
165
|
-
|
|
166
|
-
.replace(/^https?:\\/\\//, '')
|
|
167
|
-
.replace(/\\.git$/, '')
|
|
168
|
-
.split('/')
|
|
169
|
-
.filter(Boolean)
|
|
170
|
-
.pop();
|
|
171
|
-
let config = {};
|
|
172
|
-
try {
|
|
173
|
-
config = JSON.parse(fs.readFileSync(path.join(os.homedir(), '.claude/.qualia-config.json'), 'utf8'));
|
|
174
|
-
} catch {}
|
|
175
|
-
const t = JSON.parse(fs.readFileSync('.planning/tracking.json', 'utf8'));
|
|
176
|
-
const notes = fs.readFileSync(process.env.REPORT_FILE, 'utf8').substring(0, 60000);
|
|
177
|
-
const commits = [];
|
|
178
|
-
try {
|
|
179
|
-
const r = spawnSync('git', ['log', '--oneline', '--since=8 hours ago', '--format=%h'], { encoding: 'utf8', timeout: 3000 });
|
|
180
|
-
if (r.stdout) commits.push(...r.stdout.trim().split('\n').filter(Boolean));
|
|
181
|
-
} catch {}
|
|
182
|
-
const gitRemote = t.git_remote || git(['config', '--get', 'remote.origin.url']);
|
|
183
|
-
const projectKey = t.project_id || repoSlug(gitRemote) || require('path').basename(process.cwd());
|
|
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());
|
|
184
190
|
console.log(JSON.stringify({
|
|
185
|
-
project:
|
|
186
|
-
project_id:
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
total_phases: t.total_phases,
|
|
198
|
-
status: t.status,
|
|
199
|
-
tasks_done: t.tasks_done || 0,
|
|
200
|
-
tasks_total: t.tasks_total || 0,
|
|
201
|
-
verification: t.verification || 'pending',
|
|
202
|
-
gap_cycles: (t.gap_cycles || {})[String(t.phase)] || 0,
|
|
203
|
-
build_count: t.build_count || 0,
|
|
204
|
-
deploy_count: t.deploy_count || 0,
|
|
205
|
-
deployed_url: t.deployed_url || '',
|
|
206
|
-
session_started_at: t.session_started_at || '',
|
|
207
|
-
last_pushed_at: t.last_pushed_at || '',
|
|
208
|
-
lifetime: t.lifetime || {},
|
|
209
|
-
commits: commits,
|
|
210
|
-
notes: notes,
|
|
211
|
-
submitted_by: process.env.SUBMITTED_BY || 'unknown',
|
|
212
|
-
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
|
|
213
203
|
}));
|
|
214
204
|
"
|
|
215
205
|
)
|
|
216
206
|
|
|
217
|
-
# --dry-run: print
|
|
207
|
+
# --dry-run: print and stop
|
|
218
208
|
if [ "$DRY_RUN" = "true" ]; then
|
|
219
|
-
echo "--- DRY RUN · payload ---"
|
|
220
|
-
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))"
|
|
221
210
|
echo "--- DRY RUN · would POST to: $ERP_URL/api/v1/reports ---"
|
|
222
211
|
echo "--- DRY RUN · client_report_id would be: $CLIENT_REPORT_ID ---"
|
|
223
212
|
exit 0
|
|
224
213
|
fi
|
|
225
214
|
|
|
226
|
-
#
|
|
227
|
-
# The local report file is already committed, so a failed upload doesn't
|
|
228
|
-
# lose data — it just leaves the ERP view stale until the next push or
|
|
229
|
-
# manual retry.
|
|
215
|
+
# Upload — 3 attempts with 1s/3s/9s backoff
|
|
230
216
|
if [ "$ERP_ENABLED" = "true" ]; then
|
|
231
|
-
|
|
232
|
-
ATTEMPT=1
|
|
233
|
-
SUCCESS=false
|
|
234
|
-
while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do
|
|
217
|
+
for ATTEMPT in 1 2 3; do
|
|
235
218
|
RESPONSE=$(curl -sS -X POST "$ERP_URL/api/v1/reports" \
|
|
236
|
-
-H "Authorization: Bearer $API_KEY" \
|
|
237
|
-
-
|
|
238
|
-
-d "$PAYLOAD" \
|
|
239
|
-
--max-time 10 \
|
|
240
|
-
-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)
|
|
241
221
|
HTTP_CODE=$(echo "$RESPONSE" | grep -o "__HTTP__[0-9]*" | sed 's/__HTTP__//')
|
|
242
222
|
BODY=$(echo "$RESPONSE" | sed 's/__HTTP__[0-9]*//g')
|
|
243
223
|
|
|
244
224
|
if [ "$HTTP_CODE" = "200" ]; then
|
|
245
|
-
|
|
246
|
-
# Parse and display the ERP-returned report_id alongside our local QS-REPORT-NN
|
|
247
|
-
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{}")
|
|
248
226
|
node ~/.claude/bin/qualia-ui.js ok "Uploaded as $CLIENT_REPORT_ID (ERP: ${ERP_REPORT_ID:-none})"
|
|
249
227
|
break
|
|
250
228
|
fi
|
|
251
|
-
|
|
252
|
-
# 401 / 422 are permanent failures — no retry.
|
|
253
229
|
if [ "$HTTP_CODE" = "401" ] || [ "$HTTP_CODE" = "422" ]; then
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
else
|
|
257
|
-
node ~/.claude/bin/qualia-ui.js warn "ERP rejected payload (HTTP 422) — schema validation failed. Response body:"
|
|
258
|
-
fi
|
|
259
|
-
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
|
|
260
232
|
break
|
|
261
233
|
fi
|
|
262
|
-
|
|
263
|
-
# Transient failure — back off and retry.
|
|
264
|
-
if [ $ATTEMPT -lt $MAX_ATTEMPTS ]; then
|
|
265
|
-
SLEEP=$(( 1 * 3 ** (ATTEMPT - 1) ))
|
|
266
|
-
node ~/.claude/bin/qualia-ui.js warn "ERP upload attempt $ATTEMPT failed (HTTP ${HTTP_CODE:-timeout}), retrying in ${SLEEP}s..."
|
|
267
|
-
sleep $SLEEP
|
|
268
|
-
fi
|
|
269
|
-
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; }
|
|
270
235
|
done
|
|
271
|
-
|
|
272
|
-
if [ "$SUCCESS" != "true" ]; then
|
|
273
|
-
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."
|
|
274
|
-
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."
|
|
275
237
|
fi
|
|
276
238
|
|
|
277
|
-
|
|
278
|
-
node ~/.claude/bin/qualia-ui.js info "ERP upload skipped (disabled in config). Report committed locally as $CLIENT_REPORT_ID."
|
|
279
|
-
fi
|
|
239
|
+
[ "$ERP_ENABLED" != "true" ] && node ~/.claude/bin/qualia-ui.js info "ERP upload skipped (disabled). $CLIENT_REPORT_ID committed locally."
|
|
280
240
|
```
|
|
281
|
-
|
|
282
|
-
Summary rules:
|
|
283
|
-
- **Upload succeeds:** print "Uploaded as QS-REPORT-NN (ERP: {uuid})". Employee can clock out.
|
|
284
|
-
- **401/422:** no retry. Print the error, tell the employee to ask Fawzi.
|
|
285
|
-
- **Transient (timeout, 5xx, network):** retry 3x with 1s/3s/9s backoff.
|
|
286
|
-
- **All retries fail:** tell employee the report is committed locally, ERP will be stale until retry.
|
|
287
|
-
- **ERP disabled:** skip silently with a note, local commit still happens.
|
|
288
|
-
|
|
289
|
-
### 7. Update State (SKIP on --dry-run)
|
|
290
|
-
|
|
291
|
-
```bash
|
|
292
|
-
if [ "$DRY_RUN" != "true" ]; then
|
|
293
|
-
node ~/.claude/bin/state.js transition --to activity --notes "Session report $CLIENT_REPORT_ID generated"
|
|
294
|
-
fi
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
Do NOT manually edit STATE.md or tracking.json — state.js handles both.
|
|
298
|
-
|
|
299
|
-
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.
|