@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.
Files changed (130) hide show
  1. package/AGENTS.md +1 -1
  2. package/CONTRIBUTING.md +2 -1
  3. package/README.md +45 -10
  4. package/cli/doctor.js +21 -3
  5. package/cli/index.js +5 -1
  6. package/cli/install.js +16 -2
  7. package/cli/postinstall.js +1 -1
  8. package/cli/uninstall.js +48 -6
  9. package/dist/rcode.js +178 -174
  10. package/package.json +12 -22
  11. package/rcode/agents/rcode-codebase-mapper.md +12 -0
  12. package/rcode/agents/rcode-phase-researcher.md +13 -0
  13. package/rcode/agents/rcode-project-researcher.md +32 -0
  14. package/rcode/agents/rcode-roadmapper.md +15 -0
  15. package/rcode/agents/rules/planner/common-patterns.md +40 -0
  16. package/rcode/bin/lib/roadmap.cjs +184 -11
  17. package/rcode/bin/rcode-hooks.cjs +303 -14
  18. package/rcode/bin/rcode-tools.cjs +669 -55
  19. package/rcode/commands/lens-audit.md +8 -6
  20. package/rcode/data/intent-table.json +87 -0
  21. package/rcode/modules/seo.yaml +22 -0
  22. package/rcode/references/project-types.yaml +75 -0
  23. package/rcode/skills/SKILLS_INDEX.md +20 -2
  24. package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/SKILL.md +16 -21
  25. package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/rules/backlog-building.md +13 -0
  26. package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/rules/merge-strategy.md +17 -0
  27. package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/rules/orchestrator-rhythm.md +9 -0
  28. package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/rules/wave-design.md +21 -0
  29. package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/templates/heartbeat.sh +0 -0
  30. package/rcode/skills/actions/4-implementation/rcode-scaffold-project/SKILL.md +60 -1
  31. package/rcode/skills/core/rcode-lazy/SKILL.md +149 -0
  32. package/rcode/skills/seo/module.yaml +20 -0
  33. package/rcode/skills/seo/on-page-seo-auditor/SKILL.md +308 -0
  34. package/rcode/skills/seo/on-page-seo-auditor/references/audit-example.md +109 -0
  35. package/rcode/skills/seo/on-page-seo-auditor/references/audit-templates.md +427 -0
  36. package/rcode/skills/seo/on-page-seo-auditor/references/bulk-audit-playbook.md +116 -0
  37. package/rcode/skills/seo/on-page-seo-auditor/references/scoring-rubric.md +525 -0
  38. package/rcode/skills/seo/rank-and-rent-local-seo/SKILL.md +94 -0
  39. package/rcode/skills/seo/rank-and-rent-local-seo/rules/city-matrix-and-pages.md +76 -0
  40. package/rcode/skills/seo/rank-and-rent-local-seo/rules/monetization.md +70 -0
  41. package/rcode/skills/seo/rank-and-rent-local-seo/rules/niche-selection.md +60 -0
  42. package/rcode/skills/seo/rank-and-rent-local-seo/rules/subniche-discovery.md +66 -0
  43. package/rcode/skills/seo/rank-and-rent-local-seo/templates/service-city-page.md +36 -0
  44. package/rcode/skills/seo/rank-and-rent-local-seo/templates/subniche-research-prompt.md +34 -0
  45. package/rcode/skills/seo/seo-audit/SKILL.md +412 -0
  46. package/rcode/skills/seo/seo-audit/evals/evals.json +136 -0
  47. package/rcode/skills/seo/seo-audit/references/ai-writing-detection.md +200 -0
  48. package/rcode/skills/seo/seo-content-factory/SKILL.md +130 -0
  49. package/rcode/skills/seo/seo-content-factory/rules/agents.md +80 -0
  50. package/rcode/skills/seo/seo-content-factory/rules/herdr-wave-mapping.md +80 -0
  51. package/rcode/skills/seo/seo-content-factory/rules/pipeline.md +102 -0
  52. package/rcode/skills/seo/seo-content-factory/rules/programmatic-pages.md +107 -0
  53. package/rcode/skills/seo/seo-content-factory/rules/quality-gates.md +68 -0
  54. package/rcode/skills/seo/seo-content-factory/rules/weekly-cadence.md +61 -0
  55. package/rcode/skills/seo/seo-content-factory/templates/BACKLOG.md +23 -0
  56. package/rcode/skills/seo/seo-content-factory/templates/agent-prompts/01-funnel-A1-A4.md +63 -0
  57. package/rcode/skills/seo/seo-content-factory/templates/agent-prompts/02-fanout-editorial-A5-A6-A8.md +28 -0
  58. package/rcode/skills/seo/seo-content-factory/templates/agent-prompts/03-fanout-programmatic-A7-A6-A8.md +25 -0
  59. package/rcode/skills/seo/seo-content-factory/templates/agent-prompts/04-loop-A9-A10.md +34 -0
  60. package/rcode/skills/seo/seo-content-factory/templates/article.mdx +39 -0
  61. package/rcode/skills/seo/seo-content-factory/templates/cluster.schema.json +52 -0
  62. package/rcode/skills/seo/seo-content-factory/templates/content-brief.md +51 -0
  63. package/rcode/skills/seo/seo-content-factory/templates/jsonld-builders.ts +49 -0
  64. package/rcode/skills/seo/seo-content-factory/templates/keywords.csv +13 -0
  65. package/rcode/skills/seo/seo-content-factory/templates/programmatic-page.tsx +58 -0
  66. package/rcode/skills/seo/seo-content-writer/SKILL.md +263 -0
  67. package/rcode/skills/seo/seo-content-writer/references/content-structure-templates.md +875 -0
  68. package/rcode/skills/seo/seo-content-writer/references/instructions-detail.md +239 -0
  69. package/rcode/skills/seo/seo-content-writer/references/seo-writing-checklist.md +250 -0
  70. package/rcode/skills/seo/seo-content-writer/references/title-formulas.md +339 -0
  71. package/rcode/skills/seo/seo-growth-orchestrator/SKILL.md +79 -0
  72. package/rcode/skills/seo/seo-growth-orchestrator/rules/backlinks.md +29 -0
  73. package/rcode/skills/seo/seo-growth-orchestrator/rules/content-engine.md +27 -0
  74. package/rcode/skills/seo/seo-growth-orchestrator/rules/dos-and-donts.md +40 -0
  75. package/rcode/skills/seo/seo-growth-orchestrator/rules/goals-protocol.md +34 -0
  76. package/rcode/skills/seo/seo-growth-orchestrator/rules/local-seo-stack.md +35 -0
  77. package/rcode/skills/seo/seo-growth-orchestrator/rules/mrr-sprint.md +27 -0
  78. package/rcode/skills/seo/seo-growth-orchestrator/rules/tools.md +19 -0
  79. package/rcode/skills/seo/seo-growth-orchestrator/templates/backlink-research.md +29 -0
  80. package/rcode/skills/seo/seo-growth-orchestrator/templates/goal-protocol.md +35 -0
  81. package/rcode/skills/seo/seo-growth-orchestrator/templates/local-citations.md +24 -0
  82. package/rcode/skills/seo/seo-growth-orchestrator/templates/local-city-pages.md +27 -0
  83. package/rcode/skills/seo/seo-growth-orchestrator/templates/local-gbp-categories.md +23 -0
  84. package/rcode/skills/seo/seo-growth-orchestrator/templates/local-gsc-goldmine.md +27 -0
  85. package/rcode/skills/seo/seo-growth-orchestrator/templates/local-review-mining.md +26 -0
  86. package/rcode/skills/seo/seo-growth-orchestrator/templates/mrr-30day-plan.md +19 -0
  87. package/rcode/skills/seo/seo-growth-orchestrator/templates/mrr-cut.md +13 -0
  88. package/rcode/skills/seo/seo-growth-orchestrator/templates/mrr-opportunity-finder.md +17 -0
  89. package/rcode/skills/seo/seo-growth-orchestrator/templates/mrr-role-model.md +18 -0
  90. package/rcode/skills/seo/seo-site-builder/SKILL.md +149 -0
  91. package/rcode/skills/seo/seo-site-builder/rules/01-niche-discovery.md +64 -0
  92. package/rcode/skills/seo/seo-site-builder/rules/02-deep-research.md +164 -0
  93. package/rcode/skills/seo/seo-site-builder/rules/03-keyword-strategy.md +82 -0
  94. package/rcode/skills/seo/seo-site-builder/rules/04-content-plan.md +173 -0
  95. package/rcode/skills/seo/seo-site-builder/rules/05-site-build.md +187 -0
  96. package/rcode/skills/seo/seo-site-builder/rules/06-yolo.md +127 -0
  97. package/rcode/skills/seo/seo-site-builder/templates/article-stub.mdx +62 -0
  98. package/rcode/skills/seo/seo-site-builder/templates/image-prompt.md +97 -0
  99. package/rcode/skills/seo/seo-site-builder/templates/keyword-export.md +45 -0
  100. package/rcode/skills/seo/seo-site-builder/templates/niche-scorecard.md +28 -0
  101. package/rcode/skills/seo/technical-seo-checker/SKILL.md +329 -0
  102. package/rcode/skills/seo/technical-seo-checker/references/bulk-audit-playbook.md +118 -0
  103. package/rcode/skills/seo/technical-seo-checker/references/ecommerce-platform-patterns.md +166 -0
  104. package/rcode/skills/seo/technical-seo-checker/references/http-status-codes.md +705 -0
  105. package/rcode/skills/seo/technical-seo-checker/references/llm-crawler-handling.md +257 -0
  106. package/rcode/skills/seo/technical-seo-checker/references/pre-migration-playbook.md +137 -0
  107. package/rcode/skills/seo/technical-seo-checker/references/robots-txt-reference.md +717 -0
  108. package/rcode/skills/seo/technical-seo-checker/references/technical-audit-example.md +169 -0
  109. package/rcode/skills/seo/technical-seo-checker/references/technical-audit-templates.md +436 -0
  110. package/rcode/templates/settings-hooks.json +16 -1
  111. package/rcode/workflows/do.md +17 -0
  112. package/rcode/workflows/enable-hooks.md +3 -2
  113. package/rcode/workflows/execute-sprint.md +22 -0
  114. package/rcode/workflows/execute.md +17 -3
  115. package/rcode/workflows/lens-audit.md +49 -8
  116. package/rcode/workflows/new-milestone.md +21 -9
  117. package/rcode/workflows/new-project-create-roadmap.md +2 -0
  118. package/rcode/workflows/new-project-roadmap.md +2 -0
  119. package/rcode/workflows/new-project.md +3 -0
  120. package/rcode/workflows/plan.md +15 -2
  121. package/server/dashboard.js +6 -2
  122. package/server/lib/api.js +35 -20
  123. package/server/lib/html/client/components/OrchPanel.js +43 -2
  124. package/server/lib/html/client/components/dashboard/InProgress.js +5 -4
  125. package/server/lib/html/client/components/shared.js +14 -0
  126. package/server/lib/html/client/orchestrator.js +13 -0
  127. package/server/lib/html/client/views/KanbanView.js +33 -4
  128. package/server/lib/html/css.js +57 -3
  129. package/server/lib/scanner.js +40 -4
  130. 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
- main().catch((err) => {
709
- console.error(`Fatal error: ${err.message}`);
710
- process.exit(1);
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 };