@hanzlaa/rcode 4.8.0 → 4.9.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzlaa/rcode",
3
- "version": "4.8.0",
3
+ "version": "4.9.0",
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": {
@@ -6,7 +6,7 @@ color: purple
6
6
  ---
7
7
 
8
8
  @.rcode/references/response-style.md
9
- @.rcode/references/karpathy-guidelines-full.md
9
+ @.rcode/references/karpathy-guidelines.md
10
10
  @.rcode/references/no-unauthorized-git-ops.md
11
11
  @.rcode/references/code-reviewer-playbook.md
12
12
 
@@ -6,7 +6,7 @@ color: yellow
6
6
  ---
7
7
 
8
8
  @.rcode/references/response-style.md
9
- @.rcode/references/karpathy-guidelines-full.md
9
+ @.rcode/references/karpathy-guidelines.md
10
10
  @.rcode/references/no-unauthorized-git-ops.md
11
11
  @.rcode/references/auditor-shared-checklists.md
12
12
  @.rcode/references/docs-auditor-playbook.md
@@ -6,7 +6,7 @@ color: red
6
6
  ---
7
7
 
8
8
  @.rcode/references/response-style.md
9
- @.rcode/references/karpathy-guidelines-full.md
9
+ @.rcode/references/karpathy-guidelines.md
10
10
  @.rcode/references/no-unauthorized-git-ops.md
11
11
  @.rcode/references/auditor-shared-checklists.md
12
12
 
@@ -6,7 +6,7 @@ color: red
6
6
  ---
7
7
 
8
8
  @.rcode/references/response-style.md
9
- @.rcode/references/karpathy-guidelines-full.md
9
+ @.rcode/references/karpathy-guidelines.md
10
10
  @.rcode/references/no-unauthorized-git-ops.md
11
11
  @.rcode/references/auditor-shared-checklists.md
12
12
 
@@ -6,7 +6,7 @@ color: purple
6
6
  ---
7
7
 
8
8
  @.rcode/references/response-style.md
9
- @.rcode/references/karpathy-guidelines-full.md
9
+ @.rcode/references/karpathy-guidelines.md
10
10
  @.rcode/references/no-unauthorized-git-ops.md
11
11
  @.rcode/references/auditor-shared-checklists.md
12
12
 
@@ -6,7 +6,7 @@ color: green
6
6
  ---
7
7
 
8
8
  @.rcode/references/response-style.md
9
- @.rcode/references/karpathy-guidelines-full.md
9
+ @.rcode/references/karpathy-guidelines.md
10
10
  @.rcode/references/sprint-checker-playbook.md
11
11
 
12
12
  <role>
@@ -6,7 +6,7 @@ color: green
6
6
  ---
7
7
 
8
8
  @.rcode/references/response-style.md
9
- @.rcode/references/karpathy-guidelines-full.md
9
+ @.rcode/references/karpathy-guidelines.md
10
10
  @.rcode/references/no-unauthorized-git-ops.md
11
11
  @.rcode/references/verifier-playbook.md
12
12
 
@@ -5402,6 +5402,109 @@ function cmdPlanCheckWaveOverlaps(rawArgs) {
5402
5402
  return { phase: phaseArg, phase_dir: path.relative(PROJECT_ROOT, phaseDir), plans_checked: plans.length, conflicts };
5403
5403
  }
5404
5404
 
5405
+ /**
5406
+ * Deterministic frontend/backend glob check for classify-plan (issue #1021).
5407
+ * Mirrors the FRONTEND_GLOBS / BACKEND_GLOBS rules formerly hand-applied by
5408
+ * the orchestrating LLM in execute-waves.md — kept here as the single source
5409
+ * of truth so both the CLI and the workflow doc describe the same behavior.
5410
+ */
5411
+ function matchesFrontendGlob(file) {
5412
+ const f = String(file || '').toLowerCase();
5413
+ if (/\.(tsx|jsx|css)$/.test(f)) return true;
5414
+ return f.includes('client') || f.includes('ui');
5415
+ }
5416
+ function matchesBackendGlob(file) {
5417
+ const f = String(file || '').toLowerCase();
5418
+ return f.includes('api') || f.includes('server') || f.includes('db') || f.includes('service');
5419
+ }
5420
+
5421
+ const CLASSIFY_PLAN_ROUTE = { frontend: 'rcode-haitham', backend: 'rcode-yousef', 'full-stack': 'rcode-hanzla', other: 'rcode-executor' };
5422
+
5423
+ function classifyPlanFiles(files, objective) {
5424
+ const touchesFrontend = files.some(matchesFrontendGlob);
5425
+ const touchesBackend = files.some(matchesBackendGlob);
5426
+ let classification;
5427
+ if (touchesFrontend && touchesBackend) classification = 'full-stack';
5428
+ else if (touchesFrontend) classification = 'frontend';
5429
+ else if (touchesBackend) classification = 'backend';
5430
+ else classification = 'other';
5431
+
5432
+ if (classification === 'other') {
5433
+ const obj = String(objective || '').toLowerCase();
5434
+ const frontendKeywords = ['react', 'component', 'ui', 'css', 'tailwind', 'frontend', 'client-side', 'accessibility', 'a11y'];
5435
+ const backendKeywords = ['api', 'endpoint', 'database', 'schema', 'service', 'queue', 'backend', 'server-side'];
5436
+ if (frontendKeywords.some((k) => obj.includes(k))) classification = 'frontend';
5437
+ else if (backendKeywords.some((k) => obj.includes(k))) classification = 'backend';
5438
+ }
5439
+ return classification;
5440
+ }
5441
+
5442
+ /**
5443
+ * classify-plan — deterministic replacement for execute-waves.md's
5444
+ * hand-computed FRONTEND_GLOBS/BACKEND_GLOBS classification (issue #1021).
5445
+ * A live execution run showed the orchestrating LLM never actually carried
5446
+ * out the prose pseudocode, so a plan with a "db"-containing path still fell
5447
+ * back to rcode-executor instead of rcode-yousef.
5448
+ *
5449
+ * Two call shapes:
5450
+ * classify-plan <phase> <plan-id> — reads files_modified/objective from the plan's SPRINT.md
5451
+ * classify-plan --files=a,b,c --objective="..." — classify an already-parsed list directly
5452
+ */
5453
+ function cmdClassifyPlan(args) {
5454
+ const flags = {};
5455
+ const positional = [];
5456
+ for (const t of args) {
5457
+ if (t.startsWith('--files=')) flags.files = t.slice('--files='.length);
5458
+ else if (t.startsWith('--objective=')) flags.objective = t.slice('--objective='.length);
5459
+ else positional.push(t);
5460
+ }
5461
+
5462
+ let files = [];
5463
+ let objective = '';
5464
+
5465
+ if (flags.files !== undefined || flags.objective !== undefined) {
5466
+ files = flags.files ? flags.files.split(',').map((s) => s.trim()).filter(Boolean) : [];
5467
+ objective = flags.objective || '';
5468
+ } else {
5469
+ const [phaseArg, planArg] = positional;
5470
+ if (!phaseArg || !planArg) {
5471
+ throw new Error('Usage: classify-plan <phase> <plan-id> OR classify-plan --files=a,b,c --objective="text"');
5472
+ }
5473
+ const phasesDir = path.join(PLANNING_DIR, 'phases');
5474
+ const norm = phaseArg.replace(/^0+/, '') || '0';
5475
+ let phaseDir = null;
5476
+ if (fs.existsSync(phasesDir)) {
5477
+ for (const d of fs.readdirSync(phasesDir)) {
5478
+ const m = d.match(/^(\d+)(?:[-.])/);
5479
+ if (m && (m[1].replace(/^0+/, '') || '0') === norm) { phaseDir = path.join(phasesDir, d); break; }
5480
+ }
5481
+ }
5482
+ if (!phaseDir) throw new Error(`Phase not found: ${phaseArg}`);
5483
+ let planFile = null;
5484
+ for (const file of fs.readdirSync(phaseDir).filter((f) => /-SPRINT\.md$/i.test(f)).sort()) {
5485
+ const stem = file.replace(/-SPRINT\.md$/i, '');
5486
+ if (stem === planArg || stem.endsWith(`-${planArg}`)) { planFile = file; break; }
5487
+ const text = fs.readFileSync(path.join(phaseDir, file), 'utf8');
5488
+ const { frontmatter } = parseFrontmatter(text);
5489
+ if ((frontmatter.sprint || frontmatter.plan) === planArg) { planFile = file; break; }
5490
+ }
5491
+ if (!planFile) throw new Error(`Plan not found: ${planArg} in phase ${phaseArg}`);
5492
+ const text = fs.readFileSync(path.join(phaseDir, planFile), 'utf8');
5493
+ const { frontmatter, body } = parseFrontmatter(text);
5494
+ let block = '';
5495
+ if (text.startsWith('---\n')) {
5496
+ const end = text.indexOf('\n---\n', 4);
5497
+ if (end !== -1) block = text.slice(4, end);
5498
+ }
5499
+ files = fmListField(block, 'files_modified');
5500
+ const objMatch = body.match(/^##\s+(?:Objective|Goal)\s*\n+([^\n]+)/mi);
5501
+ objective = objMatch ? objMatch[1].trim() : (frontmatter.goal || '').replace(/^["']|["']$/g, '');
5502
+ }
5503
+
5504
+ const classification = classifyPlanFiles(files, objective);
5505
+ return { classification, subagent_type: CLASSIFY_PLAN_ROUTE[classification], files_checked: files.length };
5506
+ }
5507
+
5405
5508
  /** phases list — directory inventory under .planning/phases with optional --type filter and --pick path. */
5406
5509
  function cmdPhasesList(args) {
5407
5510
  const argv = Array.isArray(args) ? args : String(args || '').trim().split(/\s+/).filter(Boolean);
@@ -6658,6 +6761,9 @@ async function main() {
6658
6761
  case 'phase-plan-index':
6659
6762
  result = cmdPhasePlanIndex(args.join(' '));
6660
6763
  break;
6764
+ case 'classify-plan':
6765
+ result = cmdClassifyPlan(args);
6766
+ break;
6661
6767
  case 'phases':
6662
6768
  if (args[0] === 'list') { result = cmdPhasesList(args.slice(1)); if (result === undefined) return; }
6663
6769
  else { console.error('Unknown phases subcommand. Valid: list'); process.exit(1); }
@@ -14,8 +14,11 @@ Run these read-only commands in order. Any failure halts the workflow with the f
14
14
  # Check 1: working tree clean
15
15
  DIRTY=$(git status --porcelain 2>/dev/null)
16
16
 
17
- # Check 2: not on a protected branch
17
+ # Check 2: not on a protected branch (skipped entirely when `git.branching_strategy`
18
+ # config is `none` — committing directly to main/master is the deliberately configured
19
+ # workflow in that case)
18
20
  BRANCH=$(git branch --show-current 2>/dev/null)
21
+ BRANCHING_STRATEGY=$(node .rcode/bin/rcode-tools.cjs config-get git.branching_strategy 2>/dev/null)
19
22
  PROTECTED="main master develop v2-prototype"
20
23
 
21
24
  # Check 3: branch follows naming convention
@@ -35,7 +38,7 @@ fi
35
38
  The workflow MUST stop and print the banner below if ANY of:
36
39
 
37
40
  - `DIRTY` is non-empty AND user did not pass `--allow-dirty`
38
- - `BRANCH` is in `$PROTECTED` AND user did not pass `--on-main`
41
+ - `BRANCH` is in `$PROTECTED` AND `BRANCHING_STRATEGY` is not `none` AND user did not pass `--on-main`
39
42
  - `BRANCH_OK` is `no` AND user did not pass `--allow-dirty` (branch-name lint is advisory if working tree is dirty AND user accepted the dirty override)
40
43
  - `OUT_OF_SCOPE` is non-empty AND user did not pass `--allow-scope-drift`
41
44
 
@@ -42,7 +42,7 @@ Use for major workflow transitions.
42
42
 
43
43
  ```
44
44
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
45
- RCODE ► {STAGE NAME}
45
+ rcode ► {STAGE NAME}
46
46
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
47
47
  ```
48
48
 
@@ -69,7 +69,7 @@ Use this when a router command dispatches to another command:
69
69
 
70
70
  ```
71
71
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
72
- RCODE ► ROUTING
72
+ rcode ► ROUTING
73
73
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
74
74
 
75
75
  Input: {user's question or intent}
@@ -328,7 +328,7 @@ Use standard markdown pipe tables with status symbols:
328
328
  **Majlis banner** (multi-agent council):
329
329
  ```
330
330
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
331
- RCODE ► MAJLIS CONVENING
331
+ rcode ► MAJLIS CONVENING
332
332
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
333
333
  ```
334
334
 
@@ -354,7 +354,7 @@ the banner, not inside it.
354
354
 
355
355
  ```
356
356
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
357
- RCODE ► PLANNING SPRINT 01.1
357
+ rcode ► PLANNING SPRINT 01.1
358
358
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
359
359
  التخطيط للسباق 01.1 — يرجى الانتظار
360
360
  ```
@@ -389,7 +389,7 @@ translated prose goes outside the art, on its own line(s).
389
389
 
390
390
  - Varying box/banner widths within same output
391
391
  - Mixing banner styles (`===`, `---`, `***`)
392
- - Skipping `RCODE ►` prefix in stage banners
392
+ - Skipping `rcode ►` prefix in stage banners
393
393
  - Random emoji (`🚀`, `✨`, `💫`) outside the approved set
394
394
  - Missing Next Up block after workflow completions
395
395
  - Hardcoding references to other methodologies in rcode's UX
@@ -29,11 +29,21 @@ Exit.
29
29
  Load phase operation context:
30
30
 
31
31
  ```bash
32
- INIT=$(node ".rcode/bin/rcode-tools.cjs" init phase-op "0" 2>/dev/null)
32
+ INIT=$(node ".rcode/bin/rcode-tools.cjs" init phase-op "1" 2>/dev/null)
33
33
  if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
34
34
  ```
35
35
 
36
- If `INIT` is empty or `INIT.ok` is false, print error and exit:
36
+ `init phase-op` only populates the phase-aware fields (`roadmap_exists`,
37
+ `planning_exists`, `phase_found`, ...) when its `question` argument's first
38
+ token parses as an integer `> 0` (rcode-tools.cjs's `phase-op` handler gates
39
+ on `phaseNum > 0`). add-phase has no target phase to pass — it's creating a
40
+ new one — so a placeholder is required. `"0"` fails that guard, silently
41
+ dropping `roadmap_exists` from every response (#1017); `"1"` satisfies it.
42
+ The dummy value only affects unused fields like `phase_found`/`phase_name`
43
+ (harmless here) — `roadmap_exists` itself is purely a file-existence check
44
+ and doesn't depend on phase 1 actually existing.
45
+
46
+ If `INIT` is empty, print error and exit:
37
47
  ```
38
48
  Error: rcode-tools init failed. Verify .rcode/ is installed and state.json is valid.
39
49
  ```
@@ -92,7 +102,13 @@ The CLI handles:
92
102
  - Creating the phase directory (`.planning/phases/{NN}-{slug}/`)
93
103
  - Inserting the phase entry into ROADMAP.md with Goal, Depends on, and Plans sections
94
104
 
95
- Extract from result: `phase_number`, `padded`, `name`, `slug`, `directory`.
105
+ Extract from result: `phase_number`, `padded`, `name`, `slug`, `directory`,
106
+ `milestone_health` (object: `open_phases`, `recommendation`, `threshold_should`,
107
+ `threshold_consider`), `nudge` (present only when `recommendation` isn't
108
+ `healthy` — a ready-to-print one-liner naming the milestone). `phase add`
109
+ computes these via `milestoneCloseNudge()` (issue #942) as part of the same
110
+ call, so `milestone_health_check` below reads them straight off `$RESULT`
111
+ instead of re-deriving them.
96
112
 
97
113
  **If `BULK_MODE=true`:** after the CLI returns, write the bulk body to `${directory}/TASKS.md` per the structure defined in `detect_task_list`. This step is non-destructive — it only ADDs a TASKS.md file inside the new phase directory.
98
114
  </step>
@@ -110,26 +126,30 @@ If "Roadmap Evolution" section doesn't exist, create it.
110
126
  </step>
111
127
 
112
128
  <step name="milestone_health_check">
113
- After the phase is added, run the milestone-health gauge (issue #718):
129
+ After the phase is added, read the milestone-health gauge (issue #718)
130
+ straight off the `phase add` result captured in `add_phase` — no extra
131
+ subprocess calls. Previously this step spawned a separate `milestone-health`
132
+ call plus 3 `node -e` JSON field extractions to re-derive data that `phase
133
+ add` already returns inline via `milestoneCloseNudge()` (#942); that was
134
+ 4 wasted calls per phase-add for data already sitting in `$RESULT` (#1018).
114
135
 
115
- ```bash
116
- HEALTH=$(node ".rcode/bin/rcode-tools.cjs" milestone-health 2>/dev/null)
117
- RECOMMENDATION=$(echo "$HEALTH" | node -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{try{console.log(JSON.parse(s).recommendation||'unknown')}catch{console.log('unknown')}})")
118
- OPEN_COUNT=$(echo "$HEALTH" | node -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{try{console.log(JSON.parse(s).open_phases||0)}catch{console.log(0)}})")
119
- MILESTONE_NAME=$(echo "$HEALTH" | node -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{try{console.log(JSON.parse(s).milestone||'')}catch{console.log('')}})")
136
+ ```
137
+ RECOMMENDATION="$RESULT.milestone_health.recommendation" # e.g. healthy | consider-closing | should-close
138
+ OPEN_COUNT="$RESULT.milestone_health.open_phases"
139
+ NUDGE="$RESULT.nudge" # ready-to-print, names the milestone; absent when healthy
120
140
  ```
121
141
 
122
142
  If `RECOMMENDATION` is `should-close` (≥12 open phases), surface a hard nudge:
123
143
 
124
144
  ```
125
- Milestone health: {MILESTONE_NAME} has {OPEN_COUNT} open phases.
145
+ ⚠ {NUDGE}
126
146
 
127
147
  Phase {N} is now in this milestone, but the milestone is well past the
128
148
  12-phase threshold for considering closure. Phases are accumulating without
129
149
  a milestone boundary — historically this is where roadmaps lose structure.
130
150
 
131
151
  Recommended next step:
132
- /rcode-complete-milestone close {MILESTONE_NAME} cleanly + archive done phases
152
+ /rcode-complete-milestone close the milestone cleanly + archive done phases
133
153
  /rcode-new-milestone start a fresh milestone for ongoing work
134
154
 
135
155
  If you genuinely want a giant single-milestone roadmap, ignore this and
@@ -139,11 +159,10 @@ continue. The threshold is conservative on purpose.
139
159
  If `RECOMMENDATION` is `consider-closing` (8-11 open phases), softer nudge:
140
160
 
141
161
  ```
142
- Milestone health: {MILESTONE_NAME} has {OPEN_COUNT} open phases — getting full.
143
- Consider /rcode-complete-milestone before adding more.
162
+ ℹ {NUDGE}
144
163
  ```
145
164
 
146
- If `RECOMMENDATION` is `healthy`, say nothing.
165
+ If `RECOMMENDATION` is `healthy` or `milestone_health` is absent (no state.json / no milestone), say nothing.
147
166
  </step>
148
167
 
149
168
  <step name="completion">
@@ -198,11 +198,10 @@ Deviations are normal — handle via rules below.
198
198
  - `type="auto"`: if `tdd="true"` → TDD execution. Implement with deviation rules + auth gates. Verify done criteria. Commit (see task_commit). Track hash for Summary.
199
199
  - `type="checkpoint:*"`: STOP → checkpoint_protocol → wait for user → continue only after confirmation.
200
200
  - **Task completion precedence (when signals conflict):**
201
- 1. `<verify><automated>` — machine-executable shell commands. **Highest authority.** If these pass, the task is done. If these fail, the task is NOT done — regardless of what `<acceptance_criteria>` says.
201
+ 1. `<verify><automated>` — machine-executable shell commands. **Highest authority.** If these pass, the task is done. If these fail, the task is NOT done — regardless of what `<done>` says.
202
202
  2. `<done>` — single observable sentence. Use as the human-readable confirmation once automated checks pass.
203
- 3. `<acceptance_criteria>` prose checklist. **Lowest authority.** Use as a guide during implementation, but automated results override prose judgments.
204
- - If `<verify><automated>` is absent: fall back to `<done>`, then `<acceptance_criteria>`.
205
- - **MANDATORY acceptance_criteria check:** After completing each task, if it has `<acceptance_criteria>`, verify EVERY criterion before moving to the next task. Use grep, file reads, or CLI commands to confirm each criterion. If any criterion fails, fix the implementation before proceeding. Do not skip criteria or mark them as "will verify later".
203
+ - If `<verify><automated>` is absent: fall back to `<done>` alone. `<evidence>` (grep hits, line ranges, or a creates-justification recorded by the planner per issue #649) is supporting grounding, not a completion signal to re-check here — the real plan schema (planner-playbook.md, sprint.md) has no such tag.
204
+ - **MANDATORY completion check:** After completing each task, confirm `<verify><automated>` passes (or, if absent, that the task's `<done>` sentence is observably true). Use grep, file reads, or CLI commands to confirm. If any check fails, fix the implementation before proceeding. Do not skip this or mark it as "will verify later".
206
205
  3. Run `<verification>` checks
207
206
  4. Confirm `<success_criteria>` met
208
207
  5. Document deviations in Summary
@@ -73,45 +73,38 @@ Execute each selected wave in sequence. Within a wave: parallel if `PARALLELIZAT
73
73
 
74
74
  **Classify plan and select subagent_type (BEFORE spawning, once per plan):**
75
75
 
76
- Reuse the `files_modified` list already parsed in step 1's intra-wave overlap check for this
77
- plan do not re-read the plan file to get it again.
76
+ This used to be prose pseudocode the orchestrating LLM was expected to hand-apply
77
+ (FRONTEND_GLOBS/BACKEND_GLOBS matching + keyword fallback). A live execution run showed
78
+ that computation was never actually carried out — a plan whose `files_modified` clearly
79
+ matched the backend glob rule (a path containing `db`) still fell back to `rcode-executor`.
80
+ Classification is now a deterministic CLI call — do not hand-compute it.
78
81
 
79
- ```
80
- FRONTEND_GLOBS = ["*.tsx", "*.jsx", "*.css"] + paths containing "client" or "ui"
81
- BACKEND_GLOBS = paths containing "api", "server", "db", or "service"
82
-
83
- touches_frontend = any(file matches FRONTEND_GLOBS for file in files_modified)
84
- touches_backend = any(file matches BACKEND_GLOBS for file in files_modified)
85
-
86
- if touches_frontend and touches_backend:
87
- classification = "full-stack"
88
- elif touches_frontend:
89
- classification = "frontend"
90
- elif touches_backend:
91
- classification = "backend"
92
- else:
93
- classification = "other" # files_modified empty/absent, or no glob matched
94
- ```
95
-
96
- **If `classification` is `"other"`** (ambiguous, or `files_modified` empty/absent), fall back
97
- to keyword-matching the plan's `<objective>` text before giving up:
98
- - Frontend keywords (React, component, UI, CSS, Tailwind, frontend, client-side, accessibility, a11y) → `classification = "frontend"`
99
- - Backend keywords (API, endpoint, database, schema, service, queue, backend, server-side) → `classification = "backend"`
100
- - Neither matches (pure docs/config/infra plan) → `classification` stays `"other"`
82
+ Call `classify-plan` with the phase and this plan's id (it reads `files_modified` and the
83
+ `<objective>` directly from the plan's SPRINT.md, so no need to re-parse step 1's overlap
84
+ data yourself):
101
85
 
102
- **Route to `subagent_type`:**
86
+ ```bash
87
+ CLASSIFY_JSON=$(node ".rcode/bin/rcode-tools.cjs" classify-plan "$PHASE" "$PLAN_ID" 2>/dev/null)
88
+ SUBAGENT_TYPE=$(echo "$CLASSIFY_JSON" | node -e "let d='';process.stdin.on('data',c=>d+=c).on('end',()=>{try{console.log(JSON.parse(d).subagent_type)}catch{console.log('rcode-executor')}})")
89
+ SUBAGENT_TYPE=${SUBAGENT_TYPE:-rcode-executor}
90
+ ```
103
91
 
104
- | classification | subagent_type |
105
- |---|---|
106
- | frontend | rcode-haitham |
107
- | backend | rcode-yousef |
108
- | full-stack | rcode-hanzla |
109
- | other | rcode-executor |
92
+ Use the literal `subagent_type` value returned — do not second-guess or override it.
110
93
 
111
94
  This decision is computed once per plan, before that plan's Task() spawn(s) below, and the
112
95
  resulting `subagent_type` value is used in the Task() call template (worktree and sequential
113
96
  modes both reuse this same value — see "Sequential mode" further below).
114
97
 
98
+ **Resolve executor model (once per wave, before spawning):**
99
+
100
+ `executor_model` from `init` is the raw `model_profile` string (e.g. `balanced`), not a
101
+ resolved model id — it must be passed through `resolve-model` first, the same way
102
+ `code_review_gate` in execute.md resolves `REVIEWER_MODEL` before its Task() spawn.
103
+ ```bash
104
+ EXECUTOR_MODEL=$(node ".rcode/bin/rcode-tools.cjs" resolve-model executor 2>/dev/null | node -e "let d='';process.stdin.on('data',c=>d+=c).on('end',()=>{try{console.log(JSON.parse(d).model)}catch{console.log('')}})" || echo "sonnet")
105
+ EXECUTOR_MODEL=${EXECUTOR_MODEL:-sonnet}
106
+ ```
107
+
115
108
  **Worktree mode** (`USE_WORKTREES` is not `false`):
116
109
 
117
110
  Before spawning, capture the current HEAD:
@@ -139,7 +132,7 @@ Execute each selected wave in sequence. Within a wave: parallel if `PARALLELIZAT
139
132
  Task(
140
133
  subagent_type="{subagent_type}",
141
134
  description="Execute plan {plan_number} of phase {phase_number}",
142
- model="{executor_model}",
135
+ model="${EXECUTOR_MODEL}",
143
136
  isolation="worktree",
144
137
  prompt="
145
138
  <objective>
@@ -35,11 +35,12 @@ route back to the user.
35
35
  4. **Branch check**: confirm current git branch is appropriate
36
36
  for the work. Two checks, both blocking:
37
37
 
38
- a. **Not on main/master without consent**: if `git branch --show-current`
39
- returns `main` or `master`, refuse to execute. Suggest:
40
- `git switch -c <phase>-<plan>-<slug>` (e.g. `git switch -c 8-1-aria`).
41
- User can override only by passing `--allow-main` to /rcode-execute and
42
- explicitly typing the override on this turn.
38
+ a. **Not on main/master without consent** (skip entirely when `git.branching_strategy`
39
+ config is `none` check via `node .rcode/bin/rcode-tools.cjs config-get
40
+ git.branching_strategy`): if `git branch --show-current` returns `main` or
41
+ `master`, refuse to execute. Suggest: `git switch -c <phase>-<plan>-<slug>`
42
+ (e.g. `git switch -c 8-1-aria`). User can override only by passing `--on-main`
43
+ to /rcode-execute and explicitly typing the override on this turn.
43
44
 
44
45
  b. **Working tree clean enough**: if `git status --porcelain` shows
45
46
  modified files unrelated to this phase's `files_modified` frontmatter,
@@ -49,8 +50,7 @@ route back to the user.
49
50
 
50
51
  The branch name should align with the phase/plan IDs from state — check
51
52
  `workflow.branch_pattern` config (default `<phase>-<plan>-<slug>`).
52
- 5. **Worktree config**: read `workflow.use_worktrees` — if true + parallelization
53
- is true + no file overlaps, plans in a wave run parallel via worktrees
53
+ 5. **Worktree config**: read `workflow.use_worktrees` — if true + no file overlaps, plans in a wave run parallel via worktrees. (`parallelization` is not a real field in `init execute`'s output — see the "initialize" step below; don't gate on it.)
54
54
  </pre_flight>
55
55
 
56
56
  <insight_block>
@@ -238,14 +238,16 @@ If `INIT` is empty or `INIT.ok` is false, print error and exit:
238
238
  Error: rcode-tools init failed. Verify .rcode/ is installed and state.json is valid.
239
239
  ```
240
240
 
241
- Parse JSON for: `executor_model`, `verifier_model`, `commit_docs`, `parallelization`, `branching_strategy`, `branch_name`, `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `plans`, `incomplete_plans`, `plan_count`, `incomplete_count`, `state_exists`, `roadmap_exists`, `phase_req_ids`, `response_language`.
241
+ Parse JSON for these real, top-level fields: `executor_model`, `verifier_model`, `phase_dir`, `plans`, `state_exists`, `response_language`. Fields commonly assumed to exist but that are NOT top-level (verified live this session against `init execute`'s real output, `cmdInitExecute` in rcode-tools.cjs): `branching_strategy` is nested under `config.branching_strategy`; `commit_docs` doesn't exist (closest real value is `config.commit_planning`, a `"true"`/`"false"` string); `parallelization` has no source anywhere (not top-level, not under `config`, not in `phase-plan-index`'s output either); `branch_name` isn't returned (the `handle_branching` step below now computes it from config instead); `phase_name` isn't derivable either (`phase_dir`'s basename is a slug, not the human-readable name — read ROADMAP.md if a step needs it); `incomplete_plans`/`incomplete_count` don't exist (`plans[]` items only carry `{path, depends_on, wave, plan}`, no completion field); `roadmap_exists`/`phase_req_ids` are returned only by the separate `init sprint-plan` command, not `init execute`.
242
+ Derivable, not literal: `phase_found` as `phase_dir !== null`; `phase_number` as the `target` field (the raw phase argument as passed, e.g. `"45"`); `phase_slug` from `phase_dir`'s basename (the part after the first `-`); `plan_count` as `plans.length`. Downstream `${PHASE_NUMBER}`/`${PLAN_COUNT}` references later in this workflow (snapshot tag, review prompts, `phase complete`, etc.) resolve from `target`/`plans.length` per these derivations; `${PHASE_NAME}` and `${INCOMPLETE_COUNT}` have no source here — read `PHASE_NAME` from ROADMAP.md if a later step needs it, and treat `INCOMPLETE_COUNT` as unknown until `phase-plan-index` runs in `discover_and_group_plans` (which does return a real per-plan `has_summary` completion signal).
242
243
 
243
244
  **If `response_language` is set:** Include `response_language: {value}` in all spawned subagent prompts so any user-facing output stays in the configured language.
244
245
 
245
246
  Read worktree config:
246
247
 
247
248
  ```bash
248
- USE_WORKTREES=$(node ".rcode/bin/rcode-tools.cjs" config-get workflow.use_worktrees 2>/dev/null || echo "true")
249
+ USE_WORKTREES=$(node ".rcode/bin/rcode-tools.cjs" config-get workflow.use_worktrees 2>/dev/null)
250
+ USE_WORKTREES=${USE_WORKTREES:-true} # config-get exits 0 with empty output when key absent; || fallback won't fire
249
251
  ```
250
252
 
251
253
  When `USE_WORKTREES` is `false`, all executor agents run without `isolation="worktree"` — they execute sequentially on the main working tree instead of in parallel worktrees.
@@ -265,16 +267,17 @@ When `CONTEXT_WINDOW >= 500000` (1M-class models), subagent prompts include rich
265
267
  - Verifier agents receive all SPRINT.md, SUMMARY.md, CONTEXT.md files plus REQUIREMENTS.md
266
268
  - This enables cross-phase awareness and history-aware verification
267
269
 
268
- **If `phase_found` is false:** Error — phase directory not found. Run `/rcode-status` to inspect state or `/rcode-plan {N}` to create the phase.
269
- **If `plan_count` is 0:** Error — no plans found in phase. Run `/rcode-plan {N}` to generate plans or `/rcode-help` for the command surface.
270
+ **If `phase_dir` is `null` (derived `phase_found` false):** Error — phase directory not found. Run `/rcode-status` to inspect state or `/rcode-plan {N}` to create the phase.
271
+ **If `plans.length` is 0 (derived `plan_count` 0):** Error — no plans found in phase. Run `/rcode-plan {N}` to generate plans or `/rcode-help` for the command surface.
270
272
  **If `state_exists` is false but `.planning/` exists:** Offer reconstruct or continue.
271
273
 
272
- When `parallelization` is false, plans within a wave execute sequentially.
274
+ `parallelization` is not a real field in `init execute`'s output (see the "initialize" step's field notes above) — this line currently documents behavior with no data source; don't treat it as a working toggle until a real source is wired in.
273
275
 
274
276
  **Runtime detection for Copilot:**
275
277
  Check if the current runtime is Copilot by testing for the `@rcode-executor` agent pattern
276
278
  or absence of the `Task()` subagent API. If running under Copilot, force sequential inline
277
- execution regardless of the `parallelization` settingCopilot's subagent completion
279
+ execution unconditionally (there is no real `parallelization` toggle to override see the
280
+ "initialize" step's field notes above) — Copilot's subagent completion
278
281
  signals are unreliable (see `<runtime_compatibility>`). Set `COPILOT_SEQUENTIAL=true`
279
282
  internally and skip the `execute_waves` step in favor of `check_interactive_mode`'s
280
283
  inline path for each plan.
@@ -352,7 +355,7 @@ Check `branching_strategy` from init:
352
355
 
353
356
  **"none":** Skip, continue on current branch.
354
357
 
355
- **"phase" or "milestone":** Use pre-computed `branch_name` from init:
358
+ **"phase" or "milestone":** `init execute` does not return `branch_name` (see the "initialize" step's field notes above) — compute `BRANCH_NAME` from `workflow.branch_pattern` config (default `<phase>-<plan>-<slug>`) before running:
356
359
  ```bash
357
360
  git checkout -b "$BRANCH_NAME" 2>/dev/null || git checkout "$BRANCH_NAME"
358
361
  ```
@@ -361,7 +364,7 @@ All subsequent commits go to this branch. User handles merging.
361
364
  </step>
362
365
 
363
366
  <step name="validate_phase">
364
- From init JSON: `phase_dir`, `plan_count`, `incomplete_count`.
367
+ From init JSON: `phase_dir` (real); `plan_count` derives as `plans.length`; `incomplete_count` has no source at this point (see the "initialize" step's field notes) — treat as unknown until `phase-plan-index` runs.
365
368
 
366
369
  Report: "Found {plan_count} plans in {phase_dir} ({incomplete_count} incomplete)"
367
370
 
@@ -527,7 +530,7 @@ Selected wave finished successfully. This phase still has incomplete plans, so p
527
530
  <step name="run_verify_commands">
528
531
  **Run per-task `<verify>` shell commands from all completed SPRINT.md plans.**
529
532
 
530
- After all executor agents finish, extract and run any `<verify>` blocks defined in plan tasks. These are the machine-executable counterpart to `<acceptance_criteria>` prose.
533
+ After all executor agents finish, extract and run any `<verify>` blocks defined in plan tasks. These are the machine-executable proof that a task's `<done>` criteria are met — the plan schema has no such tag; `<verify><automated>` plus `<evidence>` grounding are what the planner and executor actually emit and enforce.
531
534
 
532
535
  ```bash
533
536
  # Extract all <verify> blocks from all SPRINT.md files for this phase
@@ -745,10 +748,10 @@ fi
745
748
  ```
746
749
  ⚠ Phase {X} EXECUTED but not yet verified.
747
750
 
748
- The following acceptance criteria require human verification before
751
+ The following task completion criteria require human verification before
749
752
  the phase can advance to `status: complete`:
750
753
 
751
- {list AC items from SPRINT.md}
754
+ {list each task's <done> sentence from SPRINT.md}
752
755
 
753
756
  Recommended next steps:
754
757
  /rcode-add-tests {X} — generate unit + E2E tests before UAT
@@ -761,7 +764,7 @@ fi
761
764
  **If `VERIFICATION_STATUS` is `fail`:**
762
765
 
763
766
  1. Mark the phase as `status: executed` (so /rcode-plan --gaps can run a closure cycle).
764
- 2. Surface the failed AC items.
767
+ 2. Surface the tasks whose `<done>` criteria failed human verification.
765
768
  3. STOP. Don't mark complete on a failing verification.
766
769
 
767
770
  **Only when `VERIFICATION_STATUS` is `pass`** — proceed to `update_roadmap` below.
@@ -279,10 +279,10 @@ if [ -s .rcode/context/active.md ] && ! grep -q "Run \`/rcode" .rcode/context/ac
279
279
  fi
280
280
  ```
281
281
 
282
- After writing both files, refresh the memory bank fingerprint so staleness checks see the project as fresh:
282
+ After writing both files, refresh the memory bank fingerprint so staleness checks see the project as fresh. `context refresh` reads `.rcode/sources.yaml`, but nothing in rcode ever creates that file (the installer only scaffolds the unrelated `.rcode/brain/sources.yaml`, used by `brain pull`) — so on every fresh project this call is a guaranteed no-op. Skip it when the file is known not to exist rather than spending a call to learn that (#1018):
283
283
 
284
284
  ```bash
285
- node .rcode/bin/rcode-tools.cjs context refresh >/dev/null 2>&1 || true
285
+ test -f .rcode/sources.yaml && node .rcode/bin/rcode-tools.cjs context refresh >/dev/null 2>&1 || true
286
286
  ```
287
287
 
288
288
  ## Step 4c — Scaffold CLAUDE.md / AGENTS.md if missing
@@ -327,6 +327,14 @@ Or strategic question about the codebase:
327
327
  /rcode-council {your question}
328
328
  ```
329
329
 
330
+ **In all three cases, append this tip:**
331
+ ```
332
+ Tip: run /rcode-enable-hooks to turn on a one-line project status primer at
333
+ the start of every session, plus 9 other opt-in guardrails (read-before-edit
334
+ checks, dangerous-command blocking, auto-formatting). Off by default so a
335
+ fresh install never surprises you.
336
+ ```
337
+
330
338
  **If `returning` with `--reset`:**
331
339
  ```
332
340
  ✓ rcode reconfigured. Prior state preserved in state.json.