@hanzlaa/rcode 3.4.24 → 3.4.25
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 +90 -0
- package/cli/uninstall.js +30 -30
- package/dist/rcode.js +90 -26
- package/package.json +1 -1
package/cli/install.js
CHANGED
|
@@ -324,6 +324,11 @@ function detectIdeSignals(target) {
|
|
|
324
324
|
* actually wanted cursor or gemini.
|
|
325
325
|
*/
|
|
326
326
|
async function resolveIde(opts) {
|
|
327
|
+
// Issue #692: when the wizard has already collected opts.ides (interactive
|
|
328
|
+
// run from main()), resolveIde was re-prompting because it only checked
|
|
329
|
+
// opts.ideProvided (set by --ide flag, not by the wizard). Honor any
|
|
330
|
+
// pre-existing array result so we don't double-prompt.
|
|
331
|
+
if (Array.isArray(opts.ides) && opts.ides.length > 0) return opts.ides;
|
|
327
332
|
if (opts.ideProvided) return [opts.ide]; // user passed --ide, respect it
|
|
328
333
|
if (opts.noPrompt || opts.global) return ['claude']; // auto-install: always claude
|
|
329
334
|
if (opts.yes || !process.stdin.isTTY) {
|
|
@@ -1491,6 +1496,86 @@ async function install(opts) {
|
|
|
1491
1496
|
return 2;
|
|
1492
1497
|
}
|
|
1493
1498
|
|
|
1499
|
+
// Issue #691: file lock prevents concurrent installs from racing on the
|
|
1500
|
+
// same .rihal/_config/manifest.yaml + files-manifest.csv. Without it, two
|
|
1501
|
+
// parallel runs (two terminals, postinstall + manual install, etc.) can
|
|
1502
|
+
// each write a manifest the OTHER doesn't see → orphan sweep on the next
|
|
1503
|
+
// install deletes files the other run considered legit.
|
|
1504
|
+
let releaseLock = () => {};
|
|
1505
|
+
if (!opts.global) {
|
|
1506
|
+
const lockResult = acquireInstallLock(opts.target);
|
|
1507
|
+
if (!lockResult.ok) {
|
|
1508
|
+
console.log('');
|
|
1509
|
+
console.log(' ' + warn(`Another install is already running here (PID ${lockResult.pid}).`));
|
|
1510
|
+
console.log(' ' + dim(` Lock: ${lockResult.lockPath}`));
|
|
1511
|
+
console.log(' ' + dim(' If the other process crashed, delete the lock file and retry:'));
|
|
1512
|
+
console.log(' ' + dim(` rm ${lockResult.lockPath}`));
|
|
1513
|
+
console.log('');
|
|
1514
|
+
return 3;
|
|
1515
|
+
}
|
|
1516
|
+
releaseLock = lockResult.release;
|
|
1517
|
+
// Make sure the lock is released even if install throws unexpectedly.
|
|
1518
|
+
process.on('exit', releaseLock);
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
try {
|
|
1522
|
+
return await installInner(opts);
|
|
1523
|
+
} finally {
|
|
1524
|
+
releaseLock();
|
|
1525
|
+
process.removeListener('exit', releaseLock);
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
/**
|
|
1530
|
+
* Acquire an exclusive install lock at .rihal/.install.lock (issue #691).
|
|
1531
|
+
*
|
|
1532
|
+
* Returns:
|
|
1533
|
+
* { ok: true, release: () => void } lock acquired
|
|
1534
|
+
* { ok: false, pid: number, lockPath: string } another process holds it
|
|
1535
|
+
*
|
|
1536
|
+
* Stale-lock detection: if the recorded PID is not alive, the lock is
|
|
1537
|
+
* reclaimed automatically.
|
|
1538
|
+
*/
|
|
1539
|
+
function acquireInstallLock(target) {
|
|
1540
|
+
const lockDir = path.join(target, '.rihal');
|
|
1541
|
+
const lockPath = path.join(lockDir, '.install.lock');
|
|
1542
|
+
try {
|
|
1543
|
+
fs.mkdirSync(lockDir, { recursive: true });
|
|
1544
|
+
} catch { /* fall through; openSync will fail with a clearer error */ }
|
|
1545
|
+
|
|
1546
|
+
function isAlive(pid) {
|
|
1547
|
+
try { process.kill(pid, 0); return true; } catch { return false; }
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
1551
|
+
try {
|
|
1552
|
+
const fd = fs.openSync(lockPath, 'wx'); // exclusive create
|
|
1553
|
+
fs.writeSync(fd, String(process.pid));
|
|
1554
|
+
fs.closeSync(fd);
|
|
1555
|
+
return {
|
|
1556
|
+
ok: true,
|
|
1557
|
+
release: () => {
|
|
1558
|
+
try { fs.unlinkSync(lockPath); } catch {}
|
|
1559
|
+
},
|
|
1560
|
+
};
|
|
1561
|
+
} catch (err) {
|
|
1562
|
+
if (err.code !== 'EEXIST') throw err;
|
|
1563
|
+
// Lock exists — check if holder is alive.
|
|
1564
|
+
let pid = 0;
|
|
1565
|
+
try { pid = parseInt(fs.readFileSync(lockPath, 'utf8'), 10); } catch {}
|
|
1566
|
+
if (pid && !isAlive(pid)) {
|
|
1567
|
+
// Stale lock — remove and retry once.
|
|
1568
|
+
try { fs.unlinkSync(lockPath); } catch {}
|
|
1569
|
+
continue;
|
|
1570
|
+
}
|
|
1571
|
+
return { ok: false, pid, lockPath };
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
// Should be unreachable, but degrade gracefully.
|
|
1575
|
+
return { ok: false, pid: 0, lockPath };
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
async function installInner(opts) {
|
|
1494
1579
|
const pkgVersion = readPackageVersion();
|
|
1495
1580
|
|
|
1496
1581
|
// Header banner — only shown for interactive runs to keep CI/non-TTY logs terse.
|
|
@@ -2423,6 +2508,11 @@ async function runInstallWizard(opts) {
|
|
|
2423
2508
|
});
|
|
2424
2509
|
if (isCancel(editorChoices)) { cancel('Installation cancelled.'); process.exit(0); }
|
|
2425
2510
|
opts.ides = editorChoices;
|
|
2511
|
+
// Issue #692: keep opts.ide and opts.ides consistent so downstream callers
|
|
2512
|
+
// that historically read either field see the same answer. Mark provided
|
|
2513
|
+
// so any later resolveIde call exits early.
|
|
2514
|
+
opts.ide = editorChoices[0];
|
|
2515
|
+
opts.ideProvided = true;
|
|
2426
2516
|
|
|
2427
2517
|
// ── 3. Communication language ─────────────────────────────────────────
|
|
2428
2518
|
const langChoice = await select({
|
package/cli/uninstall.js
CHANGED
|
@@ -220,35 +220,29 @@ function buildPlan(cwd, editors) {
|
|
|
220
220
|
}
|
|
221
221
|
|
|
222
222
|
/**
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
223
|
+
* Names of action-skill directories the installer places under .claude/skills/.
|
|
224
|
+
*
|
|
225
|
+
* Issue #693: this used to be a hardcoded array of 23 names that drifted from
|
|
226
|
+
* the source the moment anyone added or removed a skill in `rihal/skills/`.
|
|
227
|
+
* We now derive it from the package's own manifest (cli/lib/manifest.cjs)
|
|
228
|
+
* with a static fallback for the rare case where the manifest module isn't
|
|
229
|
+
* resolvable from the uninstall context.
|
|
226
230
|
*/
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
'
|
|
237
|
-
'rihal-
|
|
238
|
-
'rihal
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
'rihal-market-research',
|
|
243
|
-
'rihal-product-brief',
|
|
244
|
-
'rihal-qa-generate-e2e-tests',
|
|
245
|
-
'rihal-retrospective',
|
|
246
|
-
'rihal-sprint-planning',
|
|
247
|
-
'rihal-sprint-status',
|
|
248
|
-
'rihal-technical-research',
|
|
249
|
-
'rihal-validate-prd',
|
|
250
|
-
'rihal-clone-website',
|
|
251
|
-
];
|
|
231
|
+
function discoverKnownActionSkills() {
|
|
232
|
+
try {
|
|
233
|
+
const { readPackageManifest } = require(path.join(__dirname, 'lib', 'manifest.cjs'));
|
|
234
|
+
const packageRoot = path.resolve(__dirname, '..');
|
|
235
|
+
const pkg = readPackageManifest(packageRoot);
|
|
236
|
+
if (pkg && pkg.actions instanceof Set && pkg.actions.size > 0) {
|
|
237
|
+
return Array.from(pkg.actions);
|
|
238
|
+
}
|
|
239
|
+
} catch { /* fall through to static list */ }
|
|
240
|
+
// Static fallback — kept minimal, only the names that don't start with
|
|
241
|
+
// 'rihal-' would actually need this list since we already match
|
|
242
|
+
// 'rihal-*' via prefix. This is defensive only.
|
|
243
|
+
return [];
|
|
244
|
+
}
|
|
245
|
+
const KNOWN_ACTION_SKILLS = discoverKnownActionSkills();
|
|
252
246
|
|
|
253
247
|
function isKnownSkillName(name) {
|
|
254
248
|
return KNOWN_ACTION_SKILLS.includes(name);
|
|
@@ -425,9 +419,15 @@ async function runUninstall(args) {
|
|
|
425
419
|
const opts = parseArgs(args);
|
|
426
420
|
const cwd = process.cwd();
|
|
427
421
|
|
|
422
|
+
// Issue #693: keep the IDE list in sync with the installer. The installer
|
|
423
|
+
// ships claude/cursor/gemini/vscode/antigravity. The previous uninstaller
|
|
424
|
+
// list (claude/cursor/windsurf/antigravity) was missing gemini + vscode
|
|
425
|
+
// and included windsurf (which the installer never writes). Result: a
|
|
426
|
+
// user with vscode-style commands could never `rcode uninstall`.
|
|
427
|
+
const SUPPORTED_EDITORS = ['claude', 'cursor', 'gemini', 'vscode', 'antigravity'];
|
|
428
428
|
const editors = opts.editor
|
|
429
|
-
? (opts.editor === 'all' ?
|
|
430
|
-
:
|
|
429
|
+
? (opts.editor === 'all' ? SUPPORTED_EDITORS : [opts.editor])
|
|
430
|
+
: SUPPORTED_EDITORS;
|
|
431
431
|
|
|
432
432
|
console.log(`\n🕌 Rihal Code — Uninstall\n`);
|
|
433
433
|
console.log(` Project: ${cwd}`);
|
package/dist/rcode.js
CHANGED
|
@@ -15158,6 +15158,7 @@ var require_install = __commonJS({
|
|
|
15158
15158
|
return signals;
|
|
15159
15159
|
}
|
|
15160
15160
|
async function resolveIde(opts) {
|
|
15161
|
+
if (Array.isArray(opts.ides) && opts.ides.length > 0) return opts.ides;
|
|
15161
15162
|
if (opts.ideProvided) return [opts.ide];
|
|
15162
15163
|
if (opts.noPrompt || opts.global) return ["claude"];
|
|
15163
15164
|
if (opts.yes || !process.stdin.isTTY) {
|
|
@@ -16046,6 +16047,78 @@ ${BLOCK}`, { mode: 493 });
|
|
|
16046
16047
|
console.log("");
|
|
16047
16048
|
return 2;
|
|
16048
16049
|
}
|
|
16050
|
+
let releaseLock = () => {
|
|
16051
|
+
};
|
|
16052
|
+
if (!opts.global) {
|
|
16053
|
+
const lockResult = acquireInstallLock(opts.target);
|
|
16054
|
+
if (!lockResult.ok) {
|
|
16055
|
+
console.log("");
|
|
16056
|
+
console.log(" " + warn(`Another install is already running here (PID ${lockResult.pid}).`));
|
|
16057
|
+
console.log(" " + dim(` Lock: ${lockResult.lockPath}`));
|
|
16058
|
+
console.log(" " + dim(" If the other process crashed, delete the lock file and retry:"));
|
|
16059
|
+
console.log(" " + dim(` rm ${lockResult.lockPath}`));
|
|
16060
|
+
console.log("");
|
|
16061
|
+
return 3;
|
|
16062
|
+
}
|
|
16063
|
+
releaseLock = lockResult.release;
|
|
16064
|
+
process.on("exit", releaseLock);
|
|
16065
|
+
}
|
|
16066
|
+
try {
|
|
16067
|
+
return await installInner(opts);
|
|
16068
|
+
} finally {
|
|
16069
|
+
releaseLock();
|
|
16070
|
+
process.removeListener("exit", releaseLock);
|
|
16071
|
+
}
|
|
16072
|
+
}
|
|
16073
|
+
function acquireInstallLock(target) {
|
|
16074
|
+
const lockDir = path2.join(target, ".rihal");
|
|
16075
|
+
const lockPath = path2.join(lockDir, ".install.lock");
|
|
16076
|
+
try {
|
|
16077
|
+
fs2.mkdirSync(lockDir, { recursive: true });
|
|
16078
|
+
} catch {
|
|
16079
|
+
}
|
|
16080
|
+
function isAlive(pid) {
|
|
16081
|
+
try {
|
|
16082
|
+
process.kill(pid, 0);
|
|
16083
|
+
return true;
|
|
16084
|
+
} catch {
|
|
16085
|
+
return false;
|
|
16086
|
+
}
|
|
16087
|
+
}
|
|
16088
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
16089
|
+
try {
|
|
16090
|
+
const fd = fs2.openSync(lockPath, "wx");
|
|
16091
|
+
fs2.writeSync(fd, String(process.pid));
|
|
16092
|
+
fs2.closeSync(fd);
|
|
16093
|
+
return {
|
|
16094
|
+
ok: true,
|
|
16095
|
+
release: () => {
|
|
16096
|
+
try {
|
|
16097
|
+
fs2.unlinkSync(lockPath);
|
|
16098
|
+
} catch {
|
|
16099
|
+
}
|
|
16100
|
+
}
|
|
16101
|
+
};
|
|
16102
|
+
} catch (err) {
|
|
16103
|
+
if (err.code !== "EEXIST") throw err;
|
|
16104
|
+
let pid = 0;
|
|
16105
|
+
try {
|
|
16106
|
+
pid = parseInt(fs2.readFileSync(lockPath, "utf8"), 10);
|
|
16107
|
+
} catch {
|
|
16108
|
+
}
|
|
16109
|
+
if (pid && !isAlive(pid)) {
|
|
16110
|
+
try {
|
|
16111
|
+
fs2.unlinkSync(lockPath);
|
|
16112
|
+
} catch {
|
|
16113
|
+
}
|
|
16114
|
+
continue;
|
|
16115
|
+
}
|
|
16116
|
+
return { ok: false, pid, lockPath };
|
|
16117
|
+
}
|
|
16118
|
+
}
|
|
16119
|
+
return { ok: false, pid: 0, lockPath };
|
|
16120
|
+
}
|
|
16121
|
+
async function installInner(opts) {
|
|
16049
16122
|
const pkgVersion = readPackageVersion();
|
|
16050
16123
|
const isInteractive = process.stdin.isTTY && !opts.yes;
|
|
16051
16124
|
if (isInteractive) printInstallHeader(pkgVersion);
|
|
@@ -16796,6 +16869,8 @@ commit_planning: ${desired}
|
|
|
16796
16869
|
process.exit(0);
|
|
16797
16870
|
}
|
|
16798
16871
|
opts.ides = editorChoices;
|
|
16872
|
+
opts.ide = editorChoices[0];
|
|
16873
|
+
opts.ideProvided = true;
|
|
16799
16874
|
const langChoice = await select({
|
|
16800
16875
|
message: "Communication language?",
|
|
16801
16876
|
options: [
|
|
@@ -17779,31 +17854,19 @@ var require_uninstall = __commonJS({
|
|
|
17779
17854
|
}
|
|
17780
17855
|
return plan;
|
|
17781
17856
|
}
|
|
17782
|
-
|
|
17783
|
-
|
|
17784
|
-
|
|
17785
|
-
|
|
17786
|
-
|
|
17787
|
-
|
|
17788
|
-
|
|
17789
|
-
|
|
17790
|
-
|
|
17791
|
-
|
|
17792
|
-
|
|
17793
|
-
|
|
17794
|
-
|
|
17795
|
-
"rihal-frontend-design",
|
|
17796
|
-
"rihal-generate-project-context",
|
|
17797
|
-
"rihal-market-research",
|
|
17798
|
-
"rihal-product-brief",
|
|
17799
|
-
"rihal-qa-generate-e2e-tests",
|
|
17800
|
-
"rihal-retrospective",
|
|
17801
|
-
"rihal-sprint-planning",
|
|
17802
|
-
"rihal-sprint-status",
|
|
17803
|
-
"rihal-technical-research",
|
|
17804
|
-
"rihal-validate-prd",
|
|
17805
|
-
"rihal-clone-website"
|
|
17806
|
-
];
|
|
17857
|
+
function discoverKnownActionSkills() {
|
|
17858
|
+
try {
|
|
17859
|
+
const { readPackageManifest } = require(path2.join(__dirname, "lib", "manifest.cjs"));
|
|
17860
|
+
const packageRoot = path2.resolve(__dirname, "..");
|
|
17861
|
+
const pkg = readPackageManifest(packageRoot);
|
|
17862
|
+
if (pkg && pkg.actions instanceof Set && pkg.actions.size > 0) {
|
|
17863
|
+
return Array.from(pkg.actions);
|
|
17864
|
+
}
|
|
17865
|
+
} catch {
|
|
17866
|
+
}
|
|
17867
|
+
return [];
|
|
17868
|
+
}
|
|
17869
|
+
var KNOWN_ACTION_SKILLS = discoverKnownActionSkills();
|
|
17807
17870
|
function isKnownSkillName(name) {
|
|
17808
17871
|
return KNOWN_ACTION_SKILLS.includes(name);
|
|
17809
17872
|
}
|
|
@@ -17918,7 +17981,8 @@ var require_uninstall = __commonJS({
|
|
|
17918
17981
|
async function runUninstall(args) {
|
|
17919
17982
|
const opts = parseArgs(args);
|
|
17920
17983
|
const cwd = process.cwd();
|
|
17921
|
-
const
|
|
17984
|
+
const SUPPORTED_EDITORS = ["claude", "cursor", "gemini", "vscode", "antigravity"];
|
|
17985
|
+
const editors = opts.editor ? opts.editor === "all" ? SUPPORTED_EDITORS : [opts.editor] : SUPPORTED_EDITORS;
|
|
17922
17986
|
console.log(`
|
|
17923
17987
|
\u{1F54C} Rihal Code \u2014 Uninstall
|
|
17924
17988
|
`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzlaa/rcode",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.25",
|
|
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": {
|