@hanzlaa/rcode 4.3.2 → 4.4.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 +1 -1
- package/CONTRIBUTING.md +2 -1
- package/README.md +45 -10
- package/cli/doctor.js +21 -3
- package/cli/index.js +5 -1
- package/cli/install.js +16 -2
- package/cli/postinstall.js +1 -1
- package/cli/uninstall.js +48 -6
- package/dist/rcode.js +178 -174
- package/package.json +12 -22
- package/rcode/agents/rcode-codebase-mapper.md +12 -0
- package/rcode/agents/rcode-phase-researcher.md +13 -0
- package/rcode/agents/rcode-project-researcher.md +32 -0
- package/rcode/agents/rcode-roadmapper.md +15 -0
- package/rcode/agents/rules/planner/common-patterns.md +40 -0
- package/rcode/bin/lib/roadmap.cjs +184 -11
- package/rcode/bin/rcode-hooks.cjs +303 -14
- package/rcode/bin/rcode-tools.cjs +669 -55
- package/rcode/commands/lens-audit.md +8 -6
- package/rcode/data/intent-table.json +87 -0
- package/rcode/modules/seo.yaml +22 -0
- package/rcode/references/project-types.yaml +75 -0
- package/rcode/skills/SKILLS_INDEX.md +20 -2
- package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/SKILL.md +16 -21
- package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/rules/backlog-building.md +13 -0
- package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/rules/merge-strategy.md +17 -0
- package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/rules/orchestrator-rhythm.md +9 -0
- package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/rules/wave-design.md +21 -0
- package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/templates/heartbeat.sh +0 -0
- package/rcode/skills/actions/4-implementation/rcode-scaffold-project/SKILL.md +60 -1
- package/rcode/skills/core/rcode-lazy/SKILL.md +149 -0
- package/rcode/skills/seo/module.yaml +20 -0
- package/rcode/skills/seo/on-page-seo-auditor/SKILL.md +308 -0
- package/rcode/skills/seo/on-page-seo-auditor/references/audit-example.md +109 -0
- package/rcode/skills/seo/on-page-seo-auditor/references/audit-templates.md +427 -0
- package/rcode/skills/seo/on-page-seo-auditor/references/bulk-audit-playbook.md +116 -0
- package/rcode/skills/seo/on-page-seo-auditor/references/scoring-rubric.md +525 -0
- package/rcode/skills/seo/rank-and-rent-local-seo/SKILL.md +94 -0
- package/rcode/skills/seo/rank-and-rent-local-seo/rules/city-matrix-and-pages.md +76 -0
- package/rcode/skills/seo/rank-and-rent-local-seo/rules/monetization.md +70 -0
- package/rcode/skills/seo/rank-and-rent-local-seo/rules/niche-selection.md +60 -0
- package/rcode/skills/seo/rank-and-rent-local-seo/rules/subniche-discovery.md +66 -0
- package/rcode/skills/seo/rank-and-rent-local-seo/templates/service-city-page.md +36 -0
- package/rcode/skills/seo/rank-and-rent-local-seo/templates/subniche-research-prompt.md +34 -0
- package/rcode/skills/seo/seo-audit/SKILL.md +412 -0
- package/rcode/skills/seo/seo-audit/evals/evals.json +136 -0
- package/rcode/skills/seo/seo-audit/references/ai-writing-detection.md +200 -0
- package/rcode/skills/seo/seo-content-factory/SKILL.md +130 -0
- package/rcode/skills/seo/seo-content-factory/rules/agents.md +80 -0
- package/rcode/skills/seo/seo-content-factory/rules/herdr-wave-mapping.md +80 -0
- package/rcode/skills/seo/seo-content-factory/rules/pipeline.md +102 -0
- package/rcode/skills/seo/seo-content-factory/rules/programmatic-pages.md +107 -0
- package/rcode/skills/seo/seo-content-factory/rules/quality-gates.md +68 -0
- package/rcode/skills/seo/seo-content-factory/rules/weekly-cadence.md +61 -0
- package/rcode/skills/seo/seo-content-factory/templates/BACKLOG.md +23 -0
- package/rcode/skills/seo/seo-content-factory/templates/agent-prompts/01-funnel-A1-A4.md +63 -0
- package/rcode/skills/seo/seo-content-factory/templates/agent-prompts/02-fanout-editorial-A5-A6-A8.md +28 -0
- package/rcode/skills/seo/seo-content-factory/templates/agent-prompts/03-fanout-programmatic-A7-A6-A8.md +25 -0
- package/rcode/skills/seo/seo-content-factory/templates/agent-prompts/04-loop-A9-A10.md +34 -0
- package/rcode/skills/seo/seo-content-factory/templates/article.mdx +39 -0
- package/rcode/skills/seo/seo-content-factory/templates/cluster.schema.json +52 -0
- package/rcode/skills/seo/seo-content-factory/templates/content-brief.md +51 -0
- package/rcode/skills/seo/seo-content-factory/templates/jsonld-builders.ts +49 -0
- package/rcode/skills/seo/seo-content-factory/templates/keywords.csv +13 -0
- package/rcode/skills/seo/seo-content-factory/templates/programmatic-page.tsx +58 -0
- package/rcode/skills/seo/seo-content-writer/SKILL.md +263 -0
- package/rcode/skills/seo/seo-content-writer/references/content-structure-templates.md +875 -0
- package/rcode/skills/seo/seo-content-writer/references/instructions-detail.md +239 -0
- package/rcode/skills/seo/seo-content-writer/references/seo-writing-checklist.md +250 -0
- package/rcode/skills/seo/seo-content-writer/references/title-formulas.md +339 -0
- package/rcode/skills/seo/seo-growth-orchestrator/SKILL.md +79 -0
- package/rcode/skills/seo/seo-growth-orchestrator/rules/backlinks.md +29 -0
- package/rcode/skills/seo/seo-growth-orchestrator/rules/content-engine.md +27 -0
- package/rcode/skills/seo/seo-growth-orchestrator/rules/dos-and-donts.md +40 -0
- package/rcode/skills/seo/seo-growth-orchestrator/rules/goals-protocol.md +34 -0
- package/rcode/skills/seo/seo-growth-orchestrator/rules/local-seo-stack.md +35 -0
- package/rcode/skills/seo/seo-growth-orchestrator/rules/mrr-sprint.md +27 -0
- package/rcode/skills/seo/seo-growth-orchestrator/rules/tools.md +19 -0
- package/rcode/skills/seo/seo-growth-orchestrator/templates/backlink-research.md +29 -0
- package/rcode/skills/seo/seo-growth-orchestrator/templates/goal-protocol.md +35 -0
- package/rcode/skills/seo/seo-growth-orchestrator/templates/local-citations.md +24 -0
- package/rcode/skills/seo/seo-growth-orchestrator/templates/local-city-pages.md +27 -0
- package/rcode/skills/seo/seo-growth-orchestrator/templates/local-gbp-categories.md +23 -0
- package/rcode/skills/seo/seo-growth-orchestrator/templates/local-gsc-goldmine.md +27 -0
- package/rcode/skills/seo/seo-growth-orchestrator/templates/local-review-mining.md +26 -0
- package/rcode/skills/seo/seo-growth-orchestrator/templates/mrr-30day-plan.md +19 -0
- package/rcode/skills/seo/seo-growth-orchestrator/templates/mrr-cut.md +13 -0
- package/rcode/skills/seo/seo-growth-orchestrator/templates/mrr-opportunity-finder.md +17 -0
- package/rcode/skills/seo/seo-growth-orchestrator/templates/mrr-role-model.md +18 -0
- package/rcode/skills/seo/seo-site-builder/SKILL.md +149 -0
- package/rcode/skills/seo/seo-site-builder/rules/01-niche-discovery.md +64 -0
- package/rcode/skills/seo/seo-site-builder/rules/02-deep-research.md +164 -0
- package/rcode/skills/seo/seo-site-builder/rules/03-keyword-strategy.md +82 -0
- package/rcode/skills/seo/seo-site-builder/rules/04-content-plan.md +173 -0
- package/rcode/skills/seo/seo-site-builder/rules/05-site-build.md +187 -0
- package/rcode/skills/seo/seo-site-builder/rules/06-yolo.md +127 -0
- package/rcode/skills/seo/seo-site-builder/templates/article-stub.mdx +62 -0
- package/rcode/skills/seo/seo-site-builder/templates/image-prompt.md +97 -0
- package/rcode/skills/seo/seo-site-builder/templates/keyword-export.md +45 -0
- package/rcode/skills/seo/seo-site-builder/templates/niche-scorecard.md +28 -0
- package/rcode/skills/seo/technical-seo-checker/SKILL.md +329 -0
- package/rcode/skills/seo/technical-seo-checker/references/bulk-audit-playbook.md +118 -0
- package/rcode/skills/seo/technical-seo-checker/references/ecommerce-platform-patterns.md +166 -0
- package/rcode/skills/seo/technical-seo-checker/references/http-status-codes.md +705 -0
- package/rcode/skills/seo/technical-seo-checker/references/llm-crawler-handling.md +257 -0
- package/rcode/skills/seo/technical-seo-checker/references/pre-migration-playbook.md +137 -0
- package/rcode/skills/seo/technical-seo-checker/references/robots-txt-reference.md +717 -0
- package/rcode/skills/seo/technical-seo-checker/references/technical-audit-example.md +169 -0
- package/rcode/skills/seo/technical-seo-checker/references/technical-audit-templates.md +436 -0
- package/rcode/templates/settings-hooks.json +16 -1
- package/rcode/workflows/do.md +17 -0
- package/rcode/workflows/enable-hooks.md +3 -2
- package/rcode/workflows/execute-sprint.md +22 -0
- package/rcode/workflows/execute.md +17 -3
- package/rcode/workflows/lens-audit.md +49 -8
- package/rcode/workflows/new-milestone.md +21 -9
- package/rcode/workflows/new-project-create-roadmap.md +2 -0
- package/rcode/workflows/new-project-roadmap.md +2 -0
- package/rcode/workflows/new-project.md +3 -0
- package/rcode/workflows/plan.md +15 -2
- package/server/dashboard.js +6 -2
- package/server/lib/api.js +35 -20
- package/server/lib/html/client/components/OrchPanel.js +43 -2
- package/server/lib/html/client/components/dashboard/InProgress.js +5 -4
- package/server/lib/html/client/components/shared.js +14 -0
- package/server/lib/html/client/orchestrator.js +13 -0
- package/server/lib/html/client/views/KanbanView.js +33 -4
- package/server/lib/html/css.js +57 -3
- package/server/lib/scanner.js +40 -4
- package/server/orchestrator.js +42 -0
|
@@ -10,7 +10,10 @@
|
|
|
10
10
|
* pre-compact — refresh HANDOFF.json before context compaction (#743)
|
|
11
11
|
* stop-verify — syntax-check files changed during the response (#744)
|
|
12
12
|
* cost-track — append per-response token usage to cost.jsonl (#745)
|
|
13
|
+
* stop — unified Stop hook: hedging-language detection (#744) + token logging (#745)
|
|
13
14
|
* compact-nudge — advise /rcode-trim or /clear after N Edit/Write calls (#749)
|
|
15
|
+
* pre-tool-use — stderr warning before large file reads to avoid context bloat (#749)
|
|
16
|
+
* prompt-router — nudge toward rcode commands for memory consistency (#892)
|
|
14
17
|
*
|
|
15
18
|
* All subcommands read stdin JSON from the hook execution context.
|
|
16
19
|
* Pure Node stdlib. No external dependencies.
|
|
@@ -19,6 +22,7 @@
|
|
|
19
22
|
const fs = require('fs');
|
|
20
23
|
const os = require('os');
|
|
21
24
|
const path = require('path');
|
|
25
|
+
const { execSync, spawnSync } = require('child_process');
|
|
22
26
|
|
|
23
27
|
/**
|
|
24
28
|
* Read and parse stdin JSON.
|
|
@@ -337,8 +341,6 @@ async function bashGuard() {
|
|
|
337
341
|
*/
|
|
338
342
|
async function preCompact() {
|
|
339
343
|
try {
|
|
340
|
-
const path = require('path');
|
|
341
|
-
const { execSync } = require('child_process');
|
|
342
344
|
await readInputJson(); // drain the PreCompact event payload
|
|
343
345
|
|
|
344
346
|
const cwd = process.cwd();
|
|
@@ -518,8 +520,6 @@ async function preCompact() {
|
|
|
518
520
|
*/
|
|
519
521
|
async function stopVerify() {
|
|
520
522
|
try {
|
|
521
|
-
const path = require('path');
|
|
522
|
-
const { spawnSync } = require('child_process');
|
|
523
523
|
const input = await readInputJson();
|
|
524
524
|
|
|
525
525
|
let changed =
|
|
@@ -623,13 +623,211 @@ async function costTrack() {
|
|
|
623
623
|
}
|
|
624
624
|
}
|
|
625
625
|
|
|
626
|
+
// INTENT_TABLE — keyword map for prompt-router (#892).
|
|
627
|
+
// Source of truth: rcode/workflows/do.md routing table (~285-320). First-match-wins.
|
|
628
|
+
// Loaded from data file to keep this file under 1000 lines (#896).
|
|
629
|
+
const INTENT_TABLE = JSON.parse(
|
|
630
|
+
require('fs').readFileSync(
|
|
631
|
+
require('path').join(__dirname, '..', 'data', 'intent-table.json'),
|
|
632
|
+
'utf8'
|
|
633
|
+
)
|
|
634
|
+
);
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Inline flat-YAML parser — mirrors parseSimpleYaml in rcode-tools.cjs:91.
|
|
638
|
+
* Supports `key: value` lines only; strips `#` comments; unquotes.
|
|
639
|
+
* Kept inline so this file stays standalone (no cross-file require).
|
|
640
|
+
*/
|
|
641
|
+
function parseSimpleYamlInline(text) {
|
|
642
|
+
const out = {};
|
|
643
|
+
for (const raw of text.split('\n')) {
|
|
644
|
+
const line = raw.replace(/#.*$/, '').trim();
|
|
645
|
+
if (!line) continue;
|
|
646
|
+
const colonAt = line.indexOf(':');
|
|
647
|
+
if (colonAt === -1) continue;
|
|
648
|
+
const key = line.slice(0, colonAt).trim();
|
|
649
|
+
let val = line.slice(colonAt + 1).trim();
|
|
650
|
+
if (val.startsWith('"') && val.endsWith('"')) val = val.slice(1, -1);
|
|
651
|
+
if (val.startsWith("'") && val.endsWith("'")) val = val.slice(1, -1);
|
|
652
|
+
out[key] = val;
|
|
653
|
+
}
|
|
654
|
+
return out;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Read prompt_nudge from .rcode/config.yaml.
|
|
659
|
+
* Returns 'every' | 'once-per-intent' | 'when-stale' | 'off'.
|
|
660
|
+
* Defaults to 'every' when key is absent, file is missing, or value is unknown.
|
|
661
|
+
*/
|
|
662
|
+
function readPromptNudgeToggle(cwd) {
|
|
663
|
+
const VALID = new Set(['every', 'once-per-intent', 'when-stale', 'off']);
|
|
664
|
+
try {
|
|
665
|
+
const cfgPath = path.join(cwd, '.rcode', 'config.yaml');
|
|
666
|
+
const text = fs.readFileSync(cfgPath, 'utf8');
|
|
667
|
+
const parsed = parseSimpleYamlInline(text);
|
|
668
|
+
const val = (parsed.prompt_nudge || '').trim().toLowerCase();
|
|
669
|
+
return VALID.has(val) ? val : 'every';
|
|
670
|
+
} catch {
|
|
671
|
+
return 'every';
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Returns true when state.json is older than the last commit, or absent in a .planning/ project.
|
|
677
|
+
* Fail-open: returns true on any I/O error so the nudge fires rather than silently skips.
|
|
678
|
+
*/
|
|
679
|
+
function isStateStaleFallbackTrue(cwd) {
|
|
680
|
+
try {
|
|
681
|
+
const statePath = path.join(cwd, '.rcode', 'state.json');
|
|
682
|
+
const planningDir = path.join(cwd, '.planning');
|
|
683
|
+
const hasPlanning = fs.existsSync(planningDir);
|
|
684
|
+
|
|
685
|
+
if (!fs.existsSync(statePath)) {
|
|
686
|
+
// No state.json at all — if there IS a .planning/ dir, that means we're
|
|
687
|
+
// in a project that should have state but doesn't: treat as stale.
|
|
688
|
+
return hasPlanning;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// state.json exists — check its mtime vs last commit timestamp.
|
|
692
|
+
const stateMtime = fs.statSync(statePath).mtimeMs;
|
|
693
|
+
let lastCommitTs = null;
|
|
694
|
+
try {
|
|
695
|
+
const tsStr = execSync('git log -1 --format=%ct 2>/dev/null', {
|
|
696
|
+
cwd, encoding: 'utf8', timeout: 2000,
|
|
697
|
+
}).trim();
|
|
698
|
+
if (tsStr) lastCommitTs = parseInt(tsStr, 10) * 1000;
|
|
699
|
+
} catch { /* git unavailable or no commits */ }
|
|
700
|
+
|
|
701
|
+
if (lastCommitTs === null) return false; // can't determine, don't nag
|
|
702
|
+
return stateMtime < lastCommitTs;
|
|
703
|
+
} catch {
|
|
704
|
+
return true; // fail open: treat as stale
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* prompt-router: Nudge toward rcode commands for memory consistency (#892).
|
|
710
|
+
* Reads stdin synchronously (NOT async — rejects bad JSON). Keyword-matches INTENT_TABLE,
|
|
711
|
+
* emits additionalContext advisory. Gated by prompt_nudge config. Always exits 0.
|
|
712
|
+
*/
|
|
713
|
+
function promptRouter() {
|
|
714
|
+
try {
|
|
715
|
+
// Read stdin synchronously — mirrors cli/rcode-slash-router.cjs readStdin().
|
|
716
|
+
let raw = '';
|
|
717
|
+
try {
|
|
718
|
+
raw = fs.readFileSync(0, 'utf8');
|
|
719
|
+
} catch {
|
|
720
|
+
process.exit(0);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
if (!raw.trim()) process.exit(0);
|
|
724
|
+
|
|
725
|
+
let data;
|
|
726
|
+
try {
|
|
727
|
+
data = JSON.parse(raw);
|
|
728
|
+
} catch {
|
|
729
|
+
process.exit(0);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// Multi-spelling prompt field fallback — mirrors rcode-slash-router.cjs.
|
|
733
|
+
const prompt =
|
|
734
|
+
data.prompt ??
|
|
735
|
+
data.user_prompt ??
|
|
736
|
+
data.userPrompt ??
|
|
737
|
+
data.message ??
|
|
738
|
+
data.input ??
|
|
739
|
+
'';
|
|
740
|
+
|
|
741
|
+
if (typeof prompt !== 'string' || !prompt.trim()) process.exit(0);
|
|
742
|
+
|
|
743
|
+
const trimmed = prompt.trimStart();
|
|
744
|
+
|
|
745
|
+
// Skip prompts that already start with /rcode- — slash router handles those.
|
|
746
|
+
if (/^\/rcode-/.test(trimmed)) process.exit(0);
|
|
747
|
+
|
|
748
|
+
const hookEventName =
|
|
749
|
+
data.hook_event_name || data.hookEventName || 'UserPromptSubmit';
|
|
750
|
+
const cwd = process.cwd();
|
|
751
|
+
|
|
752
|
+
// ── Config toggle ────────────────────────────────────────────────────
|
|
753
|
+
const nudgeMode = readPromptNudgeToggle(cwd);
|
|
754
|
+
if (nudgeMode === 'off') process.exit(0);
|
|
755
|
+
|
|
756
|
+
// ── when-stale: check if state is stale ──────────────────────────────
|
|
757
|
+
if (nudgeMode === 'when-stale' && !isStateStaleFallbackTrue(cwd)) {
|
|
758
|
+
process.exit(0);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// ── Keyword match (first-match-wins, case-insensitive) ───────────────
|
|
762
|
+
const lower = prompt.toLowerCase();
|
|
763
|
+
let matched = null;
|
|
764
|
+
for (const entry of INTENT_TABLE) {
|
|
765
|
+
for (const kw of entry.keywords) {
|
|
766
|
+
if (lower.includes(kw.toLowerCase())) {
|
|
767
|
+
matched = entry;
|
|
768
|
+
break;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
if (matched) break;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
if (!matched) process.exit(0);
|
|
775
|
+
|
|
776
|
+
// ── once-per-intent dedupe ───────────────────────────────────────────
|
|
777
|
+
if (nudgeMode === 'once-per-intent') {
|
|
778
|
+
// Fallback key: parent PID + hourly bucket — scopes naturally to the
|
|
779
|
+
// current shell session without requiring session_id in the payload.
|
|
780
|
+
// Without this, every session shares 'default' and a dedupe file from
|
|
781
|
+
// session A silences nudges in session B permanently.
|
|
782
|
+
const sessionFallback =
|
|
783
|
+
String(process.ppid) + '-' + String(Math.floor(Date.now() / 3600000));
|
|
784
|
+
const sessionId =
|
|
785
|
+
data.session_id || data.tool_input?.session_id || sessionFallback;
|
|
786
|
+
const dedupeFile = path.join(
|
|
787
|
+
os.tmpdir(),
|
|
788
|
+
'rcode-prompt-nudge-' + sessionId + '.json'
|
|
789
|
+
);
|
|
790
|
+
try {
|
|
791
|
+
let seen = [];
|
|
792
|
+
try {
|
|
793
|
+
seen = JSON.parse(fs.readFileSync(dedupeFile, 'utf8'));
|
|
794
|
+
if (!Array.isArray(seen)) seen = [];
|
|
795
|
+
} catch { /* first run or missing file */ }
|
|
796
|
+
|
|
797
|
+
if (seen.includes(matched.intent)) process.exit(0);
|
|
798
|
+
|
|
799
|
+
seen.push(matched.intent);
|
|
800
|
+
try { fs.writeFileSync(dedupeFile, JSON.stringify(seen)); } catch {}
|
|
801
|
+
} catch {
|
|
802
|
+
// dedupe file unreadable/locked → fire anyway (fail open)
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
// ── Emit advisory ────────────────────────────────────────────────────
|
|
807
|
+
// #907 RC2: lead with the directive ("use X"), not a soft "consider" — a
|
|
808
|
+
// gentle memory-framed tip loses the skill-selection race to imperative
|
|
809
|
+
// SessionStart primers (e.g. superpowers' "you MUST invoke"). The memory
|
|
810
|
+
// rationale stays, but as the fallback note rather than the headline.
|
|
811
|
+
const advisory =
|
|
812
|
+
`Use ${matched.command} for this ${matched.intent} task — it's the rcode workflow built for it, and it records the outcome in .rcode/state.json. ` +
|
|
813
|
+
`Prefer it over handling this ad-hoc; if you do proceed manually, run /rcode-memory-update afterward so long-term memory stays consistent.`;
|
|
814
|
+
|
|
815
|
+
const payload = {
|
|
816
|
+
hookSpecificOutput: {
|
|
817
|
+
hookEventName,
|
|
818
|
+
additionalContext: advisory,
|
|
819
|
+
},
|
|
820
|
+
};
|
|
821
|
+
process.stdout.write(JSON.stringify(payload));
|
|
822
|
+
} catch {
|
|
823
|
+
// Fail open — never break the host CLI's prompt.
|
|
824
|
+
}
|
|
825
|
+
process.exit(0);
|
|
826
|
+
}
|
|
827
|
+
|
|
626
828
|
/**
|
|
627
829
|
* compact-nudge: Advise /rcode-trim or /clear after N Edit/Write calls (#749).
|
|
628
|
-
*
|
|
629
|
-
* Triggered by the PreToolUse:Edit|Write hook. Maintains a per-session call
|
|
630
|
-
* counter in a temp file and, once the count crosses RCODE_NUDGE_THRESHOLD
|
|
631
|
-
* (default 50), prints an advisory to reclaim context budget. Purely
|
|
632
|
-
* advisory — always exits 0, never blocks a tool call.
|
|
830
|
+
* Maintains a per-session counter; warns at RCODE_NUDGE_THRESHOLD (default 50). Advisory only.
|
|
633
831
|
*/
|
|
634
832
|
async function compactNudge() {
|
|
635
833
|
try {
|
|
@@ -667,6 +865,84 @@ async function compactNudge() {
|
|
|
667
865
|
}
|
|
668
866
|
}
|
|
669
867
|
|
|
868
|
+
/** Returns true when a Read/Bash targets known large planning files (RESEARCH/SUMMARY/ROADMAP). */
|
|
869
|
+
function shouldNudgeCompact(toolName, toolInput) {
|
|
870
|
+
if (toolName !== 'Read' && toolName !== 'Bash') return false;
|
|
871
|
+
const target = toolInput?.file_path || toolInput?.command || '';
|
|
872
|
+
return /RESEARCH\.md|SUMMARY\.md|ROADMAP\.md/.test(target);
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
/**
|
|
876
|
+
* pre-tool-use: Strategic compact nudge before large file reads (#749).
|
|
877
|
+
*
|
|
878
|
+
* Triggered by the PreToolUse hook (Read + Bash tool names). Checks whether
|
|
879
|
+
* the tool call targets a known large planning file and, if so, writes an
|
|
880
|
+
* advisory line to stderr so the executor sees it without being interrupted.
|
|
881
|
+
* Always exits 0 — never blocks the tool call.
|
|
882
|
+
*/
|
|
883
|
+
async function preToolUse() {
|
|
884
|
+
try {
|
|
885
|
+
const input = await readInputJson();
|
|
886
|
+
const toolName = input.tool_name || input.toolName || '';
|
|
887
|
+
const toolInput = input.tool_input || input.toolInput || {};
|
|
888
|
+
|
|
889
|
+
if (shouldNudgeCompact(toolName, toolInput)) {
|
|
890
|
+
process.stderr.write(
|
|
891
|
+
'[rcode] Context nearing limit — consider /compact before the next large file read\n'
|
|
892
|
+
);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
process.exit(0);
|
|
896
|
+
} catch {
|
|
897
|
+
// Advisory hook must never break the session.
|
|
898
|
+
process.exit(0);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
/**
|
|
903
|
+
* stop: Stop hook — hedging-language detection (#744) + token/cost logging (#745).
|
|
904
|
+
* Warns when response text suggests incomplete execution ("I'll implement this", etc.).
|
|
905
|
+
* Appends per-response token usage to ~/.rcode/logs/token-usage.jsonl.
|
|
906
|
+
* Rates: $3/M input, $15/M output (Sonnet 4.x approximation).
|
|
907
|
+
* Never blocks.
|
|
908
|
+
*/
|
|
909
|
+
async function stopHandler() {
|
|
910
|
+
try {
|
|
911
|
+
const input = await readInputJson();
|
|
912
|
+
const responseText = input?.response || '';
|
|
913
|
+
const HEDGING_PATTERNS = [
|
|
914
|
+
/I'll\s+implement\s+this/i,
|
|
915
|
+
/I\s+would\s+add/i,
|
|
916
|
+
/you\s+could\s+add/i,
|
|
917
|
+
/TODO:/,
|
|
918
|
+
];
|
|
919
|
+
const incomplete = HEDGING_PATTERNS.some((re) => re.test(responseText));
|
|
920
|
+
if (incomplete) {
|
|
921
|
+
process.stderr.write(
|
|
922
|
+
'[rcode] Stop hook: response contains hedging language — verify implementation is complete\n'
|
|
923
|
+
);
|
|
924
|
+
}
|
|
925
|
+
const usage = input?.usage;
|
|
926
|
+
if (usage && typeof usage === 'object') {
|
|
927
|
+
const logDir = path.join(os.homedir(), '.rcode', 'logs');
|
|
928
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
929
|
+
const entry = JSON.stringify({
|
|
930
|
+
ts: new Date().toISOString(),
|
|
931
|
+
input: usage.input_tokens || 0,
|
|
932
|
+
output: usage.output_tokens || 0,
|
|
933
|
+
cost_usd:
|
|
934
|
+
((usage.input_tokens || 0) * 3) / 1e6 +
|
|
935
|
+
((usage.output_tokens || 0) * 15) / 1e6,
|
|
936
|
+
});
|
|
937
|
+
fs.appendFileSync(path.join(logDir, 'token-usage.jsonl'), entry + '\n');
|
|
938
|
+
}
|
|
939
|
+
process.exit(0);
|
|
940
|
+
} catch (err) {
|
|
941
|
+
console.error(`Hook error: ${err.message}`);
|
|
942
|
+
process.exit(1);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
|
|
670
946
|
/**
|
|
671
947
|
* Main entry point.
|
|
672
948
|
*/
|
|
@@ -695,17 +971,30 @@ async function main() {
|
|
|
695
971
|
case 'cost-track':
|
|
696
972
|
await costTrack();
|
|
697
973
|
break;
|
|
974
|
+
case 'stop':
|
|
975
|
+
await stopHandler();
|
|
976
|
+
break;
|
|
698
977
|
case 'compact-nudge':
|
|
699
978
|
await compactNudge();
|
|
700
979
|
break;
|
|
980
|
+
case 'pre-tool-use':
|
|
981
|
+
await preToolUse();
|
|
982
|
+
break;
|
|
983
|
+
case 'prompt-router':
|
|
984
|
+
promptRouter(); // synchronous — exits inside; never falls through to async path
|
|
985
|
+
break;
|
|
701
986
|
default:
|
|
702
987
|
console.error(`Unknown subcommand: ${subcommand}`);
|
|
703
|
-
console.error('Usage: rcode-hooks.cjs pre-edit|pre-workflow|post-commit|bash-guard|pre-compact|stop-verify|cost-track|compact-nudge');
|
|
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');
|
|
704
989
|
process.exit(1);
|
|
705
990
|
}
|
|
706
991
|
}
|
|
707
992
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
993
|
+
if (require.main === module) {
|
|
994
|
+
main().catch((err) => {
|
|
995
|
+
console.error(`Fatal error: ${err.message}`);
|
|
996
|
+
process.exit(1);
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
module.exports = { INTENT_TABLE };
|