@hanzlaa/rcode 3.4.20 → 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/install.js +46 -13
- package/cli/lib/manifest.cjs +19 -0
- package/dist/rcode.js +37 -3
- package/package.json +1 -1
- package/rihal/bin/rihal-tools.cjs +59 -0
- package/rihal/state.json +5 -6
- package/rihal/workflows/new-project.md +50 -8
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/
|
|
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\
|
|
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
|
-
//
|
|
587
|
-
//
|
|
588
|
-
//
|
|
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:
|
|
611
|
+
project: null,
|
|
612
|
+
_seeded_stub: true,
|
|
595
613
|
created: now,
|
|
596
614
|
updated: now,
|
|
597
|
-
current_phase:
|
|
615
|
+
current_phase: null,
|
|
598
616
|
current_plan: 0,
|
|
599
617
|
current_sprint: null,
|
|
600
|
-
milestone:
|
|
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) {
|
package/cli/lib/manifest.cjs
CHANGED
|
@@ -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/
|
|
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.
|
|
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": {
|
|
@@ -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":
|
|
3
|
+
"project": null,
|
|
4
|
+
"_seeded_stub": true,
|
|
4
5
|
"created": "__INSTALL_DATE__",
|
|
5
6
|
"updated": "__INSTALL_DATE__",
|
|
6
|
-
"current_phase":
|
|
7
|
+
"current_phase": null,
|
|
7
8
|
"current_plan": 0,
|
|
8
9
|
"current_sprint": null,
|
|
9
|
-
"milestone":
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
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
|
|