qualia-framework-v2 2.0.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -13
- package/agents/builder.md +22 -5
- package/agents/planner.md +13 -1
- package/agents/verifier.md +40 -0
- package/bin/cli.js +20 -0
- package/bin/install.js +352 -207
- package/hooks/block-env-edit.sh +5 -2
- package/hooks/branch-guard.sh +5 -0
- package/hooks/migration-guard.sh +43 -0
- package/hooks/pre-deploy-gate.sh +18 -0
- package/hooks/pre-push.sh +10 -4
- package/package.json +7 -4
- package/skills/qualia/SKILL.md +7 -1
- package/skills/qualia-build/SKILL.md +1 -1
- package/skills/qualia-plan/SKILL.md +14 -1
- package/skills/qualia-quick/SKILL.md +1 -1
- package/skills/qualia-task/SKILL.md +92 -0
- package/skills/qualia-verify/SKILL.md +1 -1
- package/tests/hooks.test.sh +144 -0
- package/install.sh +0 -223
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Catch dangerous SQL patterns in migration files
|
|
3
|
+
# Runs as PreToolUse hook on Write/Edit of migration files
|
|
4
|
+
|
|
5
|
+
INPUT=$(cat)
|
|
6
|
+
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""' 2>/dev/null)
|
|
7
|
+
CONTENT=$(echo "$INPUT" | jq -r '.tool_input.content // .tool_input.new_string // ""' 2>/dev/null)
|
|
8
|
+
|
|
9
|
+
# Only check migration/SQL files
|
|
10
|
+
case "$FILE" in
|
|
11
|
+
*migration*|*migrate*|*.sql) ;;
|
|
12
|
+
*) exit 0 ;;
|
|
13
|
+
esac
|
|
14
|
+
|
|
15
|
+
ERRORS=""
|
|
16
|
+
|
|
17
|
+
# DROP TABLE without safeguards
|
|
18
|
+
if echo "$CONTENT" | grep -qi "DROP TABLE" && ! echo "$CONTENT" | grep -qi "IF EXISTS"; then
|
|
19
|
+
ERRORS="${ERRORS}\n ✗ DROP TABLE without IF EXISTS"
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
# DELETE without WHERE
|
|
23
|
+
if echo "$CONTENT" | grep -qi "DELETE FROM" && ! echo "$CONTENT" | grep -qi "WHERE"; then
|
|
24
|
+
ERRORS="${ERRORS}\n ✗ DELETE FROM without WHERE clause"
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# TRUNCATE (almost always wrong in migrations)
|
|
28
|
+
if echo "$CONTENT" | grep -qi "TRUNCATE"; then
|
|
29
|
+
ERRORS="${ERRORS}\n ✗ TRUNCATE detected — are you sure?"
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# CREATE TABLE without RLS
|
|
33
|
+
if echo "$CONTENT" | grep -qi "CREATE TABLE" && ! echo "$CONTENT" | grep -qi "ENABLE ROW LEVEL SECURITY"; then
|
|
34
|
+
ERRORS="${ERRORS}\n ✗ CREATE TABLE without ENABLE ROW LEVEL SECURITY"
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
if [ -n "$ERRORS" ]; then
|
|
38
|
+
echo "◆ Migration guard — dangerous patterns found:"
|
|
39
|
+
echo -e "$ERRORS"
|
|
40
|
+
echo ""
|
|
41
|
+
echo "Fix these before proceeding. If intentional, ask Fawzi to approve."
|
|
42
|
+
exit 2
|
|
43
|
+
fi
|
package/hooks/pre-deploy-gate.sh
CHANGED
|
@@ -12,6 +12,24 @@ if [ -f "tsconfig.json" ]; then
|
|
|
12
12
|
echo " ✓ TypeScript"
|
|
13
13
|
fi
|
|
14
14
|
|
|
15
|
+
# Lint check
|
|
16
|
+
if [ -f "package.json" ] && grep -q '"lint"' package.json; then
|
|
17
|
+
if ! npm run lint 2>/dev/null; then
|
|
18
|
+
echo "BLOCKED: Lint errors. Fix before deploying."
|
|
19
|
+
exit 1
|
|
20
|
+
fi
|
|
21
|
+
echo " ✓ Lint"
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# Test check
|
|
25
|
+
if [ -f "package.json" ] && grep -q '"test"' package.json; then
|
|
26
|
+
if ! npm test 2>/dev/null; then
|
|
27
|
+
echo "BLOCKED: Tests failed. Fix before deploying."
|
|
28
|
+
exit 1
|
|
29
|
+
fi
|
|
30
|
+
echo " ✓ Tests"
|
|
31
|
+
fi
|
|
32
|
+
|
|
15
33
|
# Build check
|
|
16
34
|
if [ -f "package.json" ] && grep -q '"build"' package.json; then
|
|
17
35
|
if ! npm run build 2>/dev/null; then
|
package/hooks/pre-push.sh
CHANGED
|
@@ -12,8 +12,12 @@ if [ -f "$STATE" ] && [ -f "$TRACKING" ]; then
|
|
|
12
12
|
NOW=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
13
13
|
|
|
14
14
|
# Update tracking.json with current values
|
|
15
|
-
if command -v python3 &>/dev/null; then
|
|
16
|
-
python3
|
|
15
|
+
if ! command -v python3 &>/dev/null; then
|
|
16
|
+
echo "WARNING: python3 not found — tracking.json not updated" >&2
|
|
17
|
+
exit 0
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
if ! python3 -c "
|
|
17
21
|
import json
|
|
18
22
|
with open('$TRACKING', 'r') as f:
|
|
19
23
|
t = json.load(f)
|
|
@@ -23,7 +27,9 @@ t['last_commit'] = '${LAST_COMMIT}'
|
|
|
23
27
|
t['last_updated'] = '${NOW}'
|
|
24
28
|
with open('$TRACKING', 'w') as f:
|
|
25
29
|
json.dump(t, f, indent=2)
|
|
26
|
-
" 2>/
|
|
27
|
-
|
|
30
|
+
" 2>/tmp/qualia-push-err.txt; then
|
|
31
|
+
echo "WARNING: Failed to update tracking.json — $(cat /tmp/qualia-push-err.txt)" >&2
|
|
32
|
+
exit 0
|
|
28
33
|
fi
|
|
34
|
+
git add "$TRACKING" 2>/dev/null
|
|
29
35
|
fi
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qualia-framework-v2",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "Claude Code workflow framework by Qualia Solutions. Plan, build, verify, ship.",
|
|
5
5
|
"bin": {
|
|
6
|
-
"qualia-framework-v2": "./bin/
|
|
6
|
+
"qualia-framework-v2": "./bin/cli.js"
|
|
7
7
|
},
|
|
8
8
|
"keywords": [
|
|
9
9
|
"claude-code",
|
|
@@ -22,6 +22,9 @@
|
|
|
22
22
|
"url": "https://github.com/qualia-solutions/qualia-framework-v2"
|
|
23
23
|
},
|
|
24
24
|
"homepage": "https://github.com/qualia-solutions/qualia-framework-v2#readme",
|
|
25
|
+
"scripts": {
|
|
26
|
+
"test": "bash tests/hooks.test.sh"
|
|
27
|
+
},
|
|
25
28
|
"files": [
|
|
26
29
|
"bin/",
|
|
27
30
|
"agents/",
|
|
@@ -29,10 +32,10 @@
|
|
|
29
32
|
"rules/",
|
|
30
33
|
"skills/",
|
|
31
34
|
"templates/",
|
|
35
|
+
"tests/",
|
|
32
36
|
"CLAUDE.md",
|
|
33
37
|
"guide.md",
|
|
34
|
-
"statusline.sh"
|
|
35
|
-
"install.sh"
|
|
38
|
+
"statusline.sh"
|
|
36
39
|
],
|
|
37
40
|
"engines": {
|
|
38
41
|
"node": ">=18"
|
package/skills/qualia/SKILL.md
CHANGED
|
@@ -22,7 +22,13 @@ echo "---VERIFICATIONS---"
|
|
|
22
22
|
ls .planning/phase-*-verification.md 2>/dev/null || echo "NO_VERIFICATIONS"
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
###
|
|
25
|
+
### Task Routing (size-based)
|
|
26
|
+
|
|
27
|
+
- Single file change, config tweak, typo fix, bug with known cause → `/qualia-quick`
|
|
28
|
+
- One feature, 1-5 files, clear scope, under 2 hours → `/qualia-task`
|
|
29
|
+
- Multi-feature, 5+ files, needs architecture decisions, or part of a roadmap phase → `/qualia-plan`
|
|
30
|
+
|
|
31
|
+
### 2. Route (state-based)
|
|
26
32
|
|
|
27
33
|
| Condition | Command |
|
|
28
34
|
|-----------|---------|
|
|
@@ -49,7 +49,7 @@ YOUR TASK:
|
|
|
49
49
|
{paste the single task block from the plan — title, files, action, context refs, done-when}
|
|
50
50
|
|
|
51
51
|
Execute this task. Read all @file references before writing. Commit when done.
|
|
52
|
-
", subagent_type="
|
|
52
|
+
", subagent_type="qualia-builder", description="Task {N}: {title}")
|
|
53
53
|
```
|
|
54
54
|
|
|
55
55
|
**After each task completes:**
|
|
@@ -48,7 +48,7 @@ Phase {N} success criteria: {criteria from STATE.md}
|
|
|
48
48
|
{If --gaps: Also read @.planning/phase-{N}-verification.md for failures to fix}
|
|
49
49
|
|
|
50
50
|
Create the plan file at .planning/phase-{N}-plan.md
|
|
51
|
-
", subagent_type="
|
|
51
|
+
", subagent_type="qualia-planner", description="Plan phase {N}")
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
### 3. Review Plan
|
|
@@ -82,3 +82,16 @@ Update tracking.json: status → "planned"
|
|
|
82
82
|
```
|
|
83
83
|
→ Run: /qualia-build {N}
|
|
84
84
|
```
|
|
85
|
+
|
|
86
|
+
### Gap Closure Mode (`--gaps`)
|
|
87
|
+
|
|
88
|
+
When invoked as `/qualia-plan {N} --gaps`, the planner is in gap-closure mode:
|
|
89
|
+
|
|
90
|
+
1. Read `.planning/phase-{N}-verification.md` — extract ONLY the FAIL items
|
|
91
|
+
2. For each FAIL item, create a targeted fix task:
|
|
92
|
+
- **Files:** The specific files that failed verification
|
|
93
|
+
- **Action:** The specific fix needed (not "fix auth" — "add session persistence check in `src/lib/auth.ts` signIn function")
|
|
94
|
+
- **Done when:** The exact verification criterion that previously failed, restated
|
|
95
|
+
3. Do NOT re-plan passing items. Do NOT add new features. Gap plans are surgical.
|
|
96
|
+
4. Write to `.planning/phase-{N}-gaps-plan.md` (separate from original plan)
|
|
97
|
+
5. All gap tasks are Wave 1 (parallel) unless they share files
|
|
@@ -5,7 +5,7 @@ description: "Fast path for small tasks — bug fixes, tweaks, hot fixes. Skips
|
|
|
5
5
|
|
|
6
6
|
# /qualia-quick — Quick Task
|
|
7
7
|
|
|
8
|
-
For tasks under
|
|
8
|
+
For tasks under 1 hour that don't need full phase planning. Single file changes, bug fixes, config tweaks, typo fixes.
|
|
9
9
|
|
|
10
10
|
## Process
|
|
11
11
|
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: qualia-task
|
|
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
|
+
---
|
|
5
|
+
|
|
6
|
+
# /qualia-task — Single Task Builder
|
|
7
|
+
|
|
8
|
+
Build one thing properly. Fresh builder context, atomic commit, but no phase plan needed.
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
`/qualia-task` — describe what to build interactively
|
|
12
|
+
`/qualia-task {description}` — build it directly
|
|
13
|
+
|
|
14
|
+
## When to Use
|
|
15
|
+
- One feature, 1-5 files, clear scope, 1-3 hours of work
|
|
16
|
+
- Adding a single feature, component, API route, or integration
|
|
17
|
+
- Refactoring one module
|
|
18
|
+
- Building something specific someone asked for
|
|
19
|
+
|
|
20
|
+
## Process
|
|
21
|
+
|
|
22
|
+
### 1. Clarify
|
|
23
|
+
|
|
24
|
+
If no description provided, ask: **"What do you want to build?"**
|
|
25
|
+
|
|
26
|
+
Then use AskUserQuestion:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
question: "How complex is this task?"
|
|
30
|
+
header: "Scope"
|
|
31
|
+
options:
|
|
32
|
+
- label: "Small (1-2hrs)"
|
|
33
|
+
description: "Single file or component, straightforward implementation"
|
|
34
|
+
- label: "Medium (2-3hrs)"
|
|
35
|
+
description: "Multiple files, some integration work, needs testing"
|
|
36
|
+
- label: "Large (3hrs+)"
|
|
37
|
+
description: "Significant feature, multiple components — use /qualia-plan instead"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
If "Large" — suggest `/qualia-plan` instead. Ask if they want to proceed anyway.
|
|
41
|
+
|
|
42
|
+
### 2. Task Spec
|
|
43
|
+
|
|
44
|
+
Write a quick task spec (don't save to file, just confirm with user):
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
◆ QUALIA ► TASK
|
|
48
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
49
|
+
|
|
50
|
+
What: {what to build}
|
|
51
|
+
Files: {files to create/modify}
|
|
52
|
+
Done: {what "done" looks like}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Ask: **"Good to build?"**
|
|
56
|
+
|
|
57
|
+
### 3. Build
|
|
58
|
+
|
|
59
|
+
Spawn a builder agent with the task:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
Agent(subagent_type: "qualia-builder")
|
|
63
|
+
|
|
64
|
+
Task: {task description}
|
|
65
|
+
Files: {files to create/modify}
|
|
66
|
+
Done when: {completion criteria}
|
|
67
|
+
|
|
68
|
+
Context: Read PROJECT.md if it exists. Follow all rules (security, frontend, deployment).
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The builder runs in fresh context — reads before writing, follows rules, commits atomically.
|
|
72
|
+
|
|
73
|
+
### 4. Verify
|
|
74
|
+
|
|
75
|
+
After the builder finishes:
|
|
76
|
+
- Run `npx tsc --noEmit` if TypeScript
|
|
77
|
+
- Quick smoke test if applicable
|
|
78
|
+
- Review what was built
|
|
79
|
+
|
|
80
|
+
### 5. Report
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
◆ QUALIA ► TASK COMPLETE
|
|
84
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
85
|
+
|
|
86
|
+
Task {description}
|
|
87
|
+
Files {files changed}
|
|
88
|
+
Commit {commit hash}
|
|
89
|
+
Status ✓ Done
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Update `.planning/tracking.json` notes field if it exists.
|
|
@@ -41,7 +41,7 @@ Phase plan with success criteria:
|
|
|
41
41
|
{@.planning/phase-{N}-verification.md}
|
|
42
42
|
|
|
43
43
|
Verify this phase. Write report to .planning/phase-{N}-verification.md
|
|
44
|
-
", subagent_type="
|
|
44
|
+
", subagent_type="qualia-verifier", description="Verify phase {N}")
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
### 3. Present Results
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Qualia Framework v2 — Hook Tests
|
|
3
|
+
# Run: bash tests/hooks.test.sh
|
|
4
|
+
|
|
5
|
+
PASS=0
|
|
6
|
+
FAIL=0
|
|
7
|
+
HOOKS_DIR="$(dirname "$0")/../hooks"
|
|
8
|
+
|
|
9
|
+
assert_exit() {
|
|
10
|
+
local name="$1" expected="$2" actual="$3"
|
|
11
|
+
if [ "$expected" = "$actual" ]; then
|
|
12
|
+
echo " ✓ $name"
|
|
13
|
+
PASS=$((PASS + 1))
|
|
14
|
+
else
|
|
15
|
+
echo " ✗ $name (expected exit $expected, got $actual)"
|
|
16
|
+
FAIL=$((FAIL + 1))
|
|
17
|
+
fi
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
echo "=== Hook Tests ==="
|
|
21
|
+
echo ""
|
|
22
|
+
|
|
23
|
+
# --- block-env-edit.sh ---
|
|
24
|
+
echo "block-env-edit:"
|
|
25
|
+
|
|
26
|
+
echo '{"tool_input":{"file_path":".env.local"}}' | bash "$HOOKS_DIR/block-env-edit.sh" > /dev/null 2>&1
|
|
27
|
+
assert_exit "blocks .env.local" 2 $?
|
|
28
|
+
|
|
29
|
+
echo '{"tool_input":{"file_path":".env.production"}}' | bash "$HOOKS_DIR/block-env-edit.sh" > /dev/null 2>&1
|
|
30
|
+
assert_exit "blocks .env.production" 2 $?
|
|
31
|
+
|
|
32
|
+
echo '{"tool_input":{"file_path":".env"}}' | bash "$HOOKS_DIR/block-env-edit.sh" > /dev/null 2>&1
|
|
33
|
+
assert_exit "blocks .env" 2 $?
|
|
34
|
+
|
|
35
|
+
echo '{"tool_input":{"file_path":"src/app.tsx"}}' | bash "$HOOKS_DIR/block-env-edit.sh" > /dev/null 2>&1
|
|
36
|
+
assert_exit "allows src/app.tsx" 0 $?
|
|
37
|
+
|
|
38
|
+
echo '{"tool_input":{"file_path":"components/Footer.tsx"}}' | bash "$HOOKS_DIR/block-env-edit.sh" > /dev/null 2>&1
|
|
39
|
+
assert_exit "allows components/Footer.tsx" 0 $?
|
|
40
|
+
|
|
41
|
+
# --- migration-guard.sh ---
|
|
42
|
+
echo ""
|
|
43
|
+
echo "migration-guard:"
|
|
44
|
+
|
|
45
|
+
echo '{"tool_input":{"file_path":"migrations/001.sql","content":"DROP TABLE users;"}}' | bash "$HOOKS_DIR/migration-guard.sh" > /dev/null 2>&1
|
|
46
|
+
assert_exit "blocks DROP TABLE without IF EXISTS" 2 $?
|
|
47
|
+
|
|
48
|
+
echo '{"tool_input":{"file_path":"migrations/001.sql","content":"DROP TABLE IF EXISTS old_users;"}}' | bash "$HOOKS_DIR/migration-guard.sh" > /dev/null 2>&1
|
|
49
|
+
assert_exit "allows DROP TABLE IF EXISTS" 0 $?
|
|
50
|
+
|
|
51
|
+
echo '{"tool_input":{"file_path":"migrations/002.sql","content":"DELETE FROM users;"}}' | bash "$HOOKS_DIR/migration-guard.sh" > /dev/null 2>&1
|
|
52
|
+
assert_exit "blocks DELETE without WHERE" 2 $?
|
|
53
|
+
|
|
54
|
+
echo '{"tool_input":{"file_path":"migrations/003.sql","content":"TRUNCATE TABLE sessions;"}}' | bash "$HOOKS_DIR/migration-guard.sh" > /dev/null 2>&1
|
|
55
|
+
assert_exit "blocks TRUNCATE" 2 $?
|
|
56
|
+
|
|
57
|
+
echo '{"tool_input":{"file_path":"migrations/004.sql","content":"CREATE TABLE users (id uuid);"}}' | bash "$HOOKS_DIR/migration-guard.sh" > /dev/null 2>&1
|
|
58
|
+
assert_exit "blocks CREATE TABLE without RLS" 2 $?
|
|
59
|
+
|
|
60
|
+
echo '{"tool_input":{"file_path":"migrations/005.sql","content":"ALTER TABLE users ADD COLUMN email text;"}}' | bash "$HOOKS_DIR/migration-guard.sh" > /dev/null 2>&1
|
|
61
|
+
assert_exit "allows safe ALTER TABLE" 0 $?
|
|
62
|
+
|
|
63
|
+
echo '{"tool_input":{"file_path":"src/app.tsx","content":"DROP TABLE users;"}}' | bash "$HOOKS_DIR/migration-guard.sh" > /dev/null 2>&1
|
|
64
|
+
assert_exit "skips non-migration files" 0 $?
|
|
65
|
+
|
|
66
|
+
# --- branch-guard.sh ---
|
|
67
|
+
echo ""
|
|
68
|
+
echo "branch-guard:"
|
|
69
|
+
|
|
70
|
+
if [ -f "$HOOKS_DIR/branch-guard.sh" ]; then
|
|
71
|
+
echo " ✓ branch-guard.sh exists"
|
|
72
|
+
PASS=$((PASS + 1))
|
|
73
|
+
else
|
|
74
|
+
echo " ✗ branch-guard.sh missing"
|
|
75
|
+
FAIL=$((FAIL + 1))
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
if grep -q 'ROLE' "$HOOKS_DIR/branch-guard.sh"; then
|
|
79
|
+
echo " ✓ checks ROLE variable"
|
|
80
|
+
PASS=$((PASS + 1))
|
|
81
|
+
else
|
|
82
|
+
echo " ✗ missing ROLE check"
|
|
83
|
+
FAIL=$((FAIL + 1))
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
if grep -q 'z "$ROLE"' "$HOOKS_DIR/branch-guard.sh"; then
|
|
87
|
+
echo " ✓ defaults to deny on missing ROLE"
|
|
88
|
+
PASS=$((PASS + 1))
|
|
89
|
+
else
|
|
90
|
+
echo " ✗ missing empty-ROLE deny"
|
|
91
|
+
FAIL=$((FAIL + 1))
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
# --- pre-push.sh ---
|
|
95
|
+
echo ""
|
|
96
|
+
echo "pre-push:"
|
|
97
|
+
|
|
98
|
+
if grep -q 'command -v python3' "$HOOKS_DIR/pre-push.sh"; then
|
|
99
|
+
echo " ✓ checks for python3 availability"
|
|
100
|
+
PASS=$((PASS + 1))
|
|
101
|
+
else
|
|
102
|
+
echo " ✗ missing python3 check"
|
|
103
|
+
FAIL=$((FAIL + 1))
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
if grep -q 'qualia-push-err' "$HOOKS_DIR/pre-push.sh"; then
|
|
107
|
+
echo " ✓ captures python3 errors"
|
|
108
|
+
PASS=$((PASS + 1))
|
|
109
|
+
else
|
|
110
|
+
echo " ✗ missing python3 error capture"
|
|
111
|
+
FAIL=$((FAIL + 1))
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
# --- pre-deploy-gate.sh ---
|
|
115
|
+
echo ""
|
|
116
|
+
echo "pre-deploy-gate:"
|
|
117
|
+
|
|
118
|
+
if [ -f "$HOOKS_DIR/pre-deploy-gate.sh" ]; then
|
|
119
|
+
echo " ✓ pre-deploy-gate.sh exists"
|
|
120
|
+
PASS=$((PASS + 1))
|
|
121
|
+
else
|
|
122
|
+
echo " ✗ pre-deploy-gate.sh missing"
|
|
123
|
+
FAIL=$((FAIL + 1))
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
if grep -q 'tsc --noEmit' "$HOOKS_DIR/pre-deploy-gate.sh"; then
|
|
127
|
+
echo " ✓ runs TypeScript check"
|
|
128
|
+
PASS=$((PASS + 1))
|
|
129
|
+
else
|
|
130
|
+
echo " ✗ missing TypeScript check"
|
|
131
|
+
FAIL=$((FAIL + 1))
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
if grep -q 'service_role' "$HOOKS_DIR/pre-deploy-gate.sh"; then
|
|
135
|
+
echo " ✓ checks for service_role leaks"
|
|
136
|
+
PASS=$((PASS + 1))
|
|
137
|
+
else
|
|
138
|
+
echo " ✗ missing service_role check"
|
|
139
|
+
FAIL=$((FAIL + 1))
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
echo ""
|
|
143
|
+
echo "=== Results: $PASS passed, $FAIL failed ==="
|
|
144
|
+
[ "$FAIL" -eq 0 ] && exit 0 || exit 1
|
package/install.sh
DELETED
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Qualia Framework v2 — Installer
|
|
3
|
-
# Installs skills, agents, rules, hooks, templates, status line, and configures settings.json
|
|
4
|
-
|
|
5
|
-
set -e
|
|
6
|
-
|
|
7
|
-
FRAMEWORK_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
8
|
-
CLAUDE_DIR="$HOME/.claude"
|
|
9
|
-
|
|
10
|
-
echo "◆ Qualia Framework v2"
|
|
11
|
-
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
12
|
-
echo " Installing to $CLAUDE_DIR"
|
|
13
|
-
echo ""
|
|
14
|
-
|
|
15
|
-
# Skills
|
|
16
|
-
echo " Installing skills..."
|
|
17
|
-
for skill_dir in "$FRAMEWORK_DIR"/skills/*/; do
|
|
18
|
-
skill_name=$(basename "$skill_dir")
|
|
19
|
-
mkdir -p "$CLAUDE_DIR/skills/$skill_name"
|
|
20
|
-
cp "$skill_dir/SKILL.md" "$CLAUDE_DIR/skills/$skill_name/SKILL.md"
|
|
21
|
-
echo " ✓ $skill_name"
|
|
22
|
-
done
|
|
23
|
-
|
|
24
|
-
# Agents
|
|
25
|
-
echo " Installing agents..."
|
|
26
|
-
mkdir -p "$CLAUDE_DIR/agents"
|
|
27
|
-
for agent in "$FRAMEWORK_DIR"/agents/*.md; do
|
|
28
|
-
cp "$agent" "$CLAUDE_DIR/agents/"
|
|
29
|
-
echo " ✓ $(basename "$agent")"
|
|
30
|
-
done
|
|
31
|
-
|
|
32
|
-
# Rules
|
|
33
|
-
echo " Installing rules..."
|
|
34
|
-
mkdir -p "$CLAUDE_DIR/rules"
|
|
35
|
-
for rule in "$FRAMEWORK_DIR"/rules/*.md; do
|
|
36
|
-
cp "$rule" "$CLAUDE_DIR/rules/"
|
|
37
|
-
echo " ✓ $(basename "$rule")"
|
|
38
|
-
done
|
|
39
|
-
|
|
40
|
-
# Hook scripts
|
|
41
|
-
echo " Installing hook scripts..."
|
|
42
|
-
mkdir -p "$CLAUDE_DIR/hooks"
|
|
43
|
-
for hook in "$FRAMEWORK_DIR"/hooks/*.sh; do
|
|
44
|
-
cp "$hook" "$CLAUDE_DIR/hooks/"
|
|
45
|
-
chmod +x "$CLAUDE_DIR/hooks/$(basename "$hook")"
|
|
46
|
-
echo " ✓ $(basename "$hook")"
|
|
47
|
-
done
|
|
48
|
-
|
|
49
|
-
# Status line
|
|
50
|
-
echo " Installing status line..."
|
|
51
|
-
cp "$FRAMEWORK_DIR/statusline.sh" "$CLAUDE_DIR/statusline.sh"
|
|
52
|
-
chmod +x "$CLAUDE_DIR/statusline.sh"
|
|
53
|
-
echo " ✓ statusline.sh"
|
|
54
|
-
|
|
55
|
-
# Templates
|
|
56
|
-
echo " Installing templates..."
|
|
57
|
-
mkdir -p "$CLAUDE_DIR/qualia-templates"
|
|
58
|
-
for tmpl in "$FRAMEWORK_DIR"/templates/*; do
|
|
59
|
-
cp "$tmpl" "$CLAUDE_DIR/qualia-templates/"
|
|
60
|
-
echo " ✓ $(basename "$tmpl")"
|
|
61
|
-
done
|
|
62
|
-
|
|
63
|
-
# CLAUDE.md (user-level — applies to all projects)
|
|
64
|
-
echo " Installing CLAUDE.md..."
|
|
65
|
-
cp "$FRAMEWORK_DIR/CLAUDE.md" "$CLAUDE_DIR/CLAUDE.md"
|
|
66
|
-
echo " ✓ CLAUDE.md"
|
|
67
|
-
|
|
68
|
-
# Guide
|
|
69
|
-
cp "$FRAMEWORK_DIR/guide.md" "$CLAUDE_DIR/qualia-guide.md"
|
|
70
|
-
echo " ✓ guide.md"
|
|
71
|
-
|
|
72
|
-
# Configure settings.json with hooks, status line, and env
|
|
73
|
-
echo ""
|
|
74
|
-
echo " Configuring settings.json..."
|
|
75
|
-
|
|
76
|
-
python3 << 'PYEOF'
|
|
77
|
-
import json, os
|
|
78
|
-
|
|
79
|
-
settings_path = os.path.expanduser("~/.claude/settings.json")
|
|
80
|
-
|
|
81
|
-
# Load existing settings or start fresh
|
|
82
|
-
if os.path.exists(settings_path):
|
|
83
|
-
with open(settings_path) as f:
|
|
84
|
-
settings = json.load(f)
|
|
85
|
-
else:
|
|
86
|
-
settings = {}
|
|
87
|
-
|
|
88
|
-
# Env vars
|
|
89
|
-
settings.setdefault("env", {})
|
|
90
|
-
settings["env"].update({
|
|
91
|
-
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1",
|
|
92
|
-
"CLAUDE_CODE_DISABLE_AUTO_MEMORY": "0",
|
|
93
|
-
"MAX_MCP_OUTPUT_TOKENS": "25000",
|
|
94
|
-
"CLAUDE_CODE_NO_FLICKER": "1"
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
# Status line — teal branded, 2-line
|
|
98
|
-
settings["statusLine"] = {
|
|
99
|
-
"type": "command",
|
|
100
|
-
"command": "~/.claude/statusline.sh"
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
# Spinner verbs — Qualia branded
|
|
104
|
-
settings["spinnerVerbs"] = {
|
|
105
|
-
"mode": "replace",
|
|
106
|
-
"verbs": [
|
|
107
|
-
"Qualia-fying", "Solution-ing", "Teal-crafting", "Vibe-forging",
|
|
108
|
-
"Shipping", "Wiring", "Polishing", "Verifying",
|
|
109
|
-
"Orchestrating", "Architecting", "Deploying", "Hardening"
|
|
110
|
-
]
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
# Spinner tips — Qualia tips
|
|
114
|
-
settings["spinnerTipsOverride"] = {
|
|
115
|
-
"excludeDefault": True,
|
|
116
|
-
"tips": [
|
|
117
|
-
"◆ Lost? Type /qualia for the next step",
|
|
118
|
-
"◆ Small fix? Use /qualia-quick to skip planning",
|
|
119
|
-
"◆ End of day? /qualia-report before you clock out",
|
|
120
|
-
"◆ Context isolation: every task gets a fresh AI brain",
|
|
121
|
-
"◆ The verifier doesn't trust claims — it greps the code",
|
|
122
|
-
"◆ Plans are prompts — the plan IS what the builder reads",
|
|
123
|
-
"◆ Feature branches only — never push to main",
|
|
124
|
-
"◆ Read before write — no exceptions",
|
|
125
|
-
"◆ MVP first — build what's asked, nothing extra",
|
|
126
|
-
"◆ tracking.json syncs to ERP on every push"
|
|
127
|
-
]
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
# Hooks
|
|
131
|
-
hooks_dir = os.path.expanduser("~/.claude/hooks")
|
|
132
|
-
settings["hooks"] = {
|
|
133
|
-
"PreToolUse": [
|
|
134
|
-
{
|
|
135
|
-
"matcher": "Bash",
|
|
136
|
-
"hooks": [{
|
|
137
|
-
"type": "command",
|
|
138
|
-
"if": "Bash(git push*)",
|
|
139
|
-
"command": f"{hooks_dir}/branch-guard.sh",
|
|
140
|
-
"timeout": 10,
|
|
141
|
-
"statusMessage": "◆ Checking branch permissions..."
|
|
142
|
-
}]
|
|
143
|
-
},
|
|
144
|
-
{
|
|
145
|
-
"matcher": "Edit|Write",
|
|
146
|
-
"hooks": [{
|
|
147
|
-
"type": "command",
|
|
148
|
-
"if": "Edit(*.env*)",
|
|
149
|
-
"command": f"{hooks_dir}/block-env-edit.sh",
|
|
150
|
-
"timeout": 5,
|
|
151
|
-
"statusMessage": "◆ Checking file permissions..."
|
|
152
|
-
}]
|
|
153
|
-
}
|
|
154
|
-
],
|
|
155
|
-
"PostToolUse": [
|
|
156
|
-
{
|
|
157
|
-
"matcher": "Bash",
|
|
158
|
-
"hooks": [{
|
|
159
|
-
"type": "command",
|
|
160
|
-
"if": "Bash(vercel --prod*)",
|
|
161
|
-
"command": f"{hooks_dir}/pre-deploy-gate.sh",
|
|
162
|
-
"timeout": 120,
|
|
163
|
-
"statusMessage": "◆ Running quality gates..."
|
|
164
|
-
}]
|
|
165
|
-
}
|
|
166
|
-
],
|
|
167
|
-
"PreCompact": [
|
|
168
|
-
{
|
|
169
|
-
"matcher": "compact",
|
|
170
|
-
"hooks": [{
|
|
171
|
-
"type": "command",
|
|
172
|
-
"command": f"{hooks_dir}/pre-compact.sh",
|
|
173
|
-
"timeout": 15,
|
|
174
|
-
"statusMessage": "◆ Saving state..."
|
|
175
|
-
}]
|
|
176
|
-
}
|
|
177
|
-
],
|
|
178
|
-
"SubagentStart": [
|
|
179
|
-
{
|
|
180
|
-
"matcher": ".*",
|
|
181
|
-
"hooks": [{
|
|
182
|
-
"type": "command",
|
|
183
|
-
"command": "echo '{\"additionalContext\": \"◆ Qualia agent spawned\"}'"
|
|
184
|
-
}]
|
|
185
|
-
}
|
|
186
|
-
]
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
# Permissions
|
|
190
|
-
settings.setdefault("permissions", {})
|
|
191
|
-
settings["permissions"].setdefault("allow", [])
|
|
192
|
-
settings["permissions"].setdefault("deny", [
|
|
193
|
-
"Read(./.env)",
|
|
194
|
-
"Read(./.env.*)",
|
|
195
|
-
"Read(./secrets/**)"
|
|
196
|
-
])
|
|
197
|
-
|
|
198
|
-
# Effort level
|
|
199
|
-
settings["effortLevel"] = "high"
|
|
200
|
-
|
|
201
|
-
with open(settings_path, "w") as f:
|
|
202
|
-
json.dump(settings, f, indent=2)
|
|
203
|
-
|
|
204
|
-
print(" ✓ Hooks registered (branch-guard, env-block, deploy-gate, pre-compact, subagent-label)")
|
|
205
|
-
print(" ✓ Status line configured (2-line teal branded)")
|
|
206
|
-
print(" ✓ Spinner verbs: Qualia branded")
|
|
207
|
-
print(" ✓ Spinner tips: Qualia tips")
|
|
208
|
-
print(" ✓ Environment variables set")
|
|
209
|
-
PYEOF
|
|
210
|
-
|
|
211
|
-
echo ""
|
|
212
|
-
echo "◆ Installed ✓"
|
|
213
|
-
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
214
|
-
echo " Skills: 10"
|
|
215
|
-
echo " Agents: 3 (planner, builder, verifier)"
|
|
216
|
-
echo " Hooks: 4 (branch-guard, env-block, deploy-gate, pre-compact)"
|
|
217
|
-
echo " Rules: 3 (security, frontend, deployment)"
|
|
218
|
-
echo " Templates: 4"
|
|
219
|
-
echo " Status line: ✓"
|
|
220
|
-
echo " CLAUDE.md: ✓ (user-level)"
|
|
221
|
-
echo ""
|
|
222
|
-
echo " Restart Claude Code, then type /qualia in any project."
|
|
223
|
-
echo ""
|