azclaude-copilot 0.4.39 → 0.5.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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +2 -2
- package/README.md +9 -7
- package/bin/cli.js +53 -1
- package/package.json +2 -2
- package/templates/CLAUDE.md +35 -1
- package/templates/agents/cc-cli-integrator.md +5 -0
- package/templates/agents/cc-template-author.md +7 -0
- package/templates/agents/cc-test-maintainer.md +5 -0
- package/templates/agents/code-reviewer.md +11 -0
- package/templates/agents/constitution-guard.md +9 -0
- package/templates/agents/devops-engineer.md +9 -0
- package/templates/agents/loop-controller.md +7 -0
- package/templates/agents/milestone-builder.md +7 -0
- package/templates/agents/orchestrator-init.md +9 -1
- package/templates/agents/orchestrator.md +8 -0
- package/templates/agents/problem-architect.md +29 -1
- package/templates/agents/qa-engineer.md +9 -0
- package/templates/agents/security-auditor.md +9 -0
- package/templates/agents/spec-reviewer.md +9 -0
- package/templates/agents/test-writer.md +11 -0
- package/templates/capabilities/manifest.md +2 -0
- package/templates/capabilities/shared/context-inoculation.md +39 -0
- package/templates/capabilities/shared/reward-hack-detection.md +32 -0
- package/templates/commands/audit.md +8 -0
- package/templates/commands/ghost-test.md +99 -0
- package/templates/commands/inoculate.md +76 -0
- package/templates/commands/sentinel.md +3 -0
- package/templates/commands/ship.md +6 -0
- package/templates/commands/test.md +10 -0
- package/templates/hooks/post-tool-use.js +341 -277
- package/templates/hooks/pre-tool-use.js +344 -292
- package/templates/hooks/stop.js +198 -151
- package/templates/hooks/user-prompt.js +369 -163
- package/templates/scripts/statusline.sh +105 -0
- package/templates/skills/agent-creator/SKILL.md +11 -0
- package/templates/skills/architecture-advisor/SKILL.md +21 -16
- package/templates/skills/debate/SKILL.md +5 -0
- package/templates/skills/env-scanner/SKILL.md +5 -0
- package/templates/skills/frontend-design/SKILL.md +5 -0
- package/templates/skills/mcp/SKILL.md +3 -0
- package/templates/skills/security/SKILL.md +3 -0
- package/templates/skills/session-guard/SKILL.md +3 -0
- package/templates/skills/skill-creator/SKILL.md +12 -0
- package/templates/skills/test-first/SKILL.md +5 -0
|
@@ -1,163 +1,369 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
/**
|
|
4
|
-
* AZCLAUDE — UserPromptSubmit hook
|
|
5
|
-
* Runs on every session's first prompt.
|
|
6
|
-
* Injects goals.md into context so Claude always knows the current thread.
|
|
7
|
-
* If previous session was interrupted (In progress entries remain), warns Claude.
|
|
8
|
-
* Works on: Windows (PowerShell/CMD/Git Bash), macOS, Linux.
|
|
9
|
-
*/
|
|
10
|
-
const fs = require('fs');
|
|
11
|
-
const path = require('path');
|
|
12
|
-
const os = require('os');
|
|
13
|
-
|
|
14
|
-
// ── Hook profile gate ───────────────────────────────────────────────────────
|
|
15
|
-
// AZCLAUDE_HOOK_PROFILE=minimal|standard|strict (default: standard)
|
|
16
|
-
const HOOK_PROFILE = process.env.AZCLAUDE_HOOK_PROFILE || 'standard';
|
|
17
|
-
|
|
18
|
-
// ── Prompt injection scan — runs on EVERY prompt (before session gate) ────────
|
|
19
|
-
// Scans the user's actual message for injection attempts.
|
|
20
|
-
// Logs to shared session security log so stop.js can summarize.
|
|
21
|
-
try {
|
|
22
|
-
const raw = fs.readFileSync(0, 'utf8');
|
|
23
|
-
const data = JSON.parse(raw);
|
|
24
|
-
const promptText = data.prompt || '';
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
process.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
} catch (_) {}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
if (
|
|
45
|
-
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if (
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
/**
|
|
4
|
+
* AZCLAUDE — UserPromptSubmit hook
|
|
5
|
+
* Runs on every session's first prompt.
|
|
6
|
+
* Injects goals.md into context so Claude always knows the current thread.
|
|
7
|
+
* If previous session was interrupted (In progress entries remain), warns Claude.
|
|
8
|
+
* Works on: Windows (PowerShell/CMD/Git Bash), macOS, Linux.
|
|
9
|
+
*/
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const os = require('os');
|
|
13
|
+
|
|
14
|
+
// ── Hook profile gate ───────────────────────────────────────────────────────
|
|
15
|
+
// AZCLAUDE_HOOK_PROFILE=minimal|standard|strict (default: standard)
|
|
16
|
+
const HOOK_PROFILE = process.env.AZCLAUDE_HOOK_PROFILE || 'standard';
|
|
17
|
+
|
|
18
|
+
// ── Prompt injection scan — runs on EVERY prompt (before session gate) ────────
|
|
19
|
+
// Scans the user's actual message for injection attempts.
|
|
20
|
+
// Logs to shared session security log so stop.js can summarize.
|
|
21
|
+
try {
|
|
22
|
+
const raw = fs.readFileSync(0, 'utf8');
|
|
23
|
+
const data = JSON.parse(raw);
|
|
24
|
+
const promptText = data.prompt || '';
|
|
25
|
+
// Persist prompt text for brain router (below) — router reads this on every message
|
|
26
|
+
if (promptText) {
|
|
27
|
+
try { fs.writeFileSync(path.join(os.tmpdir(), `.azclaude-prompt-${process.ppid || process.pid}`), promptText); } catch (_) {}
|
|
28
|
+
}
|
|
29
|
+
if (promptText) {
|
|
30
|
+
const PROMPT_INJECT = /ignore\s+(?:all\s+)?previous\s+instructions|disregard\s+(?:all\s+)?previous\s+instructions|override\s+(?:your\s+)?(?:rules|instructions|safety)|you\s+are\s+now\s+(?:a\s+)?(?:new|different|unrestricted)/i;
|
|
31
|
+
if (PROMPT_INJECT.test(promptText)) {
|
|
32
|
+
const sid = process.ppid || process.pid;
|
|
33
|
+
const seclog = path.join(os.tmpdir(), `.azclaude-seclog-${sid}`);
|
|
34
|
+
const entry = JSON.stringify({ ts: new Date().toISOString(), hook: 'user-prompt', rule: 'prompt-injection-attempt', level: 'warn', target: promptText.slice(0, 80) });
|
|
35
|
+
try { fs.appendFileSync(seclog, entry + '\n'); } catch (_) {}
|
|
36
|
+
process.stderr.write('\n⚠ SECURITY: Prompt injection pattern detected in user input.\n');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
} catch (_) {}
|
|
40
|
+
|
|
41
|
+
// ── Session gate — first message gets full context, subsequent get routing only ─
|
|
42
|
+
const marker = path.join(os.tmpdir(), `.azclaude-session-${process.ppid || process.pid}`);
|
|
43
|
+
const isFirstMessage = !fs.existsSync(marker);
|
|
44
|
+
if (isFirstMessage) {
|
|
45
|
+
try { fs.writeFileSync(marker, ''); } catch (_) {}
|
|
46
|
+
// Stamp session start time for duration tracking (stop.js reads this)
|
|
47
|
+
try { fs.writeFileSync(path.join(os.tmpdir(), `.azclaude-session-start-${process.ppid || process.pid}`), new Date().toISOString()); } catch (_) {}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Only proceed if this is an AZCLAUDE project (goals.md exists)
|
|
51
|
+
const cfg = process.env.AZCLAUDE_CFG || '.claude';
|
|
52
|
+
const goalsPath = path.join(cfg, 'memory', 'goals.md');
|
|
53
|
+
if (!fs.existsSync(goalsPath)) process.exit(0);
|
|
54
|
+
|
|
55
|
+
// ── AZCLAUDE Brain Router — fires on EVERY message ─────────────────────────
|
|
56
|
+
// This is the enforcement layer that makes Claude Code USE AZCLAUDE.
|
|
57
|
+
// Without this, Claude Code ignores all installed agents, skills, and capabilities.
|
|
58
|
+
//
|
|
59
|
+
// Pipeline: problem-architect FIRST → Team Spec → skills + agents → implement → review
|
|
60
|
+
// No skip conditions for code tasks. problem-architect ALWAYS runs first.
|
|
61
|
+
try {
|
|
62
|
+
const promptText = (function() {
|
|
63
|
+
try {
|
|
64
|
+
return fs.readFileSync(path.join(os.tmpdir(), `.azclaude-prompt-${process.ppid || process.pid}`), 'utf8');
|
|
65
|
+
} catch (_) { return ''; }
|
|
66
|
+
})();
|
|
67
|
+
|
|
68
|
+
// Skip routing for: slash commands (command files handle it), empty prompts
|
|
69
|
+
if (promptText.startsWith('/') || promptText.length === 0) {
|
|
70
|
+
// no-op — fall through to session gate
|
|
71
|
+
} else {
|
|
72
|
+
const p = promptText.toLowerCase();
|
|
73
|
+
|
|
74
|
+
// ── Detect if this is a QUESTION-ONLY message (no action needed) ──
|
|
75
|
+
// Only skip if the message is PURELY a question with no action verb
|
|
76
|
+
const isQuestionOnly = /^(what|how|why|where|when|who|can you explain|show me|tell me|do you know)\b/.test(p.trim())
|
|
77
|
+
&& !/\b(build|add|create|implement|fix|refactor|deploy|test|review|write|make|change|update|modify|remove|delete|move|rename|install|setup|configure|migrate)\b/.test(p);
|
|
78
|
+
|
|
79
|
+
if (!isQuestionOnly) {
|
|
80
|
+
const agentsDir = path.join(cfg, 'agents');
|
|
81
|
+
const skillsDir = path.join(cfg, 'skills');
|
|
82
|
+
const hasAgents = fs.existsSync(agentsDir);
|
|
83
|
+
const hasSkills = fs.existsSync(skillsDir);
|
|
84
|
+
const agentExists = (name) => hasAgents && fs.existsSync(path.join(agentsDir, `${name}.md`));
|
|
85
|
+
const skillExists = (name) => hasSkills && fs.existsSync(path.join(skillsDir, name, 'SKILL.md'));
|
|
86
|
+
|
|
87
|
+
// ── Intent detection ──
|
|
88
|
+
const intents = [];
|
|
89
|
+
if (/\b(build|add|create|implement|feature|component|page|endpoint|function|module|new)\b/.test(p)) intents.push('BUILD');
|
|
90
|
+
if (/\b(fix|bug|broken|error|crash|issue|fail|wrong|not work)\b/.test(p)) intents.push('FIX');
|
|
91
|
+
if (/\b(review|check|audit|safe|securit|vulnerab)\b/.test(p)) intents.push('REVIEW');
|
|
92
|
+
if (/\b(test|coverage|spec|e2e|unit test|integration test)\b/.test(p)) intents.push('TEST');
|
|
93
|
+
if (/\b(plan|blueprint|architect|design system|decide|which.*better|trade.?off)\b/.test(p)) intents.push('PLAN');
|
|
94
|
+
if (/\b(deploy|ci|cd|docker|infra|pipeline|kubernetes|nginx|terraform)\b/.test(p)) intents.push('DEVOPS');
|
|
95
|
+
if (/\b(refactor|clean|improve|simplify|restructure)\b/.test(p)) intents.push('REFACTOR');
|
|
96
|
+
if (/\b(frontend|ui|ux|css|page|dashboard|landing|component|react|vue|html)\b/.test(p)) intents.push('FRONTEND');
|
|
97
|
+
if (/\b(agent|skill|capability|command)\b.*\b(create|add|new|build|write)\b/.test(p)) intents.push('EXTEND');
|
|
98
|
+
|
|
99
|
+
// If no specific intents detected but it's not a question, treat as general code task
|
|
100
|
+
if (intents.length === 0) intents.push('CODE');
|
|
101
|
+
|
|
102
|
+
// ── Build the MANDATORY pipeline ──
|
|
103
|
+
console.log('');
|
|
104
|
+
console.log('--- AZCLAUDE PIPELINE (MANDATORY) ---');
|
|
105
|
+
console.log('Detected: ' + intents.join(' + '));
|
|
106
|
+
console.log('');
|
|
107
|
+
|
|
108
|
+
// ── STEP 1: problem-architect ALWAYS runs first ──
|
|
109
|
+
// This is the AZCLAUDE brain — it decides which agents, skills, files to use.
|
|
110
|
+
// NO exceptions. NO "skip if small task". ALWAYS run pre-flight.
|
|
111
|
+
if (agentExists('problem-architect')) {
|
|
112
|
+
console.log('STEP 1 — PRE-FLIGHT (BLOCKING):');
|
|
113
|
+
console.log(' Spawn Agent(subagent_type="problem-architect") with this prompt:');
|
|
114
|
+
console.log(' "Task: [user\'s request]');
|
|
115
|
+
console.log(' Available agents: ' + (hasAgents ? fs.readdirSync(agentsDir).filter(f => f.endsWith('.md')).map(f => f.replace('.md','')).join(', ') : 'none'));
|
|
116
|
+
console.log(' Available skills: ' + (hasSkills ? fs.readdirSync(skillsDir).filter(s => fs.existsSync(path.join(skillsDir, s, 'SKILL.md'))).join(', ') : 'none') + '"');
|
|
117
|
+
console.log(' WAIT for Team Spec before proceeding. Do NOT start coding without it.');
|
|
118
|
+
console.log('');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ── STEP 1b: Web research for current best practices ──
|
|
122
|
+
console.log('STEP 1b — WEB RESEARCH (MANDATORY):');
|
|
123
|
+
console.log(' Use WebSearch to verify best practices for technologies in this task.');
|
|
124
|
+
console.log(' Claude\'s training data is frozen — APIs change, libraries break, patterns evolve.');
|
|
125
|
+
console.log(' Search: "{technology} best practices ' + new Date().getFullYear() + '" + "{technology} common pitfalls"');
|
|
126
|
+
console.log(' Fetch: official docs for any specific API/library version in use.');
|
|
127
|
+
console.log(' Skip only for: pure internal code with zero external dependencies.');
|
|
128
|
+
console.log('');
|
|
129
|
+
|
|
130
|
+
// ── STEP 2: Load skills based on intent ──
|
|
131
|
+
const skills = [];
|
|
132
|
+
if ((intents.includes('BUILD') || intents.includes('FIX') || intents.includes('CODE')) && skillExists('test-first')) {
|
|
133
|
+
skills.push('test-first');
|
|
134
|
+
}
|
|
135
|
+
if (intents.includes('FRONTEND') && skillExists('frontend-design')) {
|
|
136
|
+
skills.push('frontend-design');
|
|
137
|
+
}
|
|
138
|
+
if (intents.includes('PLAN') && skillExists('architecture-advisor')) {
|
|
139
|
+
skills.push('architecture-advisor');
|
|
140
|
+
}
|
|
141
|
+
if (intents.includes('EXTEND')) {
|
|
142
|
+
if (skillExists('agent-creator')) skills.push('agent-creator');
|
|
143
|
+
if (skillExists('skill-creator')) skills.push('skill-creator');
|
|
144
|
+
}
|
|
145
|
+
if (intents.includes('REVIEW') && skillExists('security')) {
|
|
146
|
+
skills.push('security');
|
|
147
|
+
}
|
|
148
|
+
if (skills.length > 0) {
|
|
149
|
+
console.log('STEP 2 — LOAD SKILLS:');
|
|
150
|
+
skills.forEach(s => console.log(' Read: ' + cfg + '/skills/' + s + '/SKILL.md'));
|
|
151
|
+
console.log(' Follow each skill\'s instructions exactly. These are specialized knowledge.');
|
|
152
|
+
console.log('');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ── STEP 3: Intent-specific agents ──
|
|
156
|
+
const agents = [];
|
|
157
|
+
if (intents.includes('REVIEW') && agentExists('security-auditor')) agents.push({ name: 'security-auditor', desc: '111-rule security scan' });
|
|
158
|
+
if (intents.includes('REVIEW') && agentExists('code-reviewer')) agents.push({ name: 'code-reviewer', desc: 'code quality + bug detection' });
|
|
159
|
+
if (intents.includes('TEST') && agentExists('test-writer')) agents.push({ name: 'test-writer', desc: 'generate tests matching project patterns' });
|
|
160
|
+
if (intents.includes('DEVOPS') && agentExists('devops-engineer')) agents.push({ name: 'devops-engineer', desc: 'infrastructure/CI/CD specialist' });
|
|
161
|
+
if (agents.length > 0) {
|
|
162
|
+
console.log('STEP 3 — SPECIALIST AGENTS:');
|
|
163
|
+
agents.forEach(a => console.log(' Spawn Agent(subagent_type="' + a.name + '") — ' + a.desc));
|
|
164
|
+
console.log('');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ── STEP 4: Post-implementation (ALWAYS for code tasks) ──
|
|
168
|
+
if (!intents.includes('REVIEW') && !intents.includes('PLAN')) {
|
|
169
|
+
const postAgents = [];
|
|
170
|
+
if (agentExists('code-reviewer')) postAgents.push('code-reviewer');
|
|
171
|
+
if (agentExists('test-writer')) postAgents.push('test-writer');
|
|
172
|
+
if (postAgents.length > 0) {
|
|
173
|
+
console.log('STEP 4 — AFTER IMPLEMENTATION (MANDATORY):');
|
|
174
|
+
postAgents.forEach(a => console.log(' Spawn Agent(subagent_type="' + a + '") to review/test your changes'));
|
|
175
|
+
console.log(' Do NOT mark task as done until review + tests pass.');
|
|
176
|
+
console.log('');
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ── STEP 5: Self-healing — create missing skills/agents on the fly ──
|
|
181
|
+
console.log('SELF-HEALING RULE:');
|
|
182
|
+
console.log(' If problem-architect\'s Team Spec lists a MISSING skill or agent:');
|
|
183
|
+
if (skillExists('skill-creator')) {
|
|
184
|
+
console.log(' → For missing skills: use Skill tool to invoke "skill-creator" with the domain/technology name');
|
|
185
|
+
}
|
|
186
|
+
if (skillExists('agent-creator')) {
|
|
187
|
+
console.log(' → For missing agents: use Skill tool to invoke "agent-creator" with the workstream description');
|
|
188
|
+
}
|
|
189
|
+
console.log(' Create the missing skill/agent BEFORE implementation. It will be available for all future tasks.');
|
|
190
|
+
console.log('');
|
|
191
|
+
|
|
192
|
+
console.log('This pipeline is NON-NEGOTIABLE. Do not skip steps. Do not start coding before Step 1 completes.');
|
|
193
|
+
console.log('--- END PIPELINE ---');
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
} catch (_) {}
|
|
197
|
+
|
|
198
|
+
// ── First message only — inject full context ────────────────────────────────
|
|
199
|
+
if (!isFirstMessage) process.exit(0);
|
|
200
|
+
|
|
201
|
+
// Ensure required directories exist — only in AZCLAUDE projects
|
|
202
|
+
for (const d of ['.claude/memory', '.claude/memory/checkpoints']) {
|
|
203
|
+
try { fs.mkdirSync(d, { recursive: true }); } catch (_) {}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Strip prompt-injection attempts before outputting into context
|
|
207
|
+
const INJECTION = /ignore.{0,20}previous.{0,20}instructions|curl.{0,10}\|.{0,10}bash|wget.{0,10}\|.{0,10}sh|you are now|system prompt/i;
|
|
208
|
+
const content = fs.readFileSync(goalsPath, 'utf8');
|
|
209
|
+
const filtered = content.split('\n').filter(l => !INJECTION.test(l)).join('\n');
|
|
210
|
+
|
|
211
|
+
// Warn if previous session was interrupted (In progress entries survived)
|
|
212
|
+
const ipMatch = filtered.match(/^## In progress\n((?:- .+\n?)+)/m);
|
|
213
|
+
if (ipMatch) {
|
|
214
|
+
console.log('⚠ PREVIOUS SESSION INTERRUPTED — files were being edited:');
|
|
215
|
+
console.log(ipMatch[1].trimEnd());
|
|
216
|
+
console.log('Resume or discard before starting new work.');
|
|
217
|
+
console.log('');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Cap "Done this session" to last 20 entries — older entries are still on disk
|
|
221
|
+
const doneHeading = '## Done this session';
|
|
222
|
+
const doneIdx = filtered.indexOf(doneHeading);
|
|
223
|
+
let output = filtered;
|
|
224
|
+
if (doneIdx !== -1) {
|
|
225
|
+
const before = filtered.slice(0, doneIdx);
|
|
226
|
+
const afterDone = filtered.slice(doneIdx + doneHeading.length);
|
|
227
|
+
const doneLines = afterDone.split('\n');
|
|
228
|
+
const entries = [];
|
|
229
|
+
const rest = [];
|
|
230
|
+
let pastDone = false;
|
|
231
|
+
for (const line of doneLines) {
|
|
232
|
+
if (pastDone) { rest.push(line); continue; }
|
|
233
|
+
if (line.startsWith('## ') && line.trim() !== '') { pastDone = true; rest.push(line); continue; }
|
|
234
|
+
entries.push(line);
|
|
235
|
+
}
|
|
236
|
+
const MAX_DONE = 20;
|
|
237
|
+
const entryLines = entries.filter(l => l.startsWith('- '));
|
|
238
|
+
if (entryLines.length > MAX_DONE) {
|
|
239
|
+
const trimmed = entryLines.slice(0, MAX_DONE);
|
|
240
|
+
const nonEntries = entries.filter(l => !l.startsWith('- '));
|
|
241
|
+
const omitted = entryLines.length - MAX_DONE;
|
|
242
|
+
output = before + doneHeading + '\n' + trimmed.join('\n') + `\n- ... ${omitted} earlier entries (on disk)\n` + nonEntries.filter(l => l.trim()).join('\n') + '\n' + rest.join('\n');
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
console.log('--- ACTIVE GOALS ---');
|
|
247
|
+
console.log(output);
|
|
248
|
+
console.log('--- END GOALS ---');
|
|
249
|
+
|
|
250
|
+
// ── Inject blockers if present ──────────────────────────────────────────────
|
|
251
|
+
const blockersPath = path.join('.claude', 'memory', 'blockers.md');
|
|
252
|
+
if (fs.existsSync(blockersPath)) {
|
|
253
|
+
try {
|
|
254
|
+
const blockersContent = fs.readFileSync(blockersPath, 'utf8').trim();
|
|
255
|
+
if (blockersContent.length > 0) {
|
|
256
|
+
const blockersLines = blockersContent.split('\n').filter(l => !INJECTION.test(l));
|
|
257
|
+
const capped = blockersLines.slice(0, 15);
|
|
258
|
+
console.log('');
|
|
259
|
+
console.log('--- ACTIVE BLOCKERS ---');
|
|
260
|
+
console.log(capped.join('\n'));
|
|
261
|
+
if (blockersLines.length > 15) console.log(`... ${blockersLines.length - 15} more lines (on disk)`);
|
|
262
|
+
console.log('--- END BLOCKERS ---');
|
|
263
|
+
}
|
|
264
|
+
} catch (_) {}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ── Inject architecture decisions if present ────────────────────────────────
|
|
268
|
+
const decisionsPath = path.join('.claude', 'memory', 'decisions.md');
|
|
269
|
+
if (fs.existsSync(decisionsPath)) {
|
|
270
|
+
try {
|
|
271
|
+
const decisionsContent = fs.readFileSync(decisionsPath, 'utf8').trim();
|
|
272
|
+
if (decisionsContent.length > 0) {
|
|
273
|
+
const decisionsLines = decisionsContent.split('\n').filter(l => !INJECTION.test(l));
|
|
274
|
+
const capped = decisionsLines.slice(0, 30);
|
|
275
|
+
console.log('');
|
|
276
|
+
console.log('--- ARCHITECTURE DECISIONS ---');
|
|
277
|
+
console.log(capped.join('\n'));
|
|
278
|
+
if (decisionsLines.length > 30) console.log(`... ${decisionsLines.length - 30} more lines (on disk)`);
|
|
279
|
+
console.log('--- END DECISIONS ---');
|
|
280
|
+
}
|
|
281
|
+
} catch (_) {}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// ── Inject code patterns if present ─────────────────────────────────────────
|
|
285
|
+
const patternsPath = path.join('.claude', 'memory', 'patterns.md');
|
|
286
|
+
if (fs.existsSync(patternsPath)) {
|
|
287
|
+
try {
|
|
288
|
+
const patternsContent = fs.readFileSync(patternsPath, 'utf8').trim();
|
|
289
|
+
if (patternsContent.length > 0) {
|
|
290
|
+
const patternsLines = patternsContent.split('\n').filter(l => !INJECTION.test(l));
|
|
291
|
+
const capped = patternsLines.slice(0, 20);
|
|
292
|
+
console.log('');
|
|
293
|
+
console.log('--- CODE PATTERNS ---');
|
|
294
|
+
console.log(capped.join('\n'));
|
|
295
|
+
if (patternsLines.length > 20) console.log(`... ${patternsLines.length - 20} more lines (on disk)`);
|
|
296
|
+
console.log('--- END PATTERNS ---');
|
|
297
|
+
}
|
|
298
|
+
} catch (_) {}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Inject latest checkpoint if one exists — captures mid-session reasoning
|
|
302
|
+
const checkpointDir = path.join('.claude', 'memory', 'checkpoints');
|
|
303
|
+
if (fs.existsSync(checkpointDir)) {
|
|
304
|
+
const files = fs.readdirSync(checkpointDir)
|
|
305
|
+
.filter(f => f.endsWith('.md'))
|
|
306
|
+
.sort()
|
|
307
|
+
.reverse(); // latest first
|
|
308
|
+
if (files.length > 0) {
|
|
309
|
+
const latest = path.join(checkpointDir, files[0]);
|
|
310
|
+
const cpContent = fs.readFileSync(latest, 'utf8');
|
|
311
|
+
const cpLines = cpContent.split('\n').filter(l => !INJECTION.test(l));
|
|
312
|
+
const MAX_CP = 50;
|
|
313
|
+
const cpTrimmed = cpLines.length > MAX_CP
|
|
314
|
+
? cpLines.slice(0, MAX_CP).concat([`... ${cpLines.length - MAX_CP} more lines (on disk)`])
|
|
315
|
+
: cpLines;
|
|
316
|
+
console.log('');
|
|
317
|
+
console.log(`--- LAST CHECKPOINT (${files[0]}) ---`);
|
|
318
|
+
console.log(cpTrimmed.join('\n').trim());
|
|
319
|
+
console.log('--- END CHECKPOINT ---');
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// ── Plan status (standard + strict, copilot mode only) ────────────────────────
|
|
324
|
+
// Only fires when .claude/copilot-intent.md exists — copilot mode signal
|
|
325
|
+
if (HOOK_PROFILE !== 'minimal') {
|
|
326
|
+
const intentPath = path.join('.claude', 'copilot-intent.md');
|
|
327
|
+
if (fs.existsSync(intentPath)) {
|
|
328
|
+
const planPath = path.join('.claude', 'plan.md');
|
|
329
|
+
if (fs.existsSync(planPath)) {
|
|
330
|
+
try {
|
|
331
|
+
const planContent = fs.readFileSync(planPath, 'utf8');
|
|
332
|
+
const doneCount = (planContent.match(/Status:\s*done/gi) || []).length;
|
|
333
|
+
const blockedCount = (planContent.match(/Status:\s*blocked/gi) || []).length;
|
|
334
|
+
const ipCount = (planContent.match(/Status:\s*in-progress/gi) || []).length;
|
|
335
|
+
const pendingCount = (planContent.match(/Status:\s*pending/gi) || []).length;
|
|
336
|
+
const total = doneCount + blockedCount + ipCount + pendingCount;
|
|
337
|
+
if (total > 0) {
|
|
338
|
+
console.log('');
|
|
339
|
+
console.log(`--- PLAN STATUS: ${doneCount}/${total} done, ${ipCount} in-progress, ${blockedCount} blocked ---`);
|
|
340
|
+
}
|
|
341
|
+
} catch (_) {}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// ── Reflex guidance (strict profile only — confidence >= 0.8) ─────────────────
|
|
347
|
+
if (HOOK_PROFILE === 'strict') {
|
|
348
|
+
const reflexDir = path.join('.claude', 'memory', 'reflexes');
|
|
349
|
+
if (fs.existsSync(reflexDir)) {
|
|
350
|
+
try {
|
|
351
|
+
const reflexFiles = fs.readdirSync(reflexDir).filter(f => f.endsWith('.md'));
|
|
352
|
+
const strongReflexes = [];
|
|
353
|
+
for (const rf of reflexFiles) {
|
|
354
|
+
const rfContent = fs.readFileSync(path.join(reflexDir, rf), 'utf8');
|
|
355
|
+
const confMatch = rfContent.match(/confidence:\s*([\d.]+)/);
|
|
356
|
+
if (confMatch && parseFloat(confMatch[1]) >= 0.8) {
|
|
357
|
+
const actionMatch = rfContent.match(/action:\s*"?(.+?)"?\s*$/m);
|
|
358
|
+
if (actionMatch) strongReflexes.push(`• ${actionMatch[1].trim()}`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
if (strongReflexes.length > 0) {
|
|
362
|
+
console.log('');
|
|
363
|
+
console.log('--- LEARNED REFLEXES (confidence >= 0.8) ---');
|
|
364
|
+
console.log(strongReflexes.slice(0, 5).join('\n'));
|
|
365
|
+
console.log('--- END REFLEXES ---');
|
|
366
|
+
}
|
|
367
|
+
} catch (_) {}
|
|
368
|
+
}
|
|
369
|
+
}
|