@hanzlaa/rcode 4.0.0 → 4.1.1
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/README.md +2 -2
- package/cli/doctor.js +17 -0
- package/cli/github-sync.js +3 -2
- package/cli/index.js +16 -0
- package/cli/install.js +1 -0
- package/cli/lib/manifest.cjs +13 -0
- package/cli/set-mode.js +10 -0
- package/cli/set-profile.js +10 -0
- package/cli/uninstall.js +100 -39
- package/cli/workflow.js +97 -0
- package/dist/rcode.js +249 -229
- package/package.json +1 -1
- package/rcode/bin/lib/config.cjs +3 -2
- package/rcode/bin/rcode-tools.cjs +8 -3
- package/rcode/skills/SKILLS_INDEX.md +4 -3
- package/rcode/skills/actions/1-analysis/rcode-document-project/SKILL.md +6 -0
- package/rcode/skills/actions/3-solutioning/rcode-check-implementation-readiness/SKILL.md +6 -0
- package/rcode/skills/actions/3-solutioning/rcode-create-architecture/steps/step-01-init.md +1 -1
- package/rcode/skills/actions/3-solutioning/rcode-create-architecture/workflow.md +13 -1
- package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/SKILL.md +166 -0
- package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/references.md +136 -0
- package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/rules/backlog-building.md +113 -0
- package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/rules/composition-with-herdr.md +85 -0
- package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/rules/integration-branch.md +191 -0
- package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/rules/merge-strategy.md +113 -0
- package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/rules/orchestrator-rhythm.md +119 -0
- package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/rules/wave-design.md +100 -0
- package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/templates/BACKLOG-template.md +34 -0
- package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/templates/STATE-template.md +40 -0
- package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/templates/heartbeat.sh +29 -0
- package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/templates/wave-prompt.md +69 -0
- package/rcode/skills/actions/4-implementation/rcode-retrospective/workflow.md +61 -246
- package/rcode/templates/sprint.md +16 -0
- package/rcode/workflows/audit.md +3 -0
- package/rcode/workflows/brainstorm.md +1 -1
- package/rcode/workflows/council.md +8 -1
- package/rcode/workflows/create-architecture.md +5 -1
- package/rcode/workflows/create-prd.md +5 -1
- package/rcode/workflows/dashboard.md +5 -2
- package/rcode/workflows/discuss-phase.md +3 -15
- package/rcode/workflows/execute-sprint.md +3 -1
- package/rcode/workflows/plan-spawn-planner.md +96 -0
- package/rcode/workflows/plan.md +67 -0
- package/rcode/workflows/retrospective.md +5 -1
- package/rcode/workflows/sprint-planning.md +9 -5
- package/server/dashboard.js +2 -2
package/AGENTS.md
CHANGED
|
@@ -48,7 +48,7 @@ If a user says "just keep going" or "don't stop until done", that authorization
|
|
|
48
48
|
|
|
49
49
|
## Naming & Branding (per `BRAND.md`)
|
|
50
50
|
|
|
51
|
-
- **Skill names** in frontmatter: `rcode-<verb>-<noun>` for legacy skills; new branded skills use `rcode-<verb>-<noun>` ONLY in slash command surface (`/rcode
|
|
51
|
+
- **Skill names** in frontmatter: `rcode-<verb>-<noun>` for legacy skills; new branded skills use `rcode-<verb>-<noun>` ONLY in slash command surface (`/rcode-<name>`); folder names stay `rcode-*` because `cli/install.js` hardcodes that prefix.
|
|
52
52
|
- **Persona IDs** in `team.yaml` stay `rcode-<name>` (dashboard scanner reads them by id; renaming breaks rendering).
|
|
53
53
|
- **Persona display names** keep Arabic alongside Latin: `Sadiq (صادق)`, `Dalil (دليل)`, etc.
|
|
54
54
|
- **Concept primitives** (Memory Bank, Distillate, Majlis, Diwan) are named tooling — capitalised, used consistently in user-facing copy.
|
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ pnpm dlx @hanzlaa/rcode install
|
|
|
13
13
|
[](https://github.com/hanzlahabib/rihal-code/actions/workflows/test.yml)
|
|
14
14
|
[](LICENSE)
|
|
15
15
|
|
|
16
|
-
Status: `@hanzlaa/rcode` v4.0.0 on npm.
|
|
16
|
+
Status: `@hanzlaa/rcode` v4.0.0 on npm. 457 automated tests across 63 files, 45 agents, 116 commands, 87 skills. Actively dogfooded on real projects every week.
|
|
17
17
|
|
|
18
18
|
---
|
|
19
19
|
|
|
@@ -42,7 +42,7 @@ Three layers, specialised for software delivery:
|
|
|
42
42
|
| Layer | What lives here | Example |
|
|
43
43
|
|-------|-----------------|---------|
|
|
44
44
|
| **Memory** | `.rcode/memory/` — git-tracked markdown, lossless distillates | "We chose Postgres over Mongo because of JSON-B + RLS — see ADR-007" |
|
|
45
|
-
| **Skills** | `rcode/skills/` —
|
|
45
|
+
| **Skills** | `rcode/skills/` — 87 phrase-activated playbooks | `rcode-sprint-checker` validates file/symbol refs before execute |
|
|
46
46
|
| **Workflows** | `rcode/workflows/` — orchestrated multi-step paths | `/rcode-plan` runs research → planner → checker → confirm |
|
|
47
47
|
|
|
48
48
|
Single agent navigates the structure. No LangChain, no AutoGen, no orchestrator process. Just folders the model can read.
|
package/cli/doctor.js
CHANGED
|
@@ -52,13 +52,30 @@ function findAgentFiles(dir) {
|
|
|
52
52
|
.map((e) => path.join(dir, e.name));
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
// Negative-boundary signal — an explicit statement of what a skill does NOT do.
|
|
56
|
+
// Mirrors the same constant in cli/lib/schemas.cjs so both checks stay in sync.
|
|
57
|
+
const NEGATIVE_BOUNDARY_RE = /not for|do not|does not|don't|never\b|audit-only|negative/i;
|
|
58
|
+
|
|
55
59
|
function checkCompliance(filePath) {
|
|
56
60
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
61
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
57
62
|
const missing = [];
|
|
58
63
|
if (!/^name:/m.test(content)) missing.push('name');
|
|
59
64
|
if (!/^description:/m.test(content)) missing.push('description');
|
|
65
|
+
if (!/^## Overview/m.test(content)) missing.push('Overview section');
|
|
60
66
|
if (!/^## Output Format/m.test(content)) missing.push('Output Format');
|
|
61
67
|
if (!/^## Examples/m.test(content)) missing.push('Examples');
|
|
68
|
+
|
|
69
|
+
// Negative-boundary clause (component 1 of the 5-component standard).
|
|
70
|
+
// Parse the frontmatter description so folded-block YAML is normalized
|
|
71
|
+
// before the regex runs — "Do\n NOT" becomes "Do NOT" after normalization.
|
|
72
|
+
const desc = typeof frontmatter.description === 'string' ? frontmatter.description : '';
|
|
73
|
+
const hasBoundary =
|
|
74
|
+
NEGATIVE_BOUNDARY_RE.test(desc) ||
|
|
75
|
+
/##[^\n]*\bnot\b/i.test(body) ||
|
|
76
|
+
/\bdo not (use|include)\b/i.test(body);
|
|
77
|
+
if (!hasBoundary) missing.push('negative-boundary clause');
|
|
78
|
+
|
|
62
79
|
return missing;
|
|
63
80
|
}
|
|
64
81
|
|
package/cli/github-sync.js
CHANGED
|
@@ -481,8 +481,9 @@ async function main(args) {
|
|
|
481
481
|
console.error(` Check the filter value or run without filters to see available ids.`);
|
|
482
482
|
process.exit(1);
|
|
483
483
|
}
|
|
484
|
-
console.
|
|
485
|
-
|
|
484
|
+
console.log(`ℹ No phases found in .rcode/phases/ — nothing to sync.`);
|
|
485
|
+
console.log(` Run 'rcode init' or create a phase to get started.`);
|
|
486
|
+
process.exit(0);
|
|
486
487
|
}
|
|
487
488
|
|
|
488
489
|
console.log(` ✓ Phases found: ${phases.length}`);
|
package/cli/index.js
CHANGED
|
@@ -33,6 +33,7 @@ const COMMANDS = {
|
|
|
33
33
|
team: require('./team'),
|
|
34
34
|
agent: require('./agent'),
|
|
35
35
|
doctor: require('./doctor'),
|
|
36
|
+
workflow: require('./workflow'), // lifecycle bridge for non-Claude runtimes
|
|
36
37
|
'set-profile': require('./set-profile'),
|
|
37
38
|
'set-mode': require('./set-mode'),
|
|
38
39
|
config: require('./config'),
|
|
@@ -68,6 +69,21 @@ Usage:
|
|
|
68
69
|
context Memory bank freshness (--check | --refresh | --install-hook)
|
|
69
70
|
github-sync Sync .rcode/ phases/epics/stories to GitHub (dry-run default)
|
|
70
71
|
|
|
72
|
+
🔄 LIFECYCLE (Codex / Copilot / Grok bridge)
|
|
73
|
+
workflow list List all lifecycle workflow names
|
|
74
|
+
workflow show <name> Print a workflow's full instructions to stdout
|
|
75
|
+
workflow show new-project → project setup + ROADMAP
|
|
76
|
+
workflow show create-prd → write / update the PRD
|
|
77
|
+
workflow show discuss-phase → gather phase context
|
|
78
|
+
workflow show plan → create a SPRINT plan
|
|
79
|
+
workflow show execute-sprint → execute a SPRINT
|
|
80
|
+
workflow show verify-phase → verify phase completion
|
|
81
|
+
workflow show retrospective → retrospective + velocity
|
|
82
|
+
workflow show ship → deploy / release workflow
|
|
83
|
+
|
|
84
|
+
Non-Claude agents: pipe to your agent instead of using slash commands.
|
|
85
|
+
Example: rcode workflow show plan | codex run -
|
|
86
|
+
|
|
71
87
|
👥 TEAM
|
|
72
88
|
team List the team roster
|
|
73
89
|
digest Print compact digests for all agents
|
package/cli/install.js
CHANGED
package/cli/lib/manifest.cjs
CHANGED
|
@@ -47,12 +47,25 @@ function readPackageManifest(packageRoot) {
|
|
|
47
47
|
// Mirror installSkills() walkForSkills: recurse into action bucket dirs
|
|
48
48
|
// (1-analysis, 2-plan, etc.) until a dir with SKILL.md is found, then add
|
|
49
49
|
// the dir name as installed. Bucket dirs themselves are never installed.
|
|
50
|
+
// Issue #873: skills with `internal: true` in frontmatter are installed to
|
|
51
|
+
// .rcode/skills/ (not .claude/skills/), so omit them from manifest.actions
|
|
52
|
+
// to avoid false drift reports.
|
|
53
|
+
function isInternalSkill(skillDir) {
|
|
54
|
+
try {
|
|
55
|
+
const text = fs.readFileSync(path.join(skillDir, 'SKILL.md'), 'utf8');
|
|
56
|
+
return /^internal:\s*true\s*$/m.test(text);
|
|
57
|
+
} catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
50
62
|
function walkActions(dir) {
|
|
51
63
|
if (!fs.existsSync(dir)) return;
|
|
52
64
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
53
65
|
if (!entry.isDirectory()) continue;
|
|
54
66
|
const full = path.join(dir, entry.name);
|
|
55
67
|
if (fs.existsSync(path.join(full, 'SKILL.md'))) {
|
|
68
|
+
if (isInternalSkill(full)) continue; // internal → .rcode/skills/, not .claude/skills/
|
|
56
69
|
// Use the name as it lands in .claude/skills/ (installSkills prefixes
|
|
57
70
|
// non-rcode- dirs with 'rcode-', but all current skills already have it)
|
|
58
71
|
const installedName = entry.name.startsWith('rcode-')
|
package/cli/set-mode.js
CHANGED
|
@@ -51,6 +51,16 @@ module.exports = function setMode(args) {
|
|
|
51
51
|
|
|
52
52
|
const requested = args[0];
|
|
53
53
|
|
|
54
|
+
if (requested === '--help' || requested === '-h') {
|
|
55
|
+
console.log(`Usage: rcode set-mode [<mode>]`);
|
|
56
|
+
console.log();
|
|
57
|
+
console.log(` rcode set-mode show current mode + explanation`);
|
|
58
|
+
console.log(` rcode set-mode <mode> switch to mode <mode>`);
|
|
59
|
+
console.log();
|
|
60
|
+
console.log(`Available modes: ${[...VALID_COMMUNICATION_MODES].join(', ')}`);
|
|
61
|
+
process.exit(0);
|
|
62
|
+
}
|
|
63
|
+
|
|
54
64
|
if (!requested) {
|
|
55
65
|
// Show current + available modes
|
|
56
66
|
const config = loadConfig(cwd);
|
package/cli/set-profile.js
CHANGED
|
@@ -37,6 +37,16 @@ module.exports = function setProfile(args) {
|
|
|
37
37
|
const requested = args[0];
|
|
38
38
|
const available = listProfiles();
|
|
39
39
|
|
|
40
|
+
if (requested === '--help' || requested === '-h') {
|
|
41
|
+
console.log(`Usage: rcode set-profile [<name>]`);
|
|
42
|
+
console.log();
|
|
43
|
+
console.log(` rcode set-profile show current profile + available options`);
|
|
44
|
+
console.log(` rcode set-profile <name> switch to profile <name>`);
|
|
45
|
+
console.log();
|
|
46
|
+
console.log(`Available profiles: ${available.join(', ')}`);
|
|
47
|
+
process.exit(0);
|
|
48
|
+
}
|
|
49
|
+
|
|
40
50
|
if (!requested) {
|
|
41
51
|
// Show current profile + available options
|
|
42
52
|
const current = getProjectProfile(cwd);
|
package/cli/uninstall.js
CHANGED
|
@@ -87,6 +87,47 @@ function stripRcodeGitignoreBlock(text) {
|
|
|
87
87
|
.replace(/\n{3,}/g, '\n\n');
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Strip the rcode-managed block from .git/hooks/pre-commit.
|
|
92
|
+
* Removes the file entirely when only the shebang + rcode block remain.
|
|
93
|
+
* Returns 'removed' | 'stripped' | 'unchanged' | 'skipped'.
|
|
94
|
+
*/
|
|
95
|
+
function cleanRcodePreCommitHook(cwd) {
|
|
96
|
+
const hookPath = path.join(cwd, '.git', 'hooks', 'pre-commit');
|
|
97
|
+
if (!fs.existsSync(hookPath)) return 'skipped';
|
|
98
|
+
|
|
99
|
+
const BEGIN = '# ===== rcode-managed pre-commit block =====';
|
|
100
|
+
const END = '# ===== end rcode pre-commit block =====';
|
|
101
|
+
|
|
102
|
+
let content;
|
|
103
|
+
try { content = fs.readFileSync(hookPath, 'utf8'); } catch { return 'skipped'; }
|
|
104
|
+
|
|
105
|
+
if (!content.includes(BEGIN)) return 'unchanged';
|
|
106
|
+
|
|
107
|
+
const startIdx = content.indexOf(BEGIN);
|
|
108
|
+
const endIdx = content.indexOf(END, startIdx);
|
|
109
|
+
if (endIdx < 0) return 'unchanged'; // malformed — leave it
|
|
110
|
+
|
|
111
|
+
// Trim the newline that precedes BEGIN and the newline that follows END
|
|
112
|
+
let lo = startIdx;
|
|
113
|
+
if (lo > 0 && content[lo - 1] === '\n') lo--;
|
|
114
|
+
let hi = endIdx + END.length;
|
|
115
|
+
if (hi < content.length && content[hi] === '\n') hi++;
|
|
116
|
+
|
|
117
|
+
const stripped = content.slice(0, lo) + content.slice(hi);
|
|
118
|
+
|
|
119
|
+
// If only a shebang (or blank) remains, remove the whole file
|
|
120
|
+
const remnant = stripped.trim();
|
|
121
|
+
if (remnant === '' || remnant === '#!/bin/sh' || remnant === '#!/bin/bash') {
|
|
122
|
+
try { fs.unlinkSync(hookPath); return 'removed'; } catch { return 'skipped'; }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
writeFileAtomic(hookPath, stripped, { mode: 0o755 });
|
|
127
|
+
return 'stripped';
|
|
128
|
+
} catch { return 'skipped'; }
|
|
129
|
+
}
|
|
130
|
+
|
|
90
131
|
/**
|
|
91
132
|
* Walk a directory and remove all files/subdirs whose name matches a predicate.
|
|
92
133
|
* Returns the number of entries removed. Always skips local overrides (#382).
|
|
@@ -148,7 +189,7 @@ function cleanupEmptyDirs(cwd, relPaths) {
|
|
|
148
189
|
*/
|
|
149
190
|
function buildPlan(cwd, editors) {
|
|
150
191
|
const plan = {
|
|
151
|
-
claude: { skills: [], commands: [], agents: [] },
|
|
192
|
+
claude: { skills: [], commands: [], agents: [], agentsRulesDir: false },
|
|
152
193
|
cursor: [],
|
|
153
194
|
windsurf: [],
|
|
154
195
|
antigravity: [],
|
|
@@ -194,6 +235,10 @@ function buildPlan(cwd, editors) {
|
|
|
194
235
|
.readdirSync(agentsDir)
|
|
195
236
|
.filter((name) => name.startsWith('rcode-') && name.endsWith('.md'));
|
|
196
237
|
}
|
|
238
|
+
// Installer copies rcode/agents/rules/ tree → .claude/agents/rules/ (#876)
|
|
239
|
+
if (fs.existsSync(path.join(cwd, '.claude/agents/rules'))) {
|
|
240
|
+
plan.claude.agentsRulesDir = true;
|
|
241
|
+
}
|
|
197
242
|
}
|
|
198
243
|
|
|
199
244
|
if (editors.includes('cursor')) {
|
|
@@ -201,7 +246,8 @@ function buildPlan(cwd, editors) {
|
|
|
201
246
|
if (fs.existsSync(cursorDir)) {
|
|
202
247
|
plan.cursor = fs
|
|
203
248
|
.readdirSync(cursorDir)
|
|
204
|
-
|
|
249
|
+
// 'rcode' matches the .cursor/rules/rcode/ subdir installed by the cursor IDE path (#876)
|
|
250
|
+
.filter((name) => name.startsWith('rcode-') || name === 'rcode.mdc' || name === 'rcode-method.mdc' || name === 'rcode');
|
|
205
251
|
}
|
|
206
252
|
}
|
|
207
253
|
|
|
@@ -331,6 +377,9 @@ function planToPathList(plan, cwd, options = {}) {
|
|
|
331
377
|
for (const name of plan.claude.agents) {
|
|
332
378
|
paths.push(path.join('.claude/agents', name));
|
|
333
379
|
}
|
|
380
|
+
if (plan.claude.agentsRulesDir) {
|
|
381
|
+
paths.push('.claude/agents/rules');
|
|
382
|
+
}
|
|
334
383
|
for (const name of plan.cursor) {
|
|
335
384
|
paths.push(path.join('.cursor/rules', name));
|
|
336
385
|
}
|
|
@@ -650,6 +699,17 @@ async function runUninstall(args) {
|
|
|
650
699
|
removed += nAgents;
|
|
651
700
|
if (nAgents > 0) console.log(` ✓ removed ${nAgents} Claude agents`);
|
|
652
701
|
|
|
702
|
+
// .claude/agents/rules/ — installed by the agent-rules sub-tree (#876)
|
|
703
|
+
if (plan.claude.agentsRulesDir) {
|
|
704
|
+
const rulesDir = path.join(cwd, '.claude/agents/rules');
|
|
705
|
+
const r = safeRmSync(rulesDir, path.resolve(cwd));
|
|
706
|
+
if (r.ok && r.reason !== 'missing') {
|
|
707
|
+
console.log(` ✓ removed .claude/agents/rules/ (agent reference rules)`);
|
|
708
|
+
} else if (r.reason === 'outside-root') {
|
|
709
|
+
console.log(` ⚠ refused to remove .claude/agents/rules/ — symlink resolves outside project root`);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
653
713
|
// Clean up now-empty .claude/commands and .claude/agents dirs
|
|
654
714
|
try {
|
|
655
715
|
if (fs.existsSync(path.join(cwd, '.claude/commands')) && fs.readdirSync(path.join(cwd, '.claude/commands')).length === 0) {
|
|
@@ -664,7 +724,7 @@ async function runUninstall(args) {
|
|
|
664
724
|
if (editors.includes('cursor')) {
|
|
665
725
|
const cursorDir = path.join(cwd, '.cursor/rules');
|
|
666
726
|
const n = removeMatching(cursorDir, (name) =>
|
|
667
|
-
name.startsWith('rcode-') || name === 'rcode.mdc' || name === 'rcode-method.mdc',
|
|
727
|
+
name.startsWith('rcode-') || name === 'rcode.mdc' || name === 'rcode-method.mdc' || name === 'rcode',
|
|
668
728
|
);
|
|
669
729
|
removed += n;
|
|
670
730
|
if (n > 0) console.log(` ✓ removed ${n} Cursor rules`);
|
|
@@ -719,6 +779,29 @@ async function runUninstall(args) {
|
|
|
719
779
|
}
|
|
720
780
|
}
|
|
721
781
|
|
|
782
|
+
// Strip the rcode block from .gitignore — always, not just on --purge (#876)
|
|
783
|
+
const gitignorePath = path.join(cwd, '.gitignore');
|
|
784
|
+
if (fs.existsSync(gitignorePath)) {
|
|
785
|
+
try {
|
|
786
|
+
const before = fs.readFileSync(gitignorePath, 'utf8');
|
|
787
|
+
const after = stripRcodeGitignoreBlock(before);
|
|
788
|
+
if (after !== before) {
|
|
789
|
+
fs.writeFileSync(gitignorePath, after);
|
|
790
|
+
console.log(` ✓ stripped rcode block from .gitignore`);
|
|
791
|
+
}
|
|
792
|
+
} catch (err) {
|
|
793
|
+
console.log(` ⚠ could not strip .gitignore block: ${err.message}`);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// Remove .git/hooks/pre-commit rcode block (or the whole file if rcode-only) (#876)
|
|
798
|
+
const hookResult = cleanRcodePreCommitHook(cwd);
|
|
799
|
+
if (hookResult === 'removed') {
|
|
800
|
+
console.log(` ✓ removed .git/hooks/pre-commit (was rcode-only)`);
|
|
801
|
+
} else if (hookResult === 'stripped') {
|
|
802
|
+
console.log(` ✓ stripped rcode block from .git/hooks/pre-commit`);
|
|
803
|
+
}
|
|
804
|
+
|
|
722
805
|
// Cleanup empty editor directories left behind after removing rcode-*
|
|
723
806
|
// entries. Only removes dirs that are COMPLETELY empty — never touches
|
|
724
807
|
// user content. Order matters: innermost first so each parent gets a
|
|
@@ -784,7 +867,7 @@ async function runUninstall(args) {
|
|
|
784
867
|
}
|
|
785
868
|
}
|
|
786
869
|
|
|
787
|
-
// --purge: also wipe .planning/ artifacts
|
|
870
|
+
// --purge: also wipe .planning/ artifacts (user project data beyond .rcode/).
|
|
788
871
|
// Without this, "uninstall + reinstall" carries forward stale phases /
|
|
789
872
|
// sprints / SUMMARY files even after .rcode/ is gone.
|
|
790
873
|
if (opts.purge) {
|
|
@@ -799,36 +882,6 @@ async function runUninstall(args) {
|
|
|
799
882
|
console.log(` ⚠ could not remove .planning/: ${r.reason}`);
|
|
800
883
|
}
|
|
801
884
|
}
|
|
802
|
-
|
|
803
|
-
// Strip the rcode-managed block from .gitignore. The installer writes
|
|
804
|
-
// a fenced block; we remove it cleanly without touching user lines.
|
|
805
|
-
//
|
|
806
|
-
// Issue #684: previous regex `/\n?# rcode[\s\S]*?(?=\n\n|\n$|$)/g` was a
|
|
807
|
-
// footgun — it matched ANY user line starting with "# rcode" (e.g.
|
|
808
|
-
// "# rcode notes", "# rcode is great") and greedily consumed everything
|
|
809
|
-
// up to the next blank line, silently nuking user content.
|
|
810
|
-
//
|
|
811
|
-
// Three shapes have ever shipped:
|
|
812
|
-
// 1. Current (install.js:653-654): "# ===== rcode-managed gitignore block ... =====" ... "# ===== end rcode-managed gitignore block ====="
|
|
813
|
-
// 2. Old fenced markers: "# >>> rcode >>>" ... "# <<< rcode <<<"
|
|
814
|
-
// 3. Hypothetical legacy single-line "# rcode" — never actually
|
|
815
|
-
// committed by any installer version we can find. Removed.
|
|
816
|
-
//
|
|
817
|
-
// Both kept patterns require BOTH sentinel markers to be present —
|
|
818
|
-
// user content with "# rcode" prefix is now safe.
|
|
819
|
-
const gitignorePath = path.join(cwd, '.gitignore');
|
|
820
|
-
if (fs.existsSync(gitignorePath)) {
|
|
821
|
-
try {
|
|
822
|
-
const before = fs.readFileSync(gitignorePath, 'utf8');
|
|
823
|
-
const stripped = stripRcodeGitignoreBlock(before);
|
|
824
|
-
if (stripped !== before) {
|
|
825
|
-
fs.writeFileSync(gitignorePath, stripped);
|
|
826
|
-
console.log(` ✓ stripped rcode block from .gitignore (--purge)`);
|
|
827
|
-
}
|
|
828
|
-
} catch (err) {
|
|
829
|
-
console.log(` ⚠ could not strip .gitignore block: ${err.message}`);
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
885
|
}
|
|
833
886
|
|
|
834
887
|
console.log(`\n✅ Uninstall complete. Removed ${removed} files.`);
|
|
@@ -836,12 +889,20 @@ async function runUninstall(args) {
|
|
|
836
889
|
console.log(` Backup: ${backup.path} (restore with: tar -xzf ${backup.path})`);
|
|
837
890
|
}
|
|
838
891
|
|
|
839
|
-
//
|
|
840
|
-
|
|
841
|
-
|
|
892
|
+
// Notice about what was intentionally preserved (#876 — never delete user data silently)
|
|
893
|
+
const rcodeStillExists = plan.stateDir && fs.existsSync(path.join(cwd, '.rcode'));
|
|
894
|
+
const planningStillExists = !opts.purge && fs.existsSync(path.join(cwd, '.planning'));
|
|
895
|
+
if (rcodeStillExists || planningStillExists) {
|
|
842
896
|
console.log();
|
|
843
|
-
console.log(`ℹ
|
|
844
|
-
|
|
897
|
+
console.log(`ℹ Preserved (your project data — not removed by default):`);
|
|
898
|
+
if (rcodeStillExists) {
|
|
899
|
+
console.log(` .rcode/ phases, decisions, progress, config`);
|
|
900
|
+
console.log(` /rcode-init will detect this on reinstall`);
|
|
901
|
+
}
|
|
902
|
+
if (planningStillExists) {
|
|
903
|
+
console.log(` .planning/ planning scaffolds (ROADMAP, STATE, PROJECT)`);
|
|
904
|
+
}
|
|
905
|
+
console.log(` To remove these on next uninstall: rcode uninstall --purge`);
|
|
845
906
|
}
|
|
846
907
|
|
|
847
908
|
// IDE cache reload hint — Claude Code caches the slash-command list in memory.
|
package/cli/workflow.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rcode workflow [list|show <name>] — lifecycle bridge for non-Claude runtimes.
|
|
3
|
+
*
|
|
4
|
+
* Codex, Copilot, Grok, and other non-Claude agents cannot invoke slash
|
|
5
|
+
* commands directly. This command exposes rcode's lifecycle workflows as
|
|
6
|
+
* readable markdown so any agent can follow the same process:
|
|
7
|
+
*
|
|
8
|
+
* rcode workflow list → list available workflow names
|
|
9
|
+
* rcode workflow show <name> → print workflow markdown to stdout
|
|
10
|
+
* rcode workflow show plan → print the plan workflow
|
|
11
|
+
*
|
|
12
|
+
* An agent consuming `rcode workflow show <name>` should treat the output
|
|
13
|
+
* as its operative instructions for that lifecycle step (the same content
|
|
14
|
+
* Claude Code loads when a slash command fires).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
// Canonical lifecycle order shown in help output
|
|
21
|
+
const LIFECYCLE_ORDER = [
|
|
22
|
+
'new-project',
|
|
23
|
+
'create-prd',
|
|
24
|
+
'discuss-phase',
|
|
25
|
+
'plan',
|
|
26
|
+
'execute-sprint',
|
|
27
|
+
'verify-phase',
|
|
28
|
+
'retrospective',
|
|
29
|
+
'ship',
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
module.exports = function workflow(args, { packageRoot }) {
|
|
33
|
+
const workflowsDir = path.join(packageRoot, 'rcode', 'workflows');
|
|
34
|
+
|
|
35
|
+
const subcommand = args[0];
|
|
36
|
+
|
|
37
|
+
if (!subcommand || subcommand === 'list' || subcommand === '--list') {
|
|
38
|
+
return listWorkflows(workflowsDir);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (subcommand === 'show' || subcommand === 'get' || subcommand === 'run') {
|
|
42
|
+
const name = args[1];
|
|
43
|
+
if (!name) {
|
|
44
|
+
console.error('Usage: rcode workflow show <name>');
|
|
45
|
+
console.error(' rcode workflow list');
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
return showWorkflow(workflowsDir, name);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Treat bare `rcode workflow <name>` as show
|
|
52
|
+
return showWorkflow(workflowsDir, subcommand);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
function listWorkflows(workflowsDir) {
|
|
56
|
+
const all = fs.readdirSync(workflowsDir)
|
|
57
|
+
.filter(f => f.endsWith('.md'))
|
|
58
|
+
.map(f => f.replace(/\.md$/, ''))
|
|
59
|
+
.sort();
|
|
60
|
+
|
|
61
|
+
// Show lifecycle commands first, then remaining alphabetically
|
|
62
|
+
const lifecycle = LIFECYCLE_ORDER.filter(n => all.includes(n));
|
|
63
|
+
const rest = all.filter(n => !LIFECYCLE_ORDER.includes(n));
|
|
64
|
+
|
|
65
|
+
console.log('🕌 rcode lifecycle workflows\n');
|
|
66
|
+
console.log('Core lifecycle (in order):');
|
|
67
|
+
lifecycle.forEach(n => console.log(` rcode workflow show ${n}`));
|
|
68
|
+
if (rest.length) {
|
|
69
|
+
console.log('\nOther workflows:');
|
|
70
|
+
rest.forEach(n => console.log(` rcode workflow show ${n}`));
|
|
71
|
+
}
|
|
72
|
+
console.log('\nUsage: rcode workflow show <name> — print the full workflow instructions');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function showWorkflow(workflowsDir, name) {
|
|
76
|
+
const candidates = [
|
|
77
|
+
path.join(workflowsDir, `${name}.md`),
|
|
78
|
+
path.join(workflowsDir, `rcode-${name}.md`),
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
for (const p of candidates) {
|
|
82
|
+
if (fs.existsSync(p)) {
|
|
83
|
+
process.stdout.write(fs.readFileSync(p, 'utf8'));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Not found — list available and exit non-zero
|
|
89
|
+
const available = fs.readdirSync(workflowsDir)
|
|
90
|
+
.filter(f => f.endsWith('.md'))
|
|
91
|
+
.map(f => f.replace(/\.md$/, ''))
|
|
92
|
+
.sort()
|
|
93
|
+
.join(', ');
|
|
94
|
+
console.error(`Error: workflow '${name}' not found.`);
|
|
95
|
+
console.error(`Available: ${available}`);
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|