@hanzlaa/rcode 4.13.0 → 4.14.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.13.0",
3
+ "version": "4.14.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": {
@@ -21,7 +21,7 @@ Your files feed the roadmap:
21
21
  | File | How Roadmap Uses It |
22
22
  |------|---------------------|
23
23
  | `SUMMARY.md` | Phase structure recommendations, ordering rationale |
24
- | `STACK.md` | Technology decisions for the project |
24
+ | `STACK.md` | Technology **suggestions** for the project, with the premise behind each |
25
25
  | `FEATURES.md` | What to build in each phase |
26
26
  | `ARCHITECTURE.md` | System structure, component boundaries |
27
27
  | `PITFALLS.md` | What phases need deeper research flags |
@@ -125,3 +125,21 @@ Log each pass as:
125
125
  ## Examples
126
126
 
127
127
  See `.rcode/agents-rules/project-researcher/detailed-guide.md` for full worked examples (happy path, edge case, negative).
128
+
129
+ ## STACK.md is a suggestion, never a decision
130
+
131
+ You do not choose the stack. You surface options and the trade-off, and the user
132
+ decides at the stack gate (`new-project-research-decision.md` step 6b).
133
+
134
+ Write STACK.md accordingly:
135
+
136
+ - **Every recommendation carries its premise** — the one condition that makes it
137
+ right. Not "WordPress is popular for content sites" but "WordPress IF a
138
+ non-technical person will update content with no dev retainer."
139
+ - **Name the closest alternative and when it wins.** A recommendation with no
140
+ alternative is a decision wearing a recommendation's clothes.
141
+ - **Never write "Locked", "Final", or "Decided"** in STACK.md. You have no
142
+ authority to lock anything; only a user answer at the gate does that.
143
+
144
+ A premise you cannot state is a recommendation you have not justified. Say you
145
+ don't have enough to recommend one rather than picking the familiar option.
@@ -127,28 +127,69 @@ function extractPhases(content) {
127
127
  return phases;
128
128
  }
129
129
 
130
+ // Requirement IDs come in two shapes and BOTH are real:
131
+ // REQ-AUTH, REQ-FOO-BAR — the documented convention
132
+ // FOUND-01, RENT-04, OBJ-06, AUTHZ-04 — what projects actually write
133
+ // This must stay in step with extractReqIds() in rcode-tools.cjs. Two copies of
134
+ // this pattern already drifted once: one was widened for domain prefixes and
135
+ // this one was not, so `roadmap get-phase` kept returning requirements: [] on
136
+ // every domain-prefixed project.
137
+ const REQ_ID_RE = /\bREQ-[A-Z0-9][A-Z0-9-]*\b|\b[A-Z][A-Z0-9]{1,15}-\d+[a-z]?\b/g;
138
+
139
+ /**
140
+ * Build a matcher for a labelled block, tolerant of how the label is actually
141
+ * written. Roadmapper emits `**Success criteria:**` (colon inside the bold,
142
+ * lowercase c) while the old parser demanded `**Success Criteria**:` (colon
143
+ * outside). Two rcode components disagreeing about rcode's own format is what
144
+ * made get-phase unable to read its own roadmapper's output.
145
+ *
146
+ * Accepts: **Label:** | **Label**: | ## Label | Label:
147
+ * Returns { inline, list } — inline is same-line content, list is the block
148
+ * of bullet/numbered lines that follows. Callers use whichever is present.
149
+ */
150
+ function matchLabelledBlock(section, label) {
151
+ const l = label.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
152
+ // [ \t]* everywhere the match must NOT cross a line. Using \s* here let the
153
+ // label matcher swallow the newline, so the first bullet of the following
154
+ // list was captured as "inline" and its description text was thrown away —
155
+ // `- CITY-02 per-city page exists` came back as just `CITY-02`.
156
+ const re = new RegExp(
157
+ '(?:\\*\\*[ \\t]*' + l + '[ \\t]*:?[ \\t]*\\*\\*[ \\t]*:?|#{1,4}[ \\t]*' + l + '[ \\t]*:?|^[ \\t]*' + l + '[ \\t]*:)' +
158
+ '([^\\n]*)\\n((?:[ \\t]*(?:\\d+\\.|[-*])[ \\t]+[^\\n]+\\n?)*)',
159
+ 'im'
160
+ );
161
+ const m = section.match(re);
162
+ if (!m) return null;
163
+ return { inline: (m[1] || '').trim(), list: m[2] || '' };
164
+ }
165
+
166
+ function splitListBlock(block) {
167
+ return String(block).split('\n')
168
+ .map((l) => l.replace(/^[ \t]*(?:\d+\.|[-*])[ \t]+/, '').trim())
169
+ .filter(Boolean);
170
+ }
171
+
130
172
  function parseRequirements(section) {
131
- // Matches both bold-style (**Requirements:**) and heading-style (### Requirements)
132
- // followed by a list block.
133
- const listMatch = section.match(/(?:\*\*Requirements(?::\*\*|\*\*:)|#{1,4}\s*Requirements\s*:?)[^\n]*\n((?:\s*(?:\d+\.|[-*])\s+[^\n]+\n?)+)/i);
134
- if (listMatch) {
135
- return listMatch[1].split('\n')
136
- .map((l) => l.replace(/^\s*(?:\d+\.|[-*])\s+/, '').trim())
137
- .filter(Boolean);
173
+ const block = matchLabelledBlock(section, 'Requirements');
174
+ if (block) {
175
+ // A following list block wins; otherwise take the same-line value, which is
176
+ // how roadmapper writes it: `**Requirements:** FOUND-01, FOUND-02, RENT-04`.
177
+ const fromList = splitListBlock(block.list);
178
+ if (fromList.length > 0) return fromList;
179
+ if (block.inline) {
180
+ const ids = block.inline.match(REQ_ID_RE);
181
+ if (ids && ids.length > 0) return [...new Set(ids)];
182
+ return block.inline.split(/\s*,\s*/).map(x => x.trim()).filter(Boolean);
183
+ }
138
184
  }
139
185
 
140
- // Also capture REQ-IDs from inline lines like:
141
- // **REQs:** REQ-004, REQ-010, REQ-020
142
- // Requirements: REQ-001, REQ-002
143
- // **Covers:** REQ-001, REQ-003
144
- // Collect every line in the section that contains REQ-\d+ patterns.
186
+ // Last resort: sweep the whole section for requirement IDs on any line
187
+ // (covers `**REQs:**`, `**Covers:**`, and prose mentions).
145
188
  const seen = new Set();
146
189
  const out = [];
147
- const reqIdRe = /\bREQ-[A-Z0-9][A-Z0-9-]*\b/g;
148
190
  for (const line of section.split('\n')) {
149
- if (!/REQ-/i.test(line)) continue;
150
- const ids = line.match(reqIdRe) || [];
151
- for (const id of ids) {
191
+ const matches = line.match(REQ_ID_RE) || [];
192
+ for (const id of matches) {
152
193
  if (!seen.has(id)) { seen.add(id); out.push(id); }
153
194
  }
154
195
  }
@@ -156,12 +197,11 @@ function parseRequirements(section) {
156
197
  }
157
198
 
158
199
  function parseSuccessCriteria(section) {
159
- // Matches both bold-style (**Success Criteria:**) and heading-style (### Success Criteria)
160
- const match = section.match(/(?:\*\*Success Criteria\*\*[^\n]*:|#{1,4}\s*Success Criteria\s*:?)\s*\n((?:\s*(?:\d+\.|[-*])\s+[^\n]+\n?)+)/i);
161
- if (!match) return [];
162
- return match[1].split('\n')
163
- .map((l) => l.replace(/^\s*(?:\d+\.|[-*])\s+/, '').trim())
164
- .filter(Boolean);
200
+ const block = matchLabelledBlock(section, 'Success criteria');
201
+ if (!block) return [];
202
+ const fromList = splitListBlock(block.list);
203
+ if (fromList.length > 0) return fromList;
204
+ return block.inline ? [block.inline] : [];
165
205
  }
166
206
 
167
207
  function parsePlans(section) {
@@ -193,7 +193,13 @@ function extractReqIds(requirements) {
193
193
  if (!Array.isArray(requirements) || requirements.length === 0) return [];
194
194
  const seen = new Set();
195
195
  const out = [];
196
- const re = /\bREQ-[A-Z0-9][A-Z0-9-]*\b/g;
196
+ // Two shapes, because real projects rarely use the REQ- prefix:
197
+ // REQ-AUTH, REQ-FOO-BAR — the documented convention
198
+ // FOUND-01, RENT-04, AUTHZ-04, OBJ-06, CITY-02 — what projects actually write
199
+ // Matching only the first shape returned an empty phase_req_ids on every
200
+ // domain-prefixed project, and plan.md's Requirements Coverage Gate skips
201
+ // itself when that array is empty. The gate was silently off, not passing.
202
+ const re = /\bREQ-[A-Z0-9][A-Z0-9-]*\b|\b[A-Z][A-Z0-9]{1,15}-\d+[a-z]?\b/g;
197
203
  for (const line of requirements) {
198
204
  const matches = String(line).match(re) || [];
199
205
  for (const m of matches) {
@@ -1414,6 +1420,26 @@ function cmdState(subArgs) {
1414
1420
  // --- set-phase ---
1415
1421
  if (sub === 'set-phase') {
1416
1422
  const name = subArgs[1];
1423
+ // A flag-looking argument is never a phase name. Without this,
1424
+ // `state set-phase --phase 99 --status complete` created a phase literally
1425
+ // NAMED "--phase", set current_phase to "--phase", and returned ok:true.
1426
+ // Silent state corruption reported from a live project.
1427
+ if (typeof name === 'string' && name.startsWith('--')) {
1428
+ throw new Error(
1429
+ `set-phase takes a phase NAME as a positional argument, not flags. ` +
1430
+ `Got "${name}". Did you mean:\n` +
1431
+ ` state set-phase "Phase name" (set the current phase pointer)\n` +
1432
+ ` phase complete <N> (mark a phase complete)\n` +
1433
+ ` state planned-phase --phase <N> (record a phase as planned)`
1434
+ );
1435
+ }
1436
+ const strayFlags = subArgs.slice(2).filter(a => typeof a === 'string' && a.startsWith('--'));
1437
+ if (strayFlags.length > 0) {
1438
+ throw new Error(
1439
+ `set-phase does not accept flags (${strayFlags.join(', ')}). It sets the ` +
1440
+ `current-phase pointer only. Use 'phase complete <N>' to change a phase's status.`
1441
+ );
1442
+ }
1417
1443
  if (!name) throw new Error('set-phase requires a phase name argument');
1418
1444
  const state = readState() || defaultState();
1419
1445
  // Fix #854 — mark the previously active phase as completed before switching.
@@ -3256,6 +3282,29 @@ function cmdState(subArgs) {
3256
3282
  // instead. Its stale-executing-phase hygiene warning was ported there.
3257
3283
  // Kept only for backward compatibility with anyone scripting against it
3258
3284
  // directly; do not wire new callers to this — use `phase complete`.
3285
+ // Records what the user actually authorized this session — 'plan', 'build',
3286
+ // 'research', 'audit'. `resume-work` reads it so "resume" restores POSITION
3287
+ // AND SCOPE, not position alone. Without it, a resume after a planning
3288
+ // session reads as "keep going" and starts building work nobody asked for.
3289
+ if (sub === 'set-intent') {
3290
+ const flags = parseFlags(1);
3291
+ const intent = flags.intent || subArgs[1];
3292
+ const ALLOWED = ['plan', 'build', 'research', 'audit', 'review'];
3293
+ if (!intent) throw new Error(`set-intent requires an intent (${ALLOWED.join('|')})`);
3294
+ if (!ALLOWED.includes(intent)) {
3295
+ throw new Error(`unknown intent "${intent}" — expected one of: ${ALLOWED.join(', ')}`);
3296
+ }
3297
+ const state = readState() || defaultState();
3298
+ const previous = state.last_intent ? state.last_intent.intent : null;
3299
+ state.last_intent = {
3300
+ intent,
3301
+ recorded_at: new Date().toISOString(),
3302
+ source: flags.source || 'workflow',
3303
+ };
3304
+ writeState(state);
3305
+ return { ok: true, intent, previous };
3306
+ }
3307
+
3259
3308
  if (sub === 'complete-phase') {
3260
3309
  const flags = parseFlags(1);
3261
3310
  if (!flags.phase) throw new Error('complete-phase requires --phase <N>');
@@ -3582,6 +3631,32 @@ function cmdState(subArgs) {
3582
3631
  }
3583
3632
 
3584
3633
  if (existingIdx >= 0) {
3634
+ // Identity check BEFORE anything is carried over. Sync matched this
3635
+ // entry by NUMBER, but a number is a slot, not an identity. When a
3636
+ // roadmap is replaced, slot 3 can go from "Location Template" to
3637
+ // "Competitor Gap Analysis" — two unrelated pieces of work. Carrying
3638
+ // the old status across told a live project that competitor analysis
3639
+ // was "complete" when it had never been started, and only a manual
3640
+ // disk audit caught it.
3641
+ const priorName = String(state.phases[existingIdx].name || '').trim();
3642
+ const incomingName = String(phaseName || '').trim();
3643
+ const normName = (n) => n.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim();
3644
+ const identityChanged = priorName && incomingName
3645
+ && normName(priorName) !== normName(incomingName);
3646
+ if (identityChanged) {
3647
+ // Different work in the same slot. Drop the inherited status and
3648
+ // completion, and let the disk-derived pass below decide afresh.
3649
+ state.phases[existingIdx].status = 'planned';
3650
+ delete state.phases[existingIdx].completed;
3651
+ delete state.phases[existingIdx].started;
3652
+ parsed.identity_changed = parsed.identity_changed || [];
3653
+ parsed.identity_changed.push({
3654
+ phase: phaseNum,
3655
+ was: priorName,
3656
+ now: incomingName,
3657
+ carried_status_dropped: true,
3658
+ });
3659
+ }
3585
3660
  // Backfill both id and number so future readers using either schema find it.
3586
3661
  state.phases[existingIdx].number = state.phases[existingIdx].number || phaseNum;
3587
3662
  state.phases[existingIdx].id = state.phases[existingIdx].id || phaseNum;
@@ -4468,6 +4543,70 @@ function cmdPhase(subArgs) {
4468
4543
  // Closes #731. No --names arg required — reads the ROADMAP table directly.
4469
4544
  // Only creates directories; does NOT create .md files inside them.
4470
4545
  // =====================================================================
4546
+ // phase rename-dir <N> — align a phase directory's slug with its ROADMAP name.
4547
+ // Dry-run by default: renaming a directory moves artifacts and, without git mv,
4548
+ // detaches their history. There was no mechanism for this at all, so a roadmap
4549
+ // rewrite left every directory carrying the name of whatever it used to be.
4550
+ if (sub === 'rename-dir') {
4551
+ // cmdPhase has no shared flag parser (parseFlags is local to cmdState), so
4552
+ // read the two flags this needs directly.
4553
+ const argvIdx = subArgs.findIndex((a, i) => i > 0 && !String(a).startsWith('--'));
4554
+ const phaseFlagIdx = subArgs.indexOf('--phase');
4555
+ const target = argvIdx > 0 ? subArgs[argvIdx]
4556
+ : (phaseFlagIdx !== -1 ? subArgs[phaseFlagIdx + 1] : null);
4557
+ if (!target) throw new Error('phase rename-dir requires a phase number');
4558
+ const apply = subArgs.includes('--apply');
4559
+
4560
+ const found = cmdFindPhase([String(target)]);
4561
+ if (!found.exists) throw new Error(`No phase directory on disk for phase ${target}`);
4562
+
4563
+ const roadmapLib = require(path.join(__dirname, 'lib', 'roadmap.cjs'));
4564
+ const rp = roadmapLib.dispatch(PROJECT_ROOT, ['get-phase', String(target)]);
4565
+ if (!rp || !rp.found || !rp.name) {
4566
+ throw new Error(`Phase ${target} not found in ROADMAP.md — nothing to rename toward`);
4567
+ }
4568
+
4569
+ const slugify = (t) => String(t).toLowerCase()
4570
+ .replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').replace(/-+/g, '-');
4571
+ const newSlug = slugify(rp.name);
4572
+ const phasesDir = path.join(PLANNING_DIR, 'phases');
4573
+ const oldDirName = path.basename(found.dir);
4574
+ const newDirName = `${target}-${newSlug}`;
4575
+
4576
+ if (oldDirName === newDirName) {
4577
+ return { ok: true, renamed: false, reason: 'directory already matches the roadmap name', dir: found.dir };
4578
+ }
4579
+ const newPath = path.join(phasesDir, newDirName);
4580
+ if (fs.existsSync(newPath)) {
4581
+ throw new Error(`Target directory already exists: ${newDirName}. Resolve by hand — two phase dirs for one number is worse than a stale name.`);
4582
+ }
4583
+
4584
+ if (!apply) {
4585
+ return {
4586
+ ok: true,
4587
+ renamed: false,
4588
+ dry_run: true,
4589
+ from: oldDirName,
4590
+ to: newDirName,
4591
+ note: 'Dry run. Re-run with --apply to rename. Check first that the artifacts in this directory belong to the phase the roadmap now describes — if the phase was REPLACED rather than renamed, renaming hides that instead of fixing it.',
4592
+ };
4593
+ }
4594
+
4595
+ // Prefer `git mv` so the artifacts keep their history.
4596
+ const oldPath = path.join(phasesDir, oldDirName);
4597
+ let method = 'fs';
4598
+ const { spawnSync } = require('child_process');
4599
+ const gitMv = spawnSync('git', ['mv', oldPath, newPath], { cwd: PROJECT_ROOT, encoding: 'utf8' });
4600
+ if (gitMv.status === 0) { method = 'git mv'; }
4601
+ else { fs.renameSync(oldPath, newPath); }
4602
+
4603
+ return {
4604
+ ok: true, renamed: true, method,
4605
+ from: oldDirName, to: newDirName,
4606
+ warning: 'Any file referencing the old path (SPRINT frontmatter, SUMMARY links, notes) still points at it. Grep for the old slug.',
4607
+ };
4608
+ }
4609
+
4471
4610
  if (sub === 'scaffold-all') {
4472
4611
  const roadmapPath = path.join(PLANNING_DIR, 'ROADMAP.md');
4473
4612
  const phasesDir = path.join(PLANNING_DIR, 'phases');
@@ -5801,11 +5940,52 @@ function cmdFindPhase(args) {
5801
5940
  .map((d) => path.relative(PROJECT_ROOT, path.join(phasesDir, d)));
5802
5941
  if (!exact) return { number: target, exists: false, dir: null, slug: null, decimal_children };
5803
5942
  const slugMatch = exact.match(/^\d+(?:\.\d+)?[-](.+)$/);
5943
+ const slug = slugMatch ? slugMatch[1] : '';
5944
+
5945
+ // Name drift: the directory keeps the slug it was created with, but ROADMAP.md
5946
+ // can be rewritten under it. Resolving to the existing directory is correct —
5947
+ // that is where the artifacts and the git history live, and auto-renaming would
5948
+ // orphan both. Staying SILENT about the divergence is not: an agent reading
5949
+ // `slug: foundation-contact-loop` while the roadmap says "Rentable Contact
5950
+ // Layer" has no way to tell whether it is looking at the same work.
5951
+ // Same class as the phase-identity drift in `state sync --from-disk`.
5952
+ let name_drift = null;
5953
+ try {
5954
+ const roadmapPath = path.join(PLANNING_DIR, 'ROADMAP.md');
5955
+ if (slug && fs.existsSync(roadmapPath)) {
5956
+ const roadmapLib = require(path.join(__dirname, 'lib', 'roadmap.cjs'));
5957
+ const rp = roadmapLib.dispatch(PROJECT_ROOT, ['get-phase', String(target)]);
5958
+ const roadmapName = rp && rp.found ? String(rp.name || '') : '';
5959
+ if (roadmapName) {
5960
+ const slugify = (t) => String(t).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
5961
+ const fromRoadmap = slugify(roadmapName);
5962
+ // Compare on word sets, not exact slugs — a truncated or reordered slug
5963
+ // is normal, a different subject is the signal.
5964
+ const words = (t) => new Set(String(t).split('-').filter(w => w.length > 3));
5965
+ const dirWords = words(slug);
5966
+ const roadWords = words(fromRoadmap);
5967
+ const shared = [...dirWords].filter(w => roadWords.has(w)).length;
5968
+ if (dirWords.size > 0 && roadWords.size > 0 && shared === 0) {
5969
+ name_drift = {
5970
+ dir_slug: slug,
5971
+ roadmap_name: roadmapName,
5972
+ note: 'Directory name and ROADMAP name share no significant words. '
5973
+ + 'The phase may have been replaced under the same number. '
5974
+ + 'The directory is NOT auto-renamed: its artifacts and git history '
5975
+ + 'belong to whatever was built there. Confirm they are the same work '
5976
+ + 'before planning or executing against it.',
5977
+ };
5978
+ }
5979
+ }
5980
+ }
5981
+ } catch { /* drift detection is advisory — never fail a lookup over it */ }
5982
+
5804
5983
  return {
5805
5984
  number: target,
5806
5985
  exists: true,
5807
5986
  dir: path.relative(PROJECT_ROOT, path.join(phasesDir, exact)),
5808
- slug: slugMatch ? slugMatch[1] : '',
5987
+ slug,
5988
+ name_drift,
5809
5989
  decimal_children,
5810
5990
  };
5811
5991
  }
@@ -7434,6 +7614,7 @@ async function main() {
7434
7614
  console.log(' phase next-range [count] → return next N contiguous free phase numbers (#730)');
7435
7615
  console.log(' phase scaffold-milestone --names "n1|n2|..." → bulk-create phase folders for a milestone (#731)');
7436
7616
  console.log(' phase scaffold-all → create missing phase folders for all phases in ROADMAP.md (#731)');
7617
+ console.log(' phase rename-dir <N> [--apply] → align a phase dir slug with its ROADMAP name (dry-run by default)');
7437
7618
  console.log(' workflow-config-audit → find workflows still referencing .planning/config.json (#733)');
7438
7619
  console.log(' commit "<msg>" [--files p1 p2 ...] → atomic git commit with conventional-commits validation (no AI attribution, no --no-verify, no auto-push)');
7439
7620
  console.log(' commit-to-subrepo --subrepo <p> "<msg>" → atomic commit inside a git subrepo (same validation as commit)');
@@ -7524,7 +7705,7 @@ async function main() {
7524
7705
  console.log(' state story list [--sprint <NN.S>] [--status <status>]');
7525
7706
  return;
7526
7707
  default: {
7527
- const stateSubs = ['read','get','init','set-phase','advance-plan','snapshot','update-progress','record-execution','record-council','record-chain','add-decision','decisions-global','add-blocker','resolve-blocker','record-session','set-ids-in-state','migrate-ids','migrate-schema','next-phase-id','next-plan-id','next-task-id','resolve-id','workstream-create','workstream-switch','workstream-list','workstream-status','workstream-complete','workstream-validate','insert-phase','planned-phase','begin-phase','complete-phase','reset'];
7708
+ const stateSubs = ['read','get','init','set-phase','advance-plan','snapshot','update-progress','record-execution','record-council','record-chain','add-decision','decisions-global','add-blocker','resolve-blocker','record-session','set-ids-in-state','migrate-ids','migrate-schema','next-phase-id','next-plan-id','next-task-id','resolve-id','workstream-create','workstream-switch','workstream-list','workstream-status','workstream-complete','workstream-validate','insert-phase','planned-phase','begin-phase','complete-phase','set-intent','reset'];
7528
7709
  // Issue #656 — top-level aliases for intuitive guesses.
7529
7710
  const intuitionAliases = {
7530
7711
  blocker: 'state resolve-blocker',
@@ -41,6 +41,42 @@ on what it touches, so a high score is corroboration and a zero score is no
41
41
  information. When your reading disagrees with the score, your reading wins, and
42
42
  you name the file or decision that made you override.
43
43
 
44
+ **Elicitation is not authoring — hand the pen back.** When gathering what the user
45
+ wants, the moment you catch yourself naming the stack, picking the MVP cut, or
46
+ proposing the phase breakdown, stop and hand it back to them. Infer-and-confirm
47
+ ("I'm assuming the maintainer is you, not a client — right?") is fine; presenting
48
+ your conclusion as a finding is not. Every inferred value that reaches an artifact
49
+ carries an inline `[ASSUMPTION]` tag, and every tag gets walked with the user
50
+ before that artifact is final. An untriaged assumption in a finished document is a
51
+ decision nobody made.
52
+
53
+ **Never decide the stack for the user.** Technology choice — language, framework,
54
+ CMS, database, hosting model — is the user's call, always. Research produces a
55
+ suggestion; only the user turns it into a decision. Present it with the ONE reason
56
+ that actually drove it, in their terms, and offer three ways out: confirm it, name
57
+ their own, or ask for more comparison. Then stop. No default, no auto-selection,
58
+ no "the obvious choice for this domain" — a wrong stack is the single most
59
+ expensive thing in a project to reverse.
60
+
61
+ **Write the premise into the decision, and re-open it when the premise dies.** "X
62
+ because a non-technical client updates content themselves" stays true only while
63
+ there is a non-technical client. When the project pivots, every decision whose
64
+ stated reason the pivot invalidated goes back to the user. A decision whose reason
65
+ has expired is not locked, it is stale — and treating it as locked is how a
66
+ codebase gets built twice.
67
+
68
+ **Planning never authorizes building.** If the user asked you to plan, design,
69
+ research, or audit, the deliverable is the plan, the design, the findings — not
70
+ the implementation. Finishing the plan and continuing into code is not
71
+ thoroughness, it is doing work nobody approved, and it costs more to unwind than
72
+ it saved. The same applies to an ambiguous continuation like "resume", "carry
73
+ on", or "next": it restores POSITION, never SCOPE. When the standing instruction
74
+ was to plan, a resume continues planning.
75
+
76
+ If you believe the next step is obvious and valuable, say so in one line and stop.
77
+ The user typing the next command takes two seconds; undoing an unrequested build
78
+ took a whole session.
79
+
44
80
  ---
45
81
 
46
82
  ## Redirect protocol
@@ -154,6 +154,17 @@ autonomous: true|false
154
154
  files_modified: [...]
155
155
  requirements: [...]
156
156
  must_haves: {truths, artifacts, key_links}
157
+
158
+ **`truths` are copied from the requirement's Consequences, not invented.** For
159
+ each requirement this plan claims, read its `**Consequences (testable):**` list in
160
+ REQUIREMENTS.md and carry those lines into `must_haves.truths` verbatim. You are
161
+ transcribing a decision someone already made, not making a new one.
162
+
163
+ Invent a truth only when the requirement has no consequences recorded — and when
164
+ you do, say so in the plan (`[DERIVED]` prefix on that truth) so the verifier
165
+ knows it is checking your reconstruction rather than the requirement's own
166
+ criteria. A phase full of `[DERIVED]` truths is a signal the requirements were
167
+ never finished, not a signal to proceed quietly.
157
168
  ---
158
169
 
159
170
  ## Sprint {phase}.{plan}: {one-line sprint goal, plain English, no jargon}
@@ -105,10 +105,108 @@ A well-paced Socratic conversation follows a natural arc:
105
105
  ↓ Proceed to planning or revisit if gaps remain
106
106
  ```
107
107
 
108
- Each phase should feel **natural, conversational**, not like a checklist. If the user volunteers information, use it; don't force a predetermined sequence.
108
+ Each phase should feel **natural, conversational**, not like a checklist. If the user volunteers information, use it; don't force a predetermined sequence. **This is a rule about TONE, not about coverage** — the decisions in the Mandatory decision set below still all get resolved, in whatever order the conversation makes natural.
109
109
 
110
110
  ---
111
111
 
112
+ ## Working mode — offer it, don't read it from config
113
+
114
+ **Before any planning questions, ask how the user wants to work.** This is a
115
+ per-run choice presented to them, never a config flag read silently:
116
+
117
+ - **Fast path** — batch the remaining gaps into one or two consolidated
118
+ questions, then draft the full artifact, marking every inferred value with an
119
+ `[ASSUMPTION]` tag inline. The user reviews and iterates. Initial quality
120
+ depends on how much they gave upfront.
121
+ - **Coaching path** — walk the decisions together, section by section.
122
+
123
+ Why it must be asked: a user who never enabled autonomous mode should never be
124
+ *treated* as if they had. Confirmed live — a user asked for a project to be
125
+ planned, was never offered this choice, got no defined questions, and received a
126
+ plan built on assumptions they never saw. "You didn't turn on yolo" is not a
127
+ defence when nothing ever asked.
128
+
129
+ Auto/yolo mode picks Fast path automatically. Everything else asks.
130
+
131
+ ## Stakes calibration — one probe, before anything else
132
+
133
+ Ask once, early: **is this a hobby/solo thing, an internal tool, or a launch?**
134
+ Then scale rigor to the answer. rcode's pipeline is built for the launch case and
135
+ applying it whole to a weekend project is its own kind of failure — the user
136
+ abandons the process rather than the project.
137
+
138
+ | Stakes | Depth |
139
+ |---|---|
140
+ | Hobby / solo | Minimal artifacts. Reviewer gates run quietly or not at all |
141
+ | Internal tool | Normal pipeline, lighter review |
142
+ | Launch / production | Full pipeline, all gates, nothing skipped |
143
+
144
+ ## Elicitation, not direction — the hand-back rule
145
+
146
+ Discovery pulls the user's vision out. It does not insert yours.
147
+
148
+ **When you catch yourself naming the stack, picking the MVP cut, or proposing the
149
+ phase breakdown — stop. You have crossed from asking into authoring. Hand the pen
150
+ back.**
151
+
152
+ Infer-and-confirm is fine: *"I'm assuming the maintainer is you, not a client —
153
+ right?"* Quizzing the user through a tree of your own options is not, and neither
154
+ is presenting your conclusion as the finding.
155
+
156
+ This is the rule that would have prevented the most expensive failure in rcode's
157
+ own history: a session picked a stack, phased a roadmap around it, and built on
158
+ it, having never handed the pen back once.
159
+
160
+ ## Mandatory decision set — tone is conversational, coverage is not
161
+
162
+ The "don't feel like a checklist" rule above governs **tone**. It does not govern
163
+ **coverage**. There is a set of decisions that shape everything downstream, and
164
+ each one must be either answered by the user or recorded as an assumption with
165
+ its reason. Silently deciding one on the user's behalf is not conversational
166
+ skill, it is skipping the question.
167
+
168
+ Confirmed live: a user asked for a project to be planned, was never asked a
169
+ single defined question, and got a stack, a roadmap, and an implementation built
170
+ on a premise they had never confirmed. When the premise turned out to be wrong
171
+ the whole build was thrown away. Nothing in this file forced the question,
172
+ because this file told the asker to avoid predetermined sequences.
173
+
174
+ Every one of these must be resolved before PROJECT.md is written:
175
+
176
+ | Decision | Why it cannot be assumed |
177
+ |---|---|
178
+ | **Who maintains this after launch** | Drives the stack more than any technical factor. "Non-technical client" and "you, the technical owner" give opposite answers |
179
+ | **Stack** | Most expensive thing in the project to reverse. Never decided for the user — see the stack gate |
180
+ | **Who the users are, and whether there are roles** | Auth, permissions, and data model all hang off it |
181
+ | **What is explicitly OUT of scope for v1** | An unstated exclusion reappears later as a gap |
182
+ | **What already exists** | Greenfield vs brownfield changes every phase |
183
+ | **What "done" means for the first milestone** | Without it there is no way to verify anything |
184
+ | **Any hard constraint** — budget, deadline, hosting, compliance, locale | These invalidate otherwise-correct plans |
185
+
186
+ **How to run it without sounding like a form:** weave them into the conversation
187
+ in whatever order the user's own answers suggest — that part stays conversational.
188
+ But **track them, and before you write PROJECT.md, state which ones the user
189
+ actually answered and which you are assuming, with the assumption spelled out.**
190
+
191
+ ```
192
+ Before I write this up — you answered: maintainer (you), scope (city pages only),
193
+ users (visitors, no login).
194
+ I'm assuming: no deadline, hosting undecided, English only.
195
+ Correct any of those, or say go.
196
+ ```
197
+
198
+ **Tag assumptions in the artifact itself, not just in chat.** Every inferred value
199
+ written into PROJECT.md, REQUIREMENTS.md, or ROADMAP.md carries an inline
200
+ `[ASSUMPTION]` marker. A summary the user scrolled past is not consent; a tag in
201
+ the document survives the conversation and can be triaged later.
202
+
203
+ Before any artifact is marked final, **walk every `[ASSUMPTION]` tag with the
204
+ user**: confirm it, correct it, or defer it with an owner. An untriaged assumption
205
+ in a finalised document is a decision nobody made.
206
+
207
+ That block is not optional and auto mode does not remove it. An assumption the
208
+ user never saw is indistinguishable from a decision you made for them.
209
+
112
210
  ## Context Checklist
113
211
 
114
212
  After Socratic questioning, verify these dimensions were covered:
@@ -122,7 +220,7 @@ After Socratic questioning, verify these dimensions were covered:
122
220
  - [ ] **Auth/identity** — SSO, local accounts, guest access, or a specific IdP?
123
221
  - [ ] **Locale/i18n** — Which languages/regions must be supported, RTL needed?
124
222
 
125
- If gaps remain after natural conversation, weave questions naturally. Don't suddenly shift to checklist mode.
223
+ If gaps remain after natural conversation, weave questions naturally. Don't suddenly shift to checklist mode — but do NOT let "not a checklist" become "never asked". Anything from the Mandatory decision set still unresolved gets asked outright before you move on, plainly, rather than silently assumed.
126
224
 
127
225
  ---
128
226
 
@@ -145,6 +145,20 @@ Read only when the current task needs the detail. Don't preemptively load.
145
145
  ## Workflow
146
146
 
147
147
  1. **Read context** — REQUIREMENTS.md, FEATURES.md, ARCHITECTURE.md, STACK.md, RESEARCH.md (per `<files_to_read>`).
148
+ **STACK.md is a suggestion until a `state add-decision` entry shows the user
149
+ confirmed it.** If no such entry exists, do not build the roadmap around that
150
+ stack — say the stack is unconfirmed and route back to the stack gate. A
151
+ roadmap phased around an unconfirmed stack is what makes the wrong choice
152
+ expensive: by the time anyone questions it, every phase depends on it.
153
+ If the project has pivoted since the stack was chosen, check whether the
154
+ premise recorded with that decision still holds. If it does not, the decision
155
+ is stale, not locked.
156
+ **Read PROJECT.md's Glossary and use its terms verbatim** in phase names and
157
+ goals. A roadmap that renames the domain's nouns forces every downstream
158
+ agent to guess which concept a phase is about.
159
+ **Read PROJECT.md's Out of Scope (Non-Goals) before phasing.** A phase whose
160
+ goal reaches into a declared non-goal is scope creep with a plan attached —
161
+ flag it rather than quietly phasing it.
148
162
  2. **Cluster requirements** — group related requirements into natural delivery units.
149
163
  3. **Derive phases** — name each phase by what the user can DO after it, not what was built.
150
164
  3b. **Declare the Information Architecture** (UI projects only) — before phases are finalized, explicitly decide the app's eventual final-state IA, not per-phase: enumerate the top-level sections (e.g. Dashboard / Operations / Reports / Admin), pick sidebar vs topbar vs tabs, state max nesting depth (e.g. 2 levels: section > subsection), and group every planned phase's screens under one of those sections. Persist this as an `IA.md` (or a "## Information Architecture" section in ROADMAP.md). A flat list of nav links that grows by one item per phase is not an IA decision — it's the failure mode this step exists to prevent. Later phases must slot new routes under an existing top-level section or explicitly propose adding one, never silently append a new sidebar item.
@@ -41,6 +41,18 @@ Before verifying, discover project context:
41
41
  4. **Verify observable truths** — for each truth, status ✓ VERIFIED / ✗ FAILED / ? UNCERTAIN.
42
42
  5. **Verify artifacts (4 levels)** — exists, substantive, wired, data-flows. Use `rcode-tools.cjs verify artifacts`.
43
43
  6. **Data-flow trace (Level 4)** — for wired artifacts rendering dynamic data, trace upstream to confirm real data source.
44
+ 6e. **Check the requirement's own consequences, not your reconstruction of them.**
45
+ For every requirement this phase claims, read its `**Consequences (testable):**`
46
+ list in REQUIREMENTS.md and verify those. Where a plan's truth carries a
47
+ `[DERIVED]` prefix, the requirement had none recorded and you are checking an
48
+ invented criterion — say so in VERIFICATION.md. A phase that passes only against
49
+ derived criteria has not been verified against what anyone actually asked for.
50
+
51
+ 6d. **Unconfirmed assumptions are verification gaps.** Read PROJECT.md's
52
+ Assumptions Index. Any row still `unconfirmed` that this phase's must-haves
53
+ depend on is a gap, not a formality — the phase was built on something nobody
54
+ agreed to. Name it in VERIFICATION.md rather than passing over it.
55
+
44
56
  6c. **Production reachability (Level 5b) — EVERY phase, including backend-only.**
45
57
  For each non-UI module this phase delivered, list its importers and classify them
46
58
  production vs test. If every importer is a test file, the phase shipped dead code
@@ -37,6 +37,12 @@ Bypassing it produces a built project with no execution trace, no SUMMARY.md, an
37
37
  a dashboard frozen at `planned`. See issue #915.
38
38
 
39
39
  <pre_flight>
40
+ 0a. **Record the authorized scope** — the user ran an execute command, so building
41
+ is authorized from here:
42
+ ```bash
43
+ node ".rcode/bin/rcode-tools.cjs" state set-intent build --source execute.md
44
+ ```
45
+
40
46
  **Mandatory before execution begins.** Run these checks first and surface
41
47
  findings BEFORE any subagents are spawned. If any check fails, stop and
42
48
  route back to the user.
@@ -111,6 +111,41 @@ Create `.planning/REQUIREMENTS.md` with:
111
111
 
112
112
  **REQ-ID format:** `[CATEGORY]-[NUMBER]` (AUTH-01, CONTENT-02)
113
113
 
114
+ **Every requirement carries its testable consequences.** This is the shape:
115
+
116
+ ```markdown
117
+ - [ ] **AUTH-01**: User can log in with email and password and stay logged in
118
+ across sessions.
119
+ - **Consequences (testable):**
120
+ - A valid credential pair returns a session cookie with a 30-day expiry
121
+ - An invalid password returns 401 and does not reveal whether the email exists
122
+ - A logged-in user reloading the page stays logged in
123
+ ```
124
+
125
+ **Why the consequences live here and not in the plan.** rcode's verifier derives
126
+ `must_haves` at verification time, long after the requirement was written — so it
127
+ is guessing at what "done" meant for a requirement someone else authored. That
128
+ guess is where verification quietly goes wrong: a phase passes because the
129
+ verifier's invented criterion was met, not the one the requirement intended.
130
+
131
+ Writing the consequences with the requirement moves that decision to the moment
132
+ the person actually knows the answer. The planner then copies them into
133
+ `must_haves.truths` instead of inventing them, and the verifier checks the
134
+ requirement's own criteria rather than its own reconstruction.
135
+
136
+ A requirement whose consequences you cannot state is a requirement you have not
137
+ finished writing. "Handle authentication properly" has no consequences because it
138
+ has no meaning — that is the signal to push for specificity, not to move on.
139
+
140
+ **Scope dial:** hobby/solo — one consequence per requirement is usually enough,
141
+ and it can be a sentence. Internal tool — the happy path plus the one failure
142
+ mode that matters. Launch — every condition a reviewer would ask about, including
143
+ the negative cases.
144
+
145
+ **Do not change the traceability table's shape.** `requirements mark-complete`
146
+ rewrites the status cell of a `| ID | ... | status |` row; consequences are nested
147
+ under the requirement in the list above, not added as table columns.
148
+
114
149
  **Requirement quality criteria:**
115
150
 
116
151
  Good requirements are:
@@ -119,6 +154,7 @@ Good requirements are:
119
154
  - **User-centric:** "User can X"
120
155
  - **Atomic:** One capability per requirement
121
156
  - **Independent:** Minimal dependencies on other requirements
157
+ - **Consequential:** you can name what must be true for it to be done
122
158
 
123
159
  Reject vague requirements. Push for specificity:
124
160
 
@@ -243,7 +243,67 @@ Display research complete banner and key findings:
243
243
  Files: `.planning/research/`
244
244
  ```
245
245
 
246
- **If "Skip research":** Continue to Step 7.
246
+ ## 6b. Stack confirmation gate HARD STOP
247
+
248
+ **The stack is never decided for the user. Not by research, not by a
249
+ recommendation, not by "the obvious choice for this domain."** Research produces
250
+ a *suggestion*; only the user turns it into a decision.
251
+
252
+ This gate runs whether research ran or was skipped. If research was skipped, ask
253
+ with no recommendation attached — you have no grounds for one.
254
+
255
+ ```
256
+ AskUserQuestion:
257
+ header: "Stack"
258
+ question: "Research suggests {STACK} because {the one reason that actually drove it}. Confirm?"
259
+ options:
260
+ - label: "Confirm {STACK}"
261
+ description: "{the trade-off the user is accepting, stated plainly}"
262
+ - label: "I'll choose the stack"
263
+ description: "Tell me what to build on and I'll record that instead."
264
+ - label: "Research more first"
265
+ description: "Compare against {the closest alternative} before deciding."
266
+ ```
267
+
268
+ **Nothing proceeds until the user answers.** Not requirements, not the roadmap,
269
+ not a single file. An unanswered question is not a confirmation, and neither is
270
+ silence, `--auto`, or `auto_advance`. **Auto mode does NOT bypass this gate** —
271
+ every other question in this workflow has an auto default; this one does not,
272
+ because a wrong stack is the most expensive thing in the project to reverse.
273
+
274
+ State the reason the suggestion exists, in one sentence, in the user's terms.
275
+ "Research recommends WordPress" is not a reason. "WordPress because a
276
+ non-technical client will update content themselves, with no dev retainer" is —
277
+ and stated that way the user can see immediately whether the premise is true.
278
+
279
+ Record the answer with `state add-decision`, including the premise it rests on:
280
+
281
+ ```bash
282
+ node ".rcode/bin/rcode-tools.cjs" state add-decision \
283
+ "Stack: {chosen}. Premise: {the one reason}. Confirmed by user {date}."
284
+ ```
285
+
286
+ ### The premise is part of the decision
287
+
288
+ **A stack decision is only valid while its premise holds.** Write the premise
289
+ into the decision, then re-open the decision the moment the premise changes.
290
+
291
+ Confirmed live: a site was scoped for a non-technical client, so research picked
292
+ WordPress and the roadmap locked it. The project later pivoted to a model with no
293
+ client at all — the maintainer was the technical owner. Every planning doc was
294
+ rewritten for the pivot and the stack stayed "Locked", because nothing in the
295
+ loop treats a locked decision as re-openable. A PHP theme got built and then
296
+ migrated wholesale to a static generator to undo it.
297
+
298
+ **On any pivot, re-run this gate** for every decision whose stated premise the
299
+ pivot invalidated. A decision whose reason has expired is not locked, it is
300
+ stale.
301
+
302
+ **If "Research more first":** run the comparison against the named alternative,
303
+ then return to this gate. Do not proceed past it.
304
+
305
+ **If "Skip research":** Continue to Step 6b — you still need the stack gate,
306
+ you just have no suggestion to offer.
247
307
 
248
308
 
249
309
  ## Next Up
@@ -475,7 +475,12 @@ Proceed to Step 4 (skip Steps 3 and 5).
475
475
 
476
476
  ## 3. Deep Questioning
477
477
 
478
- **If auto mode:** Skip (already handled in Step 2a). Extract project context from provided document instead and proceed to Step 4.
478
+ **If auto mode:** extract project context from the provided document instead of
479
+ asking. **You still owe the user the Mandatory decision set** — resolve each item
480
+ from the document where it answers one, and where it does not, list what you are
481
+ assuming before Step 4 writes PROJECT.md. Auto mode removes the conversation, not
482
+ the accountability. If the document leaves the maintainer or the stack unanswered,
483
+ stop and ask those two regardless of mode.
479
484
 
480
485
  **Display stage banner:**
481
486
 
@@ -491,6 +496,20 @@ Ask inline (freeform, NOT AskUserQuestion):
491
496
 
492
497
  "What do you want to build?"
493
498
 
499
+ **Then, before any deeper questioning, run two short probes from
500
+ `@.rcode/references/questioning.md` — in this order:**
501
+
502
+ 1. **Stakes calibration** — hobby/solo, internal tool, or launch? Scale every
503
+ artifact and gate below to the answer. Do not run the launch-grade pipeline on
504
+ a weekend project.
505
+ 2. **Working mode** — Fast path (batched questions, draft with `[ASSUMPTION]`
506
+ tags) or Coaching path (walk the decisions together)? **Ask it. Never infer it
507
+ from `auto_advance`, from how detailed their opening message was, or from your
508
+ own read of their hurry.** Only `--auto`/yolo picks Fast path without asking.
509
+
510
+ These two answers govern the rest of this workflow. Record them with
511
+ `state add-decision` so a later resume does not re-guess them.
512
+
494
513
  Wait for their response. This gives you the context needed to ask intelligent follow-up questions.
495
514
 
496
515
  **Research-before-questions mode:** Check if `workflow.research_before_questions` is enabled in `.rcode/config.yaml` (via `node .rcode/bin/rcode-tools.cjs config-get workflow.research_before_questions`). When enabled, before asking follow-up questions about a topic:
@@ -527,6 +546,11 @@ Mentally check the context checklist. If gaps remain, weave questions naturally.
527
546
 
528
547
  **Decision gate:**
529
548
 
549
+ **Before the decision gate, show your coverage.** State plainly which items of the
550
+ Mandatory decision set (`@.rcode/references/questioning.md`) the user actually
551
+ answered and which you are assuming, with each assumption spelled out in one line.
552
+ An assumption the user never saw is a decision you made for them.
553
+
530
554
  When you could write a clear PROJECT.md, use AskUserQuestion:
531
555
 
532
556
  - header: "Ready?"
@@ -601,16 +625,61 @@ Synthesize all context into `.planning/PROJECT.md`. If `.rcode/templates/project
601
625
  - [ ] {Requirement 2}
602
626
  - [ ] {Requirement 3}
603
627
 
604
- ### Out of Scope
628
+ ### Out of Scope (Non-Goals)
629
+
630
+ *What this project is NOT and will NOT do. This does outsized work downstream —
631
+ it is what prevents the "let me also add this nearby thing" failure at every
632
+ level: phase, sprint, task, and code. An unstated exclusion reappears later as a
633
+ gap; a stated one ends the argument before it starts.*
605
634
 
606
635
  - {Exclusion 1} — {why}
607
636
  - {Exclusion 2} — {why}
608
637
 
638
+ **Scope dial:** hobby/solo — the two or three things you keep being tempted by.
639
+ Internal tool — plus anything a stakeholder has already asked for and been told
640
+ no. Launch — plus the "we are not becoming X" statements about the product's
641
+ identity.
642
+
643
+ ## Glossary
644
+
645
+ *Every domain noun this project uses, defined once. Downstream agents and
646
+ documents use these terms verbatim — introducing a synonym anywhere is a
647
+ discipline violation, because two names for one thing is how a codebase ends up
648
+ with two implementations of it.*
649
+
650
+ - **{Term}** — {definition}. {relationship to other terms, cardinality if it matters}
651
+
652
+ **Scope dial:** hobby/solo — only terms that are genuinely ambiguous, often 2-3.
653
+ Internal tool — every domain noun. Launch — every domain noun plus the ones the
654
+ team argues about.
655
+
609
656
  ## Key Decisions
610
657
 
611
- | Decision | Rationale | Outcome |
612
- |----------|-----------|---------|
613
- | {Choice} | {Why} | — Pending |
658
+ | Decision | Premise (what makes it right) | Rationale | Outcome |
659
+ |----------|-------------------------------|-----------|---------|
660
+ | {Choice} | {the condition this rests on} | {Why} | — Pending |
661
+
662
+ *The **Premise** column is load-bearing. A decision is valid only while its
663
+ premise holds — when the project pivots, every decision whose premise the pivot
664
+ invalidated goes back to the user. A decision whose reason has expired is not
665
+ locked, it is stale.*
666
+
667
+ ## Assumptions Index
668
+
669
+ *Every `[ASSUMPTION]` tag in this document and in REQUIREMENTS.md, gathered here
670
+ for explicit confirmation. An assumption the user never saw is a decision nobody
671
+ made.*
672
+
673
+ | # | Assumption | Where | Status |
674
+ |---|-----------|-------|--------|
675
+ | A-1 | {what was inferred} | §{section} | unconfirmed |
676
+
677
+ **This table is walked with the user before the document is treated as settled.**
678
+ Each row ends as confirmed, corrected, or deferred with an owner. Auto mode does
679
+ not skip the walk; it defers it to the first interactive turn.
680
+
681
+ **Scope dial:** the table exists at every stakes level. Hobby/solo may resolve it
682
+ in one exchange; launch resolves it row by row.
614
683
 
615
684
  ## Constraints
616
685
 
@@ -628,19 +697,39 @@ This document evolves at phase transitions and milestone boundaries.
628
697
  1. Requirements invalidated? → Move to Out of Scope with reason
629
698
  2. Requirements validated? → Move to Validated with phase reference
630
699
  3. New requirements emerged? → Add to Active
631
- 4. Decisions to log? → Add to Key Decisions
700
+ 4. Decisions to log? → Add to Key Decisions, WITH its premise
632
701
  5. "What This Is" still accurate? → Update if drifted
702
+ 6. New domain nouns introduced? → Add to Glossary in the same pass
703
+ 7. Any `[ASSUMPTION]` resolved or added? → Update the Assumptions Index
704
+ 8. Did anything invalidate a recorded premise? → That decision reopens
633
705
 
634
706
  **After each milestone** (via `/rcode-complete-milestone`):
635
707
  1. Full review of all sections
636
708
  2. Core Value check — still the right priority?
637
709
  3. Audit Out of Scope — reasons still valid?
638
710
  4. Update Context with current state
711
+ 5. Assumptions Index — any row still `unconfirmed` after a whole milestone is a
712
+ finding, not a formality. Resolve or escalate it
713
+ 6. Key Decisions — check every premise still holds
639
714
 
640
715
  ---
641
716
  *Last updated: {date} after initialization*
642
717
  ```
643
718
 
719
+ **Scale every section to the stakes answer from Step 3.** The template is one
720
+ document that serves a weekend project and a launch; the scope dials on each
721
+ section say how. Running the launch-grade depth on a hobby project is its own
722
+ failure — the user abandons the process, not the project.
723
+
724
+ | Stakes | PROJECT.md target |
725
+ |---|---|
726
+ | Hobby / solo | About a page. Glossary only where terms are ambiguous |
727
+ | Internal tool | Two to four pages. Every section present, lightly filled |
728
+ | Launch | As long as the requirements and concerns need |
729
+
730
+ Never pad a section to look thorough, and never drop one silently — if a section
731
+ genuinely does not apply, say so in one line where it would have been.
732
+
644
733
  **For greenfield projects:** Initialize requirements as hypotheses (all Active).
645
734
 
646
735
  **For brownfield projects (codebase map exists):** Read `.planning/codebase/ARCHITECTURE.md` and `STACK.md`. Identify what the codebase already does — these become the initial Validated set.
@@ -100,6 +100,15 @@ drafting tasks inline, the run has lost its orchestrator — spawn the planner
100
100
  instead. A SPRINT.md with no planner `Task()` behind it is the failure this rule
101
101
  exists to prevent (see step 8).
102
102
 
103
+ ## 0.4. Record the authorized scope
104
+
105
+ ```bash
106
+ node ".rcode/bin/rcode-tools.cjs" state set-intent plan --source plan.md
107
+ ```
108
+
109
+ This is what the user asked for on THIS invocation, and it is what `resume-work`
110
+ will restore later. Planning does not authorize building — see step 15.
111
+
103
112
  ## 0.5. Project-Status Preflight
104
113
 
105
114
  ```bash
@@ -306,11 +315,11 @@ If `TEXT_MODE` is true, present as a plain-text numbered list:
306
315
  ```
307
316
  No CONTEXT.md found for Phase {X}. Plans will use research and requirements only — your design preferences won't be included.
308
317
 
309
- 1. Continue without context — Plan using research + requirements only
310
318
  [If DISCUSS_MODE is "assumptions":]
311
- 2. Gather context (assumptions mode) — Analyze codebase and surface assumptions before planning
319
+ 1. Gather context (assumptions mode) [recommended] — Analyze codebase and surface assumptions before planning
312
320
  [If DISCUSS_MODE is "discuss" or unset:]
313
- 2. Run discuss-phase first — Capture design decisions before planning
321
+ 1. Run discuss-phase first [recommended] — Capture design decisions before planning
322
+ 2. Continue without context — Plan using research + requirements only; your design preferences will not be in the plan
314
323
 
315
324
  Enter number:
316
325
  ```
@@ -319,11 +328,13 @@ Otherwise use AskUserQuestion:
319
328
  - header: "No context"
320
329
  - question: "No CONTEXT.md found for Phase {X}. Plans will use research and requirements only — your design preferences won't be included. Continue or capture context first?"
321
330
  - options:
322
- - "Continue without context" Plan using research + requirements only
331
+ (Recommended option FIRSTrcode was recommending the skip, which is how phases
332
+ got planned with the user's design decisions never captured.)
323
333
  If `DISCUSS_MODE` is `"assumptions"`:
324
- - "Gather context (assumptions mode)" — Analyze codebase and surface assumptions before planning
334
+ - "Gather context (assumptions mode) (Recommended)" — Analyze codebase and surface assumptions before planning
325
335
  If `DISCUSS_MODE` is `"discuss"` (or unset):
326
- - "Run discuss-phase first" — Capture design decisions before planning
336
+ - "Run discuss-phase first (Recommended)" — Capture design decisions before planning
337
+ - "Continue without context" — Plan using research + requirements only; your design preferences will not be in the plan
327
338
 
328
339
  If "Continue without context": Proceed to step 5.
329
340
  If "Run discuss-phase first":
@@ -926,7 +937,30 @@ Returns (else branch only):
926
937
 
927
938
  After plans pass the checker (or checker is skipped), verify that all phase requirements are covered by at least one plan.
928
939
 
929
- **Skip if:** `phase_req_ids` is null, `TBD`, or an empty array/list (no requirements mapped to this phase) `[[ -z "$phase_req_ids" || "$phase_req_ids" == "TBD" || "$phase_req_ids" == "[]" || "$phase_req_ids" == "null" ]]` — proceed to step 14.
940
+ **If `phase_req_ids` is empty, the gate does NOT silently skipit reports why.**
941
+ An empty array has two very different causes and they must not look the same:
942
+
943
+ 1. This phase genuinely maps to no requirements. Fine, say so and continue.
944
+ 2. REQUIREMENTS.md HAS a traceability table and nothing parsed out of it. That is
945
+ a broken gate reporting as a passing one.
946
+
947
+ Distinguish them before proceeding:
948
+
949
+ ```bash
950
+ if [ -f .planning/REQUIREMENTS.md ] && grep -qE '\b[A-Z][A-Z0-9]{1,15}-[0-9]+\b' .planning/REQUIREMENTS.md; then
951
+ echo "⚠ Requirements coverage gate SKIPPED but REQUIREMENTS.md contains requirement IDs."
952
+ echo " phase_req_ids came back empty — the phase→requirement mapping in ROADMAP.md"
953
+ echo " is missing or unparseable, so nothing is verifying coverage for this phase."
954
+ echo " Fix the phase's **Requirements:** line in ROADMAP.md, then re-run."
955
+ fi
956
+ ```
957
+
958
+ Surface that warning to the user; do not bury it. Confirmed live: a project's
959
+ requirement IDs were all domain-prefixed (`FOUND-01`, `RENT-04`), the extractor
960
+ only matched `REQ-*`, and this gate skipped itself on every phase while
961
+ appearing to pass.
962
+
963
+ Then proceed to step 14 when the array really is empty.
930
964
 
931
965
  **Step 1: Extract requirement IDs claimed by plans**
932
966
  ```bash
@@ -1028,7 +1062,34 @@ if ([[ "$ARGUMENTS" =~ --auto ]] || [[ "$ARGUMENTS" =~ --chain ]]) && [[ "$AUTO_
1028
1062
  fi
1029
1063
  ```
1030
1064
 
1031
- **If `--auto` or `--chain` flag present OR `AUTO_CHAIN` is true OR `AUTO_CFG` is true:**
1065
+ **`AUTO_CFG` alone is NOT sufficient.** A persistent `workflow.auto_advance: true`
1066
+ in settings must never silently turn "plan this" into "plan and build this". The
1067
+ user's invocation is their declared scope: they typed a planning command, so
1068
+ planning is what was authorized. A config flag set weeks ago is not consent for
1069
+ this build.
1070
+
1071
+ Confirmed live: a user asked for a project to be planned, `auto_advance` was on,
1072
+ and the session planned and then built a WordPress theme, then migrated the whole
1073
+ thing to Astro to undo its own stack choice. The user's words were "plan karo".
1074
+ Nothing in the loop stopped at the boundary they actually drew.
1075
+
1076
+ **If `AUTO_CFG` is true but neither `--auto`/`--chain` nor `AUTO_CHAIN` is set:**
1077
+ ask before advancing, and default to stopping:
1078
+
1079
+ ```
1080
+ AskUserQuestion:
1081
+ question: "Plans are ready. auto_advance is on in your config — execute phase {N} now?"
1082
+ options:
1083
+ - label: "Stop here (Recommended)"
1084
+ description: "Plans written and verified. Review them, then run /rcode-execute {N} when ready."
1085
+ - label: "Execute now"
1086
+ description: "Chain straight into execution, as auto_advance requests."
1087
+ ```
1088
+
1089
+ In `--text` mode present this as a numbered list. If the user does not answer,
1090
+ STOP — an unanswered question is not approval.
1091
+
1092
+ **If `--auto` or `--chain` flag present OR `AUTO_CHAIN` is true:**
1032
1093
 
1033
1094
  Display banner:
1034
1095
  ```
@@ -1156,6 +1217,7 @@ ${WINDOWS === 'true' ? '@.rcode/references/plan-windows-troubleshooting.md' : ''
1156
1217
  - [ ] Phase directory created if needed
1157
1218
  - [ ] CONTEXT.md loaded early (step 4) and passed to ALL agents
1158
1219
  - [ ] Research completed (unless --skip-research or --gaps or exists)
1220
+ - [ ] Auto-advance fired only on an explicit `--auto`/`--chain` or an answered confirmation, never on `auto_advance` config alone
1159
1221
  - [ ] Specialist review panel spawned (Waleed + Fatima + domain agents) and its blocking issues fed into the revision loop, or `workflow.specialist_review: false` recorded
1160
1222
  - [ ] rcode-phase-researcher spawned with CONTEXT.md
1161
1223
  - [ ] Existing plans checked
@@ -197,6 +197,24 @@ Based on project state, determine the most logical next action:
197
197
  </step>
198
198
 
199
199
  <step name="offer_options">
200
+ **"Resume" restores position, never scope.** It tells you WHERE the work stopped;
201
+ it does not tell you what the user authorized. Present options and wait — do not
202
+ pick one and start.
203
+
204
+ The failure this prevents: a user asks for a project to be planned, the session
205
+ plans it, the user later types "resume", and the session reads that as "keep
206
+ going" and starts building. The standing instruction was still "plan". Confirmed
207
+ live, and the cleanup cost more than the work.
208
+
209
+ Two rules:
210
+
211
+ - **Never begin implementation from a resume.** If the state says the phase is
212
+ ready to execute, that is an option to OFFER, not an action to take. Execution
213
+ starts when the user runs `/rcode-execute`, not when a menu suggests it.
214
+ - **Say what the last authorized scope was**, if the state records one (see
215
+ `state read` → `last_intent`, written by plan.md and execute.md). If it does not, say that plainly rather than
216
+ inferring one: `Last recorded scope: unknown — tell me plan or build.`
217
+
200
218
  Present contextual options based on project state:
201
219
 
202
220
  ```