qualia-framework 4.0.0 → 4.0.5
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/CLAUDE.md +23 -11
- package/agents/plan-checker.md +1 -1
- package/agents/roadmapper.md +10 -5
- package/bin/cli.js +139 -17
- package/bin/install.js +47 -47
- package/bin/qualia-ui.js +2 -2
- package/bin/state.js +126 -9
- package/bin/statusline.js +63 -38
- package/docs/erp-contract.md +49 -2
- package/guide.md +1 -1
- package/hooks/migration-guard.js +23 -9
- package/hooks/pre-compact.js +39 -11
- package/hooks/pre-deploy-gate.js +3 -4
- package/hooks/pre-push.js +6 -3
- package/hooks/session-start.js +8 -8
- package/package.json +1 -1
- package/rules/frontend.md +5 -13
- package/skills/qualia/SKILL.md +5 -0
- package/skills/qualia-build/SKILL.md +10 -0
- package/skills/qualia-debug/SKILL.md +6 -0
- package/skills/qualia-design/SKILL.md +9 -1
- package/skills/qualia-discuss/SKILL.md +6 -0
- package/skills/qualia-handoff/SKILL.md +5 -0
- package/skills/qualia-help/SKILL.md +18 -4
- package/skills/qualia-idk/SKILL.md +6 -0
- package/skills/qualia-learn/SKILL.md +6 -0
- package/skills/qualia-map/SKILL.md +7 -0
- package/skills/qualia-milestone/SKILL.md +6 -0
- package/skills/qualia-new/SKILL.md +31 -4
- package/skills/qualia-optimize/SKILL.md +8 -0
- package/skills/qualia-pause/SKILL.md +5 -0
- package/skills/qualia-plan/SKILL.md +11 -1
- package/skills/qualia-polish/SKILL.md +8 -0
- package/skills/qualia-quick/SKILL.md +7 -0
- package/skills/qualia-report/SKILL.md +146 -60
- package/skills/qualia-research/SKILL.md +7 -0
- package/skills/qualia-resume/SKILL.md +3 -0
- package/skills/qualia-review/SKILL.md +7 -0
- package/skills/qualia-ship/SKILL.md +5 -0
- package/skills/qualia-skill-new/SKILL.md +6 -0
- package/skills/qualia-task/SKILL.md +8 -1
- package/skills/qualia-test/SKILL.md +7 -0
- package/skills/qualia-verify/SKILL.md +8 -0
- package/templates/help.html +4 -4
- package/templates/tracking.json +1 -0
- package/tests/hooks.test.sh +5 -5
- package/tests/runner.js +310 -3
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: qualia-report
|
|
3
3
|
description: "Generate session report and commit to repo. Mandatory before clock-out."
|
|
4
|
+
allowed-tools:
|
|
5
|
+
- Bash
|
|
6
|
+
- Read
|
|
7
|
+
- Write
|
|
8
|
+
- Edit
|
|
4
9
|
---
|
|
5
10
|
|
|
6
11
|
# /qualia-report — Session Report
|
|
7
12
|
|
|
8
13
|
Generate a concise report of what was done. Committed to git and uploaded to the ERP for clock-out.
|
|
9
14
|
|
|
15
|
+
## Flags
|
|
16
|
+
|
|
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.
|
|
19
|
+
|
|
10
20
|
## Process
|
|
11
21
|
|
|
12
22
|
```bash
|
|
@@ -64,16 +74,33 @@ None. / - {blocker}
|
|
|
64
74
|
{list from git log}
|
|
65
75
|
```
|
|
66
76
|
|
|
67
|
-
### 4.
|
|
77
|
+
### 4. Obtain Client Report ID (QS-REPORT-NN)
|
|
78
|
+
|
|
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`.
|
|
68
80
|
|
|
69
81
|
```bash
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
82
|
+
# --dry-run: peek without incrementing
|
|
83
|
+
if [ "$DRY_RUN" = "true" ]; then
|
|
84
|
+
CLIENT_REPORT_ID=$(node ~/.claude/bin/state.js next-report-id --peek 2>/dev/null | node -e "process.stdout.write(JSON.parse(require('fs').readFileSync(0,'utf8')).report_id||'')")
|
|
85
|
+
else
|
|
86
|
+
CLIENT_REPORT_ID=$(node ~/.claude/bin/state.js next-report-id 2>/dev/null | node -e "process.stdout.write(JSON.parse(require('fs').readFileSync(0,'utf8')).report_id||'')")
|
|
87
|
+
fi
|
|
74
88
|
```
|
|
75
89
|
|
|
76
|
-
|
|
90
|
+
Example: first report on a fresh project → `QS-REPORT-01`. Next → `QS-REPORT-02`. Etc.
|
|
91
|
+
|
|
92
|
+
### 5. Commit and Push (SKIP on --dry-run)
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
if [ "$DRY_RUN" != "true" ]; then
|
|
96
|
+
mkdir -p .planning/reports
|
|
97
|
+
git add .planning/reports/report-{date}.md .planning/tracking.json
|
|
98
|
+
git commit -m "report: {CLIENT_REPORT_ID} session {YYYY-MM-DD}"
|
|
99
|
+
git push
|
|
100
|
+
fi
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 6. Upload to ERP (SKIP on --dry-run)
|
|
77
104
|
|
|
78
105
|
Read `~/.claude/.qualia-config.json` and check the `erp` object:
|
|
79
106
|
- If `erp.enabled` is `false`, skip this step and print: "ERP upload skipped (disabled in config)."
|
|
@@ -89,67 +116,126 @@ REPORT_FILE=".planning/reports/report-{date}.md"
|
|
|
89
116
|
SUBMITTED_BY=$(git config user.name)
|
|
90
117
|
SUBMITTED_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
91
118
|
|
|
92
|
-
#
|
|
119
|
+
# Build structured JSON payload from tracking.json (matches ERP contract /api/v1/reports)
|
|
120
|
+
# v4: include milestone_name, milestones[], team_id, project_id, git_remote,
|
|
121
|
+
# session_started_at, last_pushed_at, build_count, deploy_count — the ERP
|
|
122
|
+
# uses these to render the project tree (milestone → phases → unphased) correctly.
|
|
123
|
+
# v4.0.4: client_report_id carries the QS-REPORT-NN identifier.
|
|
124
|
+
PAYLOAD=$(node -e "
|
|
125
|
+
const fs = require('fs');
|
|
126
|
+
const t = JSON.parse(fs.readFileSync('.planning/tracking.json', 'utf8'));
|
|
127
|
+
const notes = fs.readFileSync('$REPORT_FILE', 'utf8').substring(0, 60000);
|
|
128
|
+
const commits = [];
|
|
129
|
+
try {
|
|
130
|
+
const { spawnSync } = require('child_process');
|
|
131
|
+
const r = spawnSync('git', ['log', '--oneline', '--since=8 hours ago', '--format=%h'], { encoding: 'utf8', timeout: 3000 });
|
|
132
|
+
if (r.stdout) commits.push(...r.stdout.trim().split('\n').filter(Boolean));
|
|
133
|
+
} catch {}
|
|
134
|
+
console.log(JSON.stringify({
|
|
135
|
+
project: t.project || require('path').basename(process.cwd()),
|
|
136
|
+
project_id: t.project_id || '',
|
|
137
|
+
team_id: t.team_id || '',
|
|
138
|
+
git_remote: t.git_remote || '',
|
|
139
|
+
client: t.client || '',
|
|
140
|
+
client_report_id: '$CLIENT_REPORT_ID',
|
|
141
|
+
milestone: t.milestone || 1,
|
|
142
|
+
milestone_name: t.milestone_name || '',
|
|
143
|
+
milestones: Array.isArray(t.milestones) ? t.milestones : [],
|
|
144
|
+
phase: t.phase,
|
|
145
|
+
phase_name: t.phase_name,
|
|
146
|
+
total_phases: t.total_phases,
|
|
147
|
+
status: t.status,
|
|
148
|
+
tasks_done: t.tasks_done || 0,
|
|
149
|
+
tasks_total: t.tasks_total || 0,
|
|
150
|
+
verification: t.verification || 'pending',
|
|
151
|
+
gap_cycles: (t.gap_cycles || {})[String(t.phase)] || 0,
|
|
152
|
+
build_count: t.build_count || 0,
|
|
153
|
+
deploy_count: t.deploy_count || 0,
|
|
154
|
+
deployed_url: t.deployed_url || '',
|
|
155
|
+
session_started_at: t.session_started_at || '',
|
|
156
|
+
last_pushed_at: t.last_pushed_at || '',
|
|
157
|
+
lifetime: t.lifetime || {},
|
|
158
|
+
commits: commits,
|
|
159
|
+
notes: notes,
|
|
160
|
+
submitted_by: '$SUBMITTED_BY',
|
|
161
|
+
submitted_at: '$SUBMITTED_AT'
|
|
162
|
+
}));
|
|
163
|
+
")
|
|
164
|
+
|
|
165
|
+
# --dry-run: print payload and stop (no POST, no commit, no increment already handled in step 4)
|
|
166
|
+
if [ "$DRY_RUN" = "true" ]; then
|
|
167
|
+
echo "--- DRY RUN · payload ---"
|
|
168
|
+
echo "$PAYLOAD" | node -e "const d=JSON.parse(require('fs').readFileSync(0,'utf8'));console.log(JSON.stringify(d,null,2))"
|
|
169
|
+
echo "--- DRY RUN · would POST to: $ERP_URL/api/v1/reports ---"
|
|
170
|
+
echo "--- DRY RUN · client_report_id would be: $CLIENT_REPORT_ID ---"
|
|
171
|
+
exit 0
|
|
172
|
+
fi
|
|
173
|
+
|
|
174
|
+
# Real upload — 3 attempts with exponential backoff (1s, 3s, 9s).
|
|
175
|
+
# The local report file is already committed, so a failed upload doesn't
|
|
176
|
+
# lose data — it just leaves the ERP view stale until the next push or
|
|
177
|
+
# manual retry.
|
|
93
178
|
if [ "$ERP_ENABLED" = "true" ]; then
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
curl -s -X POST "$ERP_URL/api/v1/reports" \
|
|
139
|
-
-H "Authorization: Bearer $API_KEY" \
|
|
140
|
-
-H "Content-Type: application/json" \
|
|
141
|
-
-d "$PAYLOAD"
|
|
179
|
+
MAX_ATTEMPTS=3
|
|
180
|
+
ATTEMPT=1
|
|
181
|
+
SUCCESS=false
|
|
182
|
+
while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do
|
|
183
|
+
RESPONSE=$(curl -sS -X POST "$ERP_URL/api/v1/reports" \
|
|
184
|
+
-H "Authorization: Bearer $API_KEY" \
|
|
185
|
+
-H "Content-Type: application/json" \
|
|
186
|
+
-d "$PAYLOAD" \
|
|
187
|
+
--max-time 10 \
|
|
188
|
+
-w "\n__HTTP__%{http_code}" 2>&1)
|
|
189
|
+
HTTP_CODE=$(echo "$RESPONSE" | grep -o "__HTTP__[0-9]*" | sed 's/__HTTP__//')
|
|
190
|
+
BODY=$(echo "$RESPONSE" | sed 's/__HTTP__[0-9]*//g')
|
|
191
|
+
|
|
192
|
+
if [ "$HTTP_CODE" = "200" ]; then
|
|
193
|
+
SUCCESS=true
|
|
194
|
+
# Parse and display the ERP-returned report_id alongside our local QS-REPORT-NN
|
|
195
|
+
ERP_REPORT_ID=$(echo "$BODY" | node -e "try{const d=JSON.parse(require('fs').readFileSync(0,'utf8'));process.stdout.write(d.report_id||'')}catch{}")
|
|
196
|
+
node ~/.claude/bin/qualia-ui.js ok "Uploaded as $CLIENT_REPORT_ID (ERP: ${ERP_REPORT_ID:-none})"
|
|
197
|
+
break
|
|
198
|
+
fi
|
|
199
|
+
|
|
200
|
+
# 401 / 422 are permanent failures — no retry.
|
|
201
|
+
if [ "$HTTP_CODE" = "401" ] || [ "$HTTP_CODE" = "422" ]; then
|
|
202
|
+
node ~/.claude/bin/qualia-ui.js warn "ERP rejected report (HTTP $HTTP_CODE). Ask Fawzi."
|
|
203
|
+
echo "$BODY" | head -3
|
|
204
|
+
break
|
|
205
|
+
fi
|
|
206
|
+
|
|
207
|
+
# Transient failure — back off and retry.
|
|
208
|
+
if [ $ATTEMPT -lt $MAX_ATTEMPTS ]; then
|
|
209
|
+
SLEEP=$(( 1 * 3 ** (ATTEMPT - 1) ))
|
|
210
|
+
node ~/.claude/bin/qualia-ui.js warn "ERP upload attempt $ATTEMPT failed (HTTP ${HTTP_CODE:-timeout}), retrying in ${SLEEP}s..."
|
|
211
|
+
sleep $SLEEP
|
|
212
|
+
fi
|
|
213
|
+
ATTEMPT=$(( ATTEMPT + 1 ))
|
|
214
|
+
done
|
|
215
|
+
|
|
216
|
+
if [ "$SUCCESS" != "true" ]; then
|
|
217
|
+
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."
|
|
218
|
+
fi
|
|
219
|
+
fi
|
|
220
|
+
|
|
221
|
+
if [ "$ERP_ENABLED" != "true" ]; then
|
|
222
|
+
node ~/.claude/bin/qualia-ui.js info "ERP upload skipped (disabled in config). Report committed locally as $CLIENT_REPORT_ID."
|
|
142
223
|
fi
|
|
143
224
|
```
|
|
144
225
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
226
|
+
Summary rules:
|
|
227
|
+
- **Upload succeeds:** print "Uploaded as QS-REPORT-NN (ERP: {uuid})". Employee can clock out.
|
|
228
|
+
- **401/422:** no retry. Print the error, tell the employee to ask Fawzi.
|
|
229
|
+
- **Transient (timeout, 5xx, network):** retry 3x with 1s/3s/9s backoff.
|
|
230
|
+
- **All retries fail:** tell employee the report is committed locally, ERP will be stale until retry.
|
|
231
|
+
- **ERP disabled:** skip silently with a note, local commit still happens.
|
|
148
232
|
|
|
149
|
-
###
|
|
233
|
+
### 7. Update State (SKIP on --dry-run)
|
|
150
234
|
|
|
151
235
|
```bash
|
|
152
|
-
|
|
236
|
+
if [ "$DRY_RUN" != "true" ]; then
|
|
237
|
+
node ~/.claude/bin/state.js transition --to activity --notes "Session report $CLIENT_REPORT_ID generated"
|
|
238
|
+
fi
|
|
153
239
|
```
|
|
154
240
|
|
|
155
241
|
Do NOT manually edit STATE.md or tracking.json — state.js handles both.
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: qualia-research
|
|
3
3
|
description: "Deep-research a niche domain or library BEFORE planning a specific phase. Spawns the researcher agent with Context7/WebFetch access. Writes to .planning/phase-{N}-research.md."
|
|
4
|
+
allowed-tools:
|
|
5
|
+
- Bash
|
|
6
|
+
- Read
|
|
7
|
+
- Write
|
|
8
|
+
- Agent
|
|
9
|
+
- WebFetch
|
|
10
|
+
- WebSearch
|
|
4
11
|
---
|
|
5
12
|
|
|
6
13
|
# /qualia-research — Per-Phase Deep Research
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: qualia-resume
|
|
3
3
|
description: "Restore context from a previous session. Reads .continue-here.md or STATE.md, summarizes where you left off, routes to next action. Trigger on 'resume', 'continue', 'pick up where I left off', 'what was I doing'."
|
|
4
|
+
allowed-tools:
|
|
5
|
+
- Bash
|
|
6
|
+
- Read
|
|
4
7
|
---
|
|
5
8
|
|
|
6
9
|
# /qualia-resume — Resume Work
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: qualia-review
|
|
3
3
|
description: "Production audit with scored diagnostics. Runs real commands, scores findings by severity. Trigger on 'review', 'audit', 'code review', 'security check', 'production check'."
|
|
4
|
+
allowed-tools:
|
|
5
|
+
- Bash
|
|
6
|
+
- Read
|
|
7
|
+
- Write
|
|
8
|
+
- Grep
|
|
9
|
+
- Glob
|
|
10
|
+
- Agent
|
|
4
11
|
---
|
|
5
12
|
|
|
6
13
|
# /qualia-review — Production Audit
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: qualia-skill-new
|
|
3
3
|
description: "Author a new Qualia skill or agent. Use when the user says 'create a new skill', 'add a skill', 'I want to build a skill', 'make this a reusable command', 'turn this into a skill'. Generates the SKILL.md, registers it in the right location, and optionally ships to the framework repo."
|
|
4
|
+
allowed-tools:
|
|
5
|
+
- Bash
|
|
6
|
+
- Read
|
|
7
|
+
- Write
|
|
8
|
+
- Edit
|
|
9
|
+
- AskUserQuestion
|
|
4
10
|
---
|
|
5
11
|
|
|
6
12
|
# /qualia-skill-new — Author a New Skill
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: qualia-task
|
|
3
3
|
description: "Build a single task — more structured than /qualia-quick, lighter than /qualia-build. Spawns a fresh builder agent for one focused task."
|
|
4
|
+
allowed-tools:
|
|
5
|
+
- Bash
|
|
6
|
+
- Read
|
|
7
|
+
- Write
|
|
8
|
+
- Edit
|
|
9
|
+
- Agent
|
|
10
|
+
- AskUserQuestion
|
|
4
11
|
---
|
|
5
12
|
|
|
6
13
|
# /qualia-task — Single Task Builder
|
|
@@ -61,7 +68,7 @@ Agent(subagent_type: "qualia-builder")
|
|
|
61
68
|
|
|
62
69
|
Task: {task description}
|
|
63
70
|
Files: {files to create/modify}
|
|
64
|
-
|
|
71
|
+
Acceptance Criteria: {observable completion criteria, 1-3 bullet points}
|
|
65
72
|
|
|
66
73
|
Context: Read PROJECT.md if it exists. Follow all rules (security, frontend, deployment).
|
|
67
74
|
```
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: qualia-test
|
|
3
3
|
description: "Generate or run tests for client projects. Trigger on 'write tests', 'add tests', 'test this', 'run tests', 'test coverage', 'need tests for'."
|
|
4
|
+
allowed-tools:
|
|
5
|
+
- Bash
|
|
6
|
+
- Read
|
|
7
|
+
- Write
|
|
8
|
+
- Edit
|
|
9
|
+
- Grep
|
|
10
|
+
- Glob
|
|
4
11
|
---
|
|
5
12
|
|
|
6
13
|
# /qualia-test — Test Generator
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: qualia-verify
|
|
3
3
|
description: "Goal-backward verification — checks if the phase ACTUALLY works, not just if tasks completed. Spawns verifier agent."
|
|
4
|
+
allowed-tools:
|
|
5
|
+
- Bash
|
|
6
|
+
- Read
|
|
7
|
+
- Write
|
|
8
|
+
- Edit
|
|
9
|
+
- Grep
|
|
10
|
+
- Glob
|
|
11
|
+
- Agent
|
|
4
12
|
---
|
|
5
13
|
|
|
6
14
|
# /qualia-verify — Verify a Phase
|
package/templates/help.html
CHANGED
|
@@ -291,13 +291,13 @@
|
|
|
291
291
|
</head>
|
|
292
292
|
<body>
|
|
293
293
|
|
|
294
|
-
<div class="version-pill">
|
|
294
|
+
<div class="version-pill">{{VERSION}}</div>
|
|
295
295
|
|
|
296
296
|
<div class="header">
|
|
297
297
|
<div class="header-content">
|
|
298
298
|
<h1><span>Qualia</span> Framework</h1>
|
|
299
299
|
<p>Plan, build, verify, ship. The AI-powered workflow for Qualia Solutions.</p>
|
|
300
|
-
<div class="version">
|
|
300
|
+
<div class="version">{{VERSION}} · 26 skills</div>
|
|
301
301
|
</div>
|
|
302
302
|
</div>
|
|
303
303
|
|
|
@@ -430,7 +430,7 @@
|
|
|
430
430
|
<div class="cmd"><span class="cmd-name">qualia-framework install</span><span class="cmd-desc">Install or reinstall the framework.</span></div>
|
|
431
431
|
<div class="cmd"><span class="cmd-name">qualia-framework update</span><span class="cmd-desc">Update to the latest version.</span></div>
|
|
432
432
|
<div class="cmd"><span class="cmd-name">qualia-framework version</span><span class="cmd-desc">Show installed version + check for updates.</span></div>
|
|
433
|
-
<div class="cmd"><span class="cmd-name">qualia-framework migrate</span><span class="cmd-desc">Upgrade
|
|
433
|
+
<div class="cmd"><span class="cmd-name">qualia-framework migrate</span><span class="cmd-desc">Upgrade legacy settings.json to the current hook layout.</span></div>
|
|
434
434
|
<div class="cmd"><span class="cmd-name">qualia-framework analytics</span><span class="cmd-desc">Hook telemetry, verification pass rates, gap cycles.</span></div>
|
|
435
435
|
<div class="cmd"><span class="cmd-name">qualia-framework team</span><span class="cmd-desc">List, add, or remove team members.</span></div>
|
|
436
436
|
<div class="cmd"><span class="cmd-name">qualia-framework traces</span><span class="cmd-desc">View recent hook activity.</span></div>
|
|
@@ -536,7 +536,7 @@
|
|
|
536
536
|
<div class="footer">
|
|
537
537
|
<strong>Welcome to the future with Qualia.</strong><br>
|
|
538
538
|
Qualia Solutions — Nicosia, Cyprus
|
|
539
|
-
<span class="footer-version">qualia-framework
|
|
539
|
+
<span class="footer-version">qualia-framework {{VERSION}} · 26 skills</span>
|
|
540
540
|
</div>
|
|
541
541
|
|
|
542
542
|
</body>
|
package/templates/tracking.json
CHANGED
package/tests/hooks.test.sh
CHANGED
|
@@ -218,25 +218,25 @@ export default function P(){return null}
|
|
|
218
218
|
EOF
|
|
219
219
|
OUT=$(cd "$TMP" && $NODE "$HOOKS_DIR/pre-deploy-gate.js" 2>&1)
|
|
220
220
|
RC=$?
|
|
221
|
-
if [ "$RC" -eq
|
|
221
|
+
if [ "$RC" -eq 2 ] \
|
|
222
222
|
&& echo "$OUT" | grep -q "BLOCKED" \
|
|
223
223
|
&& echo "$OUT" | grep -q "service_role"; then
|
|
224
|
-
echo " ✓ service_role leak in app/ → blocked with diagnostic"
|
|
224
|
+
echo " ✓ service_role leak in app/ → blocked with diagnostic (exit 2)"
|
|
225
225
|
PASS=$((PASS + 1))
|
|
226
226
|
else
|
|
227
|
-
echo " ✗ service_role leak in app/ → blocked (exit=$RC)"
|
|
227
|
+
echo " ✗ service_role leak in app/ → blocked (exit=$RC, expected 2)"
|
|
228
228
|
FAIL=$((FAIL + 1))
|
|
229
229
|
fi
|
|
230
230
|
rm -rf "$TMP"
|
|
231
231
|
|
|
232
|
-
# service_role leak in components/ → BLOCKED
|
|
232
|
+
# service_role leak in components/ → BLOCKED (exit 2 per PreToolUse contract)
|
|
233
233
|
TMP=$(mktemp -d)
|
|
234
234
|
mkdir -p "$TMP/components"
|
|
235
235
|
cat > "$TMP/components/Widget.tsx" <<'EOF'
|
|
236
236
|
const key = "service_role_literal_leak";
|
|
237
237
|
EOF
|
|
238
238
|
(cd "$TMP" && $NODE "$HOOKS_DIR/pre-deploy-gate.js" >/dev/null 2>&1)
|
|
239
|
-
assert_exit "service_role in components/ → blocked"
|
|
239
|
+
assert_exit "service_role in components/ → blocked (exit 2)" 2 $?
|
|
240
240
|
rm -rf "$TMP"
|
|
241
241
|
|
|
242
242
|
# service_role in a *.server.ts file → allowed (skip convention)
|