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.
Files changed (45) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +2 -2
  3. package/README.md +9 -7
  4. package/bin/cli.js +53 -1
  5. package/package.json +2 -2
  6. package/templates/CLAUDE.md +35 -1
  7. package/templates/agents/cc-cli-integrator.md +5 -0
  8. package/templates/agents/cc-template-author.md +7 -0
  9. package/templates/agents/cc-test-maintainer.md +5 -0
  10. package/templates/agents/code-reviewer.md +11 -0
  11. package/templates/agents/constitution-guard.md +9 -0
  12. package/templates/agents/devops-engineer.md +9 -0
  13. package/templates/agents/loop-controller.md +7 -0
  14. package/templates/agents/milestone-builder.md +7 -0
  15. package/templates/agents/orchestrator-init.md +9 -1
  16. package/templates/agents/orchestrator.md +8 -0
  17. package/templates/agents/problem-architect.md +29 -1
  18. package/templates/agents/qa-engineer.md +9 -0
  19. package/templates/agents/security-auditor.md +9 -0
  20. package/templates/agents/spec-reviewer.md +9 -0
  21. package/templates/agents/test-writer.md +11 -0
  22. package/templates/capabilities/manifest.md +2 -0
  23. package/templates/capabilities/shared/context-inoculation.md +39 -0
  24. package/templates/capabilities/shared/reward-hack-detection.md +32 -0
  25. package/templates/commands/audit.md +8 -0
  26. package/templates/commands/ghost-test.md +99 -0
  27. package/templates/commands/inoculate.md +76 -0
  28. package/templates/commands/sentinel.md +3 -0
  29. package/templates/commands/ship.md +6 -0
  30. package/templates/commands/test.md +10 -0
  31. package/templates/hooks/post-tool-use.js +341 -277
  32. package/templates/hooks/pre-tool-use.js +344 -292
  33. package/templates/hooks/stop.js +198 -151
  34. package/templates/hooks/user-prompt.js +369 -163
  35. package/templates/scripts/statusline.sh +105 -0
  36. package/templates/skills/agent-creator/SKILL.md +11 -0
  37. package/templates/skills/architecture-advisor/SKILL.md +21 -16
  38. package/templates/skills/debate/SKILL.md +5 -0
  39. package/templates/skills/env-scanner/SKILL.md +5 -0
  40. package/templates/skills/frontend-design/SKILL.md +5 -0
  41. package/templates/skills/mcp/SKILL.md +3 -0
  42. package/templates/skills/security/SKILL.md +3 -0
  43. package/templates/skills/session-guard/SKILL.md +3 -0
  44. package/templates/skills/skill-creator/SKILL.md +12 -0
  45. 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
- if (promptText) {
26
- 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;
27
- if (PROMPT_INJECT.test(promptText)) {
28
- const sid = process.ppid || process.pid;
29
- const seclog = path.join(os.tmpdir(), `.azclaude-seclog-${sid}`);
30
- const entry = JSON.stringify({ ts: new Date().toISOString(), hook: 'user-prompt', rule: 'prompt-injection-attempt', level: 'warn', target: promptText.slice(0, 80) });
31
- try { fs.appendFileSync(seclog, entry + '\n'); } catch (_) {}
32
- process.stderr.write('\n⚠ SECURITY: Prompt injection pattern detected in user input.\n');
33
- }
34
- }
35
- } catch (_) {}
36
-
37
- // ── Fire once per session only — keyed by parent PID
38
- const marker = path.join(os.tmpdir(), `.azclaude-session-${process.ppid || process.pid}`);
39
- if (fs.existsSync(marker)) process.exit(0);
40
- try { fs.writeFileSync(marker, ''); } catch (_) {}
41
-
42
- // Only proceed if this is an AZCLAUDE project (goals.md exists)
43
- const goalsPath = path.join('.claude', 'memory', 'goals.md');
44
- if (!fs.existsSync(goalsPath)) process.exit(0);
45
-
46
- // Ensure required directories exist only in AZCLAUDE projects
47
- for (const d of ['.claude/memory', '.claude/memory/checkpoints']) {
48
- try { fs.mkdirSync(d, { recursive: true }); } catch (_) {}
49
- }
50
-
51
- // Strip prompt-injection attempts before outputting into context
52
- 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;
53
- const content = fs.readFileSync(goalsPath, 'utf8');
54
- const filtered = content.split('\n').filter(l => !INJECTION.test(l)).join('\n');
55
-
56
- // Warn if previous session was interrupted (In progress entries survived)
57
- const ipMatch = filtered.match(/^## In progress\n((?:- .+\n?)+)/m);
58
- if (ipMatch) {
59
- console.log('⚠ PREVIOUS SESSION INTERRUPTED files were being edited:');
60
- console.log(ipMatch[1].trimEnd());
61
- console.log('Resume or discard before starting new work.');
62
- console.log('');
63
- }
64
-
65
- // Cap "Done this session" to last 20 entries — older entries are still on disk
66
- const doneHeading = '## Done this session';
67
- const doneIdx = filtered.indexOf(doneHeading);
68
- let output = filtered;
69
- if (doneIdx !== -1) {
70
- const before = filtered.slice(0, doneIdx);
71
- const afterDone = filtered.slice(doneIdx + doneHeading.length);
72
- const doneLines = afterDone.split('\n');
73
- const entries = [];
74
- const rest = [];
75
- let pastDone = false;
76
- for (const line of doneLines) {
77
- if (pastDone) { rest.push(line); continue; }
78
- if (line.startsWith('## ') && line.trim() !== '') { pastDone = true; rest.push(line); continue; }
79
- entries.push(line);
80
- }
81
- const MAX_DONE = 20;
82
- const entryLines = entries.filter(l => l.startsWith('- '));
83
- if (entryLines.length > MAX_DONE) {
84
- const trimmed = entryLines.slice(0, MAX_DONE);
85
- const nonEntries = entries.filter(l => !l.startsWith('- '));
86
- const omitted = entryLines.length - MAX_DONE;
87
- 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');
88
- }
89
- }
90
-
91
- console.log('--- ACTIVE GOALS ---');
92
- console.log(output);
93
- console.log('--- END GOALS ---');
94
-
95
- // Inject latest checkpoint if one exists — captures mid-session reasoning
96
- const checkpointDir = path.join('.claude', 'memory', 'checkpoints');
97
- if (fs.existsSync(checkpointDir)) {
98
- const files = fs.readdirSync(checkpointDir)
99
- .filter(f => f.endsWith('.md'))
100
- .sort()
101
- .reverse(); // latest first
102
- if (files.length > 0) {
103
- const latest = path.join(checkpointDir, files[0]);
104
- const cpContent = fs.readFileSync(latest, 'utf8');
105
- const cpLines = cpContent.split('\n').filter(l => !INJECTION.test(l));
106
- const MAX_CP = 50;
107
- const cpTrimmed = cpLines.length > MAX_CP
108
- ? cpLines.slice(0, MAX_CP).concat([`... ${cpLines.length - MAX_CP} more lines (on disk)`])
109
- : cpLines;
110
- console.log('');
111
- console.log(`--- LAST CHECKPOINT (${files[0]}) ---`);
112
- console.log(cpTrimmed.join('\n').trim());
113
- console.log('--- END CHECKPOINT ---');
114
- }
115
- }
116
-
117
- // ── Plan status (standard + strict, copilot mode only) ────────────────────────
118
- // Only fires when .claude/copilot-intent.md exists — copilot mode signal
119
- if (HOOK_PROFILE !== 'minimal') {
120
- const intentPath = path.join('.claude', 'copilot-intent.md');
121
- if (fs.existsSync(intentPath)) {
122
- const planPath = path.join('.claude', 'plan.md');
123
- if (fs.existsSync(planPath)) {
124
- try {
125
- const planContent = fs.readFileSync(planPath, 'utf8');
126
- const doneCount = (planContent.match(/Status:\s*done/gi) || []).length;
127
- const blockedCount = (planContent.match(/Status:\s*blocked/gi) || []).length;
128
- const ipCount = (planContent.match(/Status:\s*in-progress/gi) || []).length;
129
- const pendingCount = (planContent.match(/Status:\s*pending/gi) || []).length;
130
- const total = doneCount + blockedCount + ipCount + pendingCount;
131
- if (total > 0) {
132
- console.log('');
133
- console.log(`--- PLAN STATUS: ${doneCount}/${total} done, ${ipCount} in-progress, ${blockedCount} blocked ---`);
134
- }
135
- } catch (_) {}
136
- }
137
- }
138
- }
139
-
140
- // ── Reflex guidance (strict profile only — confidence >= 0.8) ─────────────────
141
- if (HOOK_PROFILE === 'strict') {
142
- const reflexDir = path.join('.claude', 'memory', 'reflexes');
143
- if (fs.existsSync(reflexDir)) {
144
- try {
145
- const reflexFiles = fs.readdirSync(reflexDir).filter(f => f.endsWith('.md'));
146
- const strongReflexes = [];
147
- for (const rf of reflexFiles) {
148
- const rfContent = fs.readFileSync(path.join(reflexDir, rf), 'utf8');
149
- const confMatch = rfContent.match(/confidence:\s*([\d.]+)/);
150
- if (confMatch && parseFloat(confMatch[1]) >= 0.8) {
151
- const actionMatch = rfContent.match(/action:\s*"?(.+?)"?\s*$/m);
152
- if (actionMatch) strongReflexes.push(`• ${actionMatch[1].trim()}`);
153
- }
154
- }
155
- if (strongReflexes.length > 0) {
156
- console.log('');
157
- console.log('--- LEARNED REFLEXES (confidence >= 0.8) ---');
158
- console.log(strongReflexes.slice(0, 5).join('\n'));
159
- console.log('--- END REFLEXES ---');
160
- }
161
- } catch (_) {}
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
+ }