azclaude-copilot 0.3.7 → 0.3.9
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 +24 -5
- package/package.json +1 -1
- package/templates/hooks/post-tool-use.js +28 -1
- package/templates/hooks/stop.js +29 -2
- package/templates/hooks/user-prompt.js +48 -0
package/README.md
CHANGED
|
@@ -380,7 +380,16 @@ Three layers work silently. Context compaction stops being a problem.
|
|
|
380
380
|
| Reasoning snapshot | /snapshot -> checkpoints/ | Yes -- WHY decisions were made | Manual, every 15-20 turns |
|
|
381
381
|
| Session narrative | /persist -> sessions/ | Yes -- full summary + next actions | Manual, before closing |
|
|
382
382
|
|
|
383
|
-
`UserPromptSubmit` hook injects
|
|
383
|
+
`UserPromptSubmit` hook injects before your first message every session:
|
|
384
|
+
|
|
385
|
+
| What | When | Profile |
|
|
386
|
+
|------|------|---------|
|
|
387
|
+
| `goals.md` (capped at 20 done + 30 in-progress) | Every session | all |
|
|
388
|
+
| Latest checkpoint (capped at 50 lines) | Every session | all |
|
|
389
|
+
| Plan status: `X/N done, Y in-progress, Z blocked` | Copilot mode only | standard + strict |
|
|
390
|
+
| Learned reflexes (confidence ≥ 0.8, max 5) | Always | strict only |
|
|
391
|
+
|
|
392
|
+
Token cost: ~500 tokens fixed. goals.md is bounded at 80 lines by auto-rotation — same cost at session 5 or session 500.
|
|
384
393
|
|
|
385
394
|
---
|
|
386
395
|
|
|
@@ -404,9 +413,19 @@ Control hook behavior via environment variable:
|
|
|
404
413
|
```bash
|
|
405
414
|
AZCLAUDE_HOOK_PROFILE=minimal claude # goals.md tracking only
|
|
406
415
|
AZCLAUDE_HOOK_PROFILE=standard claude # all features (default)
|
|
407
|
-
AZCLAUDE_HOOK_PROFILE=strict claude # all
|
|
416
|
+
AZCLAUDE_HOOK_PROFILE=strict claude # all + reflex guidance injection
|
|
408
417
|
```
|
|
409
418
|
|
|
419
|
+
| Feature | minimal | standard | strict |
|
|
420
|
+
|---------|---------|----------|--------|
|
|
421
|
+
| goals.md tracking | ✓ | ✓ | ✓ |
|
|
422
|
+
| Checkpoint injection | ✓ | ✓ | ✓ |
|
|
423
|
+
| Reflex observations | — | ✓ | ✓ |
|
|
424
|
+
| Cost tracking | — | ✓ | ✓ |
|
|
425
|
+
| Plan status (copilot) | — | ✓ | ✓ |
|
|
426
|
+
| Reflex guidance (≥0.8) | — | — | ✓ |
|
|
427
|
+
| Memory rotation | ✓ | ✓ | ✓ |
|
|
428
|
+
|
|
410
429
|
### Doctor Audit
|
|
411
430
|
|
|
412
431
|
```bash
|
|
@@ -450,7 +469,7 @@ azclaude-copilot/
|
|
|
450
469
|
├── DOCS.md <- full user guide
|
|
451
470
|
├── SECURITY.md <- security policy + architecture
|
|
452
471
|
├── tests/
|
|
453
|
-
│ └── test-features.sh ←
|
|
472
|
+
│ └── test-features.sh ← 1070 tests
|
|
454
473
|
```
|
|
455
474
|
|
|
456
475
|
---
|
|
@@ -476,11 +495,11 @@ The runner is stateless. These files ARE the state.
|
|
|
476
495
|
|
|
477
496
|
## Verified
|
|
478
497
|
|
|
479
|
-
|
|
498
|
+
1070 tests. Every template, command, capability, agent, and CLI feature verified.
|
|
480
499
|
|
|
481
500
|
```bash
|
|
482
501
|
bash tests/test-features.sh
|
|
483
|
-
# Results:
|
|
502
|
+
# Results: 1070 passed, 0 failed, 1070 total
|
|
484
503
|
```
|
|
485
504
|
|
|
486
505
|
---
|
package/package.json
CHANGED
|
@@ -123,6 +123,33 @@ if (!content.includes(HEADING)) {
|
|
|
123
123
|
|
|
124
124
|
try { fs.writeFileSync(goalsPath, content); } catch (_) {}
|
|
125
125
|
|
|
126
|
+
// ── Memory rotation — keep ## In progress bounded at 30 entries ──────────────
|
|
127
|
+
const ROTATE_THRESHOLD = 30;
|
|
128
|
+
const KEEP_NEWEST = 15;
|
|
129
|
+
const rotLines = content.split('\n');
|
|
130
|
+
const rotHIdx = rotLines.findIndex(l => l.trim() === HEADING);
|
|
131
|
+
if (rotHIdx !== -1) {
|
|
132
|
+
const ipEntries = [];
|
|
133
|
+
for (let i = rotHIdx + 1; i < rotLines.length; i++) {
|
|
134
|
+
if (rotLines[i].startsWith('## ')) break;
|
|
135
|
+
if (rotLines[i].startsWith('- ')) ipEntries.push({ line: rotLines[i], idx: i });
|
|
136
|
+
}
|
|
137
|
+
if (ipEntries.length >= ROTATE_THRESHOLD) {
|
|
138
|
+
const toArchive = ipEntries.slice(KEEP_NEWEST);
|
|
139
|
+
const archiveTs = new Date().toISOString().slice(0, 16);
|
|
140
|
+
const archiveDate = new Date().toISOString().slice(0, 10);
|
|
141
|
+
const archivePath = path.join(cfg, 'memory', 'sessions', `${archiveDate}-edits.md`);
|
|
142
|
+
try { fs.mkdirSync(path.join(cfg, 'memory', 'sessions'), { recursive: true }); } catch (_) {}
|
|
143
|
+
const header = `\n<!-- archived: ${archiveTs} source: post-tool-use -->\n`;
|
|
144
|
+
const payload = toArchive.map(e => e.line).join('\n') + '\n';
|
|
145
|
+
try { fs.appendFileSync(archivePath, header + payload); } catch (_) {}
|
|
146
|
+
// Rewrite goals.md keeping only newest 15 entries
|
|
147
|
+
const archivedSet = new Set(toArchive.map(e => e.idx));
|
|
148
|
+
const pruned = rotLines.filter((_, i) => !archivedSet.has(i));
|
|
149
|
+
try { fs.writeFileSync(goalsPath, pruned.join('\n')); } catch (_) {}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
126
153
|
} // end isFileTool goals tracking
|
|
127
154
|
|
|
128
155
|
// ── Reflex observation capture (standard/strict only) ───────────────────────
|
|
@@ -192,5 +219,5 @@ let editCount = 1;
|
|
|
192
219
|
try { editCount = parseInt(fs.readFileSync(counterPath, 'utf8'), 10) + 1; } catch (_) {}
|
|
193
220
|
try { fs.writeFileSync(counterPath, String(editCount)); } catch (_) {}
|
|
194
221
|
if (editCount > 0 && editCount % 15 === 0) {
|
|
195
|
-
process.
|
|
222
|
+
process.stderr.write(`\n⚠ ${editCount} edits this session — run /snapshot before context compaction loses your reasoning\n`);
|
|
196
223
|
}
|
package/templates/hooks/stop.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
const fs = require('fs');
|
|
12
12
|
const path = require('path');
|
|
13
|
+
const os = require('os');
|
|
13
14
|
|
|
14
15
|
// ── Hook profile gate ───────────────────────────────────────────────────────
|
|
15
16
|
// AZCLAUDE_HOOK_PROFILE=minimal|standard|strict (default: standard)
|
|
@@ -62,8 +63,30 @@ if (content.includes(IN_PROGRESS)) {
|
|
|
62
63
|
|
|
63
64
|
content = withoutIP.join('\n');
|
|
64
65
|
} else {
|
|
65
|
-
// Empty In progress — just remove the heading
|
|
66
|
-
content = content.replace(
|
|
66
|
+
// Empty In progress — just remove the heading + any trailing blank lines
|
|
67
|
+
content = content.replace(new RegExp('\\n' + IN_PROGRESS + '\\n(\\n)*', 'g'), '\n');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ── Trim "Done this session" to max 20 entries (overflow → archive) ──────────
|
|
72
|
+
const DONE_KEEP = 20;
|
|
73
|
+
const trimLines = content.split('\n');
|
|
74
|
+
const dTrimIdx = trimLines.findIndex(l => l.trim() === DONE);
|
|
75
|
+
if (dTrimIdx !== -1) {
|
|
76
|
+
const doneEntries = [];
|
|
77
|
+
for (let i = dTrimIdx + 1; i < trimLines.length; i++) {
|
|
78
|
+
if (trimLines[i].startsWith('## ')) break;
|
|
79
|
+
if (trimLines[i].startsWith('- ')) doneEntries.push({ line: trimLines[i], idx: i });
|
|
80
|
+
}
|
|
81
|
+
if (doneEntries.length > DONE_KEEP) {
|
|
82
|
+
const toArchive = doneEntries.slice(DONE_KEEP);
|
|
83
|
+
const archivePath = path.join(cfg, 'memory', 'sessions', `${today}-edits.md`);
|
|
84
|
+
try { fs.mkdirSync(path.join(cfg, 'memory', 'sessions'), { recursive: true }); } catch (_) {}
|
|
85
|
+
const header = `\n<!-- archived: ${today} source: stop -->\n`;
|
|
86
|
+
const payload = toArchive.map(e => e.line).join('\n') + '\n';
|
|
87
|
+
try { fs.appendFileSync(archivePath, header + payload); } catch (_) {}
|
|
88
|
+
const archivedSet = new Set(toArchive.map(e => e.idx));
|
|
89
|
+
content = trimLines.filter((_, i) => !archivedSet.has(i)).join('\n');
|
|
67
90
|
}
|
|
68
91
|
}
|
|
69
92
|
|
|
@@ -71,6 +94,10 @@ if (content.includes(IN_PROGRESS)) {
|
|
|
71
94
|
content = content.replace(/^Updated: .*/m, `Updated: ${today}`);
|
|
72
95
|
try { fs.writeFileSync(goalsPath, content); } catch (_) {}
|
|
73
96
|
|
|
97
|
+
// ── Reset edit counter so checkpoint reminder starts fresh next session ───────
|
|
98
|
+
const counterPath = path.join(os.tmpdir(), `.azclaude-edit-count-${process.ppid || process.pid}`);
|
|
99
|
+
try { fs.writeFileSync(counterPath, '0'); } catch (_) {}
|
|
100
|
+
|
|
74
101
|
// ── Warn if /persist was not run (only in AZCLAUDE projects with obs dir) ──
|
|
75
102
|
const obsDir = path.join('ops', 'observations');
|
|
76
103
|
if (!fs.existsSync(obsDir)) process.exit(0);
|
|
@@ -94,3 +94,51 @@ if (fs.existsSync(checkpointDir)) {
|
|
|
94
94
|
console.log('--- END CHECKPOINT ---');
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
|
+
|
|
98
|
+
// ── Plan status (standard + strict, copilot mode only) ────────────────────────
|
|
99
|
+
// Only fires when .claude/copilot-intent.md exists — copilot mode signal
|
|
100
|
+
if (HOOK_PROFILE !== 'minimal') {
|
|
101
|
+
const intentPath = path.join('.claude', 'copilot-intent.md');
|
|
102
|
+
if (fs.existsSync(intentPath)) {
|
|
103
|
+
const planPath = path.join('.claude', 'plan.md');
|
|
104
|
+
if (fs.existsSync(planPath)) {
|
|
105
|
+
try {
|
|
106
|
+
const planContent = fs.readFileSync(planPath, 'utf8');
|
|
107
|
+
const doneCount = (planContent.match(/Status:\s*done/gi) || []).length;
|
|
108
|
+
const blockedCount = (planContent.match(/Status:\s*blocked/gi) || []).length;
|
|
109
|
+
const ipCount = (planContent.match(/Status:\s*in-progress/gi) || []).length;
|
|
110
|
+
const pendingCount = (planContent.match(/Status:\s*pending/gi) || []).length;
|
|
111
|
+
const total = doneCount + blockedCount + ipCount + pendingCount;
|
|
112
|
+
if (total > 0) {
|
|
113
|
+
console.log('');
|
|
114
|
+
console.log(`--- PLAN STATUS: ${doneCount}/${total} done, ${ipCount} in-progress, ${blockedCount} blocked ---`);
|
|
115
|
+
}
|
|
116
|
+
} catch (_) {}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ── Reflex guidance (strict profile only — confidence >= 0.8) ─────────────────
|
|
122
|
+
if (HOOK_PROFILE === 'strict') {
|
|
123
|
+
const reflexDir = path.join('.claude', 'memory', 'reflexes');
|
|
124
|
+
if (fs.existsSync(reflexDir)) {
|
|
125
|
+
try {
|
|
126
|
+
const reflexFiles = fs.readdirSync(reflexDir).filter(f => f.endsWith('.md'));
|
|
127
|
+
const strongReflexes = [];
|
|
128
|
+
for (const rf of reflexFiles) {
|
|
129
|
+
const rfContent = fs.readFileSync(path.join(reflexDir, rf), 'utf8');
|
|
130
|
+
const confMatch = rfContent.match(/confidence:\s*([\d.]+)/);
|
|
131
|
+
if (confMatch && parseFloat(confMatch[1]) >= 0.8) {
|
|
132
|
+
const actionMatch = rfContent.match(/action:\s*"?(.+?)"?\s*$/m);
|
|
133
|
+
if (actionMatch) strongReflexes.push(`• ${actionMatch[1].trim()}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (strongReflexes.length > 0) {
|
|
137
|
+
console.log('');
|
|
138
|
+
console.log('--- LEARNED REFLEXES (confidence >= 0.8) ---');
|
|
139
|
+
console.log(strongReflexes.slice(0, 5).join('\n'));
|
|
140
|
+
console.log('--- END REFLEXES ---');
|
|
141
|
+
}
|
|
142
|
+
} catch (_) {}
|
|
143
|
+
}
|
|
144
|
+
}
|