@hanzlaa/rcode 4.3.2 → 4.3.3

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 (31) hide show
  1. package/AGENTS.md +1 -1
  2. package/CONTRIBUTING.md +1 -0
  3. package/README.md +38 -9
  4. package/cli/install.js +14 -0
  5. package/dist/rcode.js +48 -48
  6. package/package.json +1 -1
  7. package/rcode/bin/rcode-hooks.cjs +347 -9
  8. package/rcode/commands/lens-audit.md +8 -6
  9. package/rcode/skills/SKILLS_INDEX.md +3 -2
  10. package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/SKILL.md +12 -0
  11. package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/rules/backlog-building.md +13 -0
  12. package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/rules/merge-strategy.md +17 -0
  13. package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/rules/orchestrator-rhythm.md +9 -0
  14. package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/rules/wave-design.md +21 -0
  15. package/rcode/skills/core/rcode-lazy/SKILL.md +149 -0
  16. package/rcode/templates/settings-hooks.json +12 -1
  17. package/rcode/workflows/do.md +1 -0
  18. package/rcode/workflows/enable-hooks.md +3 -2
  19. package/rcode/workflows/lens-audit.md +49 -8
  20. package/server/dashboard.js +6 -2
  21. package/server/lib/api.js +35 -20
  22. package/server/lib/html/client/components/OrchPanel.js +43 -2
  23. package/server/lib/html/client/components/dashboard/InProgress.js +5 -4
  24. package/server/lib/html/client/components/shared.js +14 -0
  25. package/server/lib/html/client/orchestrator.js +13 -0
  26. package/server/lib/html/client/views/KanbanView.js +33 -4
  27. package/server/lib/html/css.js +57 -3
  28. package/server/lib/scanner.js +40 -4
  29. package/server/orchestrator.js +42 -0
  30. package/rcode/skills/core/rcode-init/scripts/__pycache__/rcode_init.cpython-38.pyc +0 -0
  31. package/rcode/skills/core/rcode-init/scripts/tests/__pycache__/test_rcode_init.cpython-38.pyc +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzlaa/rcode",
3
- "version": "4.3.2",
3
+ "version": "4.3.3",
4
4
  "description": "rcode — the AI team that never forgets. Persistent memory, specialist agents, and slash commands for AI IDEs. Works in Claude Code, Cursor, Gemini, VS Code, and Antigravity.",
5
5
  "main": "cli/index.js",
6
6
  "bin": {
@@ -11,6 +11,7 @@
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
13
  * compact-nudge — advise /rcode-trim or /clear after N Edit/Write calls (#749)
14
+ * prompt-router — nudge toward rcode commands for memory consistency (#892)
14
15
  *
15
16
  * All subcommands read stdin JSON from the hook execution context.
16
17
  * Pure Node stdlib. No external dependencies.
@@ -19,6 +20,7 @@
19
20
  const fs = require('fs');
20
21
  const os = require('os');
21
22
  const path = require('path');
23
+ const { execSync, spawnSync } = require('child_process');
22
24
 
23
25
  /**
24
26
  * Read and parse stdin JSON.
@@ -337,8 +339,6 @@ async function bashGuard() {
337
339
  */
338
340
  async function preCompact() {
339
341
  try {
340
- const path = require('path');
341
- const { execSync } = require('child_process');
342
342
  await readInputJson(); // drain the PreCompact event payload
343
343
 
344
344
  const cwd = process.cwd();
@@ -518,8 +518,6 @@ async function preCompact() {
518
518
  */
519
519
  async function stopVerify() {
520
520
  try {
521
- const path = require('path');
522
- const { spawnSync } = require('child_process');
523
521
  const input = await readInputJson();
524
522
 
525
523
  let changed =
@@ -623,6 +621,339 @@ async function costTrack() {
623
621
  }
624
622
  }
625
623
 
624
+ // ─────────────────────────────────────────────────────────────────────────────
625
+ // INTENT_TABLE — keyword map for prompt-router (#892)
626
+ //
627
+ // WHY: When a user types a free-form prompt that matches a known rcode workflow,
628
+ // we nudge them toward the matching command so the outcome is captured in
629
+ // .rcode/state.json. Work done outside rcode commands never lands in state.
630
+ //
631
+ // Single source of truth: rcode/workflows/do.md routing table (lines ~285-320,
632
+ // "If the text describes..."). Keep in sync — see test/prompt-router-table-sync.test.cjs
633
+ // (Sprint 38.3).
634
+ //
635
+ // Order: first-match-wins, mirroring do.md's "Apply the first matching rule".
636
+ // More-specific keyword sets come before broad ones.
637
+ // ─────────────────────────────────────────────────────────────────────────────
638
+ const INTENT_TABLE = [
639
+ // do.md: "Starting a new project, 'set up', 'initialize'" → /rcode-new-project
640
+ {
641
+ intent: 'new-project',
642
+ keywords: ['set up a new project', 'initialize a new project', 'start a new project', 'create a new project'],
643
+ command: '/rcode-new-project',
644
+ },
645
+ // do.md: "Mapping or analyzing an existing codebase" → /rcode-map-codebase
646
+ {
647
+ intent: 'map-codebase',
648
+ keywords: ['map the codebase', 'map this codebase', 'analyze the codebase', 'analyse the codebase', 'map existing codebase'],
649
+ command: '/rcode-map-codebase',
650
+ },
651
+ // do.md: "A bug, error, crash, failure, or something broken" → /rcode-debug
652
+ // 'error' alone is too broad (matches "what does this error mean?" etc.).
653
+ // Use multi-word forms that signal debug intent rather than a question.
654
+ {
655
+ intent: 'debug',
656
+ keywords: ['bug', 'getting an error', 'throwing an error', 'error in the', 'fix the error', 'debug this', 'crash', 'failure', 'broken', 'not working', 'fails', 'exception', 'traceback'],
657
+ command: '/rcode-debug',
658
+ },
659
+ // do.md: "Audit code quality, 'review changes', 'karpathy', 'check my diff', 'too complex'" → /rcode-review --karpathy
660
+ {
661
+ intent: 'audit-karpathy',
662
+ keywords: ['audit', 'review changes', 'check my diff', 'karpathy', 'too complex', 'complexity', 'code review'],
663
+ command: '/rcode-review --karpathy',
664
+ },
665
+ // do.md: "Make it simpler, 'be lazy', 'simplest solution', 'yagni', 'over-engineered'" → /rcode-lazy
666
+ // Generative simplicity lens (before code is written); /rcode-trim removes bloat after.
667
+ // 'simplify' alone is too broad (overlaps rcode-trim's existing-code territory) — use intent-bearing phrases.
668
+ {
669
+ intent: 'lazy',
670
+ keywords: ['be lazy', 'lazy mode', 'simplest solution', 'yagni', 'over-engineered', 'over-engineering', 'kam code likho'],
671
+ command: '/rcode-lazy',
672
+ },
673
+ // do.md: "Walk through a change, 'checkpoint', 'explain this diff', 'human review'" → /rcode-checkpoint-preview
674
+ {
675
+ intent: 'checkpoint',
676
+ keywords: ['checkpoint', 'explain this diff', 'human review', 'walk through the change', 'walk through this change'],
677
+ command: '/rcode-checkpoint-preview',
678
+ },
679
+ // do.md: "Brainstorm, generate ideas, 'explore options', 'what could we do'" → /rcode-brainstorm
680
+ {
681
+ intent: 'brainstorm',
682
+ keywords: ['brainstorm', 'generate ideas', 'explore options', 'what could we do', 'ideate', 'ideas for'],
683
+ command: '/rcode-brainstorm',
684
+ },
685
+ // do.md: "Exploring, researching, comparing, or 'how does X work'" → /rcode-research-phase
686
+ // 'research' alone fires on "based on my research..." (past-tense reference, not navigation intent).
687
+ // 'how does'/'how do' fire on any factual question — removed in favour of intent-bearing phrases.
688
+ {
689
+ intent: 'explore',
690
+ keywords: ['explore', 'research phase', 'do some research', 'comparing', 'investigate', 'look into', 'understand how'],
691
+ command: '/rcode-research-phase',
692
+ },
693
+ // do.md: "Scope unclear, 'which one', 'better UX', 'how should X look'" → /rcode-discuss-phase
694
+ {
695
+ intent: 'discuss',
696
+ keywords: ['which one', 'better ux', 'how should', 'still have confusion', 'conflicting', 'discuss the scope', 'design this', 'architect this'],
697
+ command: '/rcode-discuss-phase',
698
+ },
699
+ // do.md: "A complex task: refactoring, migration, multi-file architecture, system redesign,
700
+ // integrating a new system/service" → /rcode-add-phase
701
+ // 'integration'/'integrate' catch "let's do X integration", "integrate with Y" — feature-sized
702
+ // architectural work that belongs in a phase, not an ad-hoc edit (#907).
703
+ // Known mild false-positive: "run the integration tests" also matches → a harmless soft
704
+ // nudge toward /rcode-add-phase. Accepted: catching real integration work outweighs it,
705
+ // and no clean substring separates "X integration" from "integration test".
706
+ {
707
+ intent: 'add-phase',
708
+ keywords: ['refactor', 'migration', 'multi-file', 'system redesign', 'multi file', 'large refactor', 'architectural', 'integration', 'integrate'],
709
+ command: '/rcode-add-phase',
710
+ },
711
+ // do.md: "'Sprint planning', 'plan the sprint', 'next sprint'" → /rcode-sprint-planning
712
+ {
713
+ intent: 'sprint-planning',
714
+ keywords: ['sprint planning', 'plan the sprint', 'next sprint', 'what\'s in this sprint', "what's in this sprint"],
715
+ command: '/rcode-sprint-planning',
716
+ },
717
+ // do.md: "Executing a sprint, 'run the sprint', 'start sprint'" → /rcode-execute-sprint
718
+ {
719
+ intent: 'execute-sprint',
720
+ keywords: ['run the sprint', 'start sprint', 'execute sprint', 'work on sprint'],
721
+ command: '/rcode-execute-sprint',
722
+ },
723
+ // do.md: "Planning a specific phase, 'plan phase N'" → /rcode-plan
724
+ {
725
+ intent: 'plan',
726
+ keywords: ["let's plan", 'plan phase', 'plan this', 'let me plan', 'planning phase', 'create a plan', 'please plan', 'plan and think', 'scope this', 'scope the feature'],
727
+ command: '/rcode-plan',
728
+ },
729
+ // do.md: "'Create milestones', 'plan milestones', 'create roadmap'" → /rcode-new-milestone
730
+ {
731
+ intent: 'new-milestone',
732
+ keywords: ['create milestones', 'plan milestones', 'create roadmap', 'break project into milestones', 'new milestone', 'what milestones'],
733
+ command: '/rcode-new-milestone',
734
+ },
735
+ // do.md: "Break milestone into epics/stories, 'create stories', 'user stories', 'epics'" → /rcode-create-epics-and-stories
736
+ {
737
+ intent: 'epics-stories',
738
+ keywords: ['create epics', 'user stories', 'create stories', 'epics and stories', 'break into epics'],
739
+ command: '/rcode-create-epics-and-stories',
740
+ },
741
+ // do.md: "Drift / out-of-date / 'audit feature docs' / 'fill out existing PRD'" → /rcode-feature-drift
742
+ {
743
+ intent: 'feature-drift',
744
+ keywords: ['out of date', 'out-of-date', 'verify docs', 'audit feature docs', 'fill out existing', 'prd drift', 'docs vs code'],
745
+ command: '/rcode-feature-drift',
746
+ },
747
+ // do.md: "General audit / re-audit / extend / fill out / expand an existing artifact" → /rcode-audit
748
+ {
749
+ intent: 'audit',
750
+ keywords: ['re-audit', 'extend the audit', 'fill out the', 'expand the', 're audit'],
751
+ command: '/rcode-audit',
752
+ },
753
+ ];
754
+
755
+ /**
756
+ * Inline flat-YAML parser — mirrors parseSimpleYaml in rcode-tools.cjs:91.
757
+ * Supports `key: value` lines only; strips `#` comments; unquotes.
758
+ * Kept inline so this file stays standalone (no cross-file require).
759
+ */
760
+ function parseSimpleYamlInline(text) {
761
+ const out = {};
762
+ for (const raw of text.split('\n')) {
763
+ const line = raw.replace(/#.*$/, '').trim();
764
+ if (!line) continue;
765
+ const colonAt = line.indexOf(':');
766
+ if (colonAt === -1) continue;
767
+ const key = line.slice(0, colonAt).trim();
768
+ let val = line.slice(colonAt + 1).trim();
769
+ if (val.startsWith('"') && val.endsWith('"')) val = val.slice(1, -1);
770
+ if (val.startsWith("'") && val.endsWith("'")) val = val.slice(1, -1);
771
+ out[key] = val;
772
+ }
773
+ return out;
774
+ }
775
+
776
+ /**
777
+ * Read prompt_nudge from .rcode/config.yaml.
778
+ * Returns 'every' | 'once-per-intent' | 'when-stale' | 'off'.
779
+ * Defaults to 'every' when key is absent, file is missing, or value is unknown.
780
+ */
781
+ function readPromptNudgeToggle(cwd) {
782
+ const VALID = new Set(['every', 'once-per-intent', 'when-stale', 'off']);
783
+ try {
784
+ const cfgPath = path.join(cwd, '.rcode', 'config.yaml');
785
+ const text = fs.readFileSync(cfgPath, 'utf8');
786
+ const parsed = parseSimpleYamlInline(text);
787
+ const val = (parsed.prompt_nudge || '').trim().toLowerCase();
788
+ return VALID.has(val) ? val : 'every';
789
+ } catch {
790
+ return 'every';
791
+ }
792
+ }
793
+
794
+ /**
795
+ * Determine whether a nudge is stale enough to fire under 'when-stale' mode.
796
+ *
797
+ * Heuristic: fire when .rcode/state.json exists AND its mtime is older than
798
+ * the most recent git commit timestamp, OR when state.json is absent in a
799
+ * .planning/ project. This is cheap and best-effort — if any check fails the
800
+ * function returns true (treat as stale = fire). All I/O is wrapped in
801
+ * try/catch to preserve the fail-open contract.
802
+ */
803
+ function isStateStaleFallbackTrue(cwd) {
804
+ try {
805
+ const statePath = path.join(cwd, '.rcode', 'state.json');
806
+ const planningDir = path.join(cwd, '.planning');
807
+ const hasPlanning = fs.existsSync(planningDir);
808
+
809
+ if (!fs.existsSync(statePath)) {
810
+ // No state.json at all — if there IS a .planning/ dir, that means we're
811
+ // in a project that should have state but doesn't: treat as stale.
812
+ return hasPlanning;
813
+ }
814
+
815
+ // state.json exists — check its mtime vs last commit timestamp.
816
+ const stateMtime = fs.statSync(statePath).mtimeMs;
817
+ let lastCommitTs = null;
818
+ try {
819
+ const tsStr = execSync('git log -1 --format=%ct 2>/dev/null', {
820
+ cwd, encoding: 'utf8', timeout: 2000,
821
+ }).trim();
822
+ if (tsStr) lastCommitTs = parseInt(tsStr, 10) * 1000;
823
+ } catch { /* git unavailable or no commits */ }
824
+
825
+ if (lastCommitTs === null) return false; // can't determine, don't nag
826
+ return stateMtime < lastCommitTs;
827
+ } catch {
828
+ return true; // fail open: treat as stale
829
+ }
830
+ }
831
+
832
+ /**
833
+ * prompt-router: Nudge user toward rcode commands for memory consistency (#892).
834
+ *
835
+ * Runs on UserPromptSubmit. Reads stdin JSON synchronously (mirror
836
+ * cli/rcode-slash-router.cjs — NOT the async readInputJson() which rejects on
837
+ * bad JSON). Keyword-matches against INTENT_TABLE (derived from
838
+ * rcode/workflows/do.md lines ~285-320). On match, emits a one-line advisory
839
+ * via hookSpecificOutput.additionalContext. Gated by prompt_nudge config toggle.
840
+ * Always exits 0 with no output on any error or non-match.
841
+ */
842
+ function promptRouter() {
843
+ try {
844
+ // Read stdin synchronously — mirrors cli/rcode-slash-router.cjs readStdin().
845
+ let raw = '';
846
+ try {
847
+ raw = fs.readFileSync(0, 'utf8');
848
+ } catch {
849
+ process.exit(0);
850
+ }
851
+
852
+ if (!raw.trim()) process.exit(0);
853
+
854
+ let data;
855
+ try {
856
+ data = JSON.parse(raw);
857
+ } catch {
858
+ process.exit(0);
859
+ }
860
+
861
+ // Multi-spelling prompt field fallback — mirrors rcode-slash-router.cjs.
862
+ const prompt =
863
+ data.prompt ??
864
+ data.user_prompt ??
865
+ data.userPrompt ??
866
+ data.message ??
867
+ data.input ??
868
+ '';
869
+
870
+ if (typeof prompt !== 'string' || !prompt.trim()) process.exit(0);
871
+
872
+ const trimmed = prompt.trimStart();
873
+
874
+ // Skip prompts that already start with /rcode- — slash router handles those.
875
+ if (/^\/rcode-/.test(trimmed)) process.exit(0);
876
+
877
+ const hookEventName =
878
+ data.hook_event_name || data.hookEventName || 'UserPromptSubmit';
879
+ const cwd = process.cwd();
880
+
881
+ // ── Config toggle ────────────────────────────────────────────────────
882
+ const nudgeMode = readPromptNudgeToggle(cwd);
883
+ if (nudgeMode === 'off') process.exit(0);
884
+
885
+ // ── when-stale: check if state is stale ──────────────────────────────
886
+ if (nudgeMode === 'when-stale' && !isStateStaleFallbackTrue(cwd)) {
887
+ process.exit(0);
888
+ }
889
+
890
+ // ── Keyword match (first-match-wins, case-insensitive) ───────────────
891
+ const lower = prompt.toLowerCase();
892
+ let matched = null;
893
+ for (const entry of INTENT_TABLE) {
894
+ for (const kw of entry.keywords) {
895
+ if (lower.includes(kw.toLowerCase())) {
896
+ matched = entry;
897
+ break;
898
+ }
899
+ }
900
+ if (matched) break;
901
+ }
902
+
903
+ if (!matched) process.exit(0);
904
+
905
+ // ── once-per-intent dedupe ───────────────────────────────────────────
906
+ if (nudgeMode === 'once-per-intent') {
907
+ // Fallback key: parent PID + hourly bucket — scopes naturally to the
908
+ // current shell session without requiring session_id in the payload.
909
+ // Without this, every session shares 'default' and a dedupe file from
910
+ // session A silences nudges in session B permanently.
911
+ const sessionFallback =
912
+ String(process.ppid) + '-' + String(Math.floor(Date.now() / 3600000));
913
+ const sessionId =
914
+ data.session_id || data.tool_input?.session_id || sessionFallback;
915
+ const dedupeFile = path.join(
916
+ os.tmpdir(),
917
+ 'rcode-prompt-nudge-' + sessionId + '.json'
918
+ );
919
+ try {
920
+ let seen = [];
921
+ try {
922
+ seen = JSON.parse(fs.readFileSync(dedupeFile, 'utf8'));
923
+ if (!Array.isArray(seen)) seen = [];
924
+ } catch { /* first run or missing file */ }
925
+
926
+ if (seen.includes(matched.intent)) process.exit(0);
927
+
928
+ seen.push(matched.intent);
929
+ try { fs.writeFileSync(dedupeFile, JSON.stringify(seen)); } catch {}
930
+ } catch {
931
+ // dedupe file unreadable/locked → fire anyway (fail open)
932
+ }
933
+ }
934
+
935
+ // ── Emit advisory ────────────────────────────────────────────────────
936
+ // #907 RC2: lead with the directive ("use X"), not a soft "consider" — a
937
+ // gentle memory-framed tip loses the skill-selection race to imperative
938
+ // SessionStart primers (e.g. superpowers' "you MUST invoke"). The memory
939
+ // rationale stays, but as the fallback note rather than the headline.
940
+ const advisory =
941
+ `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. ` +
942
+ `Prefer it over handling this ad-hoc; if you do proceed manually, run /rcode-memory-update afterward so long-term memory stays consistent.`;
943
+
944
+ const payload = {
945
+ hookSpecificOutput: {
946
+ hookEventName,
947
+ additionalContext: advisory,
948
+ },
949
+ };
950
+ process.stdout.write(JSON.stringify(payload));
951
+ } catch {
952
+ // Fail open — never break the host CLI's prompt.
953
+ }
954
+ process.exit(0);
955
+ }
956
+
626
957
  /**
627
958
  * compact-nudge: Advise /rcode-trim or /clear after N Edit/Write calls (#749).
628
959
  *
@@ -698,14 +1029,21 @@ async function main() {
698
1029
  case 'compact-nudge':
699
1030
  await compactNudge();
700
1031
  break;
1032
+ case 'prompt-router':
1033
+ promptRouter(); // synchronous — exits inside; never falls through to async path
1034
+ break;
701
1035
  default:
702
1036
  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');
1037
+ console.error('Usage: rcode-hooks.cjs pre-edit|pre-workflow|post-commit|bash-guard|pre-compact|stop-verify|cost-track|compact-nudge|prompt-router');
704
1038
  process.exit(1);
705
1039
  }
706
1040
  }
707
1041
 
708
- main().catch((err) => {
709
- console.error(`Fatal error: ${err.message}`);
710
- process.exit(1);
711
- });
1042
+ if (require.main === module) {
1043
+ main().catch((err) => {
1044
+ console.error(`Fatal error: ${err.message}`);
1045
+ process.exit(1);
1046
+ });
1047
+ }
1048
+
1049
+ module.exports = { INTENT_TABLE };
@@ -12,22 +12,24 @@ triggers:
12
12
  - run performance lens
13
13
  - run testability lens
14
14
  - run all lenses
15
- - 15 lens audit
15
+ - 16 lens audit
16
+ - yagni lens
17
+ - over-engineering audit
16
18
  not-for:
17
19
  - phase audit (use /rcode-audit phase)
18
20
  - milestone audit (use /rcode-audit milestone)
19
21
  - karpathy audit without lens context (use /rcode-review --karpathy)
20
- description: Run a structured 15-lens code audit. Picks one lens or all 15 sequentially. Prints findings and ready-to-paste GitHub issue bodies. Never auto-fixes — audit-first.
21
- argument-hint: "[<1-15> | <lens-name> | all]"
22
+ description: Run a structured 16-lens code audit. Picks one lens or all 16 sequentially. Prints findings and ready-to-paste GitHub issue bodies. Never auto-fixes — audit-first.
23
+ argument-hint: "[<1-16> | <lens-name> | all]"
22
24
  allowed-tools: Read, Write, Bash, AskUserQuestion
23
25
  ---
24
26
 
25
27
  ## Overview
26
28
 
27
- 15-lens audit entry point. Each lens is an independent inspection angle:
29
+ 16-lens audit entry point. Each lens is an independent inspection angle:
28
30
  security, performance, testability, extensibility, dep-health, error-recovery,
29
31
  state-machine, i18n, documentation, cross-platform, karpathy, sxo,
30
- observability, naming, coverage.
32
+ observability, naming, coverage, yagni.
31
33
 
32
34
  Runs the selected lens(es), prints labelled findings, outputs GH issue bodies
33
35
  for each lens with findings. Never modifies files — audit-first, fix-second.
@@ -66,5 +68,5 @@ foldable GitHub issue bodies ready to copy-paste or pipe to `gh issue create`.
66
68
  **Negative — wrong lens number:**
67
69
  ```
68
70
  /rcode-lens-audit 99
69
- → Error: lens must be 1-15 or a lens name. Run /rcode-lens-audit --help.
71
+ → Error: lens must be 1-16 or a lens name. Run /rcode-lens-audit --help.
70
72
  ```
@@ -1,6 +1,6 @@
1
1
  # rcode — Skills Index
2
2
 
3
- All 86 skills in rcode, organized by category: 23 agent skills, 38 action skills, 25 core skills, plus 2 shared modules.
3
+ All 87 skills in rcode, organized by category: 23 agent skills, 38 action skills, 26 core skills, plus 2 shared modules.
4
4
 
5
5
  ## Agent Skills (23)
6
6
 
@@ -88,11 +88,12 @@ Invoked by agents via the capabilities table in their SKILL.md. Organized by SDL
88
88
 
89
89
  ---
90
90
 
91
- ## Core Skills (25)
91
+ ## Core Skills (26)
92
92
 
93
93
  Shared utilities used across agents and workflows.
94
94
 
95
95
  - `core/rcode-init` — INTERNAL config loader (installs to .rcode/skills/, not .claude/skills/)
96
+ - `core/rcode-lazy` — always-on YAGNI/simplicity lens; prevents bloat before code is written (rcode-trim removes it after)
96
97
  - `core/rcode-help` — meta-help on available skills
97
98
  - `core/rcode-brainstorming` — structured brainstorming workflow
98
99
  - `core/rcode-advanced-elicitation` — advanced requirements elicitation
@@ -74,6 +74,16 @@ independent agents.
74
74
 
75
75
  ---
76
76
 
77
+ ## Safety — blast radius of skip-permissions agents
78
+
79
+ Every orchestrated agent runs `cld` (= `claude --dangerously-skip-permissions`). There is **no human gate** on local destructive commands — an agent can `rm`, `git reset --hard`, overwrite files, or run any shell command without asking. So:
80
+
81
+ - **Scope each agent prompt to its own worktree + area only.** State the worktree path and branch explicitly, and tell the agent to stay inside it.
82
+ - **Worktree isolation is the only containment.** With permissions skipped, a per-agent git worktree is the single boundary that keeps one agent's mistakes from touching another's work or the main tree.
83
+ - **Never point an autonomous agent at a shared or important tree** (your main checkout, a production clone, anything you can't afford to lose). Always a throwaway worktree.
84
+
85
+ ---
86
+
77
87
  ## herdr CLI reference
78
88
 
79
89
  ```bash
@@ -118,6 +128,8 @@ Full Phase 0-3 protocol, integration-branch rules, wave cadence, and anti-patter
118
128
  in **`references.md`** (sibling file). Key constraints:
119
129
 
120
130
  - Maintain one `campaign-integration` branch; sub-agents fork from it, never master.
131
+ - Each wave agent reads `.planning/campaign/SHARED.md` first and appends a one-line claim
132
+ (`area — agent N — status`) so same-wave agents don't duplicate work.
121
133
  - Wave size: 3-5 agents, 10-15 min per wave. ScheduleWakeup ends EVERY turn.
122
134
  - Phase 3: show user the full diff + ask explicitly how to land (PR / merge / squash / leave).
123
135
  - Never push to origin master without an explicit yes — per campaign, not per session.
@@ -52,6 +52,19 @@ git commit -m "chore(campaign): backlog snapshot at wave <N>"
52
52
  ```
53
53
  The `-f` is required if `.planning/` is gitignored (common in Rihal projects).
54
54
 
55
+ ### Campaign retro (optional)
56
+ At campaign end, append a short `.planning/campaign/RETRO.md` so the *next* campaign on this repo starts smarter:
57
+ - Wave sizes actually used (and which felt right vs too big).
58
+ - What stalled — areas that needed re-dispatch, conflict hotspots, agents that went silent.
59
+ - What merged clean on the first pass.
60
+
61
+ ```bash
62
+ git add -f .planning/campaign/RETRO.md
63
+ git commit -m "chore(campaign): retro notes for next campaign"
64
+ ```
65
+
66
+ **YAGNI tension (be honest)**: this is speculative tooling — a second campaign on the same repo may never happen, and a RETRO doc no one reads is exactly the kind of over-engineering Lens 16 flags. Write it only when a follow-up campaign is genuinely likely; for a one-off campaign, skip it.
67
+
55
68
  ## Examples
56
69
 
57
70
  ### Building backlog from audit docs
@@ -27,6 +27,23 @@ fi
27
27
  ```
28
28
  **Never compound regressions across waves.**
29
29
 
30
+ ### Per-agent verification gate (beyond compile)
31
+ A passing TSC count proves the tree compiles — it does NOT prove the agent did the work it claimed. Before merging a branch, verify **inside the worktree**:
32
+
33
+ 1. **Task/audit doc exists and claims the work.** The agent's `.planning/audits/AUDIT-<area>.md` (or task doc) must exist and describe what it changed. No doc → no merge.
34
+ 2. **`pnpm test` (or the project test command) passes for the affected area.** Run the scoped suite, not just the type-checker.
35
+ 3. **Lint is clean or unchanged.** Run the project linter; a new lint regression blocks the merge the same way a TSC regression does.
36
+ 4. **The diff is non-trivial and on-topic.** `git diff campaign-integration..<branch> --stat` — an empty diff, a whitespace-only diff, or edits outside the agent's area mean the agent didn't actually do the work. Reject and re-dispatch.
37
+
38
+ Only after all four pass does the branch enter the smallest-first merge order.
39
+
40
+ ### Resolvable provenance
41
+ A wave is NOT "verified" until each claimed change has an **openable evidence reference** — a passing test name you can run, a diff hunk you can read, a real `file:line` you can open. A self-declared boolean (`verified: true`, `confidence: 0.94`) is not provenance; it's a claim about provenance.
42
+
43
+ **If provenance can't resolve, the gate FAILS** — treat the change as unverified and block the merge.
44
+
45
+ Failure mode to watch for: a verifier that stamps `"verified / 94% confidence"` while its citation layer actually renders `[object Object]` has verified *nothing*. The boolean looks green; the evidence underneath is broken. Always click through to the citation — if the reference doesn't open to the thing it claims, the verification is void regardless of the confidence number.
46
+
30
47
  ### Conflict resolution (delegates to herdr-orchestration)
31
48
  - Content conflicts: read both sides, keep the **more-complete superset side**, remove markers, syntax-check, stage, commit. (See herdr-orchestration rules.)
32
49
  - AA conflicts (add/add): peek both versions; if nearly identical, keep the canonical owner's version. The "owner" is the branch whose audit doc claimed the feature.
@@ -56,6 +56,9 @@ If the user picks (c) or skips: NEVER claim "Scheduling 20-min wakeup" in chat
56
56
  | Phase 3 (draining last waves) | 270s | Sub-agents finishing close to each other; don't miss the last |
57
57
  | Idle (waiting on stuck pane) | 1200s | Sub-agent stuck — give it room or surface it |
58
58
 
59
+ ### Cost ceiling — estimate before dispatch
60
+ Before dispatching a wave, the orchestrator estimates `agents × wave-duration` (and, across the campaign, the running sum of all waves) as a rough cost proxy. If a planned campaign exceeds a ceiling — by default **~20 agent-waves** or any explicit token/budget cap the user gave — the orchestrator STOPS and asks the user to confirm before continuing rather than burning budget silently. A 17-wave × 4-agent campaign is ~68 agent-runs; surface that number up front so the user can scope it down.
61
+
59
62
  ### Stop conditions
60
63
  The heartbeat should stop ONLY when ALL three are true:
61
64
  - Every herdr pane is `idle` or `done`
@@ -96,6 +99,12 @@ If the orchestrator hits auto-compact mid-campaign, the first turn after must:
96
99
 
97
100
  ## Anti-Patterns
98
101
 
102
+ ### Pane status is text-scraping — trust it loosely
103
+
104
+ **Problem**: `agent_status` (`working`/`blocked`/`idle`) is *inferred from the pane's visible text*, not a real process signal. A stuck agent — frozen mid-output, hung on a network call, or wedged in a loop — leaves its last tokens on screen and can read `working` indefinitely. A green `working` is not proof of progress.
105
+ **Rule (timeout heuristic, now mandatory)**: if a pane reads `working` for **25 min straight**, `herdr pane read <id>` and inspect the actual output. If there's no new progress since the last peek (no new commit, same last line), `C-c` the pane and re-dispatch the item in the next wave. Do not wait out a silently-dead agent on the strength of its status string.
106
+ **Optional reinforcement**: have each agent emit a heartbeat marker line periodically (e.g. `echo "[hb] wave-3 still alive $(date -u +%T)"`); then "no new `[hb]` line in N min" is a far more reliable stuck-signal than the scraped status.
107
+
99
108
  ### Polling every 60s
100
109
 
101
110
  **Problem**: Wakeup interval shorter than 270s burns the Anthropic prompt cache repeatedly without any real work happening (sub-agents need minutes between commits).
@@ -13,6 +13,13 @@ A campaign with too few agents underuses parallelism; too many causes merge conf
13
13
  - **Maximum: 5.** Above 5 the merge stage becomes painful and TSC regressions compound across simultaneous changes.
14
14
  - **Sequential or concurrent waves?** Concurrent only if you have >8 distinct unrelated areas AND the merge bookkeeping is automated. Default is sequential: dispatch wave → merge → dispatch wave.
15
15
 
16
+ ### Shared coordination doc
17
+ Every wave agent reads `.planning/campaign/SHARED.md` **before starting**, and appends a one-line claim the moment it picks up an area:
18
+ ```
19
+ <area> — agent <N> — <status>
20
+ ```
21
+ e.g. `crm-pipeline — agent 2 — claimed`. This stops two parallel agents in the same wave from silently grabbing the same area or file domain. The orchestrator seeds the file at wave dispatch; agents only append, never rewrite. Statuses progress `claimed` → `working` → `done` (or `skipped: <reason>`). If an agent finds its target area already claimed, it stops and reports back instead of duplicating work.
22
+
16
23
  ### Wave scope rules
17
24
  Each agent in a wave must:
18
25
  - Own a **distinct audit area** (no two agents touching the same file domain).
@@ -46,6 +53,20 @@ For each backlog item, score before assigning to a wave:
46
53
  - Past 25 min: peek at all panes, identify stuck agents, decide kill-or-wait.
47
54
  - Hard stop: 45 min. If a wave hasn't produced commits in 45 min, something is wrong — abort and re-dispatch.
48
55
 
56
+ ### Log cost per wave
57
+ After each wave dispatch, append a one-line cost/agent-count note to `.planning/campaign/STATE.md` so the running campaign total stays visible (and the cost ceiling in `orchestrator-rhythm.md` can be checked against it):
58
+ ```
59
+ wave 3: 4 agents dispatched, ~12 min target — running total: 11 agent-runs
60
+ ```
61
+
62
+ ### Optional reviewer agent
63
+ For waves that touch **overlapping areas**, or campaigns running **more than 3 waves**, add one reviewer agent that runs *after* the wave's coders finish but *before* the merge step. The reviewer:
64
+ - Reads all branch diffs for the wave (`git diff campaign-integration..<branch>` per branch).
65
+ - Flags conflicts, scope drift, and duplicated work across the branches.
66
+ - Reports to `.planning/campaign/STATE.md` (or a `REVIEW-wave-<N>.md`) — it does NOT merge.
67
+
68
+ Keep it **optional**. For small bounded work (3-4 distinct, non-overlapping areas), the flat fan-out stays the default — a reviewer agent there is pure overhead. Add it only when the conflict surface or campaign length justifies the extra agent-run.
69
+
49
70
  ## Examples
50
71
 
51
72
  ### Good wave-1 composition (from real session)