@hanzlaa/rcode 4.4.4 → 4.6.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/AGENTS.md +11 -4
- package/CLAUDE.md +11 -4
- package/CONTRIBUTING.md +6 -1
- package/cli/doctor.js +122 -0
- package/cli/index.js +4 -0
- package/cli/install.js +8 -0
- package/cli/lib/namespace-migrate.cjs +216 -0
- package/cli/migrate-namespace.js +61 -0
- package/cli/update.js +19 -0
- package/dist/rcode.js +215 -208
- package/package.json +14 -12
- package/rcode/agents/rcode-mariam.md +6 -0
- package/rcode/agents/rcode-sadiq.md +6 -0
- package/rcode/agents/rcode-waleed.md +6 -0
- package/rcode/bin/lib/brain.cjs +353 -0
- package/rcode/bin/lib/gitignore.cjs +126 -0
- package/rcode/bin/lib/memory-drift.cjs +237 -0
- package/rcode/bin/lib/memory-select.cjs +263 -0
- package/rcode/bin/lib/progress.cjs +440 -0
- package/rcode/bin/lib/state-reader.cjs +127 -0
- package/rcode/bin/lib/summary.cjs +82 -0
- package/rcode/bin/rcode-hooks.cjs +230 -64
- package/rcode/bin/rcode-tools.cjs +106 -985
- package/rcode/data/intent-table.json +19 -19
- package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/templates/heartbeat.sh +0 -0
- package/rcode/skills/agents/mariam-marketing/SKILL.md +1 -0
- package/rcode/skills/agents/sadiq-analyst/SKILL.md +1 -0
- package/rcode/skills/agents/waleed-architect/SKILL.md +1 -0
- package/rcode/templates/memory/INDEX.md +6 -1
- package/rcode/templates/settings-hooks.json +12 -1
- package/rcode/workflows/council.md +29 -1
- package/rcode/workflows/do.md +2 -2
- package/rcode/workflows/enable-hooks.md +3 -2
- package/rcode/workflows/new-project-roadmap.md +2 -2
- package/server/dashboard.js +6 -3
- package/server/lib/html/client/components/App.js +1 -1
- package/server/lib/html/client/components/OrchPanel.js +2 -2
- package/server/lib/html/client/components/XtermPanel.js +98 -23
- package/server/lib/html/client/orchestrator.js +28 -15
- package/server/lib/html/client/views/OrchestrationView.js +247 -169
- package/server/lib/html/css.js +596 -227
- package/server/lib/html/shell.js +9 -3
- package/server/orchestrator.js +3 -4
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
* compact-nudge — advise /rcode-trim or /clear after N Edit/Write calls (#749)
|
|
15
15
|
* pre-tool-use — stderr warning before large file reads to avoid context bloat (#749)
|
|
16
16
|
* prompt-router — nudge toward rcode commands for memory consistency (#892)
|
|
17
|
+
* session-start — emit one-line project status primer at session open (#947)
|
|
18
|
+
* drift — print the full memory-drift report (#958)
|
|
17
19
|
*
|
|
18
20
|
* All subcommands read stdin JSON from the hook execution context.
|
|
19
21
|
* Pure Node stdlib. No external dependencies.
|
|
@@ -24,6 +26,54 @@ const os = require('os');
|
|
|
24
26
|
const path = require('path');
|
|
25
27
|
const { execSync, spawnSync } = require('child_process');
|
|
26
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Self-healing lib require (#960). When this file runs from an installed
|
|
31
|
+
* `.rcode/bin/` whose `lib/` is stale or partial (fresh git worktree, merge/
|
|
32
|
+
* pull that changed `rcode/bin/lib/` without a mirror sync), a hard require
|
|
33
|
+
* crashes EVERY hook — the user sees a SessionStart loader error and loses
|
|
34
|
+
* the status line entirely. Instead: on MODULE_NOT_FOUND, try healing from
|
|
35
|
+
* the in-repo source of truth (`rcode/bin/lib/<name>` relative to the project
|
|
36
|
+
* root that contains this `.rcode/`), retry once, and otherwise fail open so
|
|
37
|
+
* hooks degrade (no memory injection / drift check) rather than die.
|
|
38
|
+
*/
|
|
39
|
+
function requireLib(name) {
|
|
40
|
+
const local = path.join(__dirname, 'lib', name);
|
|
41
|
+
try { return require(local); } catch (err) {
|
|
42
|
+
if (err && err.code !== 'MODULE_NOT_FOUND') throw err;
|
|
43
|
+
try {
|
|
44
|
+
const src = path.join(__dirname, '..', '..', 'rcode', 'bin', 'lib', name);
|
|
45
|
+
if (fs.existsSync(src)) {
|
|
46
|
+
fs.mkdirSync(path.dirname(local), { recursive: true });
|
|
47
|
+
fs.copyFileSync(src, local);
|
|
48
|
+
return require(local);
|
|
49
|
+
}
|
|
50
|
+
} catch { /* healing is best-effort */ }
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const _stateReader = requireLib('state-reader.cjs') || {};
|
|
56
|
+
const resolveActivePhase = _stateReader.resolveActivePhase || (() => ({ activePhase: null, phaseLabel: null }));
|
|
57
|
+
const readSprintProgress = _stateReader.readSprintProgress || (() => ({ completedCount: 0, incompleteTasks: [] }));
|
|
58
|
+
const readRecentCommits = _stateReader.readRecentCommits || (() => []);
|
|
59
|
+
const readMilestoneHint = _stateReader.readMilestoneHint || (() => null);
|
|
60
|
+
|
|
61
|
+
const _memSelect = requireLib('memory-select.cjs') || {};
|
|
62
|
+
const selectMemoryChunks = _memSelect.selectMemoryChunks || (() => []);
|
|
63
|
+
const formatMemoryContext = _memSelect.formatMemoryContext || (() => '');
|
|
64
|
+
const hasMemory = _memSelect.hasMemory || (() => false);
|
|
65
|
+
|
|
66
|
+
// lib/memory-drift.cjs is optional at the module-load level: some hook-copy
|
|
67
|
+
// test fixtures deliberately stage a minimal bin/lib/ (only state-reader.cjs)
|
|
68
|
+
// to exercise other subcommands' missing-file handling. A hard top-level
|
|
69
|
+
// require would crash every subcommand, not just `drift`/`post-commit`, so
|
|
70
|
+
// this loads lazily and fails open — same pattern as INTENT_TABLE below.
|
|
71
|
+
let checkDrift = null;
|
|
72
|
+
{
|
|
73
|
+
const _drift = requireLib('memory-drift.cjs');
|
|
74
|
+
if (_drift) ({ checkDrift } = _drift);
|
|
75
|
+
}
|
|
76
|
+
|
|
27
77
|
/**
|
|
28
78
|
* Read and parse stdin JSON.
|
|
29
79
|
*/
|
|
@@ -116,6 +166,56 @@ async function preWorkflow() {
|
|
|
116
166
|
}
|
|
117
167
|
}
|
|
118
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Emit a one-line systemMessage nudge toward /rcode-memory-update when
|
|
171
|
+
* memory-drift.cjs finds drifts, at most once per session (#958).
|
|
172
|
+
*
|
|
173
|
+
* "Session" here has no reliable id on a post-commit hook payload, so we
|
|
174
|
+
* fall back to parent-pid + hourly bucket — same fallback prompt-router
|
|
175
|
+
* already uses (see promptRouter() below) — scoped naturally to the
|
|
176
|
+
* current shell without requiring a session_id in the payload.
|
|
177
|
+
*/
|
|
178
|
+
function maybeEmitDriftNudge(cwd, input) {
|
|
179
|
+
try {
|
|
180
|
+
if (!checkDrift) return;
|
|
181
|
+
const { drifts } = checkDrift(cwd);
|
|
182
|
+
if (drifts.length === 0) return;
|
|
183
|
+
|
|
184
|
+
const sessionFallback = String(process.ppid) + '-' + String(Math.floor(Date.now() / 3600000));
|
|
185
|
+
const sessionId = input?.session_id || input?.tool_input?.session_id || sessionFallback;
|
|
186
|
+
const safeId = String(sessionId).replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
187
|
+
|
|
188
|
+
const cacheDir = path.join(cwd, '.rcode', '.cache');
|
|
189
|
+
const markerFile = path.join(cacheDir, `drift-nudge-${safeId}.json`);
|
|
190
|
+
if (fs.existsSync(markerFile)) return;
|
|
191
|
+
|
|
192
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
193
|
+
fs.writeFileSync(markerFile, JSON.stringify({ ts: Date.now(), count: drifts.length }));
|
|
194
|
+
|
|
195
|
+
const kinds = [...new Set(drifts.map((d) => d.kind))].join(', ');
|
|
196
|
+
const nudge =
|
|
197
|
+
`⚠ Memory drift detected (${drifts.length} finding${drifts.length === 1 ? '' : 's'}: ${kinds}) — ` +
|
|
198
|
+
`run /rcode-memory-update, or \`rcode-hooks drift\` for the full report.`;
|
|
199
|
+
process.stdout.write(JSON.stringify({ systemMessage: nudge }) + '\n');
|
|
200
|
+
} catch {
|
|
201
|
+
// Advisory only — never break the commit flow.
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* drift: Print the full memory-drift report (#958). Standalone CLI use —
|
|
207
|
+
* not gated by the once-per-session nudge limiter in maybeEmitDriftNudge().
|
|
208
|
+
*/
|
|
209
|
+
function driftCommand() {
|
|
210
|
+
if (!checkDrift) {
|
|
211
|
+
console.error('rcode/bin/lib/memory-drift.cjs failed to load — cannot run drift check.');
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
const report = checkDrift(process.cwd());
|
|
215
|
+
console.log(JSON.stringify(report, null, 2));
|
|
216
|
+
process.exit(0);
|
|
217
|
+
}
|
|
218
|
+
|
|
119
219
|
/**
|
|
120
220
|
* post-commit: Verify commit format and banned patterns.
|
|
121
221
|
* Warns (not blocking) if violations found.
|
|
@@ -197,6 +297,8 @@ async function postCommit() {
|
|
|
197
297
|
violations.forEach((v) => console.warn(` • ${v}`));
|
|
198
298
|
}
|
|
199
299
|
|
|
300
|
+
maybeEmitDriftNudge(process.cwd(), input);
|
|
301
|
+
|
|
200
302
|
process.exit(0);
|
|
201
303
|
} catch (err) {
|
|
202
304
|
console.error(`Hook error: ${err.message}`);
|
|
@@ -353,67 +455,16 @@ async function preCompact() {
|
|
|
353
455
|
}
|
|
354
456
|
|
|
355
457
|
// ── 2. Determine active phase ────────────────────────────────────────
|
|
356
|
-
const
|
|
357
|
-
const executing = phases.find((p) => p && p.status === 'executing');
|
|
358
|
-
const matched = phases.find(
|
|
359
|
-
(p) => p && (p.name === state?.current_phase || p.number === state?.current_phase)
|
|
360
|
-
);
|
|
361
|
-
const activePhase = executing || matched || null;
|
|
362
|
-
const phaseLabel = activePhase
|
|
363
|
-
? (activePhase.number || activePhase.name || state?.current_phase)
|
|
364
|
-
: (state?.current_phase || null);
|
|
458
|
+
const { activePhase, phaseLabel } = resolveActivePhase(state);
|
|
365
459
|
|
|
366
460
|
// ── 3. Read active SPRINT.md (incomplete tasks) ──────────────────────
|
|
367
|
-
const incompleteTasks =
|
|
368
|
-
const completedCount = { done: 0, total: 0 };
|
|
369
|
-
const planningBase = path.join(cwd, '.planning', 'phases');
|
|
370
|
-
if (phaseLabel && fs.existsSync(planningBase)) {
|
|
371
|
-
try {
|
|
372
|
-
const phaseDirs = fs.readdirSync(planningBase)
|
|
373
|
-
.filter(d => d.startsWith(String(phaseLabel)));
|
|
374
|
-
for (const pd of phaseDirs) {
|
|
375
|
-
const pdPath = path.join(planningBase, pd);
|
|
376
|
-
if (!fs.statSync(pdPath).isDirectory()) continue;
|
|
377
|
-
const sprintFiles = fs.readdirSync(pdPath)
|
|
378
|
-
.filter(f => f.endsWith('-SPRINT.md'))
|
|
379
|
-
.sort()
|
|
380
|
-
.reverse(); // most recent first
|
|
381
|
-
if (sprintFiles.length === 0) continue;
|
|
382
|
-
const sprintText = fs.readFileSync(path.join(pdPath, sprintFiles[0]), 'utf8');
|
|
383
|
-
for (const line of sprintText.split('\n')) {
|
|
384
|
-
const done = /^\s*-\s*\[x\]/i.test(line);
|
|
385
|
-
const pending = /^\s*-\s*\[ \]/.test(line);
|
|
386
|
-
if (done || pending) completedCount.total++;
|
|
387
|
-
if (done) completedCount.done++;
|
|
388
|
-
if (pending) {
|
|
389
|
-
const task = line.replace(/^\s*-\s*\[ \]\s*/, '').trim();
|
|
390
|
-
if (task) incompleteTasks.push(task);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
break; // use first matching phase dir only
|
|
394
|
-
}
|
|
395
|
-
} catch {}
|
|
396
|
-
}
|
|
461
|
+
const { completedCount, incompleteTasks } = readSprintProgress(phaseLabel, cwd);
|
|
397
462
|
|
|
398
463
|
// ── 4. Recent git commits ────────────────────────────────────────────
|
|
399
|
-
|
|
400
|
-
try {
|
|
401
|
-
const log = execSync('git log --oneline -5 --no-decorate 2>/dev/null', {
|
|
402
|
-
cwd, encoding: 'utf8', timeout: 3000,
|
|
403
|
-
}).trim();
|
|
404
|
-
recentCommits = log ? log.split('\n').filter(Boolean) : [];
|
|
405
|
-
} catch {}
|
|
464
|
+
const recentCommits = readRecentCommits(cwd);
|
|
406
465
|
|
|
407
466
|
// ── 5. Read milestone / roadmap headline ────────────────────────────
|
|
408
|
-
|
|
409
|
-
if (!milestoneHint) {
|
|
410
|
-
for (const rp of ['.planning/ROADMAP.md', '.planning/milestones/ROADMAP.md']) {
|
|
411
|
-
const full = path.join(cwd, rp);
|
|
412
|
-
if (!fs.existsSync(full)) continue;
|
|
413
|
-
const m = fs.readFileSync(full, 'utf8').match(/^##\s+Milestone\s+(M\d+[^\n]*)/m);
|
|
414
|
-
if (m) { milestoneHint = m[1].trim(); break; }
|
|
415
|
-
}
|
|
416
|
-
}
|
|
467
|
+
const milestoneHint = readMilestoneHint(state, cwd);
|
|
417
468
|
|
|
418
469
|
// ── 6. Read last 3 decisions from STATE.md ───────────────────────────
|
|
419
470
|
let recentDecisions = [];
|
|
@@ -502,7 +553,24 @@ async function preCompact() {
|
|
|
502
553
|
}
|
|
503
554
|
msgParts.push('Run `/rcode-resume-work` to restore full context, or `/clear` then paste `.rcode/.continue-here.md`.');
|
|
504
555
|
|
|
505
|
-
|
|
556
|
+
// ── 10. Relevance-ranked memory survival context (#958) ──────────────
|
|
557
|
+
// Smaller budget than session-start — this rides alongside HANDOFF.json
|
|
558
|
+
// as compaction survival context, not a full primer. Degrades silently
|
|
559
|
+
// when .rcode/memory/ is missing or empty.
|
|
560
|
+
const payload = { systemMessage: msgParts.join(' | ') };
|
|
561
|
+
if (hasMemory(cwd)) {
|
|
562
|
+
try {
|
|
563
|
+
const selection = selectMemoryChunks(cwd, { defaultBudget: 600 });
|
|
564
|
+
const additionalContext = formatMemoryContext(selection);
|
|
565
|
+
if (additionalContext) {
|
|
566
|
+
payload.hookSpecificOutput = {
|
|
567
|
+
hookEventName: 'PreCompact',
|
|
568
|
+
additionalContext,
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
} catch { /* memory injection is advisory — never block compaction */ }
|
|
572
|
+
}
|
|
573
|
+
process.stdout.write(JSON.stringify(payload) + '\n');
|
|
506
574
|
process.exit(0);
|
|
507
575
|
} catch (err) {
|
|
508
576
|
console.error(`Hook error: ${err.message}`);
|
|
@@ -626,12 +694,30 @@ async function costTrack() {
|
|
|
626
694
|
// INTENT_TABLE — keyword map for prompt-router (#892).
|
|
627
695
|
// Source of truth: rcode/workflows/do.md routing table (~285-320). First-match-wins.
|
|
628
696
|
// Loaded from data file to keep this file under 1000 lines (#896).
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
697
|
+
//
|
|
698
|
+
// Fail-open (#952 review H1): the load is wrapped so a MISSING data file degrades
|
|
699
|
+
// the prompt-router to a no-op instead of throwing at module-require time, which
|
|
700
|
+
// would crash EVERY hook subcommand (bash-guard, pre-edit, session-start, …) —
|
|
701
|
+
// not just the router.
|
|
702
|
+
//
|
|
703
|
+
// #952 follow-up: a missing data file used to degrade silently (empty table,
|
|
704
|
+
// zero output, no way for the user to know auto-detection is broken). It now
|
|
705
|
+
// still fails open — prompt-router still exits 0 and never blocks — but
|
|
706
|
+
// promptRouter() emits a one-time additionalContext warning pointing at the
|
|
707
|
+
// fix (`npx @hanzlaa/rcode update`) instead of no-op-ing forever.
|
|
708
|
+
let INTENT_TABLE = [];
|
|
709
|
+
let INTENT_TABLE_LOAD_ERROR = null;
|
|
710
|
+
try {
|
|
711
|
+
INTENT_TABLE = JSON.parse(
|
|
712
|
+
require('fs').readFileSync(
|
|
713
|
+
require('path').join(__dirname, '..', 'data', 'intent-table.json'),
|
|
714
|
+
'utf8'
|
|
715
|
+
)
|
|
716
|
+
);
|
|
717
|
+
} catch (err) {
|
|
718
|
+
INTENT_TABLE = [];
|
|
719
|
+
INTENT_TABLE_LOAD_ERROR = err;
|
|
720
|
+
}
|
|
635
721
|
|
|
636
722
|
/**
|
|
637
723
|
* Inline flat-YAML parser — mirrors parseSimpleYaml in rcode-tools.cjs:91.
|
|
@@ -758,6 +844,30 @@ function promptRouter() {
|
|
|
758
844
|
process.exit(0);
|
|
759
845
|
}
|
|
760
846
|
|
|
847
|
+
// ── Missing data file: warn once instead of silently no-op-ing (#952) ──
|
|
848
|
+
// A consumer install missing rcode/data/intent-table.json used to degrade
|
|
849
|
+
// to a permanent, invisible no-op — no output, no hint anything was wrong.
|
|
850
|
+
// Warn once per machine (tmpdir marker keyed by project path) and point
|
|
851
|
+
// at the fix, then continue with the (empty) table like before.
|
|
852
|
+
if (INTENT_TABLE_LOAD_ERROR) {
|
|
853
|
+
const warnKey = cwd.replace(/[^a-zA-Z0-9]/g, '_');
|
|
854
|
+
const warnFile = path.join(os.tmpdir(), 'rcode-intent-table-missing-warned-' + warnKey);
|
|
855
|
+
if (!fs.existsSync(warnFile)) {
|
|
856
|
+
try { fs.writeFileSync(warnFile, String(process.pid)); } catch { /* fail open */ }
|
|
857
|
+
const payload = {
|
|
858
|
+
hookSpecificOutput: {
|
|
859
|
+
hookEventName,
|
|
860
|
+
additionalContext:
|
|
861
|
+
'rcode/data/intent-table.json is missing — skill auto-detection from prompts is disabled. ' +
|
|
862
|
+
'Run `npx @hanzlaa/rcode update` to reinstall the missing data files.',
|
|
863
|
+
},
|
|
864
|
+
};
|
|
865
|
+
process.stdout.write(JSON.stringify(payload));
|
|
866
|
+
process.exit(0);
|
|
867
|
+
}
|
|
868
|
+
process.exit(0);
|
|
869
|
+
}
|
|
870
|
+
|
|
761
871
|
// ── Keyword match (first-match-wins, case-insensitive) ───────────────
|
|
762
872
|
const lower = prompt.toLowerCase();
|
|
763
873
|
let matched = null;
|
|
@@ -808,8 +918,11 @@ function promptRouter() {
|
|
|
808
918
|
// gentle memory-framed tip loses the skill-selection race to imperative
|
|
809
919
|
// SessionStart primers (e.g. superpowers' "you MUST invoke"). The memory
|
|
810
920
|
// rationale stays, but as the fallback note rather than the headline.
|
|
921
|
+
// #956: dropped the "records the outcome in .rcode/state.json" claim —
|
|
922
|
+
// most routed workflows (e.g. karpathy-audit) only write a report file,
|
|
923
|
+
// they don't touch state.json, so the blanket claim was false.
|
|
811
924
|
const advisory =
|
|
812
|
-
`Use ${matched.command} for this ${matched.intent} task — it's the rcode workflow built for it
|
|
925
|
+
`Use ${matched.command} for this ${matched.intent} task — it's the rcode workflow built for it. ` +
|
|
813
926
|
`Prefer it over handling this ad-hoc; if you do proceed manually, run /rcode-memory-update afterward so long-term memory stays consistent.`;
|
|
814
927
|
|
|
815
928
|
const payload = {
|
|
@@ -943,6 +1056,53 @@ async function stopHandler() {
|
|
|
943
1056
|
}
|
|
944
1057
|
}
|
|
945
1058
|
|
|
1059
|
+
/**
|
|
1060
|
+
* session-start: Emit a one-line project status primer at session open. (#947)
|
|
1061
|
+
* Uses resolveActivePhase from state-reader.cjs. Advisory only — exits 0 on any error.
|
|
1062
|
+
*/
|
|
1063
|
+
function sessionStart() {
|
|
1064
|
+
try {
|
|
1065
|
+
try { fs.readFileSync(0, 'utf8'); } catch { /* drain stdin */ }
|
|
1066
|
+
const cwd = process.cwd();
|
|
1067
|
+
const statePath = path.join(cwd, '.rcode', 'state.json');
|
|
1068
|
+
if (!fs.existsSync(statePath)) process.exit(0);
|
|
1069
|
+
let state;
|
|
1070
|
+
try { state = JSON.parse(fs.readFileSync(statePath, 'utf8')); } catch { process.exit(0); }
|
|
1071
|
+
const { activePhase, phaseLabel } = resolveActivePhase(state);
|
|
1072
|
+
if (!phaseLabel) process.exit(0);
|
|
1073
|
+
const phaseKey = String(activePhase?.number ?? phaseLabel);
|
|
1074
|
+
const phaseSprints = (Array.isArray(state.sprints) ? state.sprints : []).filter(s => String(s.phase) === phaseKey);
|
|
1075
|
+
const doneCount = phaseSprints.filter(s => s.status === 'completed' || s.status === 'complete').length;
|
|
1076
|
+
const sprintSummary = phaseSprints.length > 0 ? `${doneCount}/${phaseSprints.length} sprints done` : 'no sprints yet';
|
|
1077
|
+
const phaseStatus = activePhase?.status || 'planned';
|
|
1078
|
+
const nextCmd = phaseStatus === 'executing' ? '/rcode-execute'
|
|
1079
|
+
: phaseStatus === 'complete' ? '/rcode-add-phase'
|
|
1080
|
+
: phaseSprints.length === 0 ? `/rcode-plan ${phaseLabel}`
|
|
1081
|
+
: '/rcode-execute';
|
|
1082
|
+
const primer = `\u{1F4CD} Phase ${phaseLabel} ${phaseStatus} · ${sprintSummary} · next: ${nextCmd}`;
|
|
1083
|
+
|
|
1084
|
+
// ── Relevance-ranked memory injection (#958) ─────────────────────────
|
|
1085
|
+
// Only attempted when .rcode/memory/ exists and has content — a missing
|
|
1086
|
+
// or empty memory bank degrades silently to the primer-only behavior
|
|
1087
|
+
// that predates this feature.
|
|
1088
|
+
const payload = { systemMessage: primer };
|
|
1089
|
+
if (hasMemory(cwd)) {
|
|
1090
|
+
try {
|
|
1091
|
+
const selection = selectMemoryChunks(cwd);
|
|
1092
|
+
const additionalContext = formatMemoryContext(selection);
|
|
1093
|
+
if (additionalContext) {
|
|
1094
|
+
payload.hookSpecificOutput = {
|
|
1095
|
+
hookEventName: 'SessionStart',
|
|
1096
|
+
additionalContext,
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
} catch { /* memory injection is advisory — never block session start */ }
|
|
1100
|
+
}
|
|
1101
|
+
process.stdout.write(JSON.stringify(payload) + '\n');
|
|
1102
|
+
} catch { /* fail open — never block session start */ }
|
|
1103
|
+
process.exit(0);
|
|
1104
|
+
}
|
|
1105
|
+
|
|
946
1106
|
/**
|
|
947
1107
|
* Main entry point.
|
|
948
1108
|
*/
|
|
@@ -983,9 +1143,15 @@ async function main() {
|
|
|
983
1143
|
case 'prompt-router':
|
|
984
1144
|
promptRouter(); // synchronous — exits inside; never falls through to async path
|
|
985
1145
|
break;
|
|
1146
|
+
case 'session-start':
|
|
1147
|
+
sessionStart();
|
|
1148
|
+
break;
|
|
1149
|
+
case 'drift':
|
|
1150
|
+
driftCommand();
|
|
1151
|
+
break;
|
|
986
1152
|
default:
|
|
987
1153
|
console.error(`Unknown subcommand: ${subcommand}`);
|
|
988
|
-
console.error('Usage: rcode-hooks.cjs pre-edit|pre-workflow|post-commit|bash-guard|pre-compact|stop-verify|cost-track|stop|compact-nudge|pre-tool-use|prompt-router');
|
|
1154
|
+
console.error('Usage: rcode-hooks.cjs pre-edit|pre-workflow|post-commit|bash-guard|pre-compact|stop-verify|cost-track|stop|compact-nudge|pre-tool-use|prompt-router|session-start|drift');
|
|
989
1155
|
process.exit(1);
|
|
990
1156
|
}
|
|
991
1157
|
}
|