@hanzlaa/rcode 3.4.19 → 3.4.21

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/cli/index.js CHANGED
File without changes
package/cli/install.js CHANGED
@@ -275,7 +275,7 @@ function printInstallHeader(targetVersion) {
275
275
  pc.cyan('│') + ' ' + dim('A persistent context-brain for your editor') + ' ' + pc.cyan('│'),
276
276
  pc.cyan('│') + ' ' + pc.cyan('│'),
277
277
  pc.cyan('│') + ' ' + dim('version ') + pc.green('v' + v) + ' ' + pc.cyan('│'),
278
- pc.cyan('│') + ' ' + dim('docs ') + 'github.com/hanzla-habib/rihal-code ' + pc.cyan('│'),
278
+ pc.cyan('│') + ' ' + dim('docs ') + 'github.com/hanzlahabib/rihal-code ' + pc.cyan('│'),
279
279
  pc.cyan('│') + ' ' + pc.cyan('│'),
280
280
  pc.cyan('╰───────────────────────────────────────────────────────────╯'),
281
281
  '',
@@ -548,7 +548,15 @@ function seedStarterPlanning(target, projectName) {
548
548
  const today = new Date().toISOString().slice(0, 10);
549
549
  const name = projectName || path.basename(target);
550
550
 
551
+ // Stub planning files: clearly marked as install templates so users (and
552
+ // /rihal-new-project Step 0.5 detection) can tell them apart from real
553
+ // planning artifacts. See issues #670 #671 #676.
554
+ const STUB_BANNER =
555
+ `<!-- INSTALL STUB — overwritten by /rihal-new-project. Delete this file or run\n` +
556
+ ` /rihal-new-project before committing. See https://github.com/hanzlahabib/rihal-code/issues/670 -->\n\n`;
557
+
551
558
  fs.writeFileSync(projectPath,
559
+ STUB_BANNER +
552
560
  `# ${name}\n\n` +
553
561
  `**One-line:** Describe what this project is in one sentence.\n\n` +
554
562
  `## Vision\n\n` +
@@ -558,6 +566,7 @@ function seedStarterPlanning(target, projectName) {
558
566
  );
559
567
 
560
568
  fs.writeFileSync(roadmapPath,
569
+ STUB_BANNER +
561
570
  `# ${name} — Roadmap\n\n` +
562
571
  `**Milestone: M1 — Initial Delivery** (v1.0)\n` +
563
572
  `Started: ${today} · Current\n\n` +
@@ -572,6 +581,7 @@ function seedStarterPlanning(target, projectName) {
572
581
  );
573
582
 
574
583
  fs.writeFileSync(statePath,
584
+ STUB_BANNER +
575
585
  `# ${name} — State\n\n` +
576
586
  `**Last updated:** ${today}\n` +
577
587
  `**Milestone:** M1 — Initial Delivery\n` +
@@ -580,27 +590,33 @@ function seedStarterPlanning(target, projectName) {
580
590
  `---\n\n` +
581
591
  `## Decisions\n\n_None yet._\n\n` +
582
592
  `## Blockers\n\n_None._\n\n` +
583
- `## Next Action\n\nSay "plan a sprint" or run \`/rihal-sprint-planning\` to break Phase 01 into stories.\n`
593
+ `## Next Action\n\nRun \`/rihal-new-project <description>\` to bootstrap, or \`/rihal-sprint-planning\` once a real phase exists.\n`
584
594
  );
585
595
 
586
- // Also pre-seed .rihal/state.json with Phase 01 so sprint tools work
587
- // immediately (otherwise auto-init in rihal-tools.cjs creates state with
588
- // empty phases[], requiring manual set-phase before sprint add).
596
+ // Issue #670: do NOT pre-seed .rihal/state.json with a fake project +
597
+ // "Setup & Scaffolding" phase. That made every fresh install look like a
598
+ // real initialized project and broke /rihal-new-project Step 0.5 detection.
599
+ //
600
+ // Write a minimal shell with _seeded_stub:true so:
601
+ // - rihal-tools doesn't have to re-init on first call (avoids race)
602
+ // - /rihal-new-project Step 0.5 (issue #671) can detect "stub" reliably
603
+ // - sprint tools that previously relied on phase 01 will surface a clear
604
+ // "no phases yet — run /rihal-new-project first" error instead of
605
+ // silently operating on a fake phase
589
606
  const rihalStateJson = path.join(target, '.rihal', 'state.json');
590
607
  if (!fs.existsSync(rihalStateJson)) {
591
608
  const now = new Date().toISOString();
592
609
  const state = {
593
610
  version: '1',
594
- project: name,
611
+ project: null,
612
+ _seeded_stub: true,
595
613
  created: now,
596
614
  updated: now,
597
- current_phase: '01',
615
+ current_phase: null,
598
616
  current_plan: 0,
599
617
  current_sprint: null,
600
- milestone: 'M1 — Initial Delivery',
601
- phases: [
602
- { id: '01', name: 'Setup & Scaffolding', status: 'planned' }
603
- ],
618
+ milestone: null,
619
+ phases: [],
604
620
  executions: [],
605
621
  decisions: [],
606
622
  blockers: [],
@@ -2023,6 +2039,7 @@ async function install(opts) {
2023
2039
  const agentsDir = idePaths.agentsDir;
2024
2040
  const commandsDir = idePaths.commandsDir;
2025
2041
  let agentCount = 0, commandCount = 0;
2042
+ let agentsFromGlobal = false, commandsFromGlobal = false;
2026
2043
  try {
2027
2044
  if (fs.existsSync(agentsDir)) {
2028
2045
  agentCount = fs.readdirSync(agentsDir).filter(f => (f.startsWith('rihal-') || f.startsWith('rcode-')) && (f.endsWith('.md') || f.endsWith('.mdc'))).length;
@@ -2034,6 +2051,22 @@ async function install(opts) {
2034
2051
  : f => f.endsWith('.md') || f.endsWith('.mdc');
2035
2052
  commandCount = fs.readdirSync(commandsDir).filter(commandFilter).length;
2036
2053
  }
2054
+ // Issue #669 — when global precedence applied (project copies were
2055
+ // intentionally removed), count from ~/.claude/ instead so the summary
2056
+ // doesn't lie about the install state.
2057
+ if (agentCount === 0 || commandCount === 0) {
2058
+ const os = require('os');
2059
+ const homeAgents = path.join(os.homedir(), '.claude/agents');
2060
+ const homeCommands = path.join(os.homedir(), '.claude/commands');
2061
+ if (agentCount === 0 && fs.existsSync(homeAgents)) {
2062
+ const n = fs.readdirSync(homeAgents).filter(f => f.startsWith('rihal-') && f.endsWith('.md')).length;
2063
+ if (n > 0) { agentCount = n; agentsFromGlobal = true; }
2064
+ }
2065
+ if (commandCount === 0 && fs.existsSync(homeCommands)) {
2066
+ const n = fs.readdirSync(homeCommands).filter(f => f.startsWith('rihal-') && f.endsWith('.md')).length;
2067
+ if (n > 0) { commandCount = n; commandsFromGlobal = true; }
2068
+ }
2069
+ }
2037
2070
  } catch {}
2038
2071
 
2039
2072
  const version = readPackageVersion();
@@ -2047,8 +2080,8 @@ async function install(opts) {
2047
2080
  // Show the actual install paths so cursor/gemini/antigravity output is accurate
2048
2081
  const relAgents = path.relative(opts.target, idePaths.agentsDir) || idePaths.agentsDir;
2049
2082
  const relCommands = path.relative(opts.target, idePaths.commandsDir) || idePaths.commandsDir;
2050
- console.log(` ${bold('Agents:')} ${pc.green(String(agentCount))} in ${relAgents}/`);
2051
- console.log(` ${bold('Commands:')} ${pc.green(String(commandCount))} slash commands in ${relCommands}/`);
2083
+ console.log(` ${bold('Agents:')} ${pc.green(String(agentCount))} in ${agentsFromGlobal ? '~/.claude/agents/ (global)' : relAgents + '/'}`);
2084
+ console.log(` ${bold('Commands:')} ${pc.green(String(commandCount))} slash commands in ${commandsFromGlobal ? '~/.claude/commands/ (global)' : relCommands + '/'}`);
2052
2085
  if (skillsInstalled > 0) console.log(` ${bold('Skills:')} ${pc.green(String(skillsInstalled))} phrase-activated`);
2053
2086
  console.log('');
2054
2087
  if (starterSeeded) {
@@ -124,6 +124,25 @@ function verifyClaudeInstall(cwd, packageRoot) {
124
124
  }
125
125
  }
126
126
 
127
+ // Issue #664 — global precedence fallback.
128
+ // The installer (cli/install.js ~line 1773) intentionally removes project-
129
+ // level .claude/agents/rihal-*.md when the user's ~/.claude/ already has
130
+ // them, to avoid duplicate commands. Without this fallback the verifier
131
+ // reports 0 agents on every successful install in that scenario.
132
+ if (installedAgents.size === 0) {
133
+ try {
134
+ const os = require('os');
135
+ const globalAgentsDir = path.join(os.homedir(), '.claude/agents');
136
+ if (fs.existsSync(globalAgentsDir)) {
137
+ for (const f of fs.readdirSync(globalAgentsDir)) {
138
+ if (f.startsWith('rihal-') && f.endsWith('.md')) {
139
+ installedAgents.add(f.replace(/^rihal-/, '').replace(/\.md$/, ''));
140
+ }
141
+ }
142
+ }
143
+ } catch { /* non-fatal — permission errors etc. */ }
144
+ }
145
+
127
146
  // Actions: .claude/skills/<bare-name>/ — exclude rihal-* dirs (those are
128
147
  // either agent stubs or command stubs, never action skills).
129
148
  const allInstalled = readInstalledDirs(skillsDir);
package/dist/rcode.js CHANGED
@@ -15129,7 +15129,7 @@ var require_install = __commonJS({
15129
15129
  pc.cyan("\u2502") + " " + dim("A persistent context-brain for your editor") + " " + pc.cyan("\u2502"),
15130
15130
  pc.cyan("\u2502") + " " + pc.cyan("\u2502"),
15131
15131
  pc.cyan("\u2502") + " " + dim("version ") + pc.green("v" + v) + " " + pc.cyan("\u2502"),
15132
- pc.cyan("\u2502") + " " + dim("docs ") + "github.com/hanzla-habib/rihal-code " + pc.cyan("\u2502"),
15132
+ pc.cyan("\u2502") + " " + dim("docs ") + "github.com/hanzlahabib/rihal-code " + pc.cyan("\u2502"),
15133
15133
  pc.cyan("\u2502") + " " + pc.cyan("\u2502"),
15134
15134
  pc.cyan("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F"),
15135
15135
  ""
@@ -16485,6 +16485,7 @@ ${BLOCK}`);
16485
16485
  const agentsDir = idePaths.agentsDir;
16486
16486
  const commandsDir = idePaths.commandsDir;
16487
16487
  let agentCount = 0, commandCount = 0;
16488
+ let agentsFromGlobal = false, commandsFromGlobal = false;
16488
16489
  try {
16489
16490
  if (fs2.existsSync(agentsDir)) {
16490
16491
  agentCount = fs2.readdirSync(agentsDir).filter((f) => (f.startsWith("rihal-") || f.startsWith("rcode-")) && (f.endsWith(".md") || f.endsWith(".mdc"))).length;
@@ -16493,6 +16494,25 @@ ${BLOCK}`);
16493
16494
  const commandFilter = primaryIde === "claude" ? (f) => f.startsWith("rihal-") && (f.endsWith(".md") || f.endsWith(".mdc")) : (f) => f.endsWith(".md") || f.endsWith(".mdc");
16494
16495
  commandCount = fs2.readdirSync(commandsDir).filter(commandFilter).length;
16495
16496
  }
16497
+ if (agentCount === 0 || commandCount === 0) {
16498
+ const os2 = require("os");
16499
+ const homeAgents = path2.join(os2.homedir(), ".claude/agents");
16500
+ const homeCommands = path2.join(os2.homedir(), ".claude/commands");
16501
+ if (agentCount === 0 && fs2.existsSync(homeAgents)) {
16502
+ const n = fs2.readdirSync(homeAgents).filter((f) => f.startsWith("rihal-") && f.endsWith(".md")).length;
16503
+ if (n > 0) {
16504
+ agentCount = n;
16505
+ agentsFromGlobal = true;
16506
+ }
16507
+ }
16508
+ if (commandCount === 0 && fs2.existsSync(homeCommands)) {
16509
+ const n = fs2.readdirSync(homeCommands).filter((f) => f.startsWith("rihal-") && f.endsWith(".md")).length;
16510
+ if (n > 0) {
16511
+ commandCount = n;
16512
+ commandsFromGlobal = true;
16513
+ }
16514
+ }
16515
+ }
16496
16516
  } catch {
16497
16517
  }
16498
16518
  const version = readPackageVersion();
@@ -16505,8 +16525,8 @@ ${BLOCK}`);
16505
16525
  console.log("");
16506
16526
  const relAgents = path2.relative(opts.target, idePaths.agentsDir) || idePaths.agentsDir;
16507
16527
  const relCommands = path2.relative(opts.target, idePaths.commandsDir) || idePaths.commandsDir;
16508
- console.log(` ${bold("Agents:")} ${pc.green(String(agentCount))} in ${relAgents}/`);
16509
- console.log(` ${bold("Commands:")} ${pc.green(String(commandCount))} slash commands in ${relCommands}/`);
16528
+ console.log(` ${bold("Agents:")} ${pc.green(String(agentCount))} in ${agentsFromGlobal ? "~/.claude/agents/ (global)" : relAgents + "/"}`);
16529
+ console.log(` ${bold("Commands:")} ${pc.green(String(commandCount))} slash commands in ${commandsFromGlobal ? "~/.claude/commands/ (global)" : relCommands + "/"}`);
16510
16530
  if (skillsInstalled > 0) console.log(` ${bold("Skills:")} ${pc.green(String(skillsInstalled))} phrase-activated`);
16511
16531
  console.log("");
16512
16532
  if (starterSeeded) {
@@ -17070,6 +17090,20 @@ var require_manifest = __commonJS({
17070
17090
  }
17071
17091
  }
17072
17092
  }
17093
+ if (installedAgents.size === 0) {
17094
+ try {
17095
+ const os = require("os");
17096
+ const globalAgentsDir = path2.join(os.homedir(), ".claude/agents");
17097
+ if (fs2.existsSync(globalAgentsDir)) {
17098
+ for (const f of fs2.readdirSync(globalAgentsDir)) {
17099
+ if (f.startsWith("rihal-") && f.endsWith(".md")) {
17100
+ installedAgents.add(f.replace(/^rihal-/, "").replace(/\.md$/, ""));
17101
+ }
17102
+ }
17103
+ }
17104
+ } catch {
17105
+ }
17106
+ }
17073
17107
  const allInstalled = readInstalledDirs(skillsDir);
17074
17108
  const actionsInstalled = new Set(
17075
17109
  [...allInstalled].filter((n) => !n.startsWith("rihal-"))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzlaa/rcode",
3
- "version": "3.4.19",
3
+ "version": "3.4.21",
4
4
  "description": "rcode — the memory bank for AI-driven SaaS teams. Persistent project context, distinctive engineering personas, and phase-based workflows. Built by Rihal. Works in Claude Code, Cursor, Gemini, VS Code, and Antigravity.",
5
5
  "main": "cli/index.js",
6
6
  "bin": {
@@ -8,6 +8,15 @@
8
8
  "rihal": "dist/rcode.js",
9
9
  "rihal-code": "dist/rcode.js"
10
10
  },
11
+ "scripts": {
12
+ "dashboard": "node server/dashboard.js",
13
+ "test": "node --test",
14
+ "test:ci": "node --test --test-reporter=spec",
15
+ "postinstall": "node cli/postinstall.js",
16
+ "build:cli": "node scripts/build.cjs",
17
+ "build": "node scripts/build.cjs",
18
+ "dogfood": "bash scripts/dogfood-check.sh"
19
+ },
11
20
  "files": [
12
21
  "cli/",
13
22
  "rihal/",
@@ -60,14 +69,5 @@
60
69
  },
61
70
  "publishConfig": {
62
71
  "access": "public"
63
- },
64
- "scripts": {
65
- "dashboard": "node server/dashboard.js",
66
- "test": "node --test",
67
- "test:ci": "node --test --test-reporter=spec",
68
- "postinstall": "node cli/postinstall.js",
69
- "build:cli": "node scripts/build.cjs",
70
- "build": "node scripts/build.cjs",
71
- "dogfood": "bash scripts/dogfood-check.sh"
72
72
  }
73
- }
73
+ }
@@ -5278,6 +5278,62 @@ function cmdSummaryExtract(args) {
5278
5278
  * Hides internal machinery (lock metadata, full history) from callers
5279
5279
  * that only need a render-ready summary.
5280
5280
  */
5281
+ /**
5282
+ * cmdProjectStatus — classify project lifecycle state into one of:
5283
+ * uninstalled — no .rihal/config.yaml
5284
+ * uninitialized — config present, no state.json
5285
+ * stub — install-seeded scaffolding only (issue #670)
5286
+ * real — /rihal-new-project has run
5287
+ *
5288
+ * Real-project signals (any → real):
5289
+ * - .planning/REQUIREMENTS.md exists
5290
+ * - .planning/research/ directory exists
5291
+ * - state.phases.length > 1
5292
+ * - first phase name ≠ "Setup & Scaffolding"
5293
+ *
5294
+ * Closes #675 — single source of truth for "is this project initialized."
5295
+ */
5296
+ function cmdProjectStatus() {
5297
+ const configPath = path.join(RIHAL_DIR, 'config.yaml');
5298
+ const statePath = path.join(RIHAL_DIR, 'state.json');
5299
+ const planningDir = path.join(PROJECT_ROOT, '.planning');
5300
+
5301
+ if (!fs.existsSync(configPath)) return { ok: true, status: 'uninstalled' };
5302
+ if (!fs.existsSync(statePath)) return { ok: true, status: 'uninitialized' };
5303
+
5304
+ let state;
5305
+ try { state = JSON.parse(fs.readFileSync(statePath, 'utf8')); }
5306
+ catch (e) { return { ok: false, error: `invalid state.json: ${e.message}` }; }
5307
+
5308
+ const hasRequirements = fs.existsSync(path.join(planningDir, 'REQUIREMENTS.md'));
5309
+ const hasResearch = fs.existsSync(path.join(planningDir, 'research'));
5310
+ const phases = state.phases || [];
5311
+ const phaseCountReal = phases.length > 1;
5312
+ const firstPhaseName = phases[0]?.name || '';
5313
+ const phaseNameReal = firstPhaseName && firstPhaseName !== 'Setup & Scaffolding';
5314
+
5315
+ const isReal = hasRequirements || hasResearch || phaseCountReal || phaseNameReal;
5316
+ const isStub = state._seeded_stub === true || !state.project || !isReal;
5317
+
5318
+ let status;
5319
+ if (isReal) status = 'real';
5320
+ else if (isStub) status = 'stub';
5321
+ else status = 'uninitialized';
5322
+
5323
+ return {
5324
+ ok: true,
5325
+ status,
5326
+ signals: {
5327
+ project: state.project || null,
5328
+ seeded_stub: state._seeded_stub === true,
5329
+ has_requirements: hasRequirements,
5330
+ has_research: hasResearch,
5331
+ phase_count: phases.length,
5332
+ first_phase_name: firstPhaseName || null,
5333
+ },
5334
+ };
5335
+ }
5336
+
5281
5337
  function cmdStateSnapshot() {
5282
5338
  const statePath = path.join(RIHAL_DIR, 'state.json');
5283
5339
  if (!fs.existsSync(statePath)) return { ok: true, state: null };
@@ -5652,6 +5708,9 @@ async function main() {
5652
5708
  case 'agent-skills':
5653
5709
  result = cmdAgentInfo(args[0]);
5654
5710
  break;
5711
+ case 'project-status':
5712
+ result = cmdProjectStatus();
5713
+ break;
5655
5714
  case 'version':
5656
5715
  console.log(readPackageVersion());
5657
5716
  return;
package/rihal/state.json CHANGED
@@ -1,15 +1,14 @@
1
1
  {
2
2
  "version": "1",
3
- "project": "__PROJECT_NAME__",
3
+ "project": null,
4
+ "_seeded_stub": true,
4
5
  "created": "__INSTALL_DATE__",
5
6
  "updated": "__INSTALL_DATE__",
6
- "current_phase": "01",
7
+ "current_phase": null,
7
8
  "current_plan": 0,
8
9
  "current_sprint": null,
9
- "milestone": "M1 — Initial Delivery",
10
- "phases": [
11
- { "id": "01", "name": "Setup & Scaffolding", "status": "planned" }
12
- ],
10
+ "milestone": null,
11
+ "phases": [],
13
12
  "executions": [],
14
13
  "decisions": [],
15
14
  "blockers": [],
@@ -88,25 +88,67 @@ Valid Rihal subagent types (use exact names — do not fall back to 'general-pur
88
88
  - rihal-roadmapper — Creates phased execution roadmaps
89
89
  </available_agent_types>
90
90
 
91
- ## Step 0.5 — Detect existing project (redirect)
91
+ ## Step 0.5 — Detect existing project (stub-aware redirect)
92
92
 
93
- Before any processing, check if a project already exists in this directory:
93
+ Before any processing, classify the project state into one of:
94
+
95
+ - **none** — no `.rihal/state.json`, no `.planning/` → proceed
96
+ - **stub** — install-seeded scaffolding only (issue #670) → proceed (overwrite stub)
97
+ - **real** — a previous `/rihal-new-project` ran here → guard, unless `--force`
94
98
 
95
99
  ```bash
96
- EXISTING=$(node .rihal/bin/rihal-tools.cjs state read 2>/dev/null | grep '"project"' | head -1)
100
+ # --force / --reinit bypasses the guard entirely (issue #672).
101
+ # --auto implies --force on stub state (issue #674).
102
+ FORCE=false
103
+ case " $ARGUMENTS " in
104
+ *" --force "*|*" --reinit "*) FORCE=true ;;
105
+ esac
106
+
107
+ # Single source of truth: rihal-tools project-status returns one of
108
+ # uninstalled | uninitialized | stub | real
109
+ # (see issue #675 for the contract). Falls back to `none` when
110
+ # rihal-tools is unavailable so the workflow still proceeds.
111
+ PROJECT_STATE=$(node .rihal/bin/rihal-tools.cjs project-status 2>/dev/null \
112
+ | node -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{try{console.log(JSON.parse(s).status||'none')}catch{console.log('none')}})" \
113
+ || echo "none")
114
+ [ "$PROJECT_STATE" = "uninstalled" ] || [ "$PROJECT_STATE" = "uninitialized" ] && PROJECT_STATE="none"
97
115
  ```
98
116
 
99
- If `$EXISTING` is non-empty (project already initialized):
117
+ **If `PROJECT_STATE=real` and `FORCE=false`:** show the guard:
100
118
 
101
119
  ```
102
120
  ⚠ A rihal project already exists here.
103
121
 
104
- To check current state: /rihal-status
105
- To find next action: /rihal-next
106
- To start a fresh phase instead: /rihal-add-phase
122
+ Quick actions:
123
+ /rihal-status check current state
124
+ /rihal-next find next action
125
+ /rihal-add-phase add a phase to the current milestone
126
+
127
+ To start over (overwrites .planning/* and .rihal/state.json):
128
+ /rihal-new-project --force <description>
129
+ rcode install --reset nuclear option — wipes config + state
130
+ ```
131
+
132
+ STOP — do not proceed.
133
+
134
+ **If `PROJECT_STATE=stub` (issue #670 install scaffolding):** print a one-liner and proceed:
135
+
136
+ ```
137
+ ℹ Install stub detected — overwriting with real project setup.
138
+ ```
139
+
140
+ **If `PROJECT_STATE=none`:** proceed silently.
141
+
142
+ **If `PROJECT_STATE=real` and `FORCE=true`:** create a rollback tag, then proceed:
143
+
144
+ ```bash
145
+ if git rev-parse --git-dir >/dev/null 2>&1; then
146
+ TAG="pre-rihal-rewrite-$(date +%Y%m%d-%H%M%S)"
147
+ git tag "$TAG" 2>/dev/null && echo "ℹ Rollback tag created: $TAG"
148
+ fi
107
149
  ```
108
150
 
109
- Only proceed past this step if no project exists (`$EXISTING` is empty).
151
+ In interactive mode (not `--auto`), confirm via AskUserQuestion before overwriting. In `--auto` or YOLO mode, proceed without confirmation.
110
152
 
111
153
  <auto_mode>
112
154