@hanzlaa/rcode 3.1.0 → 3.2.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/cli/install.js +145 -47
- package/dist/rcode.js +134 -43
- package/package.json +2 -2
package/cli/install.js
CHANGED
|
@@ -268,20 +268,26 @@ function printInstallHeader(targetVersion) {
|
|
|
268
268
|
* Returns a set like { claude: true, cursor: false, gemini: false }.
|
|
269
269
|
*/
|
|
270
270
|
function detectIdeSignals(target) {
|
|
271
|
-
const signals = { claude: false, cursor: false, gemini: false };
|
|
271
|
+
const signals = { claude: false, cursor: false, gemini: false, vscode: false, antigravity: false };
|
|
272
272
|
// 1. Project-local install dirs (strongest signal — they already use one)
|
|
273
273
|
if (fs.existsSync(path.join(target, '.claude'))) signals.claude = true;
|
|
274
274
|
if (fs.existsSync(path.join(target, '.cursor'))) signals.cursor = true;
|
|
275
275
|
if (fs.existsSync(path.join(target, '.gemini'))) signals.gemini = true;
|
|
276
|
+
if (fs.existsSync(path.join(target, '.vscode'))) signals.vscode = true;
|
|
277
|
+
if (fs.existsSync(path.join(target, '.antigravity'))) signals.antigravity = true;
|
|
276
278
|
// 2. User-level config dirs
|
|
277
279
|
const home = os.homedir();
|
|
278
280
|
if (fs.existsSync(path.join(home, '.claude'))) signals.claude = true;
|
|
279
281
|
if (fs.existsSync(path.join(home, '.cursor'))) signals.cursor = true;
|
|
280
282
|
if (fs.existsSync(path.join(home, '.config', 'Cursor'))) signals.cursor = true;
|
|
281
283
|
if (fs.existsSync(path.join(home, '.gemini'))) signals.gemini = true;
|
|
284
|
+
if (fs.existsSync(path.join(home, '.vscode'))) signals.vscode = true;
|
|
285
|
+
if (fs.existsSync(path.join(home, '.config', 'Code'))) signals.vscode = true;
|
|
286
|
+
if (fs.existsSync(path.join(home, '.antigravity'))) signals.antigravity = true;
|
|
282
287
|
// 3. Env vars commonly set by editor terminals
|
|
283
288
|
if (process.env.CURSOR_TRACE_ID || /cursor/i.test(process.env.TERM_PROGRAM || '')) signals.cursor = true;
|
|
284
289
|
if (process.env.CLAUDECODE === '1' || process.env.CLAUDE_CODE_ENTRYPOINT) signals.claude = true;
|
|
290
|
+
if (process.env.VSCODE_PID || /vscode/i.test(process.env.TERM_PROGRAM || '')) signals.vscode = true;
|
|
285
291
|
return signals;
|
|
286
292
|
}
|
|
287
293
|
|
|
@@ -297,43 +303,32 @@ async function resolveIde(opts) {
|
|
|
297
303
|
if (opts.yes || !process.stdin.isTTY) return opts.ide || 'claude';
|
|
298
304
|
|
|
299
305
|
const signals = detectIdeSignals(opts.target);
|
|
300
|
-
const detected = ['claude', 'cursor', 'gemini'].filter(k => signals[k]);
|
|
301
|
-
|
|
302
|
-
// Build the menu — detected IDEs marked with a hint
|
|
303
|
-
const choices = [
|
|
304
|
-
{ key: '1', value: 'claude', label: 'Claude Code', hint: signals.claude ? dim('(detected)') : '' },
|
|
305
|
-
{ key: '2', value: 'cursor', label: 'Cursor', hint: signals.cursor ? dim('(detected)') : '' },
|
|
306
|
-
{ key: '3', value: 'gemini', label: 'Gemini CLI', hint: signals.gemini ? dim('(detected)') : dim('(beta — limited)') },
|
|
307
|
-
];
|
|
306
|
+
const detected = ['claude', 'cursor', 'gemini', 'vscode'].filter(k => signals[k]);
|
|
308
307
|
|
|
309
308
|
// Pick a default: prefer the single detected IDE; otherwise claude
|
|
310
309
|
let defaultValue = 'claude';
|
|
311
310
|
if (detected.length === 1) defaultValue = detected[0];
|
|
312
311
|
|
|
313
|
-
|
|
314
|
-
const
|
|
315
|
-
|
|
312
|
+
// Use @clack/prompts for arrow-key navigation. Closes #449 / #450.
|
|
313
|
+
const choice = await clack.select({
|
|
314
|
+
message: '🎯 Which editor will you use rcode with?',
|
|
315
|
+
initialValue: defaultValue,
|
|
316
|
+
options: [
|
|
317
|
+
{ value: 'claude', label: 'Claude Code', hint: signals.claude ? '(detected)' : undefined },
|
|
318
|
+
{ value: 'cursor', label: 'Cursor', hint: signals.cursor ? '(detected)' : undefined },
|
|
319
|
+
{ value: 'gemini', label: 'Gemini CLI', hint: signals.gemini ? '(detected)' : '(beta — limited)' },
|
|
320
|
+
{ value: 'vscode', label: 'VS Code', hint: signals.vscode ? '(detected)' : '(via Continue / Copilot extensions)' },
|
|
321
|
+
{ value: 'antigravity', label: 'Antigravity', hint: '(experimental — installs to .antigravity/)' },
|
|
322
|
+
],
|
|
323
|
+
});
|
|
316
324
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
const label = c.value === defaultValue ? pc.bold(c.label) : c.label;
|
|
322
|
-
console.log(` ${marker} ${pc.cyan('[' + c.key + ']')} ${label} ${c.hint}`);
|
|
325
|
+
// Handle Ctrl-C cleanly
|
|
326
|
+
if (clack.isCancel(choice)) {
|
|
327
|
+
clack.cancel('Install cancelled.');
|
|
328
|
+
process.exit(0);
|
|
323
329
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
const answer = (await prompt(` Pick an editor [${defaultKey}]: `)).trim().toLowerCase();
|
|
327
|
-
rl.close();
|
|
328
|
-
|
|
329
|
-
if (!answer) return defaultValue;
|
|
330
|
-
// Accept either the number key or the name
|
|
331
|
-
const byKey = choices.find(c => c.key === answer);
|
|
332
|
-
if (byKey) return byKey.value;
|
|
333
|
-
const byName = choices.find(c => c.value === answer || c.label.toLowerCase().startsWith(answer));
|
|
334
|
-
if (byName) return byName.value;
|
|
335
|
-
console.log(dim(` Unrecognised choice "${answer}" — falling back to ${defaultValue}.`));
|
|
336
|
-
return defaultValue;
|
|
330
|
+
|
|
331
|
+
return choice;
|
|
337
332
|
}
|
|
338
333
|
|
|
339
334
|
/**
|
|
@@ -345,19 +340,21 @@ async function resolveCommitPlanning(opts) {
|
|
|
345
340
|
if (opts.commitPlanning !== null) return opts.commitPlanning;
|
|
346
341
|
if (opts.yes || !process.stdin.isTTY) return true; // non-interactive default
|
|
347
342
|
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
343
|
+
const choice = await clack.select({
|
|
344
|
+
message: '📋 .planning/ holds PRDs, roadmaps, sprints, SUMMARY files. How should they be tracked?',
|
|
345
|
+
initialValue: 'commit',
|
|
346
|
+
options: [
|
|
347
|
+
{ value: 'commit', label: 'Commit', hint: 'collaborators see the same plans (recommended)' },
|
|
348
|
+
{ value: 'gitignore', label: 'Gitignore', hint: 'planning stays local (good for sensitive PRDs)' },
|
|
349
|
+
],
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
if (clack.isCancel(choice)) {
|
|
353
|
+
clack.cancel('Install cancelled.');
|
|
354
|
+
process.exit(0);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return choice === 'commit';
|
|
361
358
|
}
|
|
362
359
|
|
|
363
360
|
function printHelp() {
|
|
@@ -1342,6 +1339,7 @@ async function install(opts) {
|
|
|
1342
1339
|
let preserved = 0;
|
|
1343
1340
|
const preservedFiles = [];
|
|
1344
1341
|
const preservedDiffs = []; // { rel, insertions, deletions, patch } for #251
|
|
1342
|
+
const conflictedFiles = []; // { rel, src, destPath, existingContent, sourceContent } for #451 / #453
|
|
1345
1343
|
const spinner = createSpinner(dim(`Installing ${plan.length} files…`), { color: 'cyan' }).start();
|
|
1346
1344
|
|
|
1347
1345
|
for (const entry of plan) {
|
|
@@ -1381,9 +1379,15 @@ async function install(opts) {
|
|
|
1381
1379
|
const sourceHash = sha256(fs.readFileSync(entry.src));
|
|
1382
1380
|
if (existingHash === sourceHash) { skipped++; continue; }
|
|
1383
1381
|
if (!opts.yes && !opts.nonDestructive) {
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1382
|
+
// Buffer the conflict instead of spamming a warning per file (#451).
|
|
1383
|
+
// Surfaced as a categorised summary post-install + interactive offer (#453).
|
|
1384
|
+
conflictedFiles.push({
|
|
1385
|
+
rel: relForward,
|
|
1386
|
+
src: entry.src,
|
|
1387
|
+
destPath,
|
|
1388
|
+
existingContent: fs.readFileSync(destPath, 'utf8'),
|
|
1389
|
+
sourceContent: fs.readFileSync(entry.src, 'utf8'),
|
|
1390
|
+
});
|
|
1387
1391
|
skipped++;
|
|
1388
1392
|
continue;
|
|
1389
1393
|
}
|
|
@@ -1406,6 +1410,100 @@ async function install(opts) {
|
|
|
1406
1410
|
|
|
1407
1411
|
spinner.success({ text: ok(`${copied} files installed`) });
|
|
1408
1412
|
|
|
1413
|
+
// Categorised conflict summary (#451) + interactive resolution offer (#453).
|
|
1414
|
+
// Replaces the per-file 'differs from package version' warning spam.
|
|
1415
|
+
if (conflictedFiles.length > 0) {
|
|
1416
|
+
const byCategory = { workflows: [], agents: [], commands: [], skills: [], references: [], other: [] };
|
|
1417
|
+
for (const c of conflictedFiles) {
|
|
1418
|
+
if (c.rel.includes('/workflows/')) byCategory.workflows.push(c);
|
|
1419
|
+
else if (c.rel.includes('/agents/')) byCategory.agents.push(c);
|
|
1420
|
+
else if (c.rel.includes('/commands/')) byCategory.commands.push(c);
|
|
1421
|
+
else if (c.rel.includes('/skills/')) byCategory.skills.push(c);
|
|
1422
|
+
else if (c.rel.includes('/references/')) byCategory.references.push(c);
|
|
1423
|
+
else byCategory.other.push(c);
|
|
1424
|
+
}
|
|
1425
|
+
console.log('');
|
|
1426
|
+
console.log(' ' + warn(`${conflictedFiles.length} file${conflictedFiles.length === 1 ? '' : 's'} have local edits AND v${readPackageVersion()} updates:`));
|
|
1427
|
+
for (const [cat, list] of Object.entries(byCategory)) {
|
|
1428
|
+
if (list.length === 0) continue;
|
|
1429
|
+
console.log(' ' + dim(`${list.length} ${cat}`));
|
|
1430
|
+
}
|
|
1431
|
+
console.log('');
|
|
1432
|
+
|
|
1433
|
+
if (!opts.yes && process.stdin.isTTY) {
|
|
1434
|
+
const action = await clack.select({
|
|
1435
|
+
message: 'How should we handle these?',
|
|
1436
|
+
initialValue: 'review',
|
|
1437
|
+
options: [
|
|
1438
|
+
{ value: 'review', label: 'Review each one', hint: 'see the diff, decide per file' },
|
|
1439
|
+
{ value: 'upstream', label: 'Take v' + readPackageVersion() + ' for all', hint: 'lose local edits, get all bug fixes' },
|
|
1440
|
+
{ value: 'keep', label: 'Keep my local edits', hint: 'skip v' + readPackageVersion() + ' updates for these files (current behaviour)' },
|
|
1441
|
+
],
|
|
1442
|
+
});
|
|
1443
|
+
if (clack.isCancel(action)) {
|
|
1444
|
+
clack.note('Skipped — local edits preserved.');
|
|
1445
|
+
} else if (action === 'upstream') {
|
|
1446
|
+
let applied = 0;
|
|
1447
|
+
for (const c of conflictedFiles) {
|
|
1448
|
+
fs.writeFileSync(c.destPath, c.sourceContent, 'utf8');
|
|
1449
|
+
applied++;
|
|
1450
|
+
}
|
|
1451
|
+
console.log(' ' + ok(`Applied v${readPackageVersion()} to ${applied} file${applied === 1 ? '' : 's'}.`));
|
|
1452
|
+
} else if (action === 'review') {
|
|
1453
|
+
let applied = 0, kept = 0;
|
|
1454
|
+
for (const c of conflictedFiles) {
|
|
1455
|
+
const patch = createTwoFilesPatch(c.rel, c.rel, c.existingContent, c.sourceContent, 'local', 'v' + readPackageVersion());
|
|
1456
|
+
let ins = 0, del = 0;
|
|
1457
|
+
for (const line of patch.split('\n')) {
|
|
1458
|
+
if (line.startsWith('+') && !line.startsWith('+++')) ins++;
|
|
1459
|
+
if (line.startsWith('-') && !line.startsWith('---')) del++;
|
|
1460
|
+
}
|
|
1461
|
+
console.log('');
|
|
1462
|
+
console.log(' ' + pc.bold(c.rel) + dim(' ') + pc.green(`+${ins}`) + ' ' + pc.red(`-${del}`));
|
|
1463
|
+
const decision = await clack.select({
|
|
1464
|
+
message: 'Take upstream, keep local, or view diff?',
|
|
1465
|
+
initialValue: 'view',
|
|
1466
|
+
options: [
|
|
1467
|
+
{ value: 'upstream', label: 'Take v' + readPackageVersion() },
|
|
1468
|
+
{ value: 'keep', label: 'Keep local' },
|
|
1469
|
+
{ value: 'view', label: 'View diff first' },
|
|
1470
|
+
],
|
|
1471
|
+
});
|
|
1472
|
+
let finalAction = decision;
|
|
1473
|
+
if (clack.isCancel(decision) || decision === 'view') {
|
|
1474
|
+
for (const line of patch.split('\n').slice(4)) {
|
|
1475
|
+
if (line.startsWith('+')) process.stdout.write(pc.green(line) + '\n');
|
|
1476
|
+
else if (line.startsWith('-')) process.stdout.write(pc.red(line) + '\n');
|
|
1477
|
+
else if (line.startsWith('@')) process.stdout.write(pc.cyan(line) + '\n');
|
|
1478
|
+
else process.stdout.write(dim(line) + '\n');
|
|
1479
|
+
}
|
|
1480
|
+
const after = await clack.select({
|
|
1481
|
+
message: 'Now: take upstream or keep local?',
|
|
1482
|
+
initialValue: 'keep',
|
|
1483
|
+
options: [
|
|
1484
|
+
{ value: 'upstream', label: 'Take v' + readPackageVersion() },
|
|
1485
|
+
{ value: 'keep', label: 'Keep local' },
|
|
1486
|
+
],
|
|
1487
|
+
});
|
|
1488
|
+
finalAction = clack.isCancel(after) ? 'keep' : after;
|
|
1489
|
+
}
|
|
1490
|
+
if (finalAction === 'upstream') {
|
|
1491
|
+
fs.writeFileSync(c.destPath, c.sourceContent, 'utf8');
|
|
1492
|
+
applied++;
|
|
1493
|
+
} else {
|
|
1494
|
+
kept++;
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
console.log(' ' + ok(`Review complete: ${applied} applied, ${kept} kept local.`));
|
|
1498
|
+
} else {
|
|
1499
|
+
console.log(' ' + dim(`${conflictedFiles.length} file${conflictedFiles.length === 1 ? '' : 's'} kept local. Re-run with --force-overwrite or 'rcode update' anytime.`));
|
|
1500
|
+
}
|
|
1501
|
+
} else {
|
|
1502
|
+
console.log(' ' + dim(`Re-run with --force-overwrite to apply v${readPackageVersion()} updates, or pipe through an interactive shell to resolve per-file.`));
|
|
1503
|
+
}
|
|
1504
|
+
console.log('');
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1409
1507
|
// Write .rihal/_config/manifest.yaml + agent-manifest.csv + files-manifest.csv
|
|
1410
1508
|
const configDir = path.join(opts.target, '.rihal', '_config');
|
|
1411
1509
|
ensureDir(configDir);
|
package/dist/rcode.js
CHANGED
|
@@ -15952,69 +15952,65 @@ var require_install = __commonJS({
|
|
|
15952
15952
|
console.log(lines.join("\n"));
|
|
15953
15953
|
}
|
|
15954
15954
|
function detectIdeSignals(target) {
|
|
15955
|
-
const signals = { claude: false, cursor: false, gemini: false };
|
|
15955
|
+
const signals = { claude: false, cursor: false, gemini: false, vscode: false, antigravity: false };
|
|
15956
15956
|
if (fs2.existsSync(path2.join(target, ".claude"))) signals.claude = true;
|
|
15957
15957
|
if (fs2.existsSync(path2.join(target, ".cursor"))) signals.cursor = true;
|
|
15958
15958
|
if (fs2.existsSync(path2.join(target, ".gemini"))) signals.gemini = true;
|
|
15959
|
+
if (fs2.existsSync(path2.join(target, ".vscode"))) signals.vscode = true;
|
|
15960
|
+
if (fs2.existsSync(path2.join(target, ".antigravity"))) signals.antigravity = true;
|
|
15959
15961
|
const home = os.homedir();
|
|
15960
15962
|
if (fs2.existsSync(path2.join(home, ".claude"))) signals.claude = true;
|
|
15961
15963
|
if (fs2.existsSync(path2.join(home, ".cursor"))) signals.cursor = true;
|
|
15962
15964
|
if (fs2.existsSync(path2.join(home, ".config", "Cursor"))) signals.cursor = true;
|
|
15963
15965
|
if (fs2.existsSync(path2.join(home, ".gemini"))) signals.gemini = true;
|
|
15966
|
+
if (fs2.existsSync(path2.join(home, ".vscode"))) signals.vscode = true;
|
|
15967
|
+
if (fs2.existsSync(path2.join(home, ".config", "Code"))) signals.vscode = true;
|
|
15968
|
+
if (fs2.existsSync(path2.join(home, ".antigravity"))) signals.antigravity = true;
|
|
15964
15969
|
if (process.env.CURSOR_TRACE_ID || /cursor/i.test(process.env.TERM_PROGRAM || "")) signals.cursor = true;
|
|
15965
15970
|
if (process.env.CLAUDECODE === "1" || process.env.CLAUDE_CODE_ENTRYPOINT) signals.claude = true;
|
|
15971
|
+
if (process.env.VSCODE_PID || /vscode/i.test(process.env.TERM_PROGRAM || "")) signals.vscode = true;
|
|
15966
15972
|
return signals;
|
|
15967
15973
|
}
|
|
15968
15974
|
async function resolveIde(opts) {
|
|
15969
15975
|
if (opts.ideProvided) return opts.ide;
|
|
15970
15976
|
if (opts.yes || !process.stdin.isTTY) return opts.ide || "claude";
|
|
15971
15977
|
const signals = detectIdeSignals(opts.target);
|
|
15972
|
-
const detected = ["claude", "cursor", "gemini"].filter((k) => signals[k]);
|
|
15973
|
-
const choices = [
|
|
15974
|
-
{ key: "1", value: "claude", label: "Claude Code", hint: signals.claude ? dim("(detected)") : "" },
|
|
15975
|
-
{ key: "2", value: "cursor", label: "Cursor", hint: signals.cursor ? dim("(detected)") : "" },
|
|
15976
|
-
{ key: "3", value: "gemini", label: "Gemini CLI", hint: signals.gemini ? dim("(detected)") : dim("(beta \u2014 limited)") }
|
|
15977
|
-
];
|
|
15978
|
+
const detected = ["claude", "cursor", "gemini", "vscode"].filter((k) => signals[k]);
|
|
15978
15979
|
let defaultValue = "claude";
|
|
15979
15980
|
if (detected.length === 1) defaultValue = detected[0];
|
|
15980
|
-
const
|
|
15981
|
-
|
|
15982
|
-
|
|
15983
|
-
|
|
15984
|
-
|
|
15985
|
-
|
|
15986
|
-
|
|
15987
|
-
|
|
15988
|
-
|
|
15981
|
+
const choice = await clack.select({
|
|
15982
|
+
message: "\u{1F3AF} Which editor will you use rcode with?",
|
|
15983
|
+
initialValue: defaultValue,
|
|
15984
|
+
options: [
|
|
15985
|
+
{ value: "claude", label: "Claude Code", hint: signals.claude ? "(detected)" : void 0 },
|
|
15986
|
+
{ value: "cursor", label: "Cursor", hint: signals.cursor ? "(detected)" : void 0 },
|
|
15987
|
+
{ value: "gemini", label: "Gemini CLI", hint: signals.gemini ? "(detected)" : "(beta \u2014 limited)" },
|
|
15988
|
+
{ value: "vscode", label: "VS Code", hint: signals.vscode ? "(detected)" : "(via Continue / Copilot extensions)" },
|
|
15989
|
+
{ value: "antigravity", label: "Antigravity", hint: "(experimental \u2014 installs to .antigravity/)" }
|
|
15990
|
+
]
|
|
15991
|
+
});
|
|
15992
|
+
if (clack.isCancel(choice)) {
|
|
15993
|
+
clack.cancel("Install cancelled.");
|
|
15994
|
+
process.exit(0);
|
|
15989
15995
|
}
|
|
15990
|
-
|
|
15991
|
-
const defaultKey = choices.find((c) => c.value === defaultValue).key;
|
|
15992
|
-
const answer = (await prompt(` Pick an editor [${defaultKey}]: `)).trim().toLowerCase();
|
|
15993
|
-
rl.close();
|
|
15994
|
-
if (!answer) return defaultValue;
|
|
15995
|
-
const byKey = choices.find((c) => c.key === answer);
|
|
15996
|
-
if (byKey) return byKey.value;
|
|
15997
|
-
const byName = choices.find((c) => c.value === answer || c.label.toLowerCase().startsWith(answer));
|
|
15998
|
-
if (byName) return byName.value;
|
|
15999
|
-
console.log(dim(` Unrecognised choice "${answer}" \u2014 falling back to ${defaultValue}.`));
|
|
16000
|
-
return defaultValue;
|
|
15996
|
+
return choice;
|
|
16001
15997
|
}
|
|
16002
15998
|
async function resolveCommitPlanning(opts) {
|
|
16003
15999
|
if (opts.commitPlanning !== null) return opts.commitPlanning;
|
|
16004
16000
|
if (opts.yes || !process.stdin.isTTY) return true;
|
|
16005
|
-
const
|
|
16006
|
-
|
|
16007
|
-
|
|
16008
|
-
|
|
16009
|
-
|
|
16010
|
-
|
|
16011
|
-
|
|
16012
|
-
|
|
16013
|
-
|
|
16014
|
-
|
|
16015
|
-
|
|
16016
|
-
|
|
16017
|
-
return
|
|
16001
|
+
const choice = await clack.select({
|
|
16002
|
+
message: "\u{1F4CB} .planning/ holds PRDs, roadmaps, sprints, SUMMARY files. How should they be tracked?",
|
|
16003
|
+
initialValue: "commit",
|
|
16004
|
+
options: [
|
|
16005
|
+
{ value: "commit", label: "Commit", hint: "collaborators see the same plans (recommended)" },
|
|
16006
|
+
{ value: "gitignore", label: "Gitignore", hint: "planning stays local (good for sensitive PRDs)" }
|
|
16007
|
+
]
|
|
16008
|
+
});
|
|
16009
|
+
if (clack.isCancel(choice)) {
|
|
16010
|
+
clack.cancel("Install cancelled.");
|
|
16011
|
+
process.exit(0);
|
|
16012
|
+
}
|
|
16013
|
+
return choice === "commit";
|
|
16018
16014
|
}
|
|
16019
16015
|
function printHelp2() {
|
|
16020
16016
|
console.log(`
|
|
@@ -16791,6 +16787,7 @@ Say "plan a sprint" or run \`/rihal:sprint-planning\` to break Phase 01 into sto
|
|
|
16791
16787
|
let preserved = 0;
|
|
16792
16788
|
const preservedFiles = [];
|
|
16793
16789
|
const preservedDiffs = [];
|
|
16790
|
+
const conflictedFiles = [];
|
|
16794
16791
|
const spinner = createSpinner(dim(`Installing ${plan.length} files\u2026`), { color: "cyan" }).start();
|
|
16795
16792
|
for (const entry of plan) {
|
|
16796
16793
|
const destPath = path2.join(opts.target, entry.rel);
|
|
@@ -16825,9 +16822,13 @@ Say "plan a sprint" or run \`/rihal:sprint-planning\` to break Phase 01 into sto
|
|
|
16825
16822
|
continue;
|
|
16826
16823
|
}
|
|
16827
16824
|
if (!opts.yes && !opts.nonDestructive) {
|
|
16828
|
-
|
|
16829
|
-
|
|
16830
|
-
|
|
16825
|
+
conflictedFiles.push({
|
|
16826
|
+
rel: relForward,
|
|
16827
|
+
src: entry.src,
|
|
16828
|
+
destPath,
|
|
16829
|
+
existingContent: fs2.readFileSync(destPath, "utf8"),
|
|
16830
|
+
sourceContent: fs2.readFileSync(entry.src, "utf8")
|
|
16831
|
+
});
|
|
16831
16832
|
skipped++;
|
|
16832
16833
|
continue;
|
|
16833
16834
|
}
|
|
@@ -16846,6 +16847,96 @@ Say "plan a sprint" or run \`/rihal:sprint-planning\` to break Phase 01 into sto
|
|
|
16846
16847
|
copied++;
|
|
16847
16848
|
}
|
|
16848
16849
|
spinner.success({ text: ok(`${copied} files installed`) });
|
|
16850
|
+
if (conflictedFiles.length > 0) {
|
|
16851
|
+
const byCategory = { workflows: [], agents: [], commands: [], skills: [], references: [], other: [] };
|
|
16852
|
+
for (const c of conflictedFiles) {
|
|
16853
|
+
if (c.rel.includes("/workflows/")) byCategory.workflows.push(c);
|
|
16854
|
+
else if (c.rel.includes("/agents/")) byCategory.agents.push(c);
|
|
16855
|
+
else if (c.rel.includes("/commands/")) byCategory.commands.push(c);
|
|
16856
|
+
else if (c.rel.includes("/skills/")) byCategory.skills.push(c);
|
|
16857
|
+
else if (c.rel.includes("/references/")) byCategory.references.push(c);
|
|
16858
|
+
else byCategory.other.push(c);
|
|
16859
|
+
}
|
|
16860
|
+
console.log("");
|
|
16861
|
+
console.log(" " + warn(`${conflictedFiles.length} file${conflictedFiles.length === 1 ? "" : "s"} have local edits AND v${readPackageVersion()} updates:`));
|
|
16862
|
+
for (const [cat, list] of Object.entries(byCategory)) {
|
|
16863
|
+
if (list.length === 0) continue;
|
|
16864
|
+
console.log(" " + dim(`${list.length} ${cat}`));
|
|
16865
|
+
}
|
|
16866
|
+
console.log("");
|
|
16867
|
+
if (!opts.yes && process.stdin.isTTY) {
|
|
16868
|
+
const action = await clack.select({
|
|
16869
|
+
message: "How should we handle these?",
|
|
16870
|
+
initialValue: "review",
|
|
16871
|
+
options: [
|
|
16872
|
+
{ value: "review", label: "Review each one", hint: "see the diff, decide per file" },
|
|
16873
|
+
{ value: "upstream", label: "Take v" + readPackageVersion() + " for all", hint: "lose local edits, get all bug fixes" },
|
|
16874
|
+
{ value: "keep", label: "Keep my local edits", hint: "skip v" + readPackageVersion() + " updates for these files (current behaviour)" }
|
|
16875
|
+
]
|
|
16876
|
+
});
|
|
16877
|
+
if (clack.isCancel(action)) {
|
|
16878
|
+
clack.note("Skipped \u2014 local edits preserved.");
|
|
16879
|
+
} else if (action === "upstream") {
|
|
16880
|
+
let applied = 0;
|
|
16881
|
+
for (const c of conflictedFiles) {
|
|
16882
|
+
fs2.writeFileSync(c.destPath, c.sourceContent, "utf8");
|
|
16883
|
+
applied++;
|
|
16884
|
+
}
|
|
16885
|
+
console.log(" " + ok(`Applied v${readPackageVersion()} to ${applied} file${applied === 1 ? "" : "s"}.`));
|
|
16886
|
+
} else if (action === "review") {
|
|
16887
|
+
let applied = 0, kept = 0;
|
|
16888
|
+
for (const c of conflictedFiles) {
|
|
16889
|
+
const patch = createTwoFilesPatch(c.rel, c.rel, c.existingContent, c.sourceContent, "local", "v" + readPackageVersion());
|
|
16890
|
+
let ins = 0, del = 0;
|
|
16891
|
+
for (const line of patch.split("\n")) {
|
|
16892
|
+
if (line.startsWith("+") && !line.startsWith("+++")) ins++;
|
|
16893
|
+
if (line.startsWith("-") && !line.startsWith("---")) del++;
|
|
16894
|
+
}
|
|
16895
|
+
console.log("");
|
|
16896
|
+
console.log(" " + pc.bold(c.rel) + dim(" ") + pc.green(`+${ins}`) + " " + pc.red(`-${del}`));
|
|
16897
|
+
const decision = await clack.select({
|
|
16898
|
+
message: "Take upstream, keep local, or view diff?",
|
|
16899
|
+
initialValue: "view",
|
|
16900
|
+
options: [
|
|
16901
|
+
{ value: "upstream", label: "Take v" + readPackageVersion() },
|
|
16902
|
+
{ value: "keep", label: "Keep local" },
|
|
16903
|
+
{ value: "view", label: "View diff first" }
|
|
16904
|
+
]
|
|
16905
|
+
});
|
|
16906
|
+
let finalAction = decision;
|
|
16907
|
+
if (clack.isCancel(decision) || decision === "view") {
|
|
16908
|
+
for (const line of patch.split("\n").slice(4)) {
|
|
16909
|
+
if (line.startsWith("+")) process.stdout.write(pc.green(line) + "\n");
|
|
16910
|
+
else if (line.startsWith("-")) process.stdout.write(pc.red(line) + "\n");
|
|
16911
|
+
else if (line.startsWith("@")) process.stdout.write(pc.cyan(line) + "\n");
|
|
16912
|
+
else process.stdout.write(dim(line) + "\n");
|
|
16913
|
+
}
|
|
16914
|
+
const after = await clack.select({
|
|
16915
|
+
message: "Now: take upstream or keep local?",
|
|
16916
|
+
initialValue: "keep",
|
|
16917
|
+
options: [
|
|
16918
|
+
{ value: "upstream", label: "Take v" + readPackageVersion() },
|
|
16919
|
+
{ value: "keep", label: "Keep local" }
|
|
16920
|
+
]
|
|
16921
|
+
});
|
|
16922
|
+
finalAction = clack.isCancel(after) ? "keep" : after;
|
|
16923
|
+
}
|
|
16924
|
+
if (finalAction === "upstream") {
|
|
16925
|
+
fs2.writeFileSync(c.destPath, c.sourceContent, "utf8");
|
|
16926
|
+
applied++;
|
|
16927
|
+
} else {
|
|
16928
|
+
kept++;
|
|
16929
|
+
}
|
|
16930
|
+
}
|
|
16931
|
+
console.log(" " + ok(`Review complete: ${applied} applied, ${kept} kept local.`));
|
|
16932
|
+
} else {
|
|
16933
|
+
console.log(" " + dim(`${conflictedFiles.length} file${conflictedFiles.length === 1 ? "" : "s"} kept local. Re-run with --force-overwrite or 'rcode update' anytime.`));
|
|
16934
|
+
}
|
|
16935
|
+
} else {
|
|
16936
|
+
console.log(" " + dim(`Re-run with --force-overwrite to apply v${readPackageVersion()} updates, or pipe through an interactive shell to resolve per-file.`));
|
|
16937
|
+
}
|
|
16938
|
+
console.log("");
|
|
16939
|
+
}
|
|
16849
16940
|
const configDir = path2.join(opts.target, ".rihal", "_config");
|
|
16850
16941
|
ensureDir(configDir);
|
|
16851
16942
|
fs2.writeFileSync(path2.join(configDir, "manifest.yaml"), generateInstallManifest(opts));
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzlaa/rcode",
|
|
3
|
-
"version": "3.
|
|
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, and
|
|
3
|
+
"version": "3.2.0",
|
|
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": {
|
|
7
7
|
"rcode": "dist/rcode.js",
|