@hanzlaa/rcode 4.3.2 → 4.4.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 +2 -1
- package/README.md +45 -10
- package/cli/doctor.js +21 -3
- package/cli/index.js +5 -1
- package/cli/install.js +16 -2
- package/cli/postinstall.js +1 -1
- package/cli/uninstall.js +48 -6
- package/dist/rcode.js +178 -174
- package/package.json +12 -22
- package/rcode/agents/rcode-codebase-mapper.md +12 -0
- package/rcode/agents/rcode-phase-researcher.md +13 -0
- package/rcode/agents/rcode-project-researcher.md +32 -0
- package/rcode/agents/rcode-roadmapper.md +15 -0
- package/rcode/agents/rules/planner/common-patterns.md +40 -0
- package/rcode/bin/lib/roadmap.cjs +184 -11
- package/rcode/bin/rcode-hooks.cjs +303 -14
- package/rcode/bin/rcode-tools.cjs +669 -55
- package/rcode/commands/lens-audit.md +8 -6
- package/rcode/data/intent-table.json +87 -0
- package/rcode/modules/seo.yaml +22 -0
- package/rcode/references/project-types.yaml +75 -0
- package/rcode/skills/SKILLS_INDEX.md +20 -2
- package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/SKILL.md +16 -21
- package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/rules/backlog-building.md +13 -0
- package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/rules/merge-strategy.md +17 -0
- package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/rules/orchestrator-rhythm.md +9 -0
- package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/rules/wave-design.md +21 -0
- package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/templates/heartbeat.sh +0 -0
- package/rcode/skills/actions/4-implementation/rcode-scaffold-project/SKILL.md +60 -1
- package/rcode/skills/core/rcode-lazy/SKILL.md +149 -0
- package/rcode/skills/seo/module.yaml +20 -0
- package/rcode/skills/seo/on-page-seo-auditor/SKILL.md +308 -0
- package/rcode/skills/seo/on-page-seo-auditor/references/audit-example.md +109 -0
- package/rcode/skills/seo/on-page-seo-auditor/references/audit-templates.md +427 -0
- package/rcode/skills/seo/on-page-seo-auditor/references/bulk-audit-playbook.md +116 -0
- package/rcode/skills/seo/on-page-seo-auditor/references/scoring-rubric.md +525 -0
- package/rcode/skills/seo/rank-and-rent-local-seo/SKILL.md +94 -0
- package/rcode/skills/seo/rank-and-rent-local-seo/rules/city-matrix-and-pages.md +76 -0
- package/rcode/skills/seo/rank-and-rent-local-seo/rules/monetization.md +70 -0
- package/rcode/skills/seo/rank-and-rent-local-seo/rules/niche-selection.md +60 -0
- package/rcode/skills/seo/rank-and-rent-local-seo/rules/subniche-discovery.md +66 -0
- package/rcode/skills/seo/rank-and-rent-local-seo/templates/service-city-page.md +36 -0
- package/rcode/skills/seo/rank-and-rent-local-seo/templates/subniche-research-prompt.md +34 -0
- package/rcode/skills/seo/seo-audit/SKILL.md +412 -0
- package/rcode/skills/seo/seo-audit/evals/evals.json +136 -0
- package/rcode/skills/seo/seo-audit/references/ai-writing-detection.md +200 -0
- package/rcode/skills/seo/seo-content-factory/SKILL.md +130 -0
- package/rcode/skills/seo/seo-content-factory/rules/agents.md +80 -0
- package/rcode/skills/seo/seo-content-factory/rules/herdr-wave-mapping.md +80 -0
- package/rcode/skills/seo/seo-content-factory/rules/pipeline.md +102 -0
- package/rcode/skills/seo/seo-content-factory/rules/programmatic-pages.md +107 -0
- package/rcode/skills/seo/seo-content-factory/rules/quality-gates.md +68 -0
- package/rcode/skills/seo/seo-content-factory/rules/weekly-cadence.md +61 -0
- package/rcode/skills/seo/seo-content-factory/templates/BACKLOG.md +23 -0
- package/rcode/skills/seo/seo-content-factory/templates/agent-prompts/01-funnel-A1-A4.md +63 -0
- package/rcode/skills/seo/seo-content-factory/templates/agent-prompts/02-fanout-editorial-A5-A6-A8.md +28 -0
- package/rcode/skills/seo/seo-content-factory/templates/agent-prompts/03-fanout-programmatic-A7-A6-A8.md +25 -0
- package/rcode/skills/seo/seo-content-factory/templates/agent-prompts/04-loop-A9-A10.md +34 -0
- package/rcode/skills/seo/seo-content-factory/templates/article.mdx +39 -0
- package/rcode/skills/seo/seo-content-factory/templates/cluster.schema.json +52 -0
- package/rcode/skills/seo/seo-content-factory/templates/content-brief.md +51 -0
- package/rcode/skills/seo/seo-content-factory/templates/jsonld-builders.ts +49 -0
- package/rcode/skills/seo/seo-content-factory/templates/keywords.csv +13 -0
- package/rcode/skills/seo/seo-content-factory/templates/programmatic-page.tsx +58 -0
- package/rcode/skills/seo/seo-content-writer/SKILL.md +263 -0
- package/rcode/skills/seo/seo-content-writer/references/content-structure-templates.md +875 -0
- package/rcode/skills/seo/seo-content-writer/references/instructions-detail.md +239 -0
- package/rcode/skills/seo/seo-content-writer/references/seo-writing-checklist.md +250 -0
- package/rcode/skills/seo/seo-content-writer/references/title-formulas.md +339 -0
- package/rcode/skills/seo/seo-growth-orchestrator/SKILL.md +79 -0
- package/rcode/skills/seo/seo-growth-orchestrator/rules/backlinks.md +29 -0
- package/rcode/skills/seo/seo-growth-orchestrator/rules/content-engine.md +27 -0
- package/rcode/skills/seo/seo-growth-orchestrator/rules/dos-and-donts.md +40 -0
- package/rcode/skills/seo/seo-growth-orchestrator/rules/goals-protocol.md +34 -0
- package/rcode/skills/seo/seo-growth-orchestrator/rules/local-seo-stack.md +35 -0
- package/rcode/skills/seo/seo-growth-orchestrator/rules/mrr-sprint.md +27 -0
- package/rcode/skills/seo/seo-growth-orchestrator/rules/tools.md +19 -0
- package/rcode/skills/seo/seo-growth-orchestrator/templates/backlink-research.md +29 -0
- package/rcode/skills/seo/seo-growth-orchestrator/templates/goal-protocol.md +35 -0
- package/rcode/skills/seo/seo-growth-orchestrator/templates/local-citations.md +24 -0
- package/rcode/skills/seo/seo-growth-orchestrator/templates/local-city-pages.md +27 -0
- package/rcode/skills/seo/seo-growth-orchestrator/templates/local-gbp-categories.md +23 -0
- package/rcode/skills/seo/seo-growth-orchestrator/templates/local-gsc-goldmine.md +27 -0
- package/rcode/skills/seo/seo-growth-orchestrator/templates/local-review-mining.md +26 -0
- package/rcode/skills/seo/seo-growth-orchestrator/templates/mrr-30day-plan.md +19 -0
- package/rcode/skills/seo/seo-growth-orchestrator/templates/mrr-cut.md +13 -0
- package/rcode/skills/seo/seo-growth-orchestrator/templates/mrr-opportunity-finder.md +17 -0
- package/rcode/skills/seo/seo-growth-orchestrator/templates/mrr-role-model.md +18 -0
- package/rcode/skills/seo/seo-site-builder/SKILL.md +149 -0
- package/rcode/skills/seo/seo-site-builder/rules/01-niche-discovery.md +64 -0
- package/rcode/skills/seo/seo-site-builder/rules/02-deep-research.md +164 -0
- package/rcode/skills/seo/seo-site-builder/rules/03-keyword-strategy.md +82 -0
- package/rcode/skills/seo/seo-site-builder/rules/04-content-plan.md +173 -0
- package/rcode/skills/seo/seo-site-builder/rules/05-site-build.md +187 -0
- package/rcode/skills/seo/seo-site-builder/rules/06-yolo.md +127 -0
- package/rcode/skills/seo/seo-site-builder/templates/article-stub.mdx +62 -0
- package/rcode/skills/seo/seo-site-builder/templates/image-prompt.md +97 -0
- package/rcode/skills/seo/seo-site-builder/templates/keyword-export.md +45 -0
- package/rcode/skills/seo/seo-site-builder/templates/niche-scorecard.md +28 -0
- package/rcode/skills/seo/technical-seo-checker/SKILL.md +329 -0
- package/rcode/skills/seo/technical-seo-checker/references/bulk-audit-playbook.md +118 -0
- package/rcode/skills/seo/technical-seo-checker/references/ecommerce-platform-patterns.md +166 -0
- package/rcode/skills/seo/technical-seo-checker/references/http-status-codes.md +705 -0
- package/rcode/skills/seo/technical-seo-checker/references/llm-crawler-handling.md +257 -0
- package/rcode/skills/seo/technical-seo-checker/references/pre-migration-playbook.md +137 -0
- package/rcode/skills/seo/technical-seo-checker/references/robots-txt-reference.md +717 -0
- package/rcode/skills/seo/technical-seo-checker/references/technical-audit-example.md +169 -0
- package/rcode/skills/seo/technical-seo-checker/references/technical-audit-templates.md +436 -0
- package/rcode/templates/settings-hooks.json +16 -1
- package/rcode/workflows/do.md +17 -0
- package/rcode/workflows/enable-hooks.md +3 -2
- package/rcode/workflows/execute-sprint.md +22 -0
- package/rcode/workflows/execute.md +17 -3
- package/rcode/workflows/lens-audit.md +49 -8
- package/rcode/workflows/new-milestone.md +21 -9
- package/rcode/workflows/new-project-create-roadmap.md +2 -0
- package/rcode/workflows/new-project-roadmap.md +2 -0
- package/rcode/workflows/new-project.md +3 -0
- package/rcode/workflows/plan.md +15 -2
- package/server/dashboard.js +6 -2
- package/server/lib/api.js +35 -20
- package/server/lib/html/client/components/OrchPanel.js +43 -2
- package/server/lib/html/client/components/dashboard/InProgress.js +5 -4
- package/server/lib/html/client/components/shared.js +14 -0
- package/server/lib/html/client/orchestrator.js +13 -0
- package/server/lib/html/client/views/KanbanView.js +33 -4
- package/server/lib/html/css.js +57 -3
- package/server/lib/scanner.js +40 -4
- package/server/orchestrator.js +42 -0
package/package.json
CHANGED
|
@@ -1,21 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzlaa/rcode",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.0",
|
|
4
4
|
"description": "rcode — the AI team that never forgets. Persistent memory, specialist agents, and slash commands for AI IDEs. 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"
|
|
8
8
|
},
|
|
9
|
-
"scripts": {
|
|
10
|
-
"dashboard": "node server/dashboard.js",
|
|
11
|
-
"test": "node --test",
|
|
12
|
-
"test:ci": "node --test --test-reporter=spec",
|
|
13
|
-
"postinstall": "node cli/postinstall.js",
|
|
14
|
-
"build:cli": "node scripts/build.cjs",
|
|
15
|
-
"build": "node scripts/build.cjs",
|
|
16
|
-
"prepack": "node scripts/build.cjs",
|
|
17
|
-
"dogfood": "bash scripts/dogfood-check.sh"
|
|
18
|
-
},
|
|
19
9
|
"files": [
|
|
20
10
|
"cli/",
|
|
21
11
|
"rcode/",
|
|
@@ -61,16 +51,6 @@
|
|
|
61
51
|
"engines": {
|
|
62
52
|
"node": ">=18.0.0"
|
|
63
53
|
},
|
|
64
|
-
"devDependencies": {
|
|
65
|
-
"@clack/prompts": "^0.9.1",
|
|
66
|
-
"diff": "^8.0.4",
|
|
67
|
-
"esbuild": "^0.25.0",
|
|
68
|
-
"fast-glob": "^3.3.3",
|
|
69
|
-
"nanospinner": "^1.2.2",
|
|
70
|
-
"picocolors": "^1.1.1",
|
|
71
|
-
"semver": "^7.7.1",
|
|
72
|
-
"zod": "^3.24.0"
|
|
73
|
-
},
|
|
74
54
|
"publishConfig": {
|
|
75
55
|
"access": "public"
|
|
76
56
|
},
|
|
@@ -79,5 +59,15 @@
|
|
|
79
59
|
},
|
|
80
60
|
"optionalDependencies": {
|
|
81
61
|
"@lydell/node-pty": "1.2.0-beta.12"
|
|
62
|
+
},
|
|
63
|
+
"scripts": {
|
|
64
|
+
"dashboard": "node server/dashboard.js",
|
|
65
|
+
"test": "node --test",
|
|
66
|
+
"test:ci": "node --test --test-reporter=spec",
|
|
67
|
+
"postinstall": "node cli/postinstall.js",
|
|
68
|
+
"build:cli": "node scripts/build.cjs",
|
|
69
|
+
"build": "node scripts/build.cjs",
|
|
70
|
+
"dogfood": "bash scripts/dogfood-check.sh",
|
|
71
|
+
"test:skills": "node test/skill-snapshot.test.cjs"
|
|
82
72
|
}
|
|
83
|
-
}
|
|
73
|
+
}
|
|
@@ -30,6 +30,18 @@ Your job: Explore thoroughly across ALL source roots (never assume `src/` is the
|
|
|
30
30
|
If the prompt contains a `<files_to_read>` block, you MUST use the `Read` tool to load every file listed there before performing any other actions. This is your primary context.
|
|
31
31
|
</role>
|
|
32
32
|
|
|
33
|
+
## Timeout Strategy
|
|
34
|
+
|
|
35
|
+
If this scan takes longer than expected (many large files, deep directories):
|
|
36
|
+
|
|
37
|
+
1. **After 3 minutes without writing an output file:** Write a partial CONVENTIONS.md or TESTING.md with what has been discovered so far, clearly marked `## [PARTIAL — scan timed out]` at the top. A partial file is better than no file.
|
|
38
|
+
|
|
39
|
+
2. **Scope reduction:** If a directory scan is taking too long, limit depth: scan top-level + src/ + lib/ + app/ only. Skip node_modules, dist, .next, .git, coverage automatically.
|
|
40
|
+
|
|
41
|
+
3. **Never emit only a greeting and go silent.** If you cannot complete the full scan, write whatever you have found to the output file immediately and explain what was skipped.
|
|
42
|
+
|
|
43
|
+
4. **On stall recovery:** If restarted after a stall, check which output files already exist under `.planning/maps/`. Skip any that are already written (non-empty). Only write the missing ones.
|
|
44
|
+
|
|
33
45
|
<why_this_matters>
|
|
34
46
|
**These documents are consumed by other rcode commands:**
|
|
35
47
|
|
|
@@ -83,6 +83,19 @@ Named rules. Cite by name when applying.
|
|
|
83
83
|
6. **Write RESEARCH.md** — sections in order: User Constraints → Standard Stack → Architecture Patterns → Don't Hand-Roll → Common Pitfalls → Code Examples.
|
|
84
84
|
7. **Return to orchestrator** — RESEARCH.md path in the return message.
|
|
85
85
|
|
|
86
|
+
## Iterative Retrieval Protocol
|
|
87
|
+
|
|
88
|
+
After your first search pass, evaluate what's still unknown:
|
|
89
|
+
1. List the open questions your initial searches did NOT answer
|
|
90
|
+
2. If there are ≥2 open questions, run a second search pass targeting those gaps
|
|
91
|
+
3. After the second pass, if critical questions remain (marked MUST-KNOW in your output), run a third pass
|
|
92
|
+
4. Stop when: (a) all MUST-KNOW gaps are filled, (b) 3 passes are complete, or (c) the same result appears in 2+ searches (diminishing returns signal)
|
|
93
|
+
|
|
94
|
+
Log each pass as:
|
|
95
|
+
Pass 1: [query list] → [results summary]
|
|
96
|
+
Pass 2: [gap-filling queries] → [results summary]
|
|
97
|
+
Pass 3 (if needed): [remaining gaps] → [results summary]
|
|
98
|
+
|
|
86
99
|
## Anti-Patterns / Refuse List
|
|
87
100
|
|
|
88
101
|
- **Never omit User Constraints** — the planner enforces them; missing constraints cause plan/user conflicts.
|
|
@@ -28,6 +28,25 @@ Your files feed the roadmap:
|
|
|
28
28
|
**Be comprehensive but opinionated.** "Use X because Y" not "Options are X, Y, Z."
|
|
29
29
|
</role>
|
|
30
30
|
|
|
31
|
+
## content-site Mode
|
|
32
|
+
|
|
33
|
+
When `project_type` is `content-site` (detected from PROJECT.md or the new-project classification step), skip the generic STACK.md / FEATURES.md / ARCHITECTURE.md research flow. Instead produce:
|
|
34
|
+
|
|
35
|
+
**KEYWORDS.md** — seed keyword list with intent classification per keyword (informational / commercial / transactional / navigational). Call `claude-seo:seo-cluster` if available to generate the SERP-overlap cluster map. Minimum: 20 keywords across at least 3 intent categories.
|
|
36
|
+
|
|
37
|
+
**CLUSTERS.md** — hub/spoke content architecture. Each cluster = one hub page + N spoke pages. Derive from keyword intent groupings. Include: page type per node (hub / spoke / programmatic / location), primary keyword, secondary keywords (≤3), and estimated internal link targets (hub links to all spokes; each spoke links back to hub + 2 sibling spokes).
|
|
38
|
+
|
|
39
|
+
**PITFALLS.md** — SEO-specific pitfalls for this project. Must include at minimum:
|
|
40
|
+
1. Thin-content risk (pages under word floor indexed before enrichment)
|
|
41
|
+
2. Canonical drift (programmatic pages with near-duplicate URLs getting split authority)
|
|
42
|
+
3. Noindex strategy (when to gate pages as noindex until enriched vs. index immediately)
|
|
43
|
+
4. Duplicate title/H1 trap (city×service matrix producing identical meta across locations)
|
|
44
|
+
5. Crawl budget waste (low-value pages consuming crawl allocation)
|
|
45
|
+
|
|
46
|
+
Reference `seo-content-factory` for the full production pipeline and `seo-growth-orchestrator` for strategy orchestration.
|
|
47
|
+
|
|
48
|
+
Do NOT produce STACK.md, FEATURES.md, or ARCHITECTURE.md for content-site projects. Technology stack research is secondary — the content pipeline is the product.
|
|
49
|
+
|
|
31
50
|
<philosophy>
|
|
32
51
|
|
|
33
52
|
## Honest Reporting
|
|
@@ -81,6 +100,19 @@ Named rules. Cite by name when applying.
|
|
|
81
100
|
- `PITFALLS.md` — risk flags for deeper research
|
|
82
101
|
6. **Return to orchestrator** — list all written files.
|
|
83
102
|
|
|
103
|
+
## Iterative Retrieval Protocol
|
|
104
|
+
|
|
105
|
+
After your first search pass, evaluate what's still unknown:
|
|
106
|
+
1. List the open questions your initial searches did NOT answer
|
|
107
|
+
2. If there are ≥2 open questions, run a second search pass targeting those gaps
|
|
108
|
+
3. After the second pass, if critical questions remain (marked MUST-KNOW in your output), run a third pass
|
|
109
|
+
4. Stop when: (a) all MUST-KNOW gaps are filled, (b) 3 passes are complete, or (c) the same result appears in 2+ searches (diminishing returns signal)
|
|
110
|
+
|
|
111
|
+
Log each pass as:
|
|
112
|
+
Pass 1: [query list] → [results summary]
|
|
113
|
+
Pass 2: [gap-filling queries] → [results summary]
|
|
114
|
+
Pass 3 (if needed): [remaining gaps] → [results summary]
|
|
115
|
+
|
|
84
116
|
## Anti-Patterns / Refuse List
|
|
85
117
|
|
|
86
118
|
- **Never present a menu of options** when a clear recommendation can be made. Per Confident-but-honest.
|
|
@@ -46,3 +46,18 @@ Named rules. Cite by name when applying.
|
|
|
46
46
|
- **Never over-phase** — for a solo developer project, 3-7 phases is typical. 15 phases is corporate theater.
|
|
47
47
|
- **Never start planning before reading the research files** — phases without research produce wrong phase structures.
|
|
48
48
|
|
|
49
|
+
## Scope Constraint
|
|
50
|
+
|
|
51
|
+
**rcode-roadmapper creates ONLY:**
|
|
52
|
+
- `ROADMAP.md` — the project roadmap with phases, requirements, and success criteria
|
|
53
|
+
- One `PHASE.md` per phase directory (if phase-level detail files are requested)
|
|
54
|
+
- `STATE.md` — project memory initialization
|
|
55
|
+
- `REQUIREMENTS.md` traceability updates
|
|
56
|
+
|
|
57
|
+
**rcode-roadmapper MUST NOT create:**
|
|
58
|
+
- `SPRINT.md` — sprint planning is handled exclusively by `/rcode-plan`
|
|
59
|
+
- `PLAN.md` — plan files are out of scope for this agent
|
|
60
|
+
- Any other sprint-level or task-level planning files
|
|
61
|
+
|
|
62
|
+
If you find yourself about to write a `SPRINT.md` file, **STOP** — that file is out of scope for this agent. Sprint planning is the responsibility of `/rcode-plan`, not `/rcode-new-project` or the roadmapper.
|
|
63
|
+
|
|
@@ -371,3 +371,43 @@ If your work doesn't match any pattern:
|
|
|
371
371
|
4. Document the rationale in the plan
|
|
372
372
|
|
|
373
373
|
Novel patterns are fine — just ensure they're still decomposed correctly.
|
|
374
|
+
|
|
375
|
+
---
|
|
376
|
+
|
|
377
|
+
## Known Stack Constraints
|
|
378
|
+
|
|
379
|
+
Hard-won version constraints and format requirements that the planner must honour
|
|
380
|
+
when generating `devDependencies`, scaffold files, or code snippets. Getting these
|
|
381
|
+
wrong causes runtime crashes or build failures that are hard to diagnose.
|
|
382
|
+
|
|
383
|
+
### Mobile / Expo
|
|
384
|
+
|
|
385
|
+
**Expo SDK 55+ requires TypeScript ≥ 5.4**
|
|
386
|
+
|
|
387
|
+
`tsconfig.base.json` in Expo SDK 55 sets `"module": "preserve"`, which TypeScript
|
|
388
|
+
< 5.4 does not support and will error on at compile time.
|
|
389
|
+
|
|
390
|
+
- When generating `devDependencies` for any Expo SDK 50+ project, always specify
|
|
391
|
+
`"typescript": "~5.8.0"` (or at minimum `"~5.4.0"`).
|
|
392
|
+
- **Never** emit `"typescript": "~5.3.x"` for an Expo project.
|
|
393
|
+
|
|
394
|
+
### Drizzle ORM — Migrations Barrel Format (v0.30.x+)
|
|
395
|
+
|
|
396
|
+
The migrations barrel passed to `drizzle-orm/expo-sqlite` (and similar drivers)
|
|
397
|
+
must be an **object**, not an array.
|
|
398
|
+
|
|
399
|
+
```ts
|
|
400
|
+
// CORRECT — object spread
|
|
401
|
+
import * as m0000 from './migrations/0000_bored_microchip.sql';
|
|
402
|
+
export const migrations = { m0000 };
|
|
403
|
+
|
|
404
|
+
// WRONG — array causes a runtime crash
|
|
405
|
+
export const migrations = [m0000];
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
Additional rules:
|
|
409
|
+
- SQL filenames are auto-generated by `drizzle-kit generate` (e.g. `0000_bored_microchip.sql`).
|
|
410
|
+
Never hardcode SQL filenames in plans — instruct the executor to match actual
|
|
411
|
+
`drizzle-kit` output or use a dynamic import glob.
|
|
412
|
+
- The barrel file must be re-generated (or updated) whenever `drizzle-kit generate`
|
|
413
|
+
produces a new migration file.
|
|
@@ -14,6 +14,69 @@ function roadmapPathFor(projectRoot) {
|
|
|
14
14
|
return path.join(projectRoot, '.planning', 'ROADMAP.md');
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Resolve the active ROADMAP file path for a project.
|
|
19
|
+
*
|
|
20
|
+
* Convention detection (#734):
|
|
21
|
+
* 1. Single file — .planning/ROADMAP.md (primary / existing behaviour)
|
|
22
|
+
* 2. Per-milestone — .planning/ROADMAP-M<N>.md, e.g. ROADMAP-M3.md
|
|
23
|
+
* Selects the file that matches state.current_milestone when present;
|
|
24
|
+
* falls back to the lexicographically last file when no state is set.
|
|
25
|
+
*
|
|
26
|
+
* Returns { path, convention, milestone } where convention is
|
|
27
|
+
* 'single' | 'per-milestone'.
|
|
28
|
+
*/
|
|
29
|
+
function resolveRoadmapPath(projectRoot) {
|
|
30
|
+
const single = path.join(projectRoot, '.planning', 'ROADMAP.md');
|
|
31
|
+
if (fs.existsSync(single)) {
|
|
32
|
+
return { path: single, convention: 'single', milestone: null };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Scan for ROADMAP-M*.md files
|
|
36
|
+
const planningDir = path.join(projectRoot, '.planning');
|
|
37
|
+
let milestoneFiles = [];
|
|
38
|
+
if (fs.existsSync(planningDir)) {
|
|
39
|
+
milestoneFiles = fs.readdirSync(planningDir)
|
|
40
|
+
.filter(f => /^ROADMAP-M\d+\.md$/i.test(f))
|
|
41
|
+
.sort();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (milestoneFiles.length === 0) {
|
|
45
|
+
// Neither convention found — return the canonical path (callers handle missing file)
|
|
46
|
+
return { path: single, convention: 'single', milestone: null };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Read state.json to find current_milestone
|
|
50
|
+
const stateJsonPath = path.join(projectRoot, '.rcode', 'state.json');
|
|
51
|
+
let currentMilestone = null;
|
|
52
|
+
if (fs.existsSync(stateJsonPath)) {
|
|
53
|
+
try {
|
|
54
|
+
const st = JSON.parse(fs.readFileSync(stateJsonPath, 'utf8'));
|
|
55
|
+
currentMilestone = st.current_milestone || null;
|
|
56
|
+
} catch { /* non-fatal */ }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Try to match by milestone id (e.g. "M3", "3", "milestone-3")
|
|
60
|
+
let matched = null;
|
|
61
|
+
if (currentMilestone) {
|
|
62
|
+
const numMatch = String(currentMilestone).match(/(\d+)/);
|
|
63
|
+
const milestoneNum = numMatch ? numMatch[1] : null;
|
|
64
|
+
if (milestoneNum) {
|
|
65
|
+
matched = milestoneFiles.find(f => {
|
|
66
|
+
const m = f.match(/^ROADMAP-M(\d+)\.md$/i);
|
|
67
|
+
return m && m[1] === milestoneNum;
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Fall back to the last file in sorted order
|
|
73
|
+
if (!matched) matched = milestoneFiles[milestoneFiles.length - 1];
|
|
74
|
+
|
|
75
|
+
const resolvedPath = path.join(planningDir, matched);
|
|
76
|
+
const milestoneLabel = matched.replace(/^ROADMAP-M(\d+)\.md$/i, 'M$1');
|
|
77
|
+
return { path: resolvedPath, convention: 'per-milestone', milestone: milestoneLabel };
|
|
78
|
+
}
|
|
79
|
+
|
|
17
80
|
/**
|
|
18
81
|
* Extract all phase sections. Each returned entry includes:
|
|
19
82
|
* number, name, goal, section (raw markdown slice), headerIndex, sectionEnd
|
|
@@ -66,11 +129,30 @@ function extractPhases(content) {
|
|
|
66
129
|
|
|
67
130
|
function parseRequirements(section) {
|
|
68
131
|
// Matches both bold-style (**Requirements:**) and heading-style (### Requirements)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
132
|
+
// followed by a list block.
|
|
133
|
+
const listMatch = section.match(/(?:\*\*Requirements(?::\*\*|\*\*:)|#{1,4}\s*Requirements\s*:?)[^\n]*\n((?:\s*(?:\d+\.|[-*])\s+[^\n]+\n?)+)/i);
|
|
134
|
+
if (listMatch) {
|
|
135
|
+
return listMatch[1].split('\n')
|
|
136
|
+
.map((l) => l.replace(/^\s*(?:\d+\.|[-*])\s+/, '').trim())
|
|
137
|
+
.filter(Boolean);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Also capture REQ-IDs from inline lines like:
|
|
141
|
+
// **REQs:** REQ-004, REQ-010, REQ-020
|
|
142
|
+
// Requirements: REQ-001, REQ-002
|
|
143
|
+
// **Covers:** REQ-001, REQ-003
|
|
144
|
+
// Collect every line in the section that contains REQ-\d+ patterns.
|
|
145
|
+
const seen = new Set();
|
|
146
|
+
const out = [];
|
|
147
|
+
const reqIdRe = /\bREQ-[A-Z0-9][A-Z0-9-]*\b/g;
|
|
148
|
+
for (const line of section.split('\n')) {
|
|
149
|
+
if (!/REQ-/i.test(line)) continue;
|
|
150
|
+
const ids = line.match(reqIdRe) || [];
|
|
151
|
+
for (const id of ids) {
|
|
152
|
+
if (!seen.has(id)) { seen.add(id); out.push(id); }
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return out;
|
|
74
156
|
}
|
|
75
157
|
|
|
76
158
|
function parseSuccessCriteria(section) {
|
|
@@ -124,7 +206,7 @@ function phaseStatus(section) {
|
|
|
124
206
|
function normalizePhaseNum(n) { return parseInt(String(n).replace(/^0+/, '') || '0', 10); }
|
|
125
207
|
|
|
126
208
|
function cmdGetPhase(projectRoot, phaseNum, opts = {}) {
|
|
127
|
-
const rp =
|
|
209
|
+
const { path: rp } = resolveRoadmapPath(projectRoot);
|
|
128
210
|
if (!fs.existsSync(rp)) {
|
|
129
211
|
return { found: false, error: 'ROADMAP.md not found', phase_number: phaseNum };
|
|
130
212
|
}
|
|
@@ -169,19 +251,67 @@ function cmdGetPhase(projectRoot, phaseNum, opts = {}) {
|
|
|
169
251
|
}
|
|
170
252
|
|
|
171
253
|
function cmdListPhases(projectRoot) {
|
|
172
|
-
const rp =
|
|
254
|
+
const { path: rp } = resolveRoadmapPath(projectRoot);
|
|
173
255
|
if (!fs.existsSync(rp)) return [];
|
|
174
256
|
const content = fs.readFileSync(rp, 'utf8');
|
|
175
257
|
// Phase 10 / #466 — prefer the parsed Status field from extractPhases over
|
|
176
258
|
// the legacy phaseStatus() heuristic, which only matched literal "completed"
|
|
177
259
|
// in the header and missed our **Status:** Complete convention. Fall back to
|
|
178
260
|
// phaseStatus() only when extractPhases couldn't parse a Status line.
|
|
179
|
-
|
|
261
|
+
const phases = extractPhases(content).map((p) => ({
|
|
180
262
|
number: p.number,
|
|
181
263
|
name: p.name,
|
|
182
264
|
status: p.status === 'unknown' ? phaseStatus(p.section) : p.status,
|
|
183
265
|
status_raw: p.status_raw,
|
|
184
266
|
}));
|
|
267
|
+
|
|
268
|
+
// Fix #856 — state.current_phase is authoritative for which phase is in_progress.
|
|
269
|
+
// ROADMAP.md may carry a stale "Active" marker from a previous phase after
|
|
270
|
+
// `state set-phase` has moved forward. Read state.json and override statuses so
|
|
271
|
+
// exactly one phase (the current one) gets in_progress, and any other phase that
|
|
272
|
+
// ROADMAP still labels active/in_progress is demoted to planned.
|
|
273
|
+
const stateJsonPath = path.join(projectRoot, '.rcode', 'state.json');
|
|
274
|
+
let currentPhase = null;
|
|
275
|
+
if (fs.existsSync(stateJsonPath)) {
|
|
276
|
+
try {
|
|
277
|
+
const st = JSON.parse(fs.readFileSync(stateJsonPath, 'utf8'));
|
|
278
|
+
currentPhase = st.current_phase || null;
|
|
279
|
+
} catch { /* non-fatal — fall through to ROADMAP-only statuses */ }
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (!currentPhase) return phases;
|
|
283
|
+
|
|
284
|
+
// Extract the numeric prefix from current_phase (e.g. "02-correctness-fixes" → "2").
|
|
285
|
+
const numPrefixMatch = String(currentPhase).match(/^(\d+)/);
|
|
286
|
+
const leadingNum = numPrefixMatch ? String(parseInt(numPrefixMatch[1], 10)) : null;
|
|
287
|
+
|
|
288
|
+
let matched = false;
|
|
289
|
+
return phases.map((p) => {
|
|
290
|
+
// Normalize the ROADMAP phase number (strip leading zeros) for comparison.
|
|
291
|
+
const normNum = String(parseInt(String(p.number), 10));
|
|
292
|
+
const isCurrentPhase =
|
|
293
|
+
!matched && (
|
|
294
|
+
p.name === currentPhase ||
|
|
295
|
+
(leadingNum !== null && normNum === leadingNum) ||
|
|
296
|
+
String(p.number) === String(currentPhase)
|
|
297
|
+
);
|
|
298
|
+
if (isCurrentPhase) {
|
|
299
|
+
matched = true;
|
|
300
|
+
// Only promote to in_progress if not already complete/closed.
|
|
301
|
+
const s = String(p.status || '').toLowerCase();
|
|
302
|
+
if (s !== 'complete' && s !== 'closed') {
|
|
303
|
+
return { ...p, status: 'in_progress' };
|
|
304
|
+
}
|
|
305
|
+
return p;
|
|
306
|
+
}
|
|
307
|
+
// Demote any other phase that ROADMAP still marks active/in_progress —
|
|
308
|
+
// state.current_phase is the single source of truth for the active phase.
|
|
309
|
+
const s = String(p.status || '').toLowerCase();
|
|
310
|
+
if (s === 'active' || s === 'in_progress') {
|
|
311
|
+
return { ...p, status: 'planned' };
|
|
312
|
+
}
|
|
313
|
+
return p;
|
|
314
|
+
});
|
|
185
315
|
}
|
|
186
316
|
|
|
187
317
|
/**
|
|
@@ -226,7 +356,7 @@ function cmdUpdatePlanProgress(projectRoot, phaseNum, planId, status) {
|
|
|
226
356
|
if (!planId || !status) {
|
|
227
357
|
throw new Error('Usage: roadmap update-plan-progress <phase> <plan-id> <status>');
|
|
228
358
|
}
|
|
229
|
-
const rp =
|
|
359
|
+
const { path: rp } = resolveRoadmapPath(projectRoot);
|
|
230
360
|
if (!fs.existsSync(rp)) {
|
|
231
361
|
return { updated: false, error: 'ROADMAP.md not found' };
|
|
232
362
|
}
|
|
@@ -278,7 +408,7 @@ function cmdUpdatePlanProgress(projectRoot, phaseNum, planId, status) {
|
|
|
278
408
|
* roadmap summary — overview of phase counts and active phase.
|
|
279
409
|
*/
|
|
280
410
|
function cmdSummary(projectRoot) {
|
|
281
|
-
const rp =
|
|
411
|
+
const { path: rp } = resolveRoadmapPath(projectRoot);
|
|
282
412
|
if (!fs.existsSync(rp)) return { found: false, error: 'ROADMAP.md not found' };
|
|
283
413
|
const phases = cmdListPhases(projectRoot);
|
|
284
414
|
const total = phases.length;
|
|
@@ -297,6 +427,8 @@ function cmdSummary(projectRoot) {
|
|
|
297
427
|
* roadmap clear — archive and scaffold.
|
|
298
428
|
*/
|
|
299
429
|
function cmdClear(projectRoot) {
|
|
430
|
+
// Clear always targets the canonical single-file path — per-milestone files
|
|
431
|
+
// are managed per-milestone and not cleared globally.
|
|
300
432
|
const rp = roadmapPathFor(projectRoot);
|
|
301
433
|
// Derive current version from config.yaml or default v1.0
|
|
302
434
|
const configPath = path.join(projectRoot, '.rcode', 'config.yaml');
|
|
@@ -322,6 +454,43 @@ function cmdClear(projectRoot) {
|
|
|
322
454
|
return { cleared: true, archived, version, roadmap: rp };
|
|
323
455
|
}
|
|
324
456
|
|
|
457
|
+
/**
|
|
458
|
+
* roadmap detect — report which ROADMAP convention is in use (#734).
|
|
459
|
+
*
|
|
460
|
+
* Outputs a human-readable line and returns structured JSON.
|
|
461
|
+
* Examples:
|
|
462
|
+
* single: .planning/ROADMAP.md
|
|
463
|
+
* per-milestone: .planning/ROADMAP-M3.md (current)
|
|
464
|
+
*/
|
|
465
|
+
function cmdDetect(projectRoot) {
|
|
466
|
+
const resolved = resolveRoadmapPath(projectRoot);
|
|
467
|
+
const exists = fs.existsSync(resolved.path);
|
|
468
|
+
const rel = path.relative(projectRoot, resolved.path);
|
|
469
|
+
|
|
470
|
+
if (resolved.convention === 'single') {
|
|
471
|
+
const label = exists ? `single: ${rel}` : `single: ${rel} (not found)`;
|
|
472
|
+
return { __raw: label, convention: 'single', path: rel, exists, milestone: null };
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// per-milestone — also list all available milestone files
|
|
476
|
+
const planningDir = path.join(projectRoot, '.planning');
|
|
477
|
+
const allFiles = fs.existsSync(planningDir)
|
|
478
|
+
? fs.readdirSync(planningDir).filter(f => /^ROADMAP-M\d+\.md$/i.test(f)).sort()
|
|
479
|
+
: [];
|
|
480
|
+
|
|
481
|
+
const label = exists
|
|
482
|
+
? `per-milestone: ${rel} (current)`
|
|
483
|
+
: `per-milestone: ${rel} (current, not found)`;
|
|
484
|
+
return {
|
|
485
|
+
__raw: label,
|
|
486
|
+
convention: 'per-milestone',
|
|
487
|
+
path: rel,
|
|
488
|
+
exists,
|
|
489
|
+
milestone: resolved.milestone,
|
|
490
|
+
all_milestone_files: allFiles.map(f => path.join('.planning', f)),
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
|
|
325
494
|
function dispatch(projectRoot, subArgs) {
|
|
326
495
|
const sub = subArgs[0];
|
|
327
496
|
const rest = subArgs.slice(1);
|
|
@@ -344,8 +513,10 @@ function dispatch(projectRoot, subArgs) {
|
|
|
344
513
|
return cmdClear(projectRoot);
|
|
345
514
|
case 'summary':
|
|
346
515
|
return cmdSummary(projectRoot);
|
|
516
|
+
case 'detect':
|
|
517
|
+
return cmdDetect(projectRoot);
|
|
347
518
|
default:
|
|
348
|
-
throw new Error(`Unknown roadmap subcommand: ${sub}. Valid: get-phase, list-phases, update-plan-progress, clear, summary`);
|
|
519
|
+
throw new Error(`Unknown roadmap subcommand: ${sub}. Valid: get-phase, list-phases, update-plan-progress, clear, summary, detect`);
|
|
349
520
|
}
|
|
350
521
|
}
|
|
351
522
|
|
|
@@ -356,4 +527,6 @@ module.exports = {
|
|
|
356
527
|
cmdUpdatePlanProgress,
|
|
357
528
|
cmdClear,
|
|
358
529
|
cmdSummary,
|
|
530
|
+
cmdDetect,
|
|
531
|
+
resolveRoadmapPath,
|
|
359
532
|
};
|