claude-code-scanner 1.0.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/DOCUMENTATION.md +1210 -0
- package/LICENSE +21 -0
- package/README.md +306 -0
- package/bin/cli.js +305 -0
- package/package.json +43 -0
- package/template/.claude/agents/api-builder.md +64 -0
- package/template/.claude/agents/architect.md +92 -0
- package/template/.claude/agents/debugger.md +69 -0
- package/template/.claude/agents/explorer.md +71 -0
- package/template/.claude/agents/frontend.md +61 -0
- package/template/.claude/agents/infra.md +66 -0
- package/template/.claude/agents/product-owner.md +73 -0
- package/template/.claude/agents/qa-lead.md +102 -0
- package/template/.claude/agents/reviewer.md +77 -0
- package/template/.claude/agents/security.md +81 -0
- package/template/.claude/agents/team-lead.md +128 -0
- package/template/.claude/agents/tester.md +72 -0
- package/template/.claude/docs/agent-error-protocol.md +89 -0
- package/template/.claude/docs/best-practices.md +93 -0
- package/template/.claude/docs/commands-template.md +73 -0
- package/template/.claude/docs/conflict-resolution-protocol.md +82 -0
- package/template/.claude/docs/context-budget.md +54 -0
- package/template/.claude/docs/execution-metrics-protocol.md +105 -0
- package/template/.claude/docs/flow-engine.md +475 -0
- package/template/.claude/docs/smithery-setup.md +51 -0
- package/template/.claude/docs/task-record-schema.md +196 -0
- package/template/.claude/hooks/drift-detector.js +143 -0
- package/template/.claude/hooks/execution-report.js +114 -0
- package/template/.claude/hooks/notify-approval.js +30 -0
- package/template/.claude/hooks/post-compact-recovery.js +68 -0
- package/template/.claude/hooks/post-edit-format.js +43 -0
- package/template/.claude/hooks/pre-compact-save.js +94 -0
- package/template/.claude/hooks/protect-files.js +39 -0
- package/template/.claude/hooks/session-start.js +76 -0
- package/template/.claude/hooks/stop-failure-handler.js +77 -0
- package/template/.claude/hooks/tool-failure-tracker.js +54 -0
- package/template/.claude/hooks/track-file-changes.js +34 -0
- package/template/.claude/hooks/validate-bash.js +34 -0
- package/template/.claude/manifest.json +22 -0
- package/template/.claude/profiles/backend.md +34 -0
- package/template/.claude/profiles/devops.md +36 -0
- package/template/.claude/profiles/frontend.md +34 -0
- package/template/.claude/rules/context-budget.md +34 -0
- package/template/.claude/scripts/verify-setup.js +210 -0
- package/template/.claude/settings.json +154 -0
- package/template/.claude/skills/context-check/SKILL.md +112 -0
- package/template/.claude/skills/execution-report/SKILL.md +229 -0
- package/template/.claude/skills/generate-environment/SKILL.md +128 -0
- package/template/.claude/skills/generate-environment/additional-skills.md +276 -0
- package/template/.claude/skills/generate-environment/artifact-templates.md +386 -0
- package/template/.claude/skills/generate-environment/domain-agents.md +202 -0
- package/template/.claude/skills/impact-analysis/SKILL.md +17 -0
- package/template/.claude/skills/metrics/SKILL.md +19 -0
- package/template/.claude/skills/progress-report/SKILL.md +27 -0
- package/template/.claude/skills/rollback/SKILL.md +75 -0
- package/template/.claude/skills/scan-codebase/SKILL.md +59 -0
- package/template/.claude/skills/scan-codebase/deep-scan-instructions.md +101 -0
- package/template/.claude/skills/scan-codebase/tech-markers.md +87 -0
- package/template/.claude/skills/setup-smithery/SKILL.md +38 -0
- package/template/.claude/skills/sync/SKILL.md +239 -0
- package/template/.claude/skills/task-tracker/SKILL.md +40 -0
- package/template/.claude/skills/validate-setup/SKILL.md +30 -0
- package/template/.claude/skills/workflow/SKILL.md +333 -0
- package/template/.claude/templates/README.md +42 -0
- package/template/CLAUDE.md +67 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# Task Record Schema
|
|
2
|
+
|
|
3
|
+
## File: `.claude/tasks/TASK-{id}.md`
|
|
4
|
+
|
|
5
|
+
### Frontmatter
|
|
6
|
+
```yaml
|
|
7
|
+
---
|
|
8
|
+
id: TASK-{number}
|
|
9
|
+
title: {title}
|
|
10
|
+
type: feature | bugfix | refactor | hotfix | tech-debt | spike
|
|
11
|
+
scope: frontend-only | backend-only | fullstack | infrastructure | cross-cutting
|
|
12
|
+
complexity: small | medium | large
|
|
13
|
+
priority: P0-critical | P1-high | P2-medium | P3-low
|
|
14
|
+
status: {state — see State Machine below}
|
|
15
|
+
branch: {branch name}
|
|
16
|
+
pr: {PR number or "pending"}
|
|
17
|
+
assigned-to: {agent name or "unassigned"}
|
|
18
|
+
depends-on: {TASK-id or "none"}
|
|
19
|
+
created: {ISO timestamp}
|
|
20
|
+
updated: {ISO timestamp}
|
|
21
|
+
---
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Current Status Section
|
|
25
|
+
- Phase, state, progress %, last activity, next action, blocked Y/N
|
|
26
|
+
|
|
27
|
+
### Loop State Section
|
|
28
|
+
Track all active correspondence loops to survive compaction:
|
|
29
|
+
```markdown
|
|
30
|
+
## Loop State
|
|
31
|
+
|
|
32
|
+
### Dev-Test Loop (Phase 6)
|
|
33
|
+
- dev-test-loop: iteration N/5
|
|
34
|
+
- coverage-baseline: N%
|
|
35
|
+
- coverage-current: N%
|
|
36
|
+
- fix-agent: @debugger|@api-builder|@frontend|@infra
|
|
37
|
+
- last-failure: [test name] — [error] at [ISO timestamp]
|
|
38
|
+
|
|
39
|
+
### Review-Rework Loop (Phase 7)
|
|
40
|
+
- review-loop: iteration N/3
|
|
41
|
+
- reviewer-status: APPROVE | REQUEST_CHANGES (N critical, M suggestions)
|
|
42
|
+
- security-status: APPROVE | REQUEST_CHANGES (N findings)
|
|
43
|
+
- open-comments: [count] critical, [count] suggestions
|
|
44
|
+
- addressed-comments: [count] fixed, [count] won't-fix
|
|
45
|
+
|
|
46
|
+
### CI Fix Loop (Phase 8)
|
|
47
|
+
- ci-fix-loop: iteration N/3
|
|
48
|
+
- last-ci-failure: [check name] — [error] at [ISO timestamp]
|
|
49
|
+
- fix-agent: @debugger|@api-builder|@frontend|@infra|@tester
|
|
50
|
+
|
|
51
|
+
### QA Bug Loop (Phase 9)
|
|
52
|
+
- qa-bug-loop:
|
|
53
|
+
- BUG-{id}-1 (P1): iteration N/3 — [OPEN|FIXED|VERIFIED|REOPENED]
|
|
54
|
+
- BUG-{id}-2 (P3): known-issue
|
|
55
|
+
- total-bugs: N found, M fixed, K verified, J known-issues
|
|
56
|
+
- regression-check-after-each-fix: true
|
|
57
|
+
|
|
58
|
+
### Sign-off Rejection Loop (Phase 10)
|
|
59
|
+
- signoff-rejection-cycle: N/2
|
|
60
|
+
- qa-signoff: APPROVED|CONDITIONAL|REJECTED|PENDING
|
|
61
|
+
- biz-signoff: APPROVED|REJECTED|PENDING
|
|
62
|
+
- tech-signoff: APPROVED|REJECTED|PENDING
|
|
63
|
+
- last-rejection: [who] — [reason] at [ISO timestamp]
|
|
64
|
+
|
|
65
|
+
### Deploy Loop (Phase 11)
|
|
66
|
+
- deploy-loop: iteration N/2
|
|
67
|
+
- last-deploy-failure: [error type] — [summary] at [ISO timestamp]
|
|
68
|
+
- rollback-executed: true|false
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Loop Counter Reset Rules
|
|
72
|
+
| Event | Counters That Reset |
|
|
73
|
+
|-------|-------------------|
|
|
74
|
+
| Phase 10 rejects -> Phase 5 | dev-test, review, ci-fix reset to 0 |
|
|
75
|
+
| Phase 10 rejects -> Phase 4 or 3 | ALL loop counters reset to 0 |
|
|
76
|
+
| Deploy fails -> Phase 5 | dev-test, review, ci-fix reset to 0 |
|
|
77
|
+
| Normal phase advance | Counters preserved for reporting |
|
|
78
|
+
| Agent timeout in loop | Counts as +1 to current loop iteration |
|
|
79
|
+
| ON_HOLD -> resume | ALL loop counters preserved (no reset) |
|
|
80
|
+
|
|
81
|
+
## Task State Machine
|
|
82
|
+
```
|
|
83
|
+
Forward flow:
|
|
84
|
+
BACKLOG -> INTAKE -> ANALYZING -> DESIGNING -> APPROVED -> DEVELOPING
|
|
85
|
+
-> DEV_TESTING -> REVIEWING -> CI_PENDING -> QA_TESTING
|
|
86
|
+
-> QA_SIGNOFF -> BIZ_SIGNOFF -> TECH_SIGNOFF
|
|
87
|
+
-> DEPLOYING -> MONITORING -> CLOSED
|
|
88
|
+
|
|
89
|
+
Phase-to-state mapping:
|
|
90
|
+
Phase 1 = INTAKE
|
|
91
|
+
Phase 2 = ANALYZING
|
|
92
|
+
Phase 3 = DESIGNING
|
|
93
|
+
Phase 4 = APPROVED (after user confirms)
|
|
94
|
+
Phase 5 = DEVELOPING
|
|
95
|
+
Phase 6 = DEV_TESTING
|
|
96
|
+
Phase 7 = REVIEWING
|
|
97
|
+
Phase 8 = CI_PENDING
|
|
98
|
+
Phase 9 = QA_TESTING
|
|
99
|
+
Phase 10 = QA_SIGNOFF -> BIZ_SIGNOFF -> TECH_SIGNOFF
|
|
100
|
+
Phase 11 = DEPLOYING
|
|
101
|
+
Phase 12 = MONITORING
|
|
102
|
+
Phase 13 = CLOSED (after final report)
|
|
103
|
+
|
|
104
|
+
Special states (from ANY active state):
|
|
105
|
+
-> BLOCKED (waiting on depends-on or manual unblock)
|
|
106
|
+
-> ON_HOLD (deferred by user/product-owner, resume with /workflow resume)
|
|
107
|
+
-> CANCELLED (terminal, cleanup executed)
|
|
108
|
+
|
|
109
|
+
Reverse transitions (rejection routing):
|
|
110
|
+
QA_SIGNOFF -> DEVELOPING (QA rejects: bugs)
|
|
111
|
+
BIZ_SIGNOFF -> APPROVED (reqs wrong) or DEVELOPING (UI wrong)
|
|
112
|
+
TECH_SIGNOFF -> DESIGNING (architecture) or DEVELOPING (perf/tests)
|
|
113
|
+
DEPLOYING -> DEVELOPING (code bug) or DEPLOYING (config retry)
|
|
114
|
+
|
|
115
|
+
Circuit breaker -> escalated to user (stays in current state until resolved)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Timeline Section
|
|
119
|
+
Every event: timestamp, phase, event description, agent/actor, duration
|
|
120
|
+
|
|
121
|
+
### Handoff Log
|
|
122
|
+
Track every agent-to-agent handoff:
|
|
123
|
+
```markdown
|
|
124
|
+
## Handoff Log
|
|
125
|
+
| Timestamp | From | To | Reason | Artifacts | Status |
|
|
126
|
+
|-----------|------|-----|--------|-----------|--------|
|
|
127
|
+
| 2026-03-26T10:00:00Z | @explorer | @team-lead | impact analysis complete | scan-results.md | complete |
|
|
128
|
+
| 2026-03-26T10:05:00Z | @team-lead | @api-builder | backend dev assigned | TASK-001.md | complete |
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Phase Detail Sections (1-13)
|
|
132
|
+
Each phase records specific outputs:
|
|
133
|
+
- **Phase 1:** type, scope, complexity, branch name, task record created
|
|
134
|
+
- **Phase 2:** files affected, blast radius, test coverage %, security flags, risk level
|
|
135
|
+
- **Phase 3:** approach chosen, alternatives rejected, files to create/modify, breaking changes
|
|
136
|
+
- **Phase 4:** acceptance criteria list with PENDING/VERIFIED/FAILED status
|
|
137
|
+
- **Phase 5:** agents active, files created/modified, lines +/-, tests written, sub-phase status
|
|
138
|
+
- **Phase 6:** unit/integration/e2e results, coverage delta, bugs found/fixed, retry count
|
|
139
|
+
- **Phase 7:** review result, critical issues, suggestions, security review, iteration count
|
|
140
|
+
- **Phase 8:** PR number/URL, CI check results, fix iterations
|
|
141
|
+
- **Phase 9:** scenario results (happy/edge/regression/perf/security/a11y), issues found
|
|
142
|
+
- **Phase 10:** QA/business/tech sign-off status with who/when/conditions
|
|
143
|
+
- **Phase 11:** target env, pre-checks, deploy method, health check, rollback needed
|
|
144
|
+
- **Phase 12:** monitoring duration, error rate, latency impact, issues closed
|
|
145
|
+
|
|
146
|
+
### Blocker Log
|
|
147
|
+
| Timestamp | Blocker | Severity | Owner | Resolved | Resolution |
|
|
148
|
+
|
|
149
|
+
### Decision Log
|
|
150
|
+
| Timestamp | Decision | Rationale | Made By | Reversible |
|
|
151
|
+
|
|
152
|
+
### Risk Register
|
|
153
|
+
| Risk | Likelihood | Impact | Mitigation | Status |
|
|
154
|
+
|
|
155
|
+
### Execution Report Section
|
|
156
|
+
Per-phase and cumulative execution analytics:
|
|
157
|
+
```markdown
|
|
158
|
+
## Execution Reports
|
|
159
|
+
| Phase | Score | Hallucination | Regression | Tokens | Agents | Handoffs |
|
|
160
|
+
|-------|-------|---------------|------------|--------|--------|----------|
|
|
161
|
+
| 2 | 85/100 | 0 (CLEAN) | 0 (CLEAN) | ~12k | 2 | 3 |
|
|
162
|
+
| 5 | 72/100 | 1 (MINOR) | 0 (CLEAN) | ~45k | 4 | 8 |
|
|
163
|
+
| 6 | 90/100 | 0 (CLEAN) | 0 (CLEAN) | ~18k | 2 | 4 |
|
|
164
|
+
| ... | | | | | | |
|
|
165
|
+
| **TOTAL** | **82/100** | **1** | **0** | **~120k** | **8** | **24** |
|
|
166
|
+
|
|
167
|
+
### Bottleneck Analysis
|
|
168
|
+
- Slowest phase: Phase 5 (Development) — 45k tokens, 4 agents
|
|
169
|
+
- Most rework: Phase 7 (Review) — 2 iterations
|
|
170
|
+
- Highest context: Phase 5 — peaked at 58%
|
|
171
|
+
|
|
172
|
+
### Lessons Learned
|
|
173
|
+
- {Actionable insight for next time}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Full execution reports saved to: `.claude/reports/executions/TASK-{id}_phase-{N}_{timestamp}.md`
|
|
177
|
+
Cumulative report saved to: `.claude/reports/executions/TASK-{id}_final.md`
|
|
178
|
+
|
|
179
|
+
## Bug Record Format
|
|
180
|
+
```
|
|
181
|
+
BUG-{task_id}-{number}
|
|
182
|
+
Severity: P0-P4
|
|
183
|
+
Summary, Steps to Reproduce, Expected/Actual, Evidence
|
|
184
|
+
Assigned: @agent-name
|
|
185
|
+
Status: OPEN -> IN_PROGRESS -> FIXED -> QA_VERIFY -> VERIFIED/REOPENED -> CLOSED
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Directory Structure
|
|
189
|
+
```
|
|
190
|
+
.claude/tasks/TASK-001.md
|
|
191
|
+
.claude/tasks/TASK-001_changes.log
|
|
192
|
+
.claude/reports/daily/{date}.md
|
|
193
|
+
.claude/reports/weekly/{week}.md
|
|
194
|
+
.claude/reports/executions/TASK-001_phase-2_2026-03-26T100000Z.md
|
|
195
|
+
.claude/reports/executions/TASK-001_final.md
|
|
196
|
+
```
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// SessionStart hook: lightweight drift detection on startup
|
|
3
|
+
// Checks for obvious staleness — full sync requires /sync skill
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const crypto = require('crypto');
|
|
8
|
+
|
|
9
|
+
const claudeDir = path.join(process.cwd(), '.claude');
|
|
10
|
+
const manifestPath = path.join(claudeDir, 'manifest.json');
|
|
11
|
+
|
|
12
|
+
// Only run if .claude/ exists (environment is set up)
|
|
13
|
+
if (!fs.existsSync(claudeDir)) process.exit(0);
|
|
14
|
+
|
|
15
|
+
const warnings = [];
|
|
16
|
+
|
|
17
|
+
// --- 1. Check manifest freshness ---
|
|
18
|
+
let manifest = null;
|
|
19
|
+
let daysSinceSync = Infinity;
|
|
20
|
+
|
|
21
|
+
if (fs.existsSync(manifestPath)) {
|
|
22
|
+
try {
|
|
23
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
24
|
+
const lastSync = new Date(manifest.last_sync);
|
|
25
|
+
daysSinceSync = Math.floor((Date.now() - lastSync.getTime()) / (1000 * 60 * 60 * 24));
|
|
26
|
+
|
|
27
|
+
if (daysSinceSync > 14) {
|
|
28
|
+
warnings.push(`DRIFT: Last sync was ${daysSinceSync} days ago. Run /sync --check`);
|
|
29
|
+
}
|
|
30
|
+
} catch (e) {
|
|
31
|
+
warnings.push('DRIFT: manifest.json is corrupted. Run /sync --fix');
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
// No manifest = never synced — only warn if environment looks established
|
|
35
|
+
const agentsDir = path.join(claudeDir, 'agents');
|
|
36
|
+
if (fs.existsSync(agentsDir) && fs.readdirSync(agentsDir).filter(f => f.endsWith('.md')).length > 0) {
|
|
37
|
+
warnings.push('DRIFT: No manifest.json found. Run /sync --check to create baseline');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// --- 2. Quick agent count check ---
|
|
42
|
+
const agentsDir = path.join(claudeDir, 'agents');
|
|
43
|
+
const claudeMdPath = path.join(process.cwd(), 'CLAUDE.md');
|
|
44
|
+
|
|
45
|
+
if (fs.existsSync(agentsDir) && fs.existsSync(claudeMdPath)) {
|
|
46
|
+
const agentFiles = fs.readdirSync(agentsDir).filter(f => f.endsWith('.md'));
|
|
47
|
+
const claudeMd = fs.readFileSync(claudeMdPath, 'utf-8');
|
|
48
|
+
|
|
49
|
+
// Count @agent mentions in CLAUDE.md (only from the Agent Team table)
|
|
50
|
+
const agentTableMatch = claudeMd.match(/## Agent Team[\s\S]*?(?=\n## |$)/);
|
|
51
|
+
const agentSection = agentTableMatch ? agentTableMatch[0] : '';
|
|
52
|
+
const agentMentions = (agentSection.match(/@[\w-]+/g) || [])
|
|
53
|
+
.filter(a => a !== '@imports' && a !== '@path');
|
|
54
|
+
const uniqueMentions = [...new Set(agentMentions)];
|
|
55
|
+
|
|
56
|
+
if (agentFiles.length !== uniqueMentions.length) {
|
|
57
|
+
warnings.push(`DRIFT: ${agentFiles.length} agent files but ${uniqueMentions.length} agents in CLAUDE.md. Run /sync --fix`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// --- 3. Quick dependency file change check ---
|
|
62
|
+
const depFiles = ['package.json', 'go.mod', 'Cargo.toml', 'requirements.txt', 'pyproject.toml', 'Gemfile', 'pom.xml'];
|
|
63
|
+
if (manifest && manifest.tech_stack) {
|
|
64
|
+
for (const depFile of depFiles) {
|
|
65
|
+
const depPath = path.join(process.cwd(), depFile);
|
|
66
|
+
if (fs.existsSync(depPath)) {
|
|
67
|
+
const currentHash = crypto.createHash('sha256')
|
|
68
|
+
.update(fs.readFileSync(depPath))
|
|
69
|
+
.digest('hex')
|
|
70
|
+
.substring(0, 16);
|
|
71
|
+
|
|
72
|
+
const manifestEntry = manifest.tech_stack[depFile];
|
|
73
|
+
if (manifestEntry && manifestEntry.hash && manifestEntry.hash !== currentHash) {
|
|
74
|
+
warnings.push(`DRIFT: ${depFile} changed since last sync. Run /sync --check`);
|
|
75
|
+
break; // One dependency warning is enough
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// --- 4. Quick hook registration check ---
|
|
82
|
+
const hooksDir = path.join(claudeDir, 'hooks');
|
|
83
|
+
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
84
|
+
|
|
85
|
+
if (fs.existsSync(hooksDir) && fs.existsSync(settingsPath)) {
|
|
86
|
+
try {
|
|
87
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
88
|
+
const hookFiles = fs.readdirSync(hooksDir).filter(f => f.endsWith('.js'));
|
|
89
|
+
const registeredHooks = new Set();
|
|
90
|
+
|
|
91
|
+
for (const [event, matchers] of Object.entries(settings.hooks || {})) {
|
|
92
|
+
for (const matcher of matchers) {
|
|
93
|
+
for (const hook of (matcher.hooks || [])) {
|
|
94
|
+
if (hook.type === 'command' && hook.command) {
|
|
95
|
+
const match = hook.command.match(/([\w-]+\.js)/);
|
|
96
|
+
if (match) registeredHooks.add(match[1]);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const orphanHooks = hookFiles.filter(f => !registeredHooks.has(f));
|
|
103
|
+
if (orphanHooks.length > 0) {
|
|
104
|
+
warnings.push(`DRIFT: ${orphanHooks.length} hook(s) not registered in settings.json: ${orphanHooks.join(', ')}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
for (const registered of registeredHooks) {
|
|
108
|
+
if (!hookFiles.includes(registered)) {
|
|
109
|
+
warnings.push(`DRIFT: settings.json references ${registered} but file not found`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
} catch (e) {
|
|
113
|
+
// settings.json parse error — protect-files hook will catch this
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// --- 5. Quick skill directory check ---
|
|
118
|
+
const skillsDir = path.join(claudeDir, 'skills');
|
|
119
|
+
if (fs.existsSync(skillsDir)) {
|
|
120
|
+
const skillDirs = fs.readdirSync(skillsDir, { withFileTypes: true })
|
|
121
|
+
.filter(e => e.isDirectory())
|
|
122
|
+
.map(e => e.name);
|
|
123
|
+
|
|
124
|
+
for (const skill of skillDirs) {
|
|
125
|
+
const skillMd = path.join(skillsDir, skill, 'SKILL.md');
|
|
126
|
+
if (!fs.existsSync(skillMd)) {
|
|
127
|
+
warnings.push(`DRIFT: Skill directory ${skill}/ exists but has no SKILL.md`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// --- Output warnings ---
|
|
133
|
+
if (warnings.length > 0) {
|
|
134
|
+
console.log('');
|
|
135
|
+
for (const w of warnings) {
|
|
136
|
+
console.log(w);
|
|
137
|
+
}
|
|
138
|
+
if (warnings.length >= 3) {
|
|
139
|
+
console.log(`\n${warnings.length} drift issues detected. Run: /sync --check (for report) or /sync --fix (to auto-repair)`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
process.exit(0);
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Stop hook: collect execution metadata and prompt for execution report generation
|
|
3
|
+
// Runs on session Stop to capture final execution state
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
const tasksDir = path.join(process.cwd(), '.claude', 'tasks');
|
|
9
|
+
const reportsDir = path.join(process.cwd(), '.claude', 'reports', 'executions');
|
|
10
|
+
|
|
11
|
+
// Find active task
|
|
12
|
+
let activeTask = null;
|
|
13
|
+
let activeTaskPath = null;
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
// Ensure reports directory exists
|
|
17
|
+
if (!fs.existsSync(reportsDir)) {
|
|
18
|
+
fs.mkdirSync(reportsDir, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (fs.existsSync(tasksDir)) {
|
|
22
|
+
const taskFiles = fs.readdirSync(tasksDir).filter(f => f.endsWith('.md'));
|
|
23
|
+
for (const tf of taskFiles) {
|
|
24
|
+
const taskPath = path.join(tasksDir, tf);
|
|
25
|
+
const content = fs.readFileSync(taskPath, 'utf-8');
|
|
26
|
+
if (/status:\s*(DEVELOPING|DEV_TESTING|REVIEWING|CI_PENDING|QA_TESTING)/.test(content)) {
|
|
27
|
+
activeTask = content;
|
|
28
|
+
activeTaskPath = taskPath;
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!activeTask) {
|
|
35
|
+
// No active task — output minimal execution snapshot
|
|
36
|
+
const snapshot = {
|
|
37
|
+
timestamp: new Date().toISOString(),
|
|
38
|
+
task: 'none',
|
|
39
|
+
status: 'no active task found'
|
|
40
|
+
};
|
|
41
|
+
console.log(`EXECUTION SNAPSHOT: ${JSON.stringify(snapshot)}`);
|
|
42
|
+
process.exit(0);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Extract task metadata
|
|
46
|
+
const idMatch = activeTask.match(/^id:\s*(.+)$/m);
|
|
47
|
+
const titleMatch = activeTask.match(/^title:\s*(.+)$/m);
|
|
48
|
+
const statusMatch = activeTask.match(/^status:\s*(.+)$/m);
|
|
49
|
+
const taskId = idMatch ? idMatch[1].trim() : 'UNKNOWN';
|
|
50
|
+
const taskTitle = titleMatch ? titleMatch[1].trim() : 'UNKNOWN';
|
|
51
|
+
const taskStatus = statusMatch ? statusMatch[1].trim() : 'UNKNOWN';
|
|
52
|
+
|
|
53
|
+
// Count handoffs from handoff log
|
|
54
|
+
const handoffMatches = activeTask.match(/\| \d{4}-\d{2}-\d{2}T.*?\|.*?\|.*?\|.*?\|.*?\|.*?\|/g);
|
|
55
|
+
const handoffCount = handoffMatches ? handoffMatches.length : 0;
|
|
56
|
+
|
|
57
|
+
// Count loop iterations
|
|
58
|
+
const devTestLoop = activeTask.match(/dev-test-loop:\s*iteration\s*(\d+)/);
|
|
59
|
+
const reviewLoop = activeTask.match(/review-loop:\s*iteration\s*(\d+)/);
|
|
60
|
+
const qaBugLoop = activeTask.match(/qa-bug-loop:\s*iteration\s*(\d+)/);
|
|
61
|
+
|
|
62
|
+
// Count agents mentioned in handoff log
|
|
63
|
+
const agentMentions = activeTask.match(/@[\w-]+/g) || [];
|
|
64
|
+
const agentCounts = {};
|
|
65
|
+
for (const agent of agentMentions) {
|
|
66
|
+
agentCounts[agent] = (agentCounts[agent] || 0) + 1;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Read changes log
|
|
70
|
+
const changesLogPath = activeTaskPath.replace(/\.md$/, '_changes.log');
|
|
71
|
+
let filesChanged = 0;
|
|
72
|
+
let changesLog = '';
|
|
73
|
+
if (fs.existsSync(changesLogPath)) {
|
|
74
|
+
changesLog = fs.readFileSync(changesLogPath, 'utf-8');
|
|
75
|
+
filesChanged = changesLog.split('\n').filter(l => l.trim()).length;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Build execution snapshot
|
|
79
|
+
const snapshot = {
|
|
80
|
+
timestamp: new Date().toISOString(),
|
|
81
|
+
task_id: taskId,
|
|
82
|
+
title: taskTitle,
|
|
83
|
+
status: taskStatus,
|
|
84
|
+
agents_used: Object.keys(agentCounts).length,
|
|
85
|
+
agent_breakdown: agentCounts,
|
|
86
|
+
handoffs: handoffCount,
|
|
87
|
+
loops: {
|
|
88
|
+
dev_test: devTestLoop ? parseInt(devTestLoop[1]) : 0,
|
|
89
|
+
review: reviewLoop ? parseInt(reviewLoop[1]) : 0,
|
|
90
|
+
qa_bug: qaBugLoop ? parseInt(qaBugLoop[1]) : 0
|
|
91
|
+
},
|
|
92
|
+
files_changed: filesChanged
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// Save execution snapshot
|
|
96
|
+
const snapshotPath = path.join(reportsDir, `${taskId}_snapshot_${Date.now()}.json`);
|
|
97
|
+
fs.writeFileSync(snapshotPath, JSON.stringify(snapshot, null, 2));
|
|
98
|
+
|
|
99
|
+
// Output summary for the Stop prompt hook to consume
|
|
100
|
+
console.log(`\nEXECUTION SNAPSHOT for ${taskId}: ${taskTitle}`);
|
|
101
|
+
console.log(` Status: ${taskStatus}`);
|
|
102
|
+
console.log(` Agents used: ${Object.keys(agentCounts).length} (${Object.keys(agentCounts).join(', ')})`);
|
|
103
|
+
console.log(` Handoffs: ${handoffCount}`);
|
|
104
|
+
console.log(` Loops: dev-test=${snapshot.loops.dev_test}, review=${snapshot.loops.review}, qa-bug=${snapshot.loops.qa_bug}`);
|
|
105
|
+
console.log(` Files changed: ${filesChanged}`);
|
|
106
|
+
console.log(` Snapshot saved: ${snapshotPath}`);
|
|
107
|
+
console.log(`\nRun /execution-report ${taskId} for full analysis with success scoring, hallucination check, and regression impact.`);
|
|
108
|
+
|
|
109
|
+
} catch (err) {
|
|
110
|
+
// Non-fatal — don't block session stop
|
|
111
|
+
console.log(`EXECUTION REPORT HOOK: non-fatal error — ${err.message}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
process.exit(0);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Cross-platform OS notification when Claude needs user approval
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
|
|
5
|
+
const MSG = 'Claude Code needs your approval';
|
|
6
|
+
|
|
7
|
+
function tryExec(cmd) {
|
|
8
|
+
try { execSync(cmd, { stdio: 'ignore', timeout: 5000 }); return true; } catch { return false; }
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function hasCommand(name) {
|
|
12
|
+
try {
|
|
13
|
+
const check = process.platform === 'win32' ? `where ${name}` : `command -v ${name}`;
|
|
14
|
+
execSync(check, { stdio: 'ignore' });
|
|
15
|
+
return true;
|
|
16
|
+
} catch { return false; }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (process.platform === 'darwin') {
|
|
20
|
+
// macOS
|
|
21
|
+
tryExec(`osascript -e 'display notification "${MSG}" with title "Claude Code"'`);
|
|
22
|
+
} else if (process.platform === 'win32') {
|
|
23
|
+
// Windows — non-blocking balloon tip notification
|
|
24
|
+
tryExec(`powershell.exe -NoProfile -Command "[void][System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); $n=New-Object System.Windows.Forms.NotifyIcon; $n.Icon=[System.Drawing.SystemIcons]::Information; $n.Visible=$true; $n.ShowBalloonTip(3000,'Claude Code','${MSG}',[System.Windows.Forms.ToolTipIcon]::Info); Start-Sleep -Milliseconds 100; $n.Dispose()"`);
|
|
25
|
+
} else {
|
|
26
|
+
// Linux
|
|
27
|
+
if (hasCommand('notify-send')) {
|
|
28
|
+
tryExec(`notify-send "Claude Code" "${MSG}"`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// PostCompact hook: re-inject critical workflow state after context compaction
|
|
3
|
+
// Compaction can lose loop counters, phase state, and active handoffs — this restores them
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
const tasksDir = path.join(process.cwd(), '.claude', 'tasks');
|
|
9
|
+
if (!fs.existsSync(tasksDir)) process.exit(0);
|
|
10
|
+
|
|
11
|
+
const files = fs.readdirSync(tasksDir).filter(f => f.endsWith('.md'));
|
|
12
|
+
for (const file of files) {
|
|
13
|
+
const filePath = path.join(tasksDir, file);
|
|
14
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
15
|
+
|
|
16
|
+
// Only process active tasks
|
|
17
|
+
if (!/status:\s*(DEVELOPING|DEV_TESTING|REVIEWING|CI_PENDING|QA_TESTING|QA_SIGNOFF|BIZ_SIGNOFF|TECH_SIGNOFF|DEPLOYING)/.test(content)) {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const id = (content.match(/^id:\s*(.+)$/m) || [])[1] || 'UNKNOWN';
|
|
22
|
+
const title = (content.match(/^title:\s*(.+)$/m) || [])[1] || 'UNKNOWN';
|
|
23
|
+
const status = (content.match(/^status:\s*(.+)$/m) || [])[1] || 'UNKNOWN';
|
|
24
|
+
const assignedTo = (content.match(/^assigned-to:\s*(.+)$/m) || [])[1] || 'unassigned';
|
|
25
|
+
|
|
26
|
+
console.log('');
|
|
27
|
+
console.log('=== CONTEXT RECOVERY (post-compaction) ===');
|
|
28
|
+
console.log(`ACTIVE TASK: ${id} — ${title.trim()}`);
|
|
29
|
+
console.log(`STATUS: ${status.trim()} | ASSIGNED: ${assignedTo.trim()}`);
|
|
30
|
+
|
|
31
|
+
// Extract and re-inject loop state
|
|
32
|
+
const loopSection = content.match(/## Loop State\n([\s\S]*?)(?=\n##|\n$|$)/);
|
|
33
|
+
if (loopSection) {
|
|
34
|
+
console.log('');
|
|
35
|
+
console.log('LOOP STATE (preserved):');
|
|
36
|
+
const lines = loopSection[1].trim().split('\n').filter(l => l.trim().startsWith('-'));
|
|
37
|
+
for (const line of lines) {
|
|
38
|
+
console.log(` ${line.trim()}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Extract last handoff
|
|
43
|
+
const handoffLines = content.match(/\| \d{4}-\d{2}-\d{2}T.*?\|.*?\|.*?\|.*?\|.*?\|.*?\|/g);
|
|
44
|
+
if (handoffLines && handoffLines.length > 0) {
|
|
45
|
+
const lastHandoff = handoffLines[handoffLines.length - 1];
|
|
46
|
+
console.log('');
|
|
47
|
+
console.log(`LAST HANDOFF: ${lastHandoff.trim()}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Extract blockers
|
|
51
|
+
if (/blocked:\s*Y/i.test(content)) {
|
|
52
|
+
const blockerMatch = content.match(/BLOCKER:\s*(.+)/i);
|
|
53
|
+
console.log('');
|
|
54
|
+
console.log(`BLOCKER: ${blockerMatch ? blockerMatch[1].trim() : 'See task file for details'}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Extract open bugs
|
|
58
|
+
const bugMatches = content.match(/BUG-\S+\s*\(P[0-4]\)/g);
|
|
59
|
+
if (bugMatches) {
|
|
60
|
+
console.log('');
|
|
61
|
+
console.log(`OPEN BUGS: ${bugMatches.join(', ')}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log('');
|
|
65
|
+
console.log(`Full state: .claude/tasks/${file}`);
|
|
66
|
+
console.log('=== END RECOVERY ===');
|
|
67
|
+
break; // Only show the first active task
|
|
68
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Post-tool hook: auto-format edited files
|
|
3
|
+
const { execFileSync } = require('child_process');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
// Timeout: exit if stdin hangs
|
|
8
|
+
setTimeout(() => process.exit(0), 10000);
|
|
9
|
+
|
|
10
|
+
let input = '';
|
|
11
|
+
process.stdin.setEncoding('utf-8');
|
|
12
|
+
process.stdin.on('data', chunk => { input += chunk; });
|
|
13
|
+
process.stdin.on('end', () => {
|
|
14
|
+
try {
|
|
15
|
+
const data = JSON.parse(input);
|
|
16
|
+
const file = (data.tool_input && data.tool_input.file_path) || '';
|
|
17
|
+
if (!file || !fs.existsSync(file)) process.exit(0);
|
|
18
|
+
|
|
19
|
+
// Validate file path is within the project directory
|
|
20
|
+
const resolved = path.resolve(file);
|
|
21
|
+
if (!resolved.startsWith(process.cwd())) process.exit(0);
|
|
22
|
+
|
|
23
|
+
const ext = path.extname(file).toLowerCase();
|
|
24
|
+
const formatters = {
|
|
25
|
+
'.ts': ['npx', ['prettier', '--write']],
|
|
26
|
+
'.tsx': ['npx', ['prettier', '--write']],
|
|
27
|
+
'.js': ['npx', ['prettier', '--write']],
|
|
28
|
+
'.jsx': ['npx', ['prettier', '--write']],
|
|
29
|
+
'.json': ['npx', ['prettier', '--write']],
|
|
30
|
+
'.css': ['npx', ['prettier', '--write']],
|
|
31
|
+
'.py': ['black', []],
|
|
32
|
+
'.go': ['gofmt', ['-w']],
|
|
33
|
+
'.rs': ['rustfmt', []],
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const formatter = formatters[ext];
|
|
37
|
+
if (formatter) {
|
|
38
|
+
const [cmd, args] = formatter;
|
|
39
|
+
try { execFileSync(cmd, [...args, resolved], { stdio: 'ignore', timeout: 10000 }); } catch {}
|
|
40
|
+
}
|
|
41
|
+
} catch {}
|
|
42
|
+
process.exit(0);
|
|
43
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// PreCompact hook: save critical state BEFORE compaction destroys conversation history
|
|
3
|
+
// Fires at ~95% context — this is our last chance to preserve state
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
const tasksDir = path.join(process.cwd(), '.claude', 'tasks');
|
|
9
|
+
const reportsDir = path.join(process.cwd(), '.claude', 'reports');
|
|
10
|
+
|
|
11
|
+
if (!fs.existsSync(tasksDir)) process.exit(0);
|
|
12
|
+
|
|
13
|
+
const files = fs.readdirSync(tasksDir).filter(f => f.endsWith('.md'));
|
|
14
|
+
let saved = false;
|
|
15
|
+
|
|
16
|
+
for (const file of files) {
|
|
17
|
+
const filePath = path.join(tasksDir, file);
|
|
18
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
19
|
+
|
|
20
|
+
if (!/status:\s*(DEVELOPING|DEV_TESTING|REVIEWING|CI_PENDING|QA_TESTING|QA_SIGNOFF|BIZ_SIGNOFF|TECH_SIGNOFF|DEPLOYING)/.test(content)) {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const id = (content.match(/^id:\s*(.+)$/m) || [])[1] || 'UNKNOWN';
|
|
25
|
+
const title = (content.match(/^title:\s*(.+)$/m) || [])[1] || 'UNKNOWN';
|
|
26
|
+
const status = (content.match(/^status:\s*(.+)$/m) || [])[1] || 'UNKNOWN';
|
|
27
|
+
|
|
28
|
+
// Save pre-compaction state snapshot
|
|
29
|
+
const snapshotDir = path.join(reportsDir, 'executions');
|
|
30
|
+
if (!fs.existsSync(snapshotDir)) {
|
|
31
|
+
fs.mkdirSync(snapshotDir, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const snapshot = {
|
|
35
|
+
event: 'pre-compaction',
|
|
36
|
+
timestamp: new Date().toISOString(),
|
|
37
|
+
task_id: id.trim(),
|
|
38
|
+
title: title.trim(),
|
|
39
|
+
status: status.trim(),
|
|
40
|
+
reason: 'Context approaching 95% — auto-compaction imminent',
|
|
41
|
+
preserved: {
|
|
42
|
+
loop_state: extractSection(content, 'Loop State'),
|
|
43
|
+
last_handoff: extractLastHandoff(content),
|
|
44
|
+
open_bugs: extractBugs(content),
|
|
45
|
+
blocked: /blocked:\s*Y/i.test(content)
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const snapshotPath = path.join(snapshotDir, `${id.trim()}_precompact_${Date.now()}.json`);
|
|
50
|
+
fs.writeFileSync(snapshotPath, JSON.stringify(snapshot, null, 2));
|
|
51
|
+
|
|
52
|
+
// Output critical context for the compaction to preserve
|
|
53
|
+
console.log('');
|
|
54
|
+
console.log('WARNING: Context at ~95% — compaction starting.');
|
|
55
|
+
console.log(`TASK: ${id.trim()} — ${title.trim()} [${status.trim()}]`);
|
|
56
|
+
|
|
57
|
+
if (snapshot.preserved.loop_state) {
|
|
58
|
+
console.log(`LOOPS: ${snapshot.preserved.loop_state}`);
|
|
59
|
+
}
|
|
60
|
+
if (snapshot.preserved.open_bugs) {
|
|
61
|
+
console.log(`BUGS: ${snapshot.preserved.open_bugs}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log('State snapshot saved. PostCompact will re-inject critical state.');
|
|
65
|
+
console.log('');
|
|
66
|
+
console.log('COMPACTION GUIDANCE: Focus on preserving the current phase instructions.');
|
|
67
|
+
console.log('Discard: file contents already read, intermediate exploration results.');
|
|
68
|
+
console.log('Preserve: task requirements, active phase, loop state, pending decisions.');
|
|
69
|
+
|
|
70
|
+
saved = true;
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!saved) {
|
|
75
|
+
console.log('Context compaction starting. No active task to preserve.');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function extractSection(content, header) {
|
|
79
|
+
const match = content.match(new RegExp(`## ${header}\\n([\\s\\S]*?)(?=\\n##|$)`));
|
|
80
|
+
if (!match) return null;
|
|
81
|
+
return match[1].trim().split('\n').filter(l => l.trim().startsWith('-')).map(l => l.trim()).join('; ');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function extractLastHandoff(content) {
|
|
85
|
+
const lines = content.match(/\| \d{4}-\d{2}-\d{2}T.*?\|.*?\|.*?\|.*?\|.*?\|.*?\|/g);
|
|
86
|
+
return lines ? lines[lines.length - 1].trim() : null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function extractBugs(content) {
|
|
90
|
+
const bugs = content.match(/BUG-\S+\s*\(P[0-4]\)/g);
|
|
91
|
+
return bugs ? bugs.join(', ') : null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
process.exit(0);
|