@hanzlaa/rcode 4.7.3 → 4.9.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/CLAUDE.md +1 -1
- package/CONTRIBUTING.md +3 -0
- package/README.md +7 -5
- package/cli/github-sync.js +17 -201
- package/cli/install.js +16 -13
- package/cli/lib/config.cjs +1 -1
- package/cli/lib/github-sync-discover.cjs +304 -0
- package/cli/lib/namespace-migrate.cjs +16 -1
- package/cli/uninstall.js +7 -2
- package/dist/rcode.js +197 -197
- package/package.json +1 -1
- package/rcode/agents/rcode-code-reviewer.md +1 -1
- package/rcode/agents/rcode-docs-auditor.md +1 -1
- package/rcode/agents/rcode-edge-case-hunter.md +1 -1
- package/rcode/agents/rcode-haitham.md +1 -1
- package/rcode/agents/rcode-hanzla.md +1 -1
- package/rcode/agents/rcode-hussain-pm.md +1 -1
- package/rcode/agents/rcode-nyquist-auditor.md +1 -1
- package/rcode/agents/rcode-omar.md +1 -1
- package/rcode/agents/rcode-remediation-planner.md +1 -1
- package/rcode/agents/rcode-security-adversary.md +1 -1
- package/rcode/agents/rcode-security-auditor.md +1 -1
- package/rcode/agents/rcode-sprint-checker.md +1 -1
- package/rcode/agents/rcode-verifier.md +1 -1
- package/rcode/agents/rcode-waleed.md +1 -1
- package/rcode/agents/rcode-yousef.md +1 -1
- package/rcode/agents/rules/planner/task-templates.md +24 -22
- package/rcode/agents/rules/verifier/verification-report.md +2 -2
- package/rcode/bin/lib/roadmap.cjs +80 -4
- package/rcode/bin/rcode-tools.cjs +124 -1
- package/rcode/brain/best-practices/state-sync-rule.md +1 -1
- package/rcode/references/council-protocol.md +9 -4
- package/rcode/references/execute-auto-copy-learnings.md +15 -0
- package/rcode/references/execute-close-parent-artifacts.md +49 -0
- package/rcode/references/execute-interactive-mode.md +44 -0
- package/rcode/references/execute-notify-webhooks.md +22 -0
- package/rcode/references/execution-protocol.md +11 -11
- package/rcode/references/git-preflight.md +7 -4
- package/rcode/references/output-format.md +5 -5
- package/rcode/references/plan-gaps-mode.md +89 -0
- package/rcode/references/plan-thinking-partner.md +20 -0
- package/rcode/references/plan-windows-troubleshooting.md +25 -0
- package/rcode/references/planner-playbook.md +23 -29
- package/rcode/skills/_shared/state-sync-rule.md +1 -1
- package/rcode/skills/actions/1-analysis/rcode-document-project/SKILL.md +2 -0
- package/rcode/skills/actions/1-analysis/rcode-prfaq/SKILL.md +2 -0
- package/rcode/skills/actions/2-plan/rcode-create-epics-and-stories/SKILL.md +11 -0
- package/rcode/skills/actions/2-plan/rcode-create-prd/SKILL.md +2 -0
- package/rcode/skills/actions/2-plan/rcode-create-story/SKILL.md +11 -0
- package/rcode/skills/actions/2-plan/rcode-edit-prd/SKILL.md +2 -0
- package/rcode/skills/actions/2-plan/rcode-validate-prd/SKILL.md +2 -0
- package/rcode/skills/actions/3-solutioning/rcode-check-implementation-readiness/SKILL.md +2 -0
- package/rcode/skills/actions/3-solutioning/rcode-create-architecture/SKILL.md +2 -0
- package/rcode/skills/actions/4-implementation/rcode-checkpoint-preview/SKILL.md +2 -0
- package/rcode/skills/actions/4-implementation/rcode-code-review/SKILL.md +2 -0
- package/rcode/skills/actions/4-implementation/rcode-correct-course/SKILL.md +2 -0
- package/rcode/skills/actions/4-implementation/rcode-debug/SKILL.md +1 -1
- package/rcode/skills/actions/4-implementation/rcode-dev-story/SKILL.md +2 -0
- package/rcode/skills/actions/4-implementation/rcode-retrospective/SKILL.md +2 -0
- package/rcode/skills/actions/4-implementation/rcode-scaffold-project/SKILL.md +2 -0
- package/rcode/skills/actions/4-implementation/rcode-sprint-planning/SKILL.md +3 -2
- package/rcode/skills/actions/4-implementation/rcode-sprint-status/SKILL.md +2 -0
- package/rcode/skills/agents/haitham-frontend/SKILL.md +6 -0
- package/rcode/skills/agents/hanzla-engineer/SKILL.md +6 -0
- package/rcode/skills/agents/hussain-pm/SKILL.md +1 -1
- package/rcode/skills/agents/hussain-sm/SKILL.md +3 -3
- package/rcode/skills/agents/majlis-council/references.md +17 -7
- package/rcode/skills/agents/raees-orchestrator/SKILL.md +13 -11
- package/rcode/skills/agents/yousef-backend/SKILL.md +6 -0
- package/rcode/templates/github/epic-template.md +1 -1
- package/rcode/templates/github/feature-template.md +1 -1
- package/rcode/templates/github/task-template.md +1 -1
- package/rcode/templates/sprint.md +14 -3
- package/rcode/templates/verification-report.md +7 -4
- package/rcode/workflows/add-phase.md +33 -14
- package/rcode/workflows/code-review-fix.md +5 -5
- package/rcode/workflows/code-review.md +1 -1
- package/rcode/workflows/council.md +1 -0
- package/rcode/workflows/create-epics-and-stories.md +12 -6
- package/rcode/workflows/dev-story.md +12 -14
- package/rcode/workflows/do.md +12 -9
- package/rcode/workflows/execute-sprint.md +7 -4
- package/rcode/workflows/execute-waves.md +38 -2
- package/rcode/workflows/execute.md +42 -138
- package/rcode/workflows/init.md +10 -2
- package/rcode/workflows/lens-audit.md +7 -7
- package/rcode/workflows/plan-research-validation.md +10 -5
- package/rcode/workflows/plan-spawn-planner.md +9 -14
- package/rcode/workflows/plan.md +27 -138
- package/rcode/workflows/quick.md +1 -1
- package/rcode/workflows/scaffold-skill.md +19 -1
- package/rcode/workflows/secure-phase.md +7 -1
- package/rcode/workflows/sprint-planning.md +14 -37
- package/rcode/workflows/validate-phase.md +7 -1
package/AGENTS.md
CHANGED
|
@@ -24,7 +24,7 @@ If a user says "just keep going" or "don't stop until done", that authorization
|
|
|
24
24
|
|
|
25
25
|
- Follow [Conventional Commits](https://www.conventionalcommits.org/) format: `type(scope): subject`
|
|
26
26
|
- Types allowed: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`, `perf`, `revert`
|
|
27
|
-
- Scopes allowed: `agents`, `skills`, `workflows`, `templates`, `dashboard`, `docs`, `config`, `github`, `commands`, `memory`, `brand`, `cli`, `ci`, `release`, `meta`, `tasks`, `migrations`, `refs`, `state`, `hooks`, `init`, `install`, `parity`, `triggers`, `dogfood`, `namespace`, `planning`, `insights`, `help`, `roadmap`, `session`, `audits`, `execute`, `executor`, `plan`, `planner`, `readme`, `sync`, `sprint`, `agent-exp`, `extensibility`, `lens-audit`, `tiers`, `build`, `council`, `doctor`, `postinstall`, `progress`, `security`, `tools`, `uninstall`, `update`, `test`, `changelog`, `scopes`, `phases`, `references`, `kanban`, `orchestrator`, `orchpanel`, `status`, `bin`, `brain`, `dogfeed`, `new-project`, `package`, `rcode-tools`, `rihal-tools`, `team`, `usp`, `v4`, `observability`, `audit`, `agent-rules`, `cursor`, `i18n`, `phase`, `scaffold`, `campaign`, `ship`, `getting-started`, `do-router`, `milestone-health`, `modules`, `project-types`, `roadmapper`, `token`, plus numeric phase/sprint scopes (e.g. `docs(15)`, `feat(8.3)`)
|
|
27
|
+
- Scopes allowed: `agents`, `skills`, `workflows`, `templates`, `dashboard`, `docs`, `config`, `github`, `github-sync`, `commands`, `memory`, `brand`, `cli`, `ci`, `release`, `meta`, `tasks`, `migrations`, `refs`, `state`, `hooks`, `init`, `install`, `parity`, `triggers`, `dogfood`, `namespace`, `planning`, `insights`, `help`, `roadmap`, `session`, `audits`, `execute`, `executor`, `plan`, `planner`, `readme`, `rcode`, `review`, `sync`, `sprint`, `agent-exp`, `extensibility`, `lens-audit`, `tiers`, `build`, `council`, `doctor`, `postinstall`, `progress`, `security`, `tools`, `uninstall`, `update`, `test`, `changelog`, `scopes`, `phases`, `references`, `kanban`, `orchestrator`, `orchpanel`, `status`, `bin`, `brain`, `dogfeed`, `new-project`, `package`, `rcode-tools`, `rihal-tools`, `team`, `usp`, `v4`, `observability`, `audit`, `agent-rules`, `cursor`, `i18n`, `phase`, `scaffold`, `campaign`, `ship`, `getting-started`, `do-router`, `milestone-health`, `modules`, `project-types`, `roadmapper`, `token`, plus numeric phase/sprint scopes (e.g. `docs(15)`, `feat(8.3)`)
|
|
28
28
|
- Subject: lowercase first letter, imperative mood, no trailing period, under 72 chars
|
|
29
29
|
- **NEVER add Claude/AI attribution to commit messages.** No "Generated with Claude Code", no "Co-Authored-By: Claude", no "🤖 Generated". The user does not want this.
|
|
30
30
|
- **NEVER use `--no-verify`** to bypass hooks. If hooks fail, fix the underlying issue.
|
package/CLAUDE.md
CHANGED
|
@@ -24,7 +24,7 @@ If a user says "just keep going" or "don't stop until done", that authorization
|
|
|
24
24
|
|
|
25
25
|
- Follow [Conventional Commits](https://www.conventionalcommits.org/) format: `type(scope): subject`
|
|
26
26
|
- Types allowed: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`, `perf`, `revert`
|
|
27
|
-
- Scopes allowed: `agents`, `skills`, `workflows`, `templates`, `dashboard`, `docs`, `config`, `github`, `commands`, `memory`, `brand`, `cli`, `ci`, `release`, `meta`, `tasks`, `migrations`, `refs`, `state`, `hooks`, `install`, `parity`, `triggers`, `dogfood`, `namespace`, `planning`, `insights`, `help`, `roadmap`, `session`, `audits`, `execute`, `executor`, `plan`, `planner`, `readme`, `sync`, `sprint`, `agent-exp`, `extensibility`, `lens-audit`, `tiers`, `build`, `council`, `doctor`, `postinstall`, `progress`, `security`, `tools`, `uninstall`, `update`, `test`, `changelog`, `scopes`, `phases`, `references`, `kanban`, `orchestrator`, `orchpanel`, `status`, `bin`, `brain`, `dogfeed`, `new-project`, `package`, `rcode-tools`, `rihal-tools`, `team`, `usp`, `v4`, `observability`, `audit`, plus numeric phase/sprint scopes (e.g. `docs(15)`, `feat(8.3)`)
|
|
27
|
+
- Scopes allowed: `agents`, `skills`, `workflows`, `templates`, `dashboard`, `docs`, `config`, `github`, `github-sync`, `commands`, `memory`, `brand`, `cli`, `ci`, `release`, `meta`, `tasks`, `migrations`, `refs`, `state`, `hooks`, `install`, `parity`, `triggers`, `dogfood`, `namespace`, `planning`, `insights`, `help`, `roadmap`, `session`, `audits`, `execute`, `executor`, `plan`, `planner`, `readme`, `rcode`, `review`, `sync`, `sprint`, `agent-exp`, `extensibility`, `lens-audit`, `tiers`, `build`, `council`, `doctor`, `postinstall`, `progress`, `security`, `tools`, `uninstall`, `update`, `test`, `changelog`, `scopes`, `phases`, `references`, `kanban`, `orchestrator`, `orchpanel`, `status`, `bin`, `brain`, `dogfeed`, `new-project`, `package`, `rcode-tools`, `rihal-tools`, `team`, `usp`, `v4`, `observability`, `audit`, `init`, `agent-rules`, `cursor`, `i18n`, `phase`, `scaffold`, `campaign`, `ship`, `getting-started`, `do-router`, `milestone-health`, `modules`, `project-types`, `roadmapper`, `token`, plus numeric phase/sprint scopes (e.g. `docs(15)`, `feat(8.3)`)
|
|
28
28
|
- Subject: lowercase first letter, imperative mood, no trailing period, under 72 chars
|
|
29
29
|
- **NEVER add Claude/AI attribution to commit messages.** No "Generated with Claude Code", no "Co-Authored-By: Claude", no "🤖 Generated". The user does not want this.
|
|
30
30
|
- **NEVER use `--no-verify`** to bypass hooks. If hooks fail, fix the underlying issue.
|
package/CONTRIBUTING.md
CHANGED
|
@@ -282,6 +282,7 @@ We use [Conventional Commits](https://www.conventionalcommits.org/) format. The
|
|
|
282
282
|
- `getting-started` — the `docs/getting-started.md` onboarding guide
|
|
283
283
|
- `config` — team.yaml, config.yaml
|
|
284
284
|
- `github` — CI/CD, issue templates, PR templates
|
|
285
|
+
- `github-sync` — `cli/github-sync.js` and its discovery/parsing logic
|
|
285
286
|
- `cli` — `cli/*.js` and `rcode/bin/rcode-tools.cjs`
|
|
286
287
|
- `state` — `.rcode/state.json` and state-manipulation code paths
|
|
287
288
|
- `refs` — files inside `rcode/references/`
|
|
@@ -311,6 +312,8 @@ We use [Conventional Commits](https://www.conventionalcommits.org/) format. The
|
|
|
311
312
|
- `plan` — plan workflow
|
|
312
313
|
- `planner` — planner agent
|
|
313
314
|
- `readme` — README.md
|
|
315
|
+
- `review` — code review reports and review-gate fixes (e.g. `NN-REVIEW.md` resolutions)
|
|
316
|
+
- `rcode` — cross-cutting rcode planning/methodology commits not tied to one narrower scope
|
|
314
317
|
- `sync` — install/state sync flows
|
|
315
318
|
- `sprint` — sprint-level workflow additions and fixes
|
|
316
319
|
- `agent-exp` — agent experience improvements
|
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ pnpm dlx @hanzlaa/rcode install
|
|
|
23
23
|
[](https://github.com/hanzlahabib/rcode/actions/workflows/test.yml)
|
|
24
24
|
[](LICENSE)
|
|
25
25
|
|
|
26
|
-
Status: `@hanzlaa/rcode` v4.
|
|
26
|
+
Status: `@hanzlaa/rcode` v4.8.0 on npm. 45 agents · 117 commands · 130 workflows · **1 runtime dependency**. Test status tracked by CI badge above. Actively dogfooded on real projects every week.
|
|
27
27
|
|
|
28
28
|
---
|
|
29
29
|
|
|
@@ -154,10 +154,10 @@ The point isn't "I beat LangChain." The point is **you don't need LangChain for
|
|
|
154
154
|
| Metric | Value | Why it's not a vanity number |
|
|
155
155
|
|---|---|---|
|
|
156
156
|
| **Portable methodology corpus** | **~105,000 lines** of markdown | A 10-person eng org's playbooks (agents + commands + workflows + skills + references) — as files you own and grep, not a SaaS you rent. |
|
|
157
|
-
| **Automated tests** | **
|
|
157
|
+
| **Automated tests** | **598** across 74 files | The methodology is *guarded*, not vibes. Live pass/fail status is on the CI badge above; `node --test` (or `node benchmarks/facts.cjs`) reproduces locally in seconds. |
|
|
158
158
|
| **Tested CLI engine** | **~9,700 lines** (`rcode-tools.cjs` + `lib/`) | The deterministic brain — routing, state, planning math — is real code under test, not prompt soup. |
|
|
159
159
|
| **Runtime dependencies** | **1** (`ws`, for the dashboard socket) | The dashboard is otherwise pure Node stdlib. Almost nothing to audit, nothing to CVE-scan, nothing to break on `npm install`. |
|
|
160
|
-
| **Specialist agents / commands / workflows / skills** | **45 / 117 /
|
|
160
|
+
| **Specialist agents / commands / workflows / skills** | **45 / 117 / 130 / 96** | An entire engineering org, phrase-activated, that travels with you across Claude Code, Cursor, Codex, VS Code, Antigravity, Windsurf. |
|
|
161
161
|
| **Core-op latency** | **~60 ms**, **0 LLM tokens** (best-of-7) | Intent routing, state reads, and milestone-health are deterministic *local* compute — a few ms over Node's own cold-start floor. The orchestration layer doesn't burn API tokens on bookkeeping the way pure-LLM agent frameworks do. |
|
|
162
162
|
|
|
163
163
|
> Counts above are produced by `node benchmarks/facts.cjs` and may drift slightly between releases — run it for the exact current figures.
|
|
@@ -181,6 +181,8 @@ pnpm dlx @hanzlaa/rcode install
|
|
|
181
181
|
|
|
182
182
|
`/rcode-init` detects your project state (fresh / existing / returning) and routes to the right first action. For a greenfield project it auto-routes to `/rcode-new-project`.
|
|
183
183
|
|
|
184
|
+
> **Want a status primer at the start of every session?** Run `/rcode-enable-hooks` to turn on a one-line project status readout (phase, plan progress, blockers) each time Claude Code starts in this project, plus 9 other opt-in guardrails (read-before-edit checks, dangerous-command blocking, auto-formatting). All off by default — a fresh install never surprises you.
|
|
185
|
+
|
|
184
186
|
### The full loop
|
|
185
187
|
|
|
186
188
|
Four commands cover most of a real week of work — decide, plan, build, check in:
|
|
@@ -227,9 +229,9 @@ The non-goal: building yet another agent framework. There are enough. rcode stay
|
|
|
227
229
|
|
|
228
230
|
## Honest state of things
|
|
229
231
|
|
|
230
|
-
- **v4.
|
|
232
|
+
- **v4.8.0** is the current release — Memory Bank now ships populated by default, brain-pull is live, and 50+ bug fixes landed from the most recent 3-project dogfeed run.
|
|
231
233
|
- **Open issues**: ~50 — half are feature requests, the rest are backlog bugs ranked by severity.
|
|
232
|
-
- **Test suite**:
|
|
234
|
+
- **Test suite**: 598 automated tests across 74 files (run `node --test`). Live pass/fail status is on the CI badge above. Coverage is structural (compliance + artifact schema + workflow behavioral), not line-coverage.
|
|
233
235
|
- **Real users**: I run it on 4 projects daily. A handful of others run it on theirs. If you find a bug, file it — most P1s ship within 48 hours.
|
|
234
236
|
- **Funding**: none. This is solo work. If your company wants commercial support, [email me](mailto:hanzla.dev@gmail.com).
|
|
235
237
|
|
package/cli/github-sync.js
CHANGED
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
* rcode github-sync --execute # actually create issues
|
|
12
12
|
*
|
|
13
13
|
* Granular targeting (push specific items):
|
|
14
|
-
* --phase=
|
|
15
|
-
* --sprint=
|
|
16
|
-
* --epic=
|
|
17
|
-
* --story=
|
|
14
|
+
* --phase=44 push one phase (all its epics + stories)
|
|
15
|
+
* --sprint=44.1 push stories belonging to one sprint
|
|
16
|
+
* --epic=EPIC-01 push one epic and its child stories
|
|
17
|
+
* --story=44.1.1 push one story (sprint-track task id or epic-track story id, e.g. 1.1)
|
|
18
18
|
*
|
|
19
19
|
* Options:
|
|
20
20
|
* --repo=owner/name target a specific repo (otherwise auto-detect)
|
|
@@ -45,6 +45,7 @@ const gh = require('./lib/github.cjs');
|
|
|
45
45
|
const { askText, PromptAbortError } = require('./lib/prompts.cjs');
|
|
46
46
|
const { writeJsonAtomic } = require('./lib/fsutil.cjs');
|
|
47
47
|
const { loadConfig } = require('./lib/config.cjs');
|
|
48
|
+
const { discoverPhases, applyGranularFilters } = require('./lib/github-sync-discover.cjs');
|
|
48
49
|
|
|
49
50
|
/**
|
|
50
51
|
* Hash a string — used to detect content changes between syncs.
|
|
@@ -140,51 +141,6 @@ function parseArgs(args) {
|
|
|
140
141
|
return opts;
|
|
141
142
|
}
|
|
142
143
|
|
|
143
|
-
/**
|
|
144
|
-
* Parse YAML-ish frontmatter from the top of a markdown file.
|
|
145
|
-
* Used to extract explicit epic/sprint linking from story files.
|
|
146
|
-
* Returns an object of key/value strings, or {} if no frontmatter block.
|
|
147
|
-
*/
|
|
148
|
-
function extractFrontmatter(content) {
|
|
149
|
-
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
150
|
-
if (!match) return {};
|
|
151
|
-
const fm = {};
|
|
152
|
-
for (const line of match[1].split(/\r?\n/)) {
|
|
153
|
-
const m = line.match(/^([\w_-]+)\s*:\s*(.*)$/);
|
|
154
|
-
if (m) fm[m[1].trim()] = m[2].trim().replace(/^["']|["']$/g, '');
|
|
155
|
-
}
|
|
156
|
-
return fm;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Parse .rcode/phases/{phase}/sprints.md if present and return a map of
|
|
161
|
-
* sprint-id → story-ids (based on "- [ ] story-X-Y" list entries under
|
|
162
|
-
* each sprint section). Used by --sprint=ID filtering.
|
|
163
|
-
*
|
|
164
|
-
* Sprint section convention:
|
|
165
|
-
* ## Sprint 1 — {goal}
|
|
166
|
-
* - [ ] story-1-1-login
|
|
167
|
-
* - [ ] story-1-2-signup
|
|
168
|
-
*/
|
|
169
|
-
function parseSprintsFile(sprintsContent) {
|
|
170
|
-
if (!sprintsContent) return {};
|
|
171
|
-
const sprintMap = {};
|
|
172
|
-
let currentSprint = null;
|
|
173
|
-
for (const line of sprintsContent.split(/\r?\n/)) {
|
|
174
|
-
const header = line.match(/^##\s+Sprint\s+(\S+)/i);
|
|
175
|
-
if (header) {
|
|
176
|
-
currentSprint = `sprint-${header[1].replace(/[^\w-]/g, '')}`;
|
|
177
|
-
sprintMap[currentSprint] = [];
|
|
178
|
-
continue;
|
|
179
|
-
}
|
|
180
|
-
if (currentSprint) {
|
|
181
|
-
const item = line.match(/^\s*-\s*\[[ xX]\]\s+(\S+)/);
|
|
182
|
-
if (item) sprintMap[currentSprint].push(item[1]);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
return sprintMap;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
144
|
// ---------- Discover .rcode/ content ----------
|
|
189
145
|
|
|
190
146
|
function loadState(cwd) {
|
|
@@ -196,148 +152,6 @@ function loadState(cwd) {
|
|
|
196
152
|
try { return JSON.parse(fs.readFileSync(statePath, 'utf8')); } catch { return null; }
|
|
197
153
|
}
|
|
198
154
|
|
|
199
|
-
function discoverPhases(cwd) {
|
|
200
|
-
const phasesDir = path.join(cwd, '.rcode/phases');
|
|
201
|
-
if (!fs.existsSync(phasesDir)) return [];
|
|
202
|
-
|
|
203
|
-
const phases = [];
|
|
204
|
-
for (const entry of fs.readdirSync(phasesDir, { withFileTypes: true })) {
|
|
205
|
-
if (!entry.isDirectory()) continue;
|
|
206
|
-
const phaseDir = path.join(phasesDir, entry.name);
|
|
207
|
-
const briefPath = path.join(phaseDir, 'brief.md');
|
|
208
|
-
const sprintsPath = path.join(phaseDir, 'sprints.md');
|
|
209
|
-
const storiesDir = path.join(phaseDir, 'stories');
|
|
210
|
-
const tasksDir = path.join(phaseDir, 'tasks');
|
|
211
|
-
|
|
212
|
-
const sprintsContent = fs.existsSync(sprintsPath)
|
|
213
|
-
? fs.readFileSync(sprintsPath, 'utf8')
|
|
214
|
-
: null;
|
|
215
|
-
|
|
216
|
-
const phase = {
|
|
217
|
-
id: entry.name,
|
|
218
|
-
brief: fs.existsSync(briefPath) ? fs.readFileSync(briefPath, 'utf8') : null,
|
|
219
|
-
sprints: sprintsContent,
|
|
220
|
-
sprintMap: parseSprintsFile(sprintsContent),
|
|
221
|
-
stories: [],
|
|
222
|
-
epics: [],
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
if (fs.existsSync(storiesDir)) {
|
|
226
|
-
for (const file of fs.readdirSync(storiesDir)) {
|
|
227
|
-
if (!file.endsWith('.md')) continue;
|
|
228
|
-
const content = fs.readFileSync(path.join(storiesDir, file), 'utf8');
|
|
229
|
-
const fm = extractFrontmatter(content);
|
|
230
|
-
const id = file.replace('.md', '');
|
|
231
|
-
// Figure out which epic this story belongs to: prefer explicit
|
|
232
|
-
// frontmatter, fall back to "story-X-..." → "epic-X-..." naming
|
|
233
|
-
// convention so existing projects without frontmatter still link.
|
|
234
|
-
let parentEpic = fm.epic || null;
|
|
235
|
-
if (!parentEpic) {
|
|
236
|
-
const m = id.match(/^story-(\d+)/);
|
|
237
|
-
if (m) parentEpic = `epic-${m[1]}`;
|
|
238
|
-
}
|
|
239
|
-
// Figure out which sprint this story belongs to: explicit
|
|
240
|
-
// frontmatter wins, else look it up in the sprint map.
|
|
241
|
-
let sprintId = fm.sprint || null;
|
|
242
|
-
if (!sprintId) {
|
|
243
|
-
for (const [sid, storyList] of Object.entries(phase.sprintMap)) {
|
|
244
|
-
if (storyList.some((s) => s === id || id.startsWith(s))) {
|
|
245
|
-
sprintId = sid;
|
|
246
|
-
break;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
phase.stories.push({
|
|
251
|
-
id,
|
|
252
|
-
file,
|
|
253
|
-
content,
|
|
254
|
-
title: extractTitle(content) || id,
|
|
255
|
-
parentEpic,
|
|
256
|
-
sprintId,
|
|
257
|
-
frontmatter: fm,
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
if (fs.existsSync(tasksDir)) {
|
|
263
|
-
for (const file of fs.readdirSync(tasksDir)) {
|
|
264
|
-
if (!file.endsWith('.md')) continue;
|
|
265
|
-
const content = fs.readFileSync(path.join(tasksDir, file), 'utf8');
|
|
266
|
-
const fm = extractFrontmatter(content);
|
|
267
|
-
phase.epics.push({
|
|
268
|
-
id: file.replace('.md', ''),
|
|
269
|
-
file,
|
|
270
|
-
content,
|
|
271
|
-
title: extractTitle(content) || file.replace('.md', ''),
|
|
272
|
-
frontmatter: fm,
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
phases.push(phase);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
return phases;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* Apply granular --sprint/--epic/--story filters to the discovered phases.
|
|
285
|
-
* Mutates a shallow copy — original discovery result is not touched.
|
|
286
|
-
* Returns a new phases array where only the requested items remain.
|
|
287
|
-
*/
|
|
288
|
-
function applyGranularFilters(phases, opts) {
|
|
289
|
-
// No filters → return as-is
|
|
290
|
-
if (!opts.sprint && !opts.epic && !opts.story) return phases;
|
|
291
|
-
|
|
292
|
-
const filtered = phases.map((p) => ({
|
|
293
|
-
...p,
|
|
294
|
-
epics: [...p.epics],
|
|
295
|
-
stories: [...p.stories],
|
|
296
|
-
}));
|
|
297
|
-
|
|
298
|
-
if (opts.sprint) {
|
|
299
|
-
for (const p of filtered) {
|
|
300
|
-
// Keep only stories whose sprintId matches
|
|
301
|
-
p.stories = p.stories.filter((s) => s.sprintId === opts.sprint);
|
|
302
|
-
// Keep only epics that have at least one remaining story or whose id
|
|
303
|
-
// the user might also be interested in (conservative: keep all epics
|
|
304
|
-
// in this phase so child stories have a visible parent reference)
|
|
305
|
-
// Actually: if filtering by sprint, user wants the sprint's work —
|
|
306
|
-
// stories only. Drop epics unless they're referenced by a remaining
|
|
307
|
-
// story.
|
|
308
|
-
const liveEpicIds = new Set(p.stories.map((s) => s.parentEpic).filter(Boolean));
|
|
309
|
-
p.epics = p.epics.filter((e) => liveEpicIds.has(e.id));
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (opts.epic) {
|
|
314
|
-
for (const p of filtered) {
|
|
315
|
-
p.epics = p.epics.filter((e) => e.id === opts.epic);
|
|
316
|
-
// Keep stories whose parentEpic matches
|
|
317
|
-
p.stories = p.stories.filter((s) => s.parentEpic === opts.epic);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
if (opts.story) {
|
|
322
|
-
for (const p of filtered) {
|
|
323
|
-
p.stories = p.stories.filter((s) => s.id === opts.story);
|
|
324
|
-
// Keep any epic a surviving story points at (so link target exists)
|
|
325
|
-
const parents = new Set(p.stories.map((s) => s.parentEpic).filter(Boolean));
|
|
326
|
-
p.epics = p.epics.filter((e) => parents.has(e.id));
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// Drop phases that ended up completely empty after filtering
|
|
331
|
-
return filtered.filter(
|
|
332
|
-
(p) => p.epics.length > 0 || p.stories.length > 0,
|
|
333
|
-
);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
function extractTitle(markdown) {
|
|
337
|
-
const match = markdown.match(/^#\s+(.+)$/m);
|
|
338
|
-
return match ? match[1].trim() : null;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
155
|
// ---------- Load/save sync map (for idempotency) ----------
|
|
342
156
|
|
|
343
157
|
function loadSyncMap(cwd) {
|
|
@@ -460,7 +274,9 @@ async function main(args) {
|
|
|
460
274
|
// ------ Discover phases ------
|
|
461
275
|
let phases = discoverPhases(cwd);
|
|
462
276
|
if (opts.phase) {
|
|
463
|
-
|
|
277
|
+
// Match either the full directory id or the bare numeric prefix so
|
|
278
|
+
// --phase=44 and --phase=44-github-sync-... both work.
|
|
279
|
+
phases = phases.filter((p) => p.id === opts.phase || p.numericId === opts.phase);
|
|
464
280
|
if (phases.length === 0) {
|
|
465
281
|
console.error(`❌ Phase '${opts.phase}' not found.`);
|
|
466
282
|
process.exit(1);
|
|
@@ -481,8 +297,8 @@ async function main(args) {
|
|
|
481
297
|
console.error(` Check the filter value or run without filters to see available ids.`);
|
|
482
298
|
process.exit(1);
|
|
483
299
|
}
|
|
484
|
-
console.log(`ℹ No phases found in .
|
|
485
|
-
console.log(` Run
|
|
300
|
+
console.log(`ℹ No phases found in .planning/phases/ or epics in .planning/epics/ — nothing to sync.`);
|
|
301
|
+
console.log(` Run /rcode-plan or /rcode-create-epics-and-stories to get started.`);
|
|
486
302
|
process.exit(0);
|
|
487
303
|
}
|
|
488
304
|
|
|
@@ -551,7 +367,7 @@ async function main(args) {
|
|
|
551
367
|
{ name: 'QA', color: 'd73a4a', description: 'Quality assurance' },
|
|
552
368
|
{ name: 'Docs', color: 'c5def5', description: 'Documentation' },
|
|
553
369
|
] : [],
|
|
554
|
-
milestones: phases.filter((p) => !syncMap.phases[p.id]),
|
|
370
|
+
milestones: phases.filter((p) => !p.noMilestone && !syncMap.phases[p.id]),
|
|
555
371
|
epics: phases.flatMap((p) =>
|
|
556
372
|
p.epics.filter((e) => !syncMap.epics[e.id]).map((e) => ({ ...e, phase: p.id })),
|
|
557
373
|
),
|
|
@@ -702,7 +518,7 @@ async function main(args) {
|
|
|
702
518
|
`## 📊 Meta`,
|
|
703
519
|
``,
|
|
704
520
|
`- **Phase:** \`${epic.phase}\``,
|
|
705
|
-
`- **Source:**
|
|
521
|
+
`- **Source:** \`${epic.sourcePath}\``,
|
|
706
522
|
`- **Synced by:** rcode github-sync`,
|
|
707
523
|
``,
|
|
708
524
|
`## 📝 Child Stories`,
|
|
@@ -785,7 +601,7 @@ async function main(args) {
|
|
|
785
601
|
parentRefLine,
|
|
786
602
|
sprintRefLine,
|
|
787
603
|
`- **Phase:** \`${story.phase}\``,
|
|
788
|
-
`- **Source:**
|
|
604
|
+
`- **Source:** \`${story.sourcePath}\``,
|
|
789
605
|
`- **Synced by:** rcode github-sync`,
|
|
790
606
|
``,
|
|
791
607
|
`> **Linking:** Reference this story in commits with \`refs #${'{issue}'}\``,
|
|
@@ -892,7 +708,7 @@ async function main(args) {
|
|
|
892
708
|
const body = [
|
|
893
709
|
`## 🎯 Epic Vision`,
|
|
894
710
|
``,
|
|
895
|
-
`_Strategic goal this Epic contributes to. Fill in from
|
|
711
|
+
`_Strategic goal this Epic contributes to. Fill in from \`${epic.sourcePath}\`._`,
|
|
896
712
|
``,
|
|
897
713
|
`## 📋 Source Content`,
|
|
898
714
|
``,
|
|
@@ -903,7 +719,7 @@ async function main(args) {
|
|
|
903
719
|
`## 📊 Meta`,
|
|
904
720
|
``,
|
|
905
721
|
`- **Phase:** \`${epic.phase}\``,
|
|
906
|
-
`- **Source:**
|
|
722
|
+
`- **Source:** \`${epic.sourcePath}\``,
|
|
907
723
|
`- **Last synced:** ${new Date().toISOString()}`,
|
|
908
724
|
].join('\n');
|
|
909
725
|
|
|
@@ -937,7 +753,7 @@ async function main(args) {
|
|
|
937
753
|
const body = [
|
|
938
754
|
`## 🎯 Problem Statement`,
|
|
939
755
|
``,
|
|
940
|
-
`_From
|
|
756
|
+
`_From \`${story.sourcePath}\`._`,
|
|
941
757
|
``,
|
|
942
758
|
`## 📋 Source Content`,
|
|
943
759
|
``,
|
|
@@ -948,7 +764,7 @@ async function main(args) {
|
|
|
948
764
|
`## 📊 Meta`,
|
|
949
765
|
``,
|
|
950
766
|
`- **Phase:** \`${story.phase}\``,
|
|
951
|
-
`- **Source:**
|
|
767
|
+
`- **Source:** \`${story.sourcePath}\``,
|
|
952
768
|
`- **Last synced:** ${new Date().toISOString()}`,
|
|
953
769
|
].join('\n');
|
|
954
770
|
|
package/cli/install.js
CHANGED
|
@@ -2502,21 +2502,32 @@ async function installInner(opts) {
|
|
|
2502
2502
|
fs.existsSync(path.join(projectClaudeCommands, 'rcode'))
|
|
2503
2503
|
);
|
|
2504
2504
|
if (globalHasrcode && !projectHasrcode) {
|
|
2505
|
-
// Global commands exist, project has none yet — filter
|
|
2506
|
-
// so we don't create duplicates. Project gets .rcode/ state only.
|
|
2505
|
+
// Global commands exist, project has none yet — filter commands out of the
|
|
2506
|
+
// plan so we don't create duplicates. Project gets .rcode/ state only.
|
|
2507
|
+
//
|
|
2508
|
+
// Issue #1022: agents are NOT deferrable to global the way commands/skills
|
|
2509
|
+
// are — they are first-class, project-local files by design (see the
|
|
2510
|
+
// createInstallBackup comment above: "closes #381 — without this,
|
|
2511
|
+
// customized .claude/agents/rcode-*.md ... were silently lost"). Lumping
|
|
2512
|
+
// `.claude/agents/` into this commands-dedup filter meant a project with
|
|
2513
|
+
// global rcode commands installed but no project-level commands yet would
|
|
2514
|
+
// never get its .claude/agents/*.md files written at all, leaving only
|
|
2515
|
+
// whatever pre-existing subdirectories (e.g. rules/) survived untouched.
|
|
2507
2516
|
const before = plan.length;
|
|
2508
2517
|
const filtered = plan.filter(e => {
|
|
2509
2518
|
const rel = e.rel.split(path.sep).join('/');
|
|
2510
|
-
return !rel.startsWith('.claude/commands/')
|
|
2519
|
+
return !rel.startsWith('.claude/commands/');
|
|
2511
2520
|
});
|
|
2512
2521
|
if (filtered.length < before) {
|
|
2513
2522
|
plan.length = 0;
|
|
2514
2523
|
filtered.forEach(e => plan.push(e));
|
|
2515
|
-
console.log(' ' + dim('Global rcode commands detected in ~/.claude/ — skipping project-level
|
|
2524
|
+
console.log(' ' + dim('Global rcode commands detected in ~/.claude/ — skipping project-level command install to avoid duplicates.'));
|
|
2516
2525
|
console.log(' ' + dim('Use --force-overwrite to install locally anyway.'));
|
|
2517
2526
|
}
|
|
2518
2527
|
} else if (globalHasrcode && projectHasrcode) {
|
|
2519
2528
|
// Both exist — project commands are duplicates. Remove project-level ones.
|
|
2529
|
+
// Agents are left untouched (#1022) — they are project-local by design,
|
|
2530
|
+
// never deduped against the global commands install.
|
|
2520
2531
|
try {
|
|
2521
2532
|
// Remove root-level rcode-*.md files
|
|
2522
2533
|
const projectCommandFiles = fs.readdirSync(projectClaudeCommands)
|
|
@@ -2530,19 +2541,11 @@ async function installInner(opts) {
|
|
|
2530
2541
|
if (fs.existsSync(rcodeSubdir)) {
|
|
2531
2542
|
safeRmSync(rcodeSubdir, opts.target);
|
|
2532
2543
|
}
|
|
2533
|
-
const projectAgentsDir = path.join(opts.target, '.claude', 'agents');
|
|
2534
|
-
if (fs.existsSync(projectAgentsDir)) {
|
|
2535
|
-
const agentFiles = fs.readdirSync(projectAgentsDir)
|
|
2536
|
-
.filter(f => f.startsWith('rcode-') && f.endsWith('.md'));
|
|
2537
|
-
for (const f of agentFiles) {
|
|
2538
|
-
fs.unlinkSync(path.join(projectAgentsDir, f));
|
|
2539
|
-
}
|
|
2540
|
-
}
|
|
2541
2544
|
console.log(' ' + dim('Removed duplicate project-level rcode commands (global ones in ~/.claude/ take precedence).'));
|
|
2542
2545
|
} catch { /* non-fatal */ }
|
|
2543
2546
|
const filtered = plan.filter(e => {
|
|
2544
2547
|
const rel = e.rel.split(path.sep).join('/');
|
|
2545
|
-
return !rel.startsWith('.claude/commands/')
|
|
2548
|
+
return !rel.startsWith('.claude/commands/');
|
|
2546
2549
|
});
|
|
2547
2550
|
plan.length = 0;
|
|
2548
2551
|
filtered.forEach(e => plan.push(e));
|
package/cli/lib/config.cjs
CHANGED