@hanzlaa/rcode 4.1.1 → 4.3.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/AGENTS.md +1 -1
- package/CONTRIBUTING.md +3 -0
- package/README.md +3 -0
- package/cli/agent.js +3 -1
- package/cli/index.js +29 -0
- package/cli/install.js +233 -15
- package/cli/lib/config.cjs +4 -2
- package/cli/lib/fsutil.cjs +13 -2
- package/cli/lib/homedir.cjs +21 -0
- package/cli/lib/schemas.cjs +6 -1
- package/cli/nuke.js +13 -8
- package/cli/postinstall.js +14 -4
- package/cli/rcode-slash-router.cjs +118 -0
- package/cli/uninstall.js +59 -1
- package/cli/update.js +10 -5
- package/cli/workflow.js +3 -1
- package/dist/rcode.js +241 -227
- package/package.json +1 -1
- package/rcode/bin/rcode-tools.cjs +15 -6
- package/rcode/commands/scaffold-project.md +2 -2
- package/rcode/skills/actions/2-plan/rcode-create-epics-and-stories/steps/step-04-final-validation.md +1 -1
- package/rcode/skills/actions/2-plan/rcode-create-milestone/steps/README.md +2 -2
- package/rcode/skills/actions/2-plan/rcode-create-milestone/steps/step-09-state-sync.md +1 -1
- package/rcode/skills/actions/4-implementation/rcode-code-review/steps/step-02-review.md +1 -1
- package/rcode/skills/actions/4-implementation/rcode-git-flow/SKILL.md +1 -1
- package/rcode/skills/actions/4-implementation/rcode-scaffold-project/SKILL.md +39 -12
- package/rcode/skills/actions/4-implementation/rcode-scaffold-project/steps/step-01-target.md +18 -3
- package/rcode/skills/actions/4-implementation/rcode-scaffold-project/steps/step-02-safety.md +27 -3
- package/rcode/skills/actions/4-implementation/rcode-scaffold-project/steps/step-03-brownfield.md +57 -0
- package/rcode/skills/actions/4-implementation/rcode-scaffold-project/steps/step-03-clone.md +4 -1
- package/rcode/skills/actions/4-implementation/rcode-scaffold-project/steps/step-04-post-setup.md +15 -1
- package/rcode/skills/actions/4-implementation/rcode-trim/SKILL.md +1 -1
- package/rcode/workflows/audit-milestone.md +1 -1
- package/rcode/workflows/discuss-phase.md +1 -1
- package/rcode/workflows/execute-milestone.md +1 -1
- package/rcode/workflows/execute-regression-gates.md +3 -0
- package/rcode/workflows/execute-sprint.md +27 -1
- package/rcode/workflows/execute-waves.md +6 -0
- package/rcode/workflows/execute.md +13 -3
- package/rcode/workflows/new-milestone.md +2 -2
- package/rcode/workflows/new-project.md +4 -0
- package/rcode/workflows/plan-research-validation.md +1 -1
- package/rcode/workflows/plan-spawn-planner.md +2 -2
- package/rcode/workflows/plan.md +34 -15
- package/rcode/workflows/review.md +2 -0
- package/rcode/workflows/scaffold-project.md +5 -1
- package/rcode/workflows/session-report.md +1 -1
- package/rcode/workflows/ship.md +39 -0
- package/rcode/workflows/sprint-planning.md +27 -0
- package/rcode/workflows/status.md +3 -3
- package/server/dashboard.js +26 -7
- package/server/lib/api.js +62 -4
- package/server/lib/html/client/agents-data.js +22 -18
- package/server/lib/html/client/app.js +3 -0
- package/server/lib/html/client/components/AgentCard.js +127 -0
- package/server/lib/html/client/components/App.js +104 -39
- package/server/lib/html/client/components/CommandPalette.js +133 -0
- package/server/lib/html/client/components/FileReader.js +116 -0
- package/server/lib/html/client/components/FilterChips.js +94 -0
- package/server/lib/html/client/components/NotifyCenter.js +117 -0
- package/server/lib/html/client/components/OrchPanel.js +80 -52
- package/server/lib/html/client/components/PhaseGraph.js +300 -0
- package/server/lib/html/client/components/RejectDialog.js +78 -0
- package/server/lib/html/client/components/RunnerPicker.js +190 -0
- package/server/lib/html/client/components/Sidebar.js +106 -61
- package/server/lib/html/client/components/StatusSummaryBar.js +76 -0
- package/server/lib/html/client/components/TaskPipeline.js +83 -0
- package/server/lib/html/client/components/Topbar.js +86 -39
- package/server/lib/html/client/components/dashboard/Blockers.js +57 -0
- package/server/lib/html/client/components/dashboard/CompletedTasks.js +47 -0
- package/server/lib/html/client/components/dashboard/CurrentPhase.js +107 -0
- package/server/lib/html/client/components/dashboard/InProgress.js +72 -0
- package/server/lib/html/client/components/dashboard/ProgressDonut.js +101 -0
- package/server/lib/html/client/components/dashboard/ProgressTimeline.js +101 -0
- package/server/lib/html/client/components/dashboard/ProjectHealth.js +80 -0
- package/server/lib/html/client/components/dashboard/RecentDecisions.js +57 -0
- package/server/lib/html/client/components/dashboard/Timeline.js +143 -0
- package/server/lib/html/client/components/shared.js +47 -11
- package/server/lib/html/client/filter-state.js +72 -0
- package/server/lib/html/client/icons-client.js +7 -0
- package/server/lib/html/client/notify.js +75 -0
- package/server/lib/html/client/orchestrator.js +168 -41
- package/server/lib/html/client/preact.js +13 -8
- package/server/lib/html/client/store.js +70 -6
- package/server/lib/html/client/util.js +78 -0
- package/server/lib/html/client/vendor/htm.js +1 -0
- package/server/lib/html/client/vendor/preact-hooks.js +2 -0
- package/server/lib/html/client/vendor/preact.js +2 -0
- package/server/lib/html/client/views/AgentsView.js +144 -51
- package/server/lib/html/client/views/FilesView.js +20 -103
- package/server/lib/html/client/views/KanbanView.js +40 -21
- package/server/lib/html/client/views/MemoryView.js +26 -9
- package/server/lib/html/client/views/MilestonesView.js +4 -4
- package/server/lib/html/client/views/OrchestrationView.js +154 -19
- package/server/lib/html/client/views/OverviewView.js +47 -239
- package/server/lib/html/client/views/PhasesView.js +50 -6
- package/server/lib/html/client/views/RoadmapView.js +6 -3
- package/server/lib/html/client/views/SprintsView.js +50 -6
- package/server/lib/html/client/views/TasksView.js +4 -3
- package/server/lib/html/client.js +21 -4
- package/server/lib/html/css.js +2761 -8
- package/server/lib/html/icons.js +7 -0
- package/server/lib/html/shell.js +10 -3
- package/server/lib/scanner.js +376 -39
- package/server/orchestrator.js +329 -5
package/cli/nuke.js
CHANGED
|
@@ -20,9 +20,12 @@
|
|
|
20
20
|
'use strict';
|
|
21
21
|
|
|
22
22
|
const fs = require('fs');
|
|
23
|
-
const os = require('os');
|
|
24
23
|
const path = require('path');
|
|
25
24
|
const { spawnSync } = require('child_process');
|
|
25
|
+
// HOME-aware home resolution (#889) — os.homedir() ignores a stubbed HOME
|
|
26
|
+
// on Windows, so tests pointing HOME at a temp dir still scanned the real
|
|
27
|
+
// profile dir there (and tripped over real ~/.rcode state).
|
|
28
|
+
const { homedir } = require('./lib/homedir.cjs');
|
|
26
29
|
|
|
27
30
|
function exists(p) {
|
|
28
31
|
try { fs.accessSync(p); return true; } catch { return false; }
|
|
@@ -37,7 +40,7 @@ function readDirSafe(p) {
|
|
|
37
40
|
* Returns a list of { manager, dir } — dir may not exist.
|
|
38
41
|
*/
|
|
39
42
|
function getGlobalNodeModulesDirs() {
|
|
40
|
-
const home =
|
|
43
|
+
const home = homedir();
|
|
41
44
|
const candidates = [];
|
|
42
45
|
|
|
43
46
|
// npm — npm root -g resolves to the active node version's lib/node_modules.
|
|
@@ -106,7 +109,7 @@ function findRcodePackages(globalNodeModules) {
|
|
|
106
109
|
* Resolve global bin directories where rcode/rcode/rcode may live.
|
|
107
110
|
*/
|
|
108
111
|
function getGlobalBinDirs() {
|
|
109
|
-
const home =
|
|
112
|
+
const home = homedir();
|
|
110
113
|
const dirs = new Set();
|
|
111
114
|
|
|
112
115
|
// npm prefix bin
|
|
@@ -214,7 +217,7 @@ function findClaudeArtifacts(claudeDir) {
|
|
|
214
217
|
}
|
|
215
218
|
|
|
216
219
|
function buildPlan({ includePlanning }) {
|
|
217
|
-
const home =
|
|
220
|
+
const home = homedir();
|
|
218
221
|
const cwd = process.cwd();
|
|
219
222
|
const plan = {
|
|
220
223
|
packages: [],
|
|
@@ -244,13 +247,15 @@ function buildPlan({ includePlanning }) {
|
|
|
244
247
|
plan.globalClaude = findClaudeArtifacts(path.join(home, '.claude'));
|
|
245
248
|
|
|
246
249
|
// Global state (~/.rcode/)
|
|
250
|
+
// #889: was `= globalRcode` (undefined) — a ReferenceError that only fired
|
|
251
|
+
// when ~/.rcode existed, crashing every dry-run on machines with global state.
|
|
247
252
|
const globalrcode = path.join(home, '.rcode');
|
|
248
|
-
if (exists(globalrcode)) plan.globalrcode =
|
|
253
|
+
if (exists(globalrcode)) plan.globalrcode = globalrcode;
|
|
249
254
|
|
|
250
255
|
// Project-level (CWD only — never recurse, user may have many projects)
|
|
251
256
|
plan.projectClaude = findClaudeArtifacts(path.join(cwd, '.claude'));
|
|
252
257
|
const projectrcode = path.join(cwd, '.rcode');
|
|
253
|
-
if (exists(projectrcode) && cwd !== home) plan.projectrcode =
|
|
258
|
+
if (exists(projectrcode) && cwd !== home) plan.projectrcode = projectrcode;
|
|
254
259
|
|
|
255
260
|
if (includePlanning) {
|
|
256
261
|
const projectPlanning = path.join(cwd, '.planning');
|
|
@@ -351,7 +356,7 @@ function executePlan(plan) {
|
|
|
351
356
|
if (rmrf(a.path)) { console.log(` ✓ removed ${a.path}`); removed++; }
|
|
352
357
|
}
|
|
353
358
|
if (plan.globalrcode && rmrf(plan.globalrcode)) {
|
|
354
|
-
console.log(` ✓ removed ${plan.
|
|
359
|
+
console.log(` ✓ removed ${plan.globalrcode}`); removed++;
|
|
355
360
|
}
|
|
356
361
|
|
|
357
362
|
// Claude artifacts (project)
|
|
@@ -359,7 +364,7 @@ function executePlan(plan) {
|
|
|
359
364
|
if (rmrf(a.path)) { console.log(` ✓ removed ${a.path}`); removed++; }
|
|
360
365
|
}
|
|
361
366
|
if (plan.projectrcode && rmrf(plan.projectrcode)) {
|
|
362
|
-
console.log(` ✓ removed ${plan.
|
|
367
|
+
console.log(` ✓ removed ${plan.projectrcode}`); removed++;
|
|
363
368
|
}
|
|
364
369
|
if (plan.projectPlanning && rmrf(plan.projectPlanning)) {
|
|
365
370
|
console.log(` ✓ removed ${plan.projectPlanning}`); removed++;
|
package/cli/postinstall.js
CHANGED
|
@@ -13,6 +13,17 @@
|
|
|
13
13
|
const os = require('os');
|
|
14
14
|
const path = require('path');
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Path containment check that survives Windows: path.relative normalizes
|
|
18
|
+
* separators (/ vs \) and compares drive letters case-insensitively, which
|
|
19
|
+
* a raw startsWith prefix compare does not.
|
|
20
|
+
*/
|
|
21
|
+
function isPathInside(child, parent) {
|
|
22
|
+
const rel = path.relative(parent, child);
|
|
23
|
+
if (rel === '') return true;
|
|
24
|
+
return rel !== '..' && !rel.startsWith(`..${path.sep}`) && !path.isAbsolute(rel);
|
|
25
|
+
}
|
|
26
|
+
|
|
16
27
|
/**
|
|
17
28
|
* Decide whether the current postinstall invocation represents a GLOBAL
|
|
18
29
|
* `npm install -g @hanzlaa/rcode` (true) or a transitive devDep install
|
|
@@ -28,17 +39,16 @@ const path = require('path');
|
|
|
28
39
|
function isGlobalInstall(env, dirname, cwd) {
|
|
29
40
|
try {
|
|
30
41
|
if (env.npm_config_global === 'true') return true;
|
|
31
|
-
if (env.PNPM_HOME && dirname
|
|
42
|
+
if (env.PNPM_HOME && isPathInside(dirname, env.PNPM_HOME)) return true;
|
|
32
43
|
const globalPatterns = [
|
|
33
|
-
|
|
44
|
+
/[/\\]node_modules[/\\]@hanzlaa[/\\]rcode/,
|
|
34
45
|
/[/\\]lib[/\\]node_modules[/\\]/,
|
|
35
46
|
/\.nvm[/\\]versions[/\\]/,
|
|
36
47
|
/\.pnpm[/\\]/,
|
|
37
48
|
/\.yarn[/\\]global/,
|
|
38
49
|
];
|
|
39
50
|
if (globalPatterns.some((re) => re.test(dirname))) return true;
|
|
40
|
-
|
|
41
|
-
if (!dirname.startsWith(localNodeModules)) return true;
|
|
51
|
+
if (!isPathInside(dirname, path.join(cwd, 'node_modules'))) return true;
|
|
42
52
|
return false;
|
|
43
53
|
} catch {
|
|
44
54
|
return false;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// rcode slash-command hook router.
|
|
5
|
+
//
|
|
6
|
+
// WHY this exists: Codex CLI and Antigravity CLI do NOT surface file-based
|
|
7
|
+
// `/slash` commands the way Claude Code / Grok do (verified live). They DO,
|
|
8
|
+
// however, support a prompt-submit hook (`UserPromptSubmit` / `UserPrompt`)
|
|
9
|
+
// that can inject extra context into the model's turn. This router is wired
|
|
10
|
+
// into that hook by the installer. When the user types `/rcode-<name> [args]`,
|
|
11
|
+
// the router loads the matching command body and injects it as additional
|
|
12
|
+
// context so the model executes that command — the closest thing to a native
|
|
13
|
+
// slash command those CLIs allow.
|
|
14
|
+
//
|
|
15
|
+
// Dependency-free (Node stdlib only) so it can run from a stable home dir
|
|
16
|
+
// (~/.rcode/bin/) without an install step. NEVER throws to the host CLI: any
|
|
17
|
+
// error exits 0 with no output so a malfunctioning router can never break or
|
|
18
|
+
// swallow the user's real prompt.
|
|
19
|
+
|
|
20
|
+
const fs = require('fs');
|
|
21
|
+
const os = require('os');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
|
|
24
|
+
// Command bodies are copied here by the installer (installSlashRouterCommands).
|
|
25
|
+
// A fixed home-dir location means the hook can always read them regardless of
|
|
26
|
+
// the user's current working directory.
|
|
27
|
+
// HOME wins over os.homedir() (#889): os.homedir() ignores HOME on Windows
|
|
28
|
+
// (it reads USERPROFILE), so HOME-redirected runs (tests, git-bash) would read
|
|
29
|
+
// the wrong profile dir. Inlined — this script is copied standalone to
|
|
30
|
+
// ~/.rcode/bin/ and must stay dependency-free (no ./lib requires).
|
|
31
|
+
const COMMANDS_DIR = path.join(process.env.HOME || os.homedir(), '.rcode', 'slash-commands');
|
|
32
|
+
|
|
33
|
+
// Matches `/rcode-<name>` at the very start, optional whitespace, then the
|
|
34
|
+
// rest of the line(s) as arguments. `\b` ends the command name so trailing
|
|
35
|
+
// punctuation/args don't leak into <name>.
|
|
36
|
+
const SLASH_RE = /^\/rcode-([a-z0-9-]+)\b[ \t]*([\s\S]*)$/;
|
|
37
|
+
|
|
38
|
+
function readStdin() {
|
|
39
|
+
try {
|
|
40
|
+
return fs.readFileSync(0, 'utf8');
|
|
41
|
+
} catch {
|
|
42
|
+
return '';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Strip a leading YAML frontmatter block (`---\n...\n---`). The frontmatter is
|
|
47
|
+
// CLI-tooling metadata (name/description/allowed-tools) that only confuses the
|
|
48
|
+
// model — we want the executable command body injected, not its header.
|
|
49
|
+
// \r?\n because Windows checkouts may deliver CRLF command bodies (#889).
|
|
50
|
+
function stripFrontmatter(text) {
|
|
51
|
+
return text.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, '');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function emit(hookEventName, additionalContext) {
|
|
55
|
+
const payload = {
|
|
56
|
+
hookSpecificOutput: {
|
|
57
|
+
hookEventName: hookEventName || 'UserPromptSubmit',
|
|
58
|
+
additionalContext,
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
process.stdout.write(JSON.stringify(payload));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function main() {
|
|
65
|
+
const raw = readStdin();
|
|
66
|
+
if (!raw.trim()) return;
|
|
67
|
+
|
|
68
|
+
let data;
|
|
69
|
+
try {
|
|
70
|
+
data = JSON.parse(raw);
|
|
71
|
+
} catch {
|
|
72
|
+
return; // not JSON we understand → pass-through (no output)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Field names vary across CLIs; accept the common spellings.
|
|
76
|
+
const prompt =
|
|
77
|
+
data.prompt ??
|
|
78
|
+
data.user_prompt ??
|
|
79
|
+
data.userPrompt ??
|
|
80
|
+
data.message ??
|
|
81
|
+
data.input ??
|
|
82
|
+
'';
|
|
83
|
+
const hookEventName = data.hook_event_name || data.hookEventName || 'UserPromptSubmit';
|
|
84
|
+
|
|
85
|
+
if (typeof prompt !== 'string') return;
|
|
86
|
+
|
|
87
|
+
const match = prompt.replace(/^\s+/, '').match(SLASH_RE);
|
|
88
|
+
if (!match) return; // not an rcode command → pass-through (no output)
|
|
89
|
+
|
|
90
|
+
const name = match[1];
|
|
91
|
+
const args = (match[2] || '').trim();
|
|
92
|
+
|
|
93
|
+
const cmdFile = path.join(COMMANDS_DIR, `${name}.md`);
|
|
94
|
+
if (!fs.existsSync(cmdFile)) {
|
|
95
|
+
// Unknown command: inject a short note rather than silently doing nothing,
|
|
96
|
+
// so the user learns the command name didn't resolve.
|
|
97
|
+
emit(
|
|
98
|
+
hookEventName,
|
|
99
|
+
`Unknown rcode command: /rcode-${name}. No matching command body was found in ${COMMANDS_DIR}.`,
|
|
100
|
+
);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let body = stripFrontmatter(fs.readFileSync(cmdFile, 'utf8')).trim();
|
|
105
|
+
if (args) {
|
|
106
|
+
// Surface user-supplied args the way the command bodies expect ($ARGUMENTS).
|
|
107
|
+
body += `\n\nArguments: ${args}`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
emit(hookEventName, body);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
main();
|
|
115
|
+
} catch {
|
|
116
|
+
// Never break the host CLI's prompt — fail open, silently.
|
|
117
|
+
}
|
|
118
|
+
process.exit(0);
|
package/cli/uninstall.js
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
27
|
const fs = require('fs');
|
|
28
|
+
const os = require('os');
|
|
28
29
|
const path = require('path');
|
|
29
30
|
const { spawnSync } = require('child_process');
|
|
30
31
|
const { askConfirm, PromptAbortError } = require('./lib/prompts.cjs');
|
|
@@ -128,6 +129,35 @@ function cleanRcodePreCommitHook(cwd) {
|
|
|
128
129
|
} catch { return 'skipped'; }
|
|
129
130
|
}
|
|
130
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Remove the rcode slash-router hook entry from a CLI hooks JSON file
|
|
134
|
+
* (codex: ~/.codex/hooks.json UserPromptSubmit, antigravity:
|
|
135
|
+
* ~/.gemini/antigravity/settings.json UserPrompt) while preserving every
|
|
136
|
+
* other entry (herdr's, the user's). Matches the router by command substring.
|
|
137
|
+
* Idempotent + guarded: missing/unparseable files are no-ops.
|
|
138
|
+
* Returns 'removed' | 'unchanged' | 'skipped'.
|
|
139
|
+
*/
|
|
140
|
+
function removeSlashRouterHook(jsonPath, eventKey) {
|
|
141
|
+
if (!fs.existsSync(jsonPath)) return 'skipped';
|
|
142
|
+
let root;
|
|
143
|
+
try { root = JSON.parse(fs.readFileSync(jsonPath, 'utf8')); } catch { return 'skipped'; }
|
|
144
|
+
if (!root || typeof root !== 'object' || !root.hooks || !Array.isArray(root.hooks[eventKey])) {
|
|
145
|
+
return 'unchanged';
|
|
146
|
+
}
|
|
147
|
+
const before = root.hooks[eventKey].length;
|
|
148
|
+
root.hooks[eventKey] = root.hooks[eventKey].filter(group =>
|
|
149
|
+
!(Array.isArray(group?.hooks) &&
|
|
150
|
+
group.hooks.some(h => typeof h?.command === 'string' && h.command.includes('rcode-slash-router.cjs'))),
|
|
151
|
+
);
|
|
152
|
+
if (root.hooks[eventKey].length === before) return 'unchanged';
|
|
153
|
+
// Drop the event key entirely if it is now empty, to leave a tidy file.
|
|
154
|
+
if (root.hooks[eventKey].length === 0) delete root.hooks[eventKey];
|
|
155
|
+
try {
|
|
156
|
+
writeFileAtomic(jsonPath, JSON.stringify(root, null, 2) + '\n');
|
|
157
|
+
return 'removed';
|
|
158
|
+
} catch { return 'skipped'; }
|
|
159
|
+
}
|
|
160
|
+
|
|
131
161
|
/**
|
|
132
162
|
* Walk a directory and remove all files/subdirs whose name matches a predicate.
|
|
133
163
|
* Returns the number of entries removed. Always skips local overrides (#382).
|
|
@@ -433,7 +463,11 @@ function planToPathList(plan, cwd, options = {}) {
|
|
|
433
463
|
* still proceed since the user already confirmed the destructive action.
|
|
434
464
|
*/
|
|
435
465
|
function createBackup(cwd, plan, options = {}) {
|
|
436
|
-
|
|
466
|
+
// Tar entry names are always '/'-separated; on Windows planToPathList
|
|
467
|
+
// produces '\'-joined paths that bsdtar may store verbatim. Normalize so
|
|
468
|
+
// the archive (and any `tar -tzf` consumer) sees portable paths.
|
|
469
|
+
const paths = planToPathList(plan, cwd, { purge: options.purge === true })
|
|
470
|
+
.map((p) => p.split(path.sep).join('/'));
|
|
437
471
|
if (paths.length === 0) {
|
|
438
472
|
return { ok: false, warning: 'nothing to back up' };
|
|
439
473
|
}
|
|
@@ -746,6 +780,29 @@ async function runUninstall(args) {
|
|
|
746
780
|
if (n > 0) console.log(` ✓ removed ${n} Antigravity agents`);
|
|
747
781
|
}
|
|
748
782
|
|
|
783
|
+
// Slash-router hooks (home-dir, CLI-native). Installed only via `--global`
|
|
784
|
+
// for codex/antigravity; remove the rcode UserPromptSubmit/UserPrompt entry
|
|
785
|
+
// from each CLI's hooks JSON, leaving herdr + other hooks intact.
|
|
786
|
+
if (editors.includes('codex')) {
|
|
787
|
+
const r = removeSlashRouterHook(path.join(os.homedir(), '.codex', 'hooks.json'), 'UserPromptSubmit');
|
|
788
|
+
if (r === 'removed') console.log(` ✓ removed rcode slash-router hook from ~/.codex/hooks.json`);
|
|
789
|
+
}
|
|
790
|
+
if (editors.includes('antigravity')) {
|
|
791
|
+
const r = removeSlashRouterHook(path.join(os.homedir(), '.gemini', 'antigravity', 'settings.json'), 'UserPrompt');
|
|
792
|
+
if (r === 'removed') console.log(` ✓ removed rcode slash-router hook from ~/.gemini/antigravity/settings.json`);
|
|
793
|
+
}
|
|
794
|
+
// The router script + command-body copies are shared by both CLIs; remove
|
|
795
|
+
// them once if either is in scope.
|
|
796
|
+
if (editors.includes('codex') || editors.includes('antigravity')) {
|
|
797
|
+
const home = os.homedir();
|
|
798
|
+
for (const dir of [path.join(home, '.rcode', 'slash-commands'), path.join(home, '.rcode', 'bin')]) {
|
|
799
|
+
if (fs.existsSync(dir)) {
|
|
800
|
+
const rr = safeRmSync(dir, home);
|
|
801
|
+
if (rr.ok) console.log(` ✓ removed ${dir}`);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
749
806
|
// #706 — gemini removal (.gemini/rcode/{agents,commands})
|
|
750
807
|
if (editors.includes('gemini')) {
|
|
751
808
|
let n = 0;
|
|
@@ -925,6 +982,7 @@ module.exports.isLocalOverride = isLocalOverride;
|
|
|
925
982
|
module.exports.planToPathList = planToPathList;
|
|
926
983
|
module.exports.discoverKnownActionSkills = discoverKnownActionSkills;
|
|
927
984
|
module.exports.stripRcodeGitignoreBlock = stripRcodeGitignoreBlock;
|
|
985
|
+
module.exports.removeSlashRouterHook = removeSlashRouterHook;
|
|
928
986
|
|
|
929
987
|
// Direct invocation — allow `node cli/uninstall.js [flags]` to run end-to-end.
|
|
930
988
|
// When called via cli/index.js, module.exports is invoked directly.
|
package/cli/update.js
CHANGED
|
@@ -34,6 +34,8 @@ const { spawnSync } = require('child_process');
|
|
|
34
34
|
const clack = require('@clack/prompts');
|
|
35
35
|
const { PromptAbortError } = require('./lib/prompts.cjs');
|
|
36
36
|
const { writeFileAtomic } = require('./lib/fsutil.cjs');
|
|
37
|
+
// HOME-aware home resolution (#889): os.homedir() ignores HOME on Windows.
|
|
38
|
+
const { homedir } = require('./lib/homedir.cjs');
|
|
37
39
|
const { verifyInstall, formatReport } = require('./lib/manifest.cjs');
|
|
38
40
|
const install = require('./install');
|
|
39
41
|
|
|
@@ -46,7 +48,9 @@ const install = require('./install');
|
|
|
46
48
|
function readConfigYaml(configPath) {
|
|
47
49
|
const text = fs.readFileSync(configPath, 'utf8');
|
|
48
50
|
const obj = {};
|
|
49
|
-
|
|
51
|
+
// CRLF tolerance (#889): split on \r?\n — a stray \r otherwise defeats the
|
|
52
|
+
// `#.*$` comment strip ($ won't cross the \r) and leaks comments into values.
|
|
53
|
+
for (const raw of text.split(/\r?\n/)) {
|
|
50
54
|
const line = raw.replace(/#.*$/, '').trimEnd();
|
|
51
55
|
if (!line) continue;
|
|
52
56
|
if (line.startsWith(' ')) continue; // ignore nested keys (rare; preserved on disk via raw text path)
|
|
@@ -68,14 +72,16 @@ function readConfigYaml(configPath) {
|
|
|
68
72
|
* If the key doesn't exist, append it.
|
|
69
73
|
*/
|
|
70
74
|
function setYamlKey(rawText, key, value) {
|
|
71
|
-
|
|
75
|
+
// CRLF tolerance (#889): [^\S\n]/[^\r\n] keep the match on one line even
|
|
76
|
+
// when the file uses \r\n (plain \s would walk across the line break).
|
|
77
|
+
const re = new RegExp(`^${key}:[^\\S\\n]*[^\\r\\n]*$`, 'm');
|
|
72
78
|
const replacement = typeof value === 'string'
|
|
73
79
|
? `${key}: "${value.replace(/"/g, '\\"')}"`
|
|
74
80
|
: `${key}: ${value}`;
|
|
75
81
|
if (re.test(rawText)) {
|
|
76
82
|
return rawText.replace(re, replacement);
|
|
77
83
|
}
|
|
78
|
-
return rawText.replace(
|
|
84
|
+
return rawText.replace(/(?:\r?\n)*$/, '') + `\n${replacement}\n`;
|
|
79
85
|
}
|
|
80
86
|
|
|
81
87
|
function parseArgs(args) {
|
|
@@ -98,8 +104,7 @@ function detectInstalledEditors(cwd) {
|
|
|
98
104
|
// .rcode/config.yaml as the canonical signal — if config exists, the
|
|
99
105
|
// project ran rcode install at least once for claude. The presence of
|
|
100
106
|
// any commands/agents/skills then becomes secondary evidence.
|
|
101
|
-
const
|
|
102
|
-
const homeSkills = path.join(os.homedir(), '.claude/skills');
|
|
107
|
+
const homeSkills = path.join(homedir(), '.claude/skills');
|
|
103
108
|
const projectClaude = (
|
|
104
109
|
(fs.existsSync(path.join(cwd, '.claude/skills')) &&
|
|
105
110
|
fs.readdirSync(path.join(cwd, '.claude/skills')).some(n => n.startsWith('rcode-'))) ||
|
package/cli/workflow.js
CHANGED
|
@@ -73,9 +73,11 @@ function listWorkflows(workflowsDir) {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
function showWorkflow(workflowsDir, name) {
|
|
76
|
+
// Normalise: strip rcode- prefix so both `plan` and `rcode-plan` resolve (#883)
|
|
77
|
+
const bare = name.startsWith('rcode-') ? name.slice('rcode-'.length) : name;
|
|
76
78
|
const candidates = [
|
|
79
|
+
path.join(workflowsDir, `${bare}.md`),
|
|
77
80
|
path.join(workflowsDir, `${name}.md`),
|
|
78
|
-
path.join(workflowsDir, `rcode-${name}.md`),
|
|
79
81
|
];
|
|
80
82
|
|
|
81
83
|
for (const p of candidates) {
|