forge-workflow 0.0.9 → 0.1.0-beta.2
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/.claude/rules/{greptile-review-process.md → review-process.md} +56 -41
- package/.claude/scripts/{greptile-resolve.sh → review-resolve.sh} +13 -3
- package/.cursor/rules/permissions-guidance.mdc +2 -2
- package/.forge/hooks/check-tdd.js +3 -0
- package/.forge/hooks/forge-native-hook.js +245 -0
- package/.forge/protected-paths.yaml +157 -0
- package/AGENTS.md +151 -61
- package/CHANGELOG.md +681 -0
- package/CLAUDE.md +9 -106
- package/QUICKSTART.md +171 -0
- package/README.md +271 -363
- package/bin/forge-cmd.js +120 -9
- package/bin/forge-preflight.js +26 -5
- package/bin/forge.js +466 -489
- package/docs/INDEX.md +93 -0
- package/docs/PROJECT_DESIGN.md +685 -0
- package/docs/architecture/index.md +66 -0
- package/docs/architecture/notes/README.md +35 -0
- package/docs/architecture/subsystems/README.md +46 -0
- package/docs/{TOOLCHAIN.md → forge/TOOLCHAIN.md} +56 -47
- package/docs/forge/VALIDATION.md +82 -0
- package/docs/{AGENT_INSTALL_PROMPT.md → guides/AGENT_INSTALL_PROMPT.md} +3 -3
- package/docs/guides/BEADS_GITHUB_SYNC.md +32 -0
- package/docs/{ENHANCED_ONBOARDING.md → guides/ENHANCED_ONBOARDING.md} +16 -12
- package/docs/guides/GREPTILE_SETUP.md +46 -0
- package/docs/guides/MANUAL_REVIEW_GUIDE.md +58 -0
- package/docs/guides/MIGRATION.md +56 -0
- package/docs/guides/SETUP.md +118 -0
- package/docs/guides/SUPPORT.md +185 -0
- package/docs/guides/WORKFLOW_TEMPLATES.md +74 -0
- package/docs/guides/memory-backends.md +183 -0
- package/docs/reference/ADAPTERS.md +128 -0
- package/docs/reference/AGENT_SKILL_PARITY.md +175 -0
- package/docs/reference/COMMANDS.md +205 -0
- package/docs/reference/DECISION_DRIFT_GUARDS.md +97 -0
- package/docs/{EXAMPLES.md → reference/EXAMPLES.md} +7 -5
- package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +135 -0
- package/docs/reference/HERMES_INTEGRATION.md +118 -0
- package/docs/reference/INSIGHTS_RECAP.md +63 -0
- package/docs/reference/INSTALL.md +164 -0
- package/docs/reference/KERNEL_TAXONOMY_VALIDATION.md +161 -0
- package/docs/reference/PROTECTED_PATH_MANIFEST.md +25 -0
- package/docs/reference/RELEASE.md +68 -0
- package/docs/reference/RESEARCH_TEMPLATE.md +292 -0
- package/docs/{ROADMAP.md → reference/ROADMAP.md} +12 -9
- package/docs/reference/SKILLS.md +35 -0
- package/docs/reference/STATUS_BOARD.md +80 -0
- package/docs/reference/TEMPLATES.md +106 -0
- package/docs/reference/TOOLCHAIN.md +658 -0
- package/docs/reference/VALIDATION.md +82 -0
- package/docs/reference/agent-permissions.md +169 -0
- package/docs/reference/beads-to-kernel-migration-ux.md +61 -0
- package/docs/reference/control-plane-guarantees.md +125 -0
- package/docs/reference/dependency-chain.md +331 -0
- package/docs/reference/forge-kernel-issue-command-contract.md +161 -0
- package/docs/reference/forge-kernel-schema.md +72 -0
- package/docs/reference/kernel-conflict-evaluators.md +27 -0
- package/docs/reference/patch-md-format.md +77 -0
- package/docs/reference/protected-state-surfaces.md +59 -0
- package/docs/reference/shepherd.md +115 -0
- package/docs/reference/superpowers-analysis.md +320 -0
- package/docs/reference/superpowers-integration-options.md +404 -0
- package/docs/reference/test-environment.md +519 -0
- package/docs/reference/upgrade-safety.md +59 -0
- package/lefthook.yml +18 -0
- package/lib/adapter-cli.js +307 -0
- package/lib/adapters/beads-issue-adapter.js +127 -0
- package/lib/adapters/beads-kernel-compat.js +1042 -0
- package/lib/adapters/greptile-review-adapter.js +141 -0
- package/lib/adapters/kernel-issue-adapter.js +101 -0
- package/lib/adapters/pr-state-adapter.js +484 -0
- package/lib/adoption-profiles.js +126 -0
- package/lib/agents/README.md +2 -6
- package/lib/agents/claude.plugin.json +3 -8
- package/lib/agents/codex.plugin.json +9 -1
- package/lib/agents/cursor.plugin.json +2 -6
- package/lib/agents/hermes.plugin.json +22 -0
- package/lib/agents-config.js +39 -1236
- package/lib/audit-evidence.js +282 -0
- package/lib/beads-setup.js +225 -28
- package/lib/beads-sync-scaffold.js +36 -107
- package/lib/codex-skills.js +51 -1
- package/lib/commands/_issue.js +744 -70
- package/lib/commands/_manifest.js +91 -0
- package/lib/commands/_registry.js +85 -34
- package/lib/commands/_resolve-command-opts.js +261 -0
- package/lib/commands/_serve-security.js +270 -0
- package/lib/commands/adapter.js +12 -0
- package/lib/commands/add.js +118 -0
- package/lib/commands/audit.js +70 -0
- package/lib/commands/blocked.js +5 -0
- package/lib/commands/board.js +64 -0
- package/lib/commands/claim.js +21 -2
- package/lib/commands/claims.js +7 -0
- package/lib/commands/clean.js +485 -75
- package/lib/commands/close.js +2 -2
- package/lib/commands/comment.js +5 -0
- package/lib/commands/control.js +148 -0
- package/lib/commands/create.js +2 -2
- package/lib/commands/dev.js +185 -7
- package/lib/commands/doc-gate.js +336 -0
- package/lib/commands/doctor.js +156 -0
- package/lib/commands/explain.js +15 -0
- package/lib/commands/export.js +237 -0
- package/lib/commands/gate.js +192 -0
- package/lib/commands/hooks.js +242 -0
- package/lib/commands/inbox.js +118 -0
- package/lib/commands/init.js +598 -0
- package/lib/commands/insights.js +79 -0
- package/lib/commands/issue.js +12 -1
- package/lib/commands/issues.js +66 -0
- package/lib/commands/lint.js +5 -0
- package/lib/commands/list.js +2 -2
- package/lib/commands/merge.js +312 -0
- package/lib/commands/migrate.js +523 -0
- package/lib/commands/new.js +12 -0
- package/lib/commands/options.js +241 -0
- package/lib/commands/orient.js +13 -0
- package/lib/commands/orphans.js +5 -0
- package/lib/commands/patch.js +67 -0
- package/lib/commands/plan.js +436 -24
- package/lib/commands/preflight.js +211 -0
- package/lib/commands/prime.js +13 -0
- package/lib/commands/push.js +69 -2
- package/lib/commands/ready.js +2 -2
- package/lib/commands/recall.js +116 -0
- package/lib/commands/recap.js +61 -0
- package/lib/commands/recommend.js +22 -2
- package/lib/commands/release.js +91 -0
- package/lib/commands/remember.js +74 -0
- package/lib/commands/role.js +99 -0
- package/lib/commands/serve.js +581 -0
- package/lib/commands/setup.js +851 -979
- package/lib/commands/shepherd.js +436 -0
- package/lib/commands/ship.js +23 -1
- package/lib/commands/show.js +2 -2
- package/lib/commands/stage.js +192 -0
- package/lib/commands/stale.js +5 -0
- package/lib/commands/status.js +329 -11
- package/lib/commands/sync.js +34 -46
- package/lib/commands/team.js +15 -2
- package/lib/commands/test.js +58 -7
- package/lib/commands/update.js +2 -2
- package/lib/commands/upgrade.js +47 -0
- package/lib/commands/validate.js +56 -25
- package/lib/commands/worktree.js +308 -128
- package/lib/config-writer.js +202 -0
- package/lib/control-plane.js +236 -0
- package/lib/core/runtime-graph.js +946 -0
- package/lib/dep-guard/keyword-ripple.js +184 -0
- package/lib/deprecated-sync-cleanup.js +362 -0
- package/lib/detect-agent.js +2 -28
- package/lib/detect-worktree.js +42 -17
- package/lib/doc-gate/declaration.js +177 -0
- package/lib/doc-gate/detect.js +289 -0
- package/lib/doc-gate/gate.js +375 -0
- package/lib/doc-gate/okf-config.js +128 -0
- package/lib/doc-gate/okf.js +429 -0
- package/lib/docs-command.js +1161 -6
- package/lib/forge-issues.js +697 -0
- package/lib/forge-lock.js +262 -0
- package/lib/gate-events.js +193 -0
- package/lib/global-flags.js +74 -0
- package/lib/greptile-match.js +7 -63
- package/lib/harness-capability-matrix.js +380 -0
- package/lib/hook-global-installer.js +347 -0
- package/lib/hook-renderer.js +451 -0
- package/lib/inbox.js +391 -0
- package/lib/insights.js +397 -0
- package/lib/issue-adapter.js +156 -0
- package/lib/issue-backend.js +145 -0
- package/lib/issue-render.js +220 -0
- package/lib/issue-sync/authority.js +100 -0
- package/lib/issue-sync/github-pull.js +184 -0
- package/lib/issue-sync/import-primitives.js +98 -0
- package/lib/issue-sync/legacy-link-bridge.js +436 -0
- package/lib/issue-sync/link-store.js +292 -0
- package/lib/issue-sync/project-github.js +123 -0
- package/lib/issue-sync/reconcile.js +195 -0
- package/lib/issue-sync/schema.js +126 -0
- package/lib/kernel/backing-issue.js +305 -0
- package/lib/kernel/broker.js +1218 -0
- package/lib/kernel/cli-broker-factory.js +130 -0
- package/lib/kernel/conflict-signal.js +82 -0
- package/lib/kernel/evaluators.js +195 -0
- package/lib/kernel/fs-class.js +495 -0
- package/lib/kernel/issue-command-contract.js +559 -0
- package/lib/kernel/issue-id-resolver.js +186 -0
- package/lib/kernel/lease-enforcer.js +158 -0
- package/lib/kernel/migrations.js +333 -0
- package/lib/kernel/planning-buckets-schema.js +109 -0
- package/lib/kernel/projection-jsonl-writer.js +450 -0
- package/lib/kernel/readiness-model.js +329 -0
- package/lib/kernel/schema.js +356 -0
- package/lib/kernel/sqlite-driver.js +2504 -0
- package/lib/kernel/taxonomy-validator.js +394 -0
- package/lib/lefthook-check.js +8 -4
- package/lib/lefthook-wiring.js +413 -0
- package/lib/mcp-config-renderer.js +288 -0
- package/lib/memory/graphiti-mcp.js +106 -0
- package/lib/memory/router.js +387 -0
- package/lib/memory/typed-api.js +102 -0
- package/lib/memory-digest.js +195 -0
- package/lib/merge-rules.js +395 -0
- package/lib/migrate-dry-run.js +466 -0
- package/lib/orientation.js +863 -0
- package/lib/package-manager-remediation.js +103 -0
- package/lib/package-root.js +381 -0
- package/lib/patch-intent.js +890 -0
- package/lib/plugin-catalog.js +3 -4
- package/lib/plugin-manager.js +0 -5
- package/lib/pr-bundle.js +186 -0
- package/lib/pr-monitor/differ.js +195 -0
- package/lib/pr-monitor/events.js +0 -0
- package/lib/pr-monitor/gather.js +124 -0
- package/lib/pr-monitor/journal.js +299 -0
- package/lib/pr-monitor/monitor.js +146 -0
- package/lib/pr-monitor/render-sticky.js +157 -0
- package/lib/pr-monitor/watch-lifecycle.js +95 -0
- package/lib/pr-monitor/watch.js +247 -0
- package/lib/pr-pull.js +1273 -0
- package/lib/pr-shepherd.js +494 -0
- package/lib/pr-state-validator.js +59 -0
- package/lib/preflight/gates.js +237 -0
- package/lib/preflight/runner.js +116 -0
- package/lib/project-discovery.js +0 -53
- package/lib/project-memory.js +166 -0
- package/lib/protected-path-manifest.js +281 -0
- package/lib/protected-state-surfaces.js +387 -0
- package/lib/release-readiness.js +2089 -0
- package/lib/reset.js +59 -45
- package/lib/review-adapter.js +68 -0
- package/lib/rules-sync.js +260 -0
- package/lib/runtime-health.js +332 -23
- package/lib/safety-config-renderer.js +268 -0
- package/lib/setup-action-log.js +1 -7
- package/lib/setup.js +27 -65
- package/lib/shell-utils.js +76 -6
- package/lib/skills-sync.js +330 -0
- package/lib/smart-status/conflicts.js +205 -0
- package/lib/smart-status/scoring.js +191 -0
- package/lib/status/beads-snapshot.js +145 -0
- package/lib/status/presenter.js +216 -0
- package/lib/status/snapshot.js +186 -0
- package/lib/sync-backend.js +202 -0
- package/lib/untrusted-content.js +52 -0
- package/lib/upgrade-safety.js +199 -0
- package/lib/workflow/enforce-stage.js +298 -47
- package/lib/workflow/stage-transition.js +115 -0
- package/lib/workflow/stages.js +30 -6
- package/lib/workflow/state-manager.js +159 -14
- package/lib/workflow/state.js +23 -1
- package/lib/workflow-profiles.js +17 -5
- package/package.json +46 -36
- package/rules/documentation.md +19 -0
- package/rules/kernel-tracking.md +26 -0
- package/rules/security.md +22 -0
- package/rules/tdd.md +20 -0
- package/rules/workflow.md +27 -0
- package/scripts/auto-backing-issue.js +47 -0
- package/scripts/beads-context.sh +165 -22
- package/scripts/beads-migrate-to-dolt.sh +7 -0
- package/scripts/beads-upgrade-smoke.sh +284 -0
- package/scripts/behavioral-judge.sh +115 -11
- package/scripts/benchmark.js +349 -63
- package/scripts/bootstrap-windows-tools.sh +78 -0
- package/scripts/branch-protection.js +2 -3
- package/scripts/check-agents.js +34 -137
- package/scripts/commitlint.js +3 -1
- package/scripts/conflict-detect.sh +3 -0
- package/scripts/dep-guard-analyze.js +52 -17
- package/scripts/dep-guard-keyword-ripple.js +29 -0
- package/scripts/dep-guard-render-review.js +86 -0
- package/scripts/dep-guard.sh +64 -232
- package/scripts/file-index.sh +3 -0
- package/scripts/forge-team/lib/claim.sh +34 -18
- package/scripts/forge-team/lib/dashboard.sh +61 -86
- package/scripts/forge-team/lib/epic.sh +99 -263
- package/scripts/forge-team/lib/hooks.sh +26 -28
- package/scripts/forge-team/lib/identity.sh +4 -4
- package/scripts/forge-team/lib/sync-github.sh +144 -47
- package/scripts/forge-team/lib/verify.sh +93 -83
- package/scripts/forge-team/lib/workload.sh +41 -65
- package/scripts/forge-team/tests/claim.test.sh +25 -19
- package/scripts/forge-team/tests/dashboard.test.sh +31 -46
- package/scripts/forge-team/tests/epic.test.sh +52 -71
- package/scripts/forge-team/tests/hooks.test.sh +38 -50
- package/scripts/forge-team/tests/identity.test.sh +3 -3
- package/scripts/forge-team/tests/integration.test.sh +44 -66
- package/scripts/forge-team/tests/sync-github.test.sh +183 -79
- package/scripts/forge-team/tests/verify.test.sh +37 -46
- package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
- package/scripts/forge-team/tests/workload.test.sh +32 -66
- package/scripts/gen-command-manifest.js +153 -0
- package/scripts/gen-embedded-assets.mjs +129 -0
- package/scripts/install.ps1 +139 -0
- package/scripts/install.sh +268 -0
- package/scripts/lib/beads-migrate-to-dolt.mjs +503 -0
- package/scripts/lib/release-asset.mjs +84 -0
- package/scripts/parity-check.mjs +145 -0
- package/scripts/parity-check.test.mjs +58 -0
- package/scripts/pin-agentic-workflow-images.js +112 -0
- package/scripts/pr-coordinator.sh +3 -0
- package/scripts/preflight-sonar.eslint.config.mjs +44 -0
- package/scripts/preflight.sh +108 -0
- package/scripts/protected-state-check.js +104 -0
- package/scripts/smart-status-score.js +31 -0
- package/scripts/smart-status-sessions.js +51 -0
- package/scripts/smart-status.sh +117 -369
- package/scripts/spikes/config-race-bench.js +111 -0
- package/scripts/spikes/harness-capability-matrix.js +13 -0
- package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
- package/scripts/spikes/protected-path-manifest.js +20 -0
- package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
- package/scripts/sync-agent-skills.js +62 -0
- package/scripts/sync-agentic-workflow.js +48 -0
- package/scripts/sync-utils.sh +3 -0
- package/scripts/test-ci-shard.js +251 -0
- package/scripts/test-dashboard.js +188 -52
- package/scripts/test-full-suite.js +186 -0
- package/scripts/test-profile.js +278 -0
- package/scripts/test.js +302 -28
- package/scripts/validate.js +143 -0
- package/scripts/validate.sh +18 -1
- package/skills/claim-safety/SKILL.md +102 -0
- package/skills/claim-safety/evals/evals.json +46 -0
- package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +46 -52
- package/skills/dev/evals/evals.json +50 -0
- package/skills/hermes-forge/SKILL.md +185 -0
- package/skills/hermes-forge/evals/evals.json +46 -0
- package/skills/issue-basics/SKILL.md +111 -0
- package/skills/issue-basics/evals/evals.json +46 -0
- package/skills/kernel/SKILL.md +166 -0
- package/skills/kernel/evals/evals.json +50 -0
- package/skills/memory/SKILL.md +102 -0
- package/skills/parallel-deep-research/SKILL.md +14 -11
- package/skills/parallel-deep-research/evals/evals.json +11 -27
- package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +134 -159
- package/skills/plan/evals/evals.json +42 -0
- package/skills/research/SKILL.md +195 -0
- package/skills/research/evals/evals.json +42 -0
- package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
- package/skills/review/evals/evals.json +42 -0
- package/skills/rollback/SKILL.md +110 -0
- package/skills/rollback/evals/evals.json +46 -0
- package/skills/rollback/references/methods.md +204 -0
- package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
- package/skills/shepherd/SKILL.md +66 -0
- package/skills/shepherd/evals/evals.json +42 -0
- package/skills/ship/SKILL.md +251 -0
- package/skills/ship/evals/evals.json +42 -0
- package/skills/smith/SKILL.md +142 -0
- package/skills/smith/evals/evals.json +46 -0
- package/skills/smith/references/autonomy-and-gates.md +94 -0
- package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
- package/skills/sonarcloud/evals/evals.json +46 -0
- package/skills/sonarcloud-analysis/SKILL.md +18 -13
- package/skills/sonarcloud-analysis/evals/evals.json +11 -15
- package/skills/status/SKILL.md +102 -0
- package/skills/status/evals/evals.json +50 -0
- package/skills/triage-ready/SKILL.md +121 -0
- package/skills/triage-ready/evals/evals.json +42 -0
- package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
- package/skills/validate/evals/evals.json +42 -0
- package/skills/verify/SKILL.md +299 -0
- package/skills/verify/evals/evals.json +50 -0
- package/.claude/commands/dev.md +0 -345
- package/.claude/commands/plan.md +0 -566
- package/.claude/commands/premerge.md +0 -186
- package/.claude/commands/research.md +0 -42
- package/.claude/commands/review.md +0 -451
- package/.claude/commands/rollback.md +0 -721
- package/.claude/commands/ship.md +0 -213
- package/.claude/commands/sonarcloud.md +0 -152
- package/.claude/commands/status.md +0 -90
- package/.claude/commands/validate.md +0 -288
- package/.claude/commands/verify.md +0 -269
- package/.claude/rules/workflow.md +0 -121
- package/.cline/workflows/dev.md +0 -342
- package/.cline/workflows/plan.md +0 -563
- package/.cline/workflows/premerge.md +0 -183
- package/.cline/workflows/research.md +0 -39
- package/.cline/workflows/review.md +0 -448
- package/.cline/workflows/rollback.md +0 -718
- package/.cline/workflows/ship.md +0 -210
- package/.cline/workflows/sonarcloud.md +0 -146
- package/.cline/workflows/status.md +0 -87
- package/.cline/workflows/validate.md +0 -285
- package/.cline/workflows/verify.md +0 -266
- package/.codex/config.toml +0 -11
- package/.codex/skills/dev/SKILL.md +0 -345
- package/.codex/skills/plan/SKILL.md +0 -566
- package/.codex/skills/premerge/SKILL.md +0 -186
- package/.codex/skills/research/SKILL.md +0 -42
- package/.codex/skills/review/SKILL.md +0 -451
- package/.codex/skills/rollback/SKILL.md +0 -721
- package/.codex/skills/ship/SKILL.md +0 -213
- package/.codex/skills/sonarcloud/SKILL.md +0 -149
- package/.codex/skills/status/SKILL.md +0 -90
- package/.codex/skills/validate/SKILL.md +0 -288
- package/.codex/skills/verify/SKILL.md +0 -269
- package/.cursor/commands/dev.md +0 -342
- package/.cursor/commands/plan.md +0 -563
- package/.cursor/commands/premerge.md +0 -183
- package/.cursor/commands/research.md +0 -39
- package/.cursor/commands/review.md +0 -448
- package/.cursor/commands/ship.md +0 -210
- package/.cursor/commands/sonarcloud.md +0 -146
- package/.cursor/commands/status.md +0 -87
- package/.cursor/commands/validate.md +0 -285
- package/.cursor/commands/verify.md +0 -266
- package/.cursorrules +0 -149
- package/.github/prompts/premerge.prompt.md +0 -188
- package/.github/prompts/research.prompt.md +0 -44
- package/.github/prompts/rollback.prompt.md +0 -723
- package/.github/prompts/ship.prompt.md +0 -215
- package/.github/prompts/status.prompt.md +0 -92
- package/.github/prompts/verify.prompt.md +0 -271
- package/.github/workflows/beads-to-github.yml +0 -56
- package/.github/workflows/github-to-beads.yml +0 -97
- package/.kilocode/workflows/dev.md +0 -346
- package/.kilocode/workflows/plan.md +0 -567
- package/.kilocode/workflows/premerge.md +0 -187
- package/.kilocode/workflows/research.md +0 -43
- package/.kilocode/workflows/review.md +0 -452
- package/.kilocode/workflows/rollback.md +0 -722
- package/.kilocode/workflows/ship.md +0 -214
- package/.kilocode/workflows/sonarcloud.md +0 -150
- package/.kilocode/workflows/status.md +0 -91
- package/.kilocode/workflows/validate.md +0 -289
- package/.kilocode/workflows/verify.md +0 -270
- package/.opencode/commands/dev.md +0 -345
- package/.opencode/commands/plan.md +0 -566
- package/.opencode/commands/premerge.md +0 -186
- package/.opencode/commands/research.md +0 -42
- package/.opencode/commands/review.md +0 -451
- package/.opencode/commands/rollback.md +0 -721
- package/.opencode/commands/ship.md +0 -213
- package/.opencode/commands/sonarcloud.md +0 -149
- package/.opencode/commands/status.md +0 -90
- package/.opencode/commands/validate.md +0 -288
- package/.opencode/commands/verify.md +0 -269
- package/.roo/commands/dev.md +0 -346
- package/.roo/commands/plan.md +0 -567
- package/.roo/commands/premerge.md +0 -187
- package/.roo/commands/research.md +0 -43
- package/.roo/commands/review.md +0 -452
- package/.roo/commands/rollback.md +0 -722
- package/.roo/commands/ship.md +0 -214
- package/.roo/commands/sonarcloud.md +0 -150
- package/.roo/commands/status.md +0 -91
- package/.roo/commands/validate.md +0 -289
- package/.roo/commands/verify.md +0 -270
- package/docs/BEADS_GITHUB_SYNC.md +0 -255
- package/docs/GREPTILE_SETUP.md +0 -400
- package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
- package/docs/SETUP.md +0 -663
- package/docs/VALIDATION.md +0 -363
- package/lib/agents/cline.plugin.json +0 -29
- package/lib/agents/copilot.plugin.json +0 -24
- package/lib/agents/kilocode.plugin.json +0 -22
- package/lib/agents/opencode.plugin.json +0 -23
- package/lib/agents/roo.plugin.json +0 -30
- package/lib/beads-health-check.js +0 -143
- package/lib/commands/commands-reset.js +0 -147
- package/opencode.json +0 -67
- package/scripts/beads-context.test.js +0 -567
- package/scripts/github-beads-sync/comment.mjs +0 -64
- package/scripts/github-beads-sync/config.mjs +0 -148
- package/scripts/github-beads-sync/github-api.mjs +0 -131
- package/scripts/github-beads-sync/index.mjs +0 -332
- package/scripts/github-beads-sync/label-mapper.mjs +0 -54
- package/scripts/github-beads-sync/mapping.mjs +0 -78
- package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
- package/scripts/github-beads-sync/reverse-sync.mjs +0 -138
- package/scripts/github-beads-sync/run-bd.mjs +0 -161
- package/scripts/github-beads-sync/sanitize.mjs +0 -121
- package/scripts/github-beads-sync.config.json +0 -26
- package/scripts/sync-commands.js +0 -600
package/lib/reset.js
CHANGED
|
@@ -1,29 +1,33 @@
|
|
|
1
1
|
const fs = require('node:fs');
|
|
2
2
|
const path = require('node:path');
|
|
3
|
+
const { listCanonicalSkills } = require('./skills-sync');
|
|
4
|
+
const { getPackageRoot } = require('./package-root');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Package root that ships the canonical `skills/` source. Used to determine
|
|
8
|
+
* which skill directories Forge itself installed (so user/third-party skills
|
|
9
|
+
* sharing a skills dir are not removed during reset).
|
|
10
|
+
*/
|
|
11
|
+
const FORGE_PACKAGE_ROOT = path.resolve(__dirname, '..');
|
|
3
12
|
|
|
4
13
|
/**
|
|
5
14
|
* Known forge-created file patterns.
|
|
6
15
|
* Used to distinguish forge files from user-created files.
|
|
7
16
|
*/
|
|
8
|
-
const
|
|
9
|
-
'.
|
|
10
|
-
'.claude/
|
|
11
|
-
'.
|
|
12
|
-
'.
|
|
13
|
-
'.claude/commands/review.md',
|
|
14
|
-
'.claude/commands/premerge.md',
|
|
15
|
-
'.claude/commands/verify.md',
|
|
16
|
-
'.claude/commands/status.md',
|
|
17
|
-
'.claude/commands/preflight.md',
|
|
17
|
+
const FORGE_SKILL_DIRS = [
|
|
18
|
+
'.agents/skills',
|
|
19
|
+
'.claude/skills',
|
|
20
|
+
'.cursor/skills',
|
|
21
|
+
'.codex/skills',
|
|
18
22
|
];
|
|
19
23
|
|
|
20
24
|
const FORGE_RULES = [
|
|
21
25
|
'.claude/rules/workflow.md',
|
|
22
|
-
'.claude/rules/
|
|
26
|
+
'.claude/rules/review-process.md',
|
|
23
27
|
];
|
|
24
28
|
|
|
25
29
|
const FORGE_SCRIPTS = [
|
|
26
|
-
'.claude/scripts/
|
|
30
|
+
'.claude/scripts/review-resolve.sh',
|
|
27
31
|
'.claude/scripts/validate.sh',
|
|
28
32
|
];
|
|
29
33
|
|
|
@@ -39,11 +43,7 @@ const FORGE_DOCS = [
|
|
|
39
43
|
|
|
40
44
|
const FORGE_AGENT_DIRS = [
|
|
41
45
|
'.cursor',
|
|
42
|
-
'.cline',
|
|
43
|
-
'.roo',
|
|
44
46
|
'.codex',
|
|
45
|
-
'.kilocode',
|
|
46
|
-
'.opencode',
|
|
47
47
|
];
|
|
48
48
|
|
|
49
49
|
const FORGE_SYNC_SCRIPTS_DIR = 'scripts/github-beads-sync';
|
|
@@ -68,7 +68,7 @@ const FORGE_SYNC_SCRIPTS_DIR = 'scripts/github-beads-sync';
|
|
|
68
68
|
function getForgeFiles(projectRoot) {
|
|
69
69
|
const result = {
|
|
70
70
|
config: [],
|
|
71
|
-
|
|
71
|
+
skillDirs: [],
|
|
72
72
|
rules: [],
|
|
73
73
|
scripts: [],
|
|
74
74
|
docs: [],
|
|
@@ -82,30 +82,11 @@ function getForgeFiles(projectRoot) {
|
|
|
82
82
|
result.config.push('.forge');
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
// Known forge
|
|
86
|
-
for (const
|
|
87
|
-
if (fs.existsSync(path.join(projectRoot,
|
|
88
|
-
result.
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Dynamic discovery: scan the package's .claude/commands/ directory to find
|
|
93
|
-
// any commands that setup may have copied but aren't in the static list.
|
|
94
|
-
// Only files that exist in both the package source AND the project are included.
|
|
95
|
-
const packageCommandsDir = path.join(__dirname, '..', '.claude', 'commands');
|
|
96
|
-
try {
|
|
97
|
-
const packageEntries = fs.readdirSync(packageCommandsDir);
|
|
98
|
-
for (const entry of packageEntries) {
|
|
99
|
-
if (entry.endsWith('.md')) {
|
|
100
|
-
const relPath = `.claude/commands/${entry}`;
|
|
101
|
-
if (!result.commands.includes(relPath) &&
|
|
102
|
-
fs.existsSync(path.join(projectRoot, relPath))) {
|
|
103
|
-
result.commands.push(relPath);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
85
|
+
// Known forge skill directories
|
|
86
|
+
for (const dir of FORGE_SKILL_DIRS) {
|
|
87
|
+
if (fs.existsSync(path.join(projectRoot, dir))) {
|
|
88
|
+
result.skillDirs.push(dir);
|
|
106
89
|
}
|
|
107
|
-
} catch (_error) {
|
|
108
|
-
// Package commands directory read failed, skip
|
|
109
90
|
}
|
|
110
91
|
|
|
111
92
|
// Known forge rules
|
|
@@ -184,10 +165,10 @@ function resetSoft(projectRoot, { force = false } = {}) {
|
|
|
184
165
|
|
|
185
166
|
// Record what was preserved
|
|
186
167
|
const inventory = getForgeFiles(projectRoot);
|
|
187
|
-
for (const f of [...inventory.
|
|
168
|
+
for (const f of [...inventory.rules, ...inventory.scripts]) {
|
|
188
169
|
preserved.push(f);
|
|
189
170
|
}
|
|
190
|
-
for (const dir of inventory.agentDirs) {
|
|
171
|
+
for (const dir of [...inventory.skillDirs, ...inventory.agentDirs]) {
|
|
191
172
|
preserved.push(dir);
|
|
192
173
|
}
|
|
193
174
|
|
|
@@ -203,11 +184,16 @@ function resetSoft(projectRoot, { force = false } = {}) {
|
|
|
203
184
|
* @param {{ force?: boolean }} options
|
|
204
185
|
* @returns {{ removed: string[], preserved: string[] }}
|
|
205
186
|
*/
|
|
206
|
-
function resetHard(projectRoot, { force = false } = {}) {
|
|
187
|
+
function resetHard(projectRoot, { force = false, sourceRoot } = {}) {
|
|
207
188
|
if (!force) {
|
|
208
189
|
throw new Error('Hard reset requires --force flag. This will remove ALL forge files.');
|
|
209
190
|
}
|
|
210
191
|
|
|
192
|
+
// Resolve the package/asset root AFTER the force guard so an unforced reset
|
|
193
|
+
// never triggers extraction work or fails with an asset-resolution error
|
|
194
|
+
// instead of the intended safety message.
|
|
195
|
+
const resolvedSourceRoot = sourceRoot ?? getPackageRoot(FORGE_PACKAGE_ROOT);
|
|
196
|
+
|
|
211
197
|
const removed = [];
|
|
212
198
|
const preserved = [];
|
|
213
199
|
const inventory = getForgeFiles(projectRoot);
|
|
@@ -223,7 +209,6 @@ function resetHard(projectRoot, { force = false } = {}) {
|
|
|
223
209
|
|
|
224
210
|
// Remove individual forge files
|
|
225
211
|
const allFiles = [
|
|
226
|
-
...inventory.commands,
|
|
227
212
|
...inventory.rules,
|
|
228
213
|
...inventory.scripts,
|
|
229
214
|
...inventory.docs,
|
|
@@ -260,6 +245,35 @@ function resetHard(projectRoot, { force = false } = {}) {
|
|
|
260
245
|
}
|
|
261
246
|
}
|
|
262
247
|
|
|
248
|
+
// Remove Forge-managed skill directories ONLY.
|
|
249
|
+
// Shared skills dirs (e.g. .claude/skills) can also hold user/third-party
|
|
250
|
+
// skills. Scope removal to the canonical Forge skill set so user work is
|
|
251
|
+
// preserved; the surrounding dir is removed only if nothing else remains.
|
|
252
|
+
const forgeSkillNames = new Set(
|
|
253
|
+
listCanonicalSkills(resolvedSourceRoot).map((s) => s.name)
|
|
254
|
+
);
|
|
255
|
+
for (const dir of inventory.skillDirs) {
|
|
256
|
+
const skillsRoot = path.join(projectRoot, dir);
|
|
257
|
+
if (!fs.existsSync(skillsRoot)) continue;
|
|
258
|
+
|
|
259
|
+
for (const entry of fs.readdirSync(skillsRoot, { withFileTypes: true })) {
|
|
260
|
+
if (!entry.isDirectory()) continue;
|
|
261
|
+
if (!forgeSkillNames.has(entry.name)) continue;
|
|
262
|
+
fs.rmSync(path.join(skillsRoot, entry.name), { recursive: true, force: true });
|
|
263
|
+
removed.push([dir, entry.name].join('/'));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Drop the now-empty skills dir, but keep it if user/third-party skills remain.
|
|
267
|
+
try {
|
|
268
|
+
if (fs.readdirSync(skillsRoot).length === 0) {
|
|
269
|
+
fs.rmSync(skillsRoot, { recursive: true, force: true });
|
|
270
|
+
removed.push(dir);
|
|
271
|
+
}
|
|
272
|
+
} catch (_error) {
|
|
273
|
+
// Directory read failed, skip
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
263
277
|
// Remove agent directories
|
|
264
278
|
for (const dir of inventory.agentDirs) {
|
|
265
279
|
const fullPath = path.join(projectRoot, dir);
|
|
@@ -300,7 +314,7 @@ module.exports = {
|
|
|
300
314
|
resetSoft,
|
|
301
315
|
resetHard,
|
|
302
316
|
reinstall,
|
|
303
|
-
|
|
317
|
+
FORGE_SKILL_DIRS,
|
|
304
318
|
FORGE_RULES,
|
|
305
319
|
FORGE_SCRIPTS,
|
|
306
320
|
FORGE_DOCS,
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const REQUIRED_REVIEW_ADAPTER_METHODS = [
|
|
4
|
+
'fetchThreads',
|
|
5
|
+
'parse',
|
|
6
|
+
'reply',
|
|
7
|
+
'resolve',
|
|
8
|
+
'score',
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
class ReviewAdapter {
|
|
12
|
+
constructor(options = {}) {
|
|
13
|
+
this.id = options.id || 'review-adapter';
|
|
14
|
+
this.kind = options.kind || 'review';
|
|
15
|
+
this.name = options.name || this.id;
|
|
16
|
+
this.version = options.version || '0.1.0';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async fetchThreads() {
|
|
20
|
+
throw new Error(`${this.id}.fetchThreads is not implemented`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
parse() {
|
|
24
|
+
throw new Error(`${this.id}.parse is not implemented`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async reply() {
|
|
28
|
+
throw new Error(`${this.id}.reply is not implemented`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async resolve() {
|
|
32
|
+
throw new Error(`${this.id}.resolve is not implemented`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
score() {
|
|
36
|
+
throw new Error(`${this.id}.score is not implemented`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function validateReviewAdapter(adapter) {
|
|
41
|
+
const errors = [];
|
|
42
|
+
|
|
43
|
+
if (!adapter || typeof adapter !== 'object') {
|
|
44
|
+
return { valid: false, errors: ['adapter must be an object'] };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!adapter.id || typeof adapter.id !== 'string') {
|
|
48
|
+
errors.push('id must be a non-empty string');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (adapter.kind !== 'review') {
|
|
52
|
+
errors.push('kind must be "review"');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
for (const method of REQUIRED_REVIEW_ADAPTER_METHODS) {
|
|
56
|
+
if (typeof adapter[method] !== 'function') {
|
|
57
|
+
errors.push(`${method} must be a function`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return { valid: errors.length === 0, errors };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = {
|
|
65
|
+
ReviewAdapter,
|
|
66
|
+
REQUIRED_REVIEW_ADAPTER_METHODS,
|
|
67
|
+
validateReviewAdapter,
|
|
68
|
+
};
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Canonical rule source → Cursor native rule renderer.
|
|
5
|
+
*
|
|
6
|
+
* Forge policy rules (workflow, TDD, security, documentation) have ONE canonical
|
|
7
|
+
* source in `rules/<name>.md`. Each canonical file is a THIN POINTER: a short
|
|
8
|
+
* frontmatter (`description`, `globs`, `alwaysApply`) plus a few lines that point
|
|
9
|
+
* at `AGENTS.md` and the relevant stage skill. The full policy bodies are NOT
|
|
10
|
+
* duplicated here — they live in `AGENTS.md` and `skills/<stage>/SKILL.md`.
|
|
11
|
+
*
|
|
12
|
+
* Only ONE harness has a first-class native rule surface, so only one renderer
|
|
13
|
+
* exists:
|
|
14
|
+
*
|
|
15
|
+
* - Cursor : `.cursor/rules/<name>.mdc` (MDC frontmatter + pointer body)
|
|
16
|
+
*
|
|
17
|
+
* Claude, Codex, and Hermes receive this same policy through their INSTRUCTION
|
|
18
|
+
* projection (`CLAUDE.md` → `AGENTS.md`, `AGENTS.md`, `forge orient`) — adding
|
|
19
|
+
* always-on `.claude/rules/*.md` files would triple-deliver the same policy into
|
|
20
|
+
* every session as pure token bloat. See `lib/harness-capability-matrix.js`
|
|
21
|
+
* (the `rules` row) and `docs/reference/AGENT_SKILL_PARITY.md`.
|
|
22
|
+
*
|
|
23
|
+
* The generated `.cursor/rules/*.mdc` files are NOT committed — they are produced
|
|
24
|
+
* at `forge setup` from the canonical source (like the gitignored, setup-populated
|
|
25
|
+
* skill dirs). The structural drift gate regenerates them into a temp dir and
|
|
26
|
+
* asserts the render is deterministic and well-formed, rather than committing a
|
|
27
|
+
* mirror. Never hand-edit a generated `.mdc` — edit `rules/<name>.md`.
|
|
28
|
+
*
|
|
29
|
+
* Dependency-free (no YAML/markdown deps) so it runs under `bun test` and the
|
|
30
|
+
* release gates without pulling `@forge/*` workspaces.
|
|
31
|
+
*
|
|
32
|
+
* @module rules-sync
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
const fs = require('node:fs');
|
|
36
|
+
const path = require('node:path');
|
|
37
|
+
|
|
38
|
+
/** Canonical rule source directory (relative to a repo/package root). */
|
|
39
|
+
const CANONICAL_RULES_DIR = 'rules';
|
|
40
|
+
|
|
41
|
+
/** Cursor native rule surface (relative to a project root). */
|
|
42
|
+
const CURSOR_RULES_DIR = '.cursor/rules';
|
|
43
|
+
|
|
44
|
+
/** Canonical rule name → Cursor `.mdc` filename. */
|
|
45
|
+
const CURSOR_RULE_FILES = {
|
|
46
|
+
workflow: 'forge-workflow.mdc',
|
|
47
|
+
tdd: 'tdd-enforcement.mdc',
|
|
48
|
+
security: 'security-scanning.mdc',
|
|
49
|
+
documentation: 'documentation.mdc',
|
|
50
|
+
'kernel-tracking': 'kernel-tracking.mdc',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const CANONICAL_RULE_NAMES = Object.keys(CURSOR_RULE_FILES);
|
|
54
|
+
|
|
55
|
+
const GENERATED_NOTE =
|
|
56
|
+
'Generated by Forge from the canonical rules/ source. Do not edit — edit ' +
|
|
57
|
+
'rules/<name>.md and re-run `forge setup` (or the rules renderer).';
|
|
58
|
+
|
|
59
|
+
// Stable substring stamped into every generated rule file. Used to distinguish a
|
|
60
|
+
// Forge-managed generated `.mdc` (safe to refresh on upgrade) from a user-authored
|
|
61
|
+
// rule (must never be clobbered).
|
|
62
|
+
const GENERATED_RULE_MARKER = 'Generated by Forge from the canonical rules/ source';
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Parse the minimal YAML frontmatter Forge authors in canonical rule files.
|
|
66
|
+
* Only flat `key: value` pairs are supported (values kept as raw strings so
|
|
67
|
+
* arrays/booleans round-trip verbatim into the rendered output).
|
|
68
|
+
*
|
|
69
|
+
* @param {string} raw
|
|
70
|
+
* @returns {{ meta: Record<string,string>, body: string }}
|
|
71
|
+
*/
|
|
72
|
+
function parseFrontmatter(raw) {
|
|
73
|
+
const norm = String(raw).replace(/\r\n/g, '\n');
|
|
74
|
+
if (!norm.startsWith('---\n')) {
|
|
75
|
+
return { meta: {}, body: norm };
|
|
76
|
+
}
|
|
77
|
+
const end = norm.indexOf('\n---\n', 4);
|
|
78
|
+
if (end === -1) {
|
|
79
|
+
return { meta: {}, body: norm };
|
|
80
|
+
}
|
|
81
|
+
const block = norm.slice(4, end);
|
|
82
|
+
const body = norm.slice(end + 5);
|
|
83
|
+
const meta = {};
|
|
84
|
+
for (const line of block.split('\n')) {
|
|
85
|
+
const m = line.match(/^([A-Za-z0-9_]+):\s*(.*)$/);
|
|
86
|
+
if (m) meta[m[1]] = m[2].trim();
|
|
87
|
+
}
|
|
88
|
+
return { meta, body };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Body with leading blank lines removed and a single trailing newline. */
|
|
92
|
+
function normalizeBody(body) {
|
|
93
|
+
return body.replace(/^\n+/, '').replace(/\s+$/, '') + '\n';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Read one canonical rule from disk into a render-ready descriptor.
|
|
98
|
+
*
|
|
99
|
+
* @param {string} sourceRoot - Repo/package root containing `rules/`.
|
|
100
|
+
* @param {string} name - Canonical rule name (stem of `rules/<name>.md`).
|
|
101
|
+
* @returns {{ name, description, globs, alwaysApply, body }}
|
|
102
|
+
*/
|
|
103
|
+
function readCanonicalRule(sourceRoot, name) {
|
|
104
|
+
const filePath = path.join(sourceRoot, CANONICAL_RULES_DIR, `${name}.md`);
|
|
105
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
106
|
+
const { meta, body } = parseFrontmatter(raw);
|
|
107
|
+
return {
|
|
108
|
+
name,
|
|
109
|
+
description: meta.description || '""',
|
|
110
|
+
globs: meta.globs || '[]',
|
|
111
|
+
alwaysApply: meta.alwaysApply || 'false',
|
|
112
|
+
body: normalizeBody(body),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* List all canonical rules (in stable CURSOR_RULE_FILES order).
|
|
118
|
+
*
|
|
119
|
+
* @param {string} sourceRoot - Repo/package root containing `rules/`.
|
|
120
|
+
* @param {{ only?: Iterable<string> }} [options]
|
|
121
|
+
* @returns {Array<ReturnType<typeof readCanonicalRule>>}
|
|
122
|
+
*/
|
|
123
|
+
function listCanonicalRules(sourceRoot, options = {}) {
|
|
124
|
+
const only = options.only ? new Set(options.only) : null;
|
|
125
|
+
return CANONICAL_RULE_NAMES
|
|
126
|
+
.filter((name) => !only || only.has(name))
|
|
127
|
+
.map((name) => readCanonicalRule(sourceRoot, name));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Render the Cursor native rule (`.mdc` — MDC frontmatter + pointer body). */
|
|
131
|
+
function renderCursorRule(rule) {
|
|
132
|
+
const frontmatter = [
|
|
133
|
+
'---',
|
|
134
|
+
`description: ${rule.description}`,
|
|
135
|
+
`globs: ${rule.globs}`,
|
|
136
|
+
`alwaysApply: ${rule.alwaysApply}`,
|
|
137
|
+
'---',
|
|
138
|
+
'',
|
|
139
|
+
`<!-- ${GENERATED_NOTE} -->`,
|
|
140
|
+
'',
|
|
141
|
+
'',
|
|
142
|
+
].join('\n');
|
|
143
|
+
return frontmatter + rule.body;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Render every canonical rule to Cursor `.mdc` content, keyed by output filename.
|
|
148
|
+
* Pure with respect to the target filesystem (only reads canonical `rules/`),
|
|
149
|
+
* so callers can diff two renders to prove determinism.
|
|
150
|
+
*
|
|
151
|
+
* @param {string} sourceRoot - Root containing canonical `rules/`.
|
|
152
|
+
* @param {{ only?: Iterable<string> }} [options]
|
|
153
|
+
* @returns {Record<string, string>} filename → rendered content
|
|
154
|
+
*/
|
|
155
|
+
function renderCursorRuleMap(sourceRoot, options = {}) {
|
|
156
|
+
const map = {};
|
|
157
|
+
for (const rule of listCanonicalRules(sourceRoot, options)) {
|
|
158
|
+
map[CURSOR_RULE_FILES[rule.name]] = renderCursorRule(rule);
|
|
159
|
+
}
|
|
160
|
+
return map;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Render canonical rules into a project's Cursor rule directory.
|
|
165
|
+
*
|
|
166
|
+
* @param {object} params
|
|
167
|
+
* @param {string} params.sourceRoot - Root containing canonical `rules/`.
|
|
168
|
+
* @param {string} params.targetRoot - Project root to write native rules under.
|
|
169
|
+
* @param {'cursor'} [params.harness='cursor'] - Only Cursor has a native rule surface.
|
|
170
|
+
* @param {boolean} [params.overwrite=false] - Overwrite existing files.
|
|
171
|
+
* @param {Iterable<string>} [params.only] - Restrict to these canonical names.
|
|
172
|
+
* @returns {{ written: string[], skipped: string[] }}
|
|
173
|
+
*/
|
|
174
|
+
function renderRulesForHarness({ sourceRoot, targetRoot, harness = 'cursor', overwrite = false, only }) {
|
|
175
|
+
if (harness !== 'cursor') {
|
|
176
|
+
throw new Error(
|
|
177
|
+
`No native rule surface for harness "${harness}"; Claude/Codex/Hermes ` +
|
|
178
|
+
'receive rule policy through their AGENTS.md instruction projection.',
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
const outDir = path.join(targetRoot, CURSOR_RULES_DIR);
|
|
182
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
183
|
+
|
|
184
|
+
const map = renderCursorRuleMap(sourceRoot, { only });
|
|
185
|
+
const written = [];
|
|
186
|
+
const skipped = [];
|
|
187
|
+
for (const [file, content] of Object.entries(map)) {
|
|
188
|
+
const outPath = path.join(outDir, file);
|
|
189
|
+
if (!overwrite && fs.existsSync(outPath)) {
|
|
190
|
+
// Refresh Forge-managed generated files even without --overwrite so an
|
|
191
|
+
// UPGRADE (`forge setup` on an existing project) picks up canonical changes
|
|
192
|
+
// and a stale drifted rule cannot survive forever. A user-authored rule
|
|
193
|
+
// (no generated marker) is respected and left untouched.
|
|
194
|
+
const existing = fs.readFileSync(outPath, 'utf-8');
|
|
195
|
+
if (!existing.includes(GENERATED_RULE_MARKER)) {
|
|
196
|
+
skipped.push(file);
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
fs.writeFileSync(outPath, content, 'utf-8');
|
|
201
|
+
written.push(file);
|
|
202
|
+
}
|
|
203
|
+
return { written, skipped };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Structural drift gate for rules: regenerate into a temp dir and assert the
|
|
208
|
+
* render is deterministic and well-formed. No committed mirror is compared —
|
|
209
|
+
* the canonical `rules/` source is the only tracked artifact, and Cursor rules
|
|
210
|
+
* are setup-populated (like the gitignored skill dirs).
|
|
211
|
+
*
|
|
212
|
+
* @param {object} params
|
|
213
|
+
* @param {string} params.sourceRoot - Root containing canonical `rules/`.
|
|
214
|
+
* @returns {{ ok: boolean, issues: object[], files: string[] }}
|
|
215
|
+
*/
|
|
216
|
+
function checkRulesRenderDeterminism({ sourceRoot }) {
|
|
217
|
+
const issues = [];
|
|
218
|
+
const first = renderCursorRuleMap(sourceRoot);
|
|
219
|
+
const second = renderCursorRuleMap(sourceRoot);
|
|
220
|
+
const files = Object.keys(first);
|
|
221
|
+
|
|
222
|
+
for (const file of files) {
|
|
223
|
+
if (first[file] !== second[file]) {
|
|
224
|
+
issues.push({ file, status: 'non-deterministic' });
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
const content = first[file];
|
|
228
|
+
if (!content.startsWith('---\n')) {
|
|
229
|
+
issues.push({ file, status: 'missing-frontmatter' });
|
|
230
|
+
}
|
|
231
|
+
if (!content.includes('description:')) {
|
|
232
|
+
issues.push({ file, status: 'missing-description' });
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Every canonical rule must map to exactly one Cursor output file.
|
|
237
|
+
for (const name of CANONICAL_RULE_NAMES) {
|
|
238
|
+
if (!files.includes(CURSOR_RULE_FILES[name])) {
|
|
239
|
+
issues.push({ file: CURSOR_RULE_FILES[name], status: 'unrendered-canonical' });
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return { ok: issues.length === 0, issues, files };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
module.exports = {
|
|
247
|
+
CANONICAL_RULES_DIR,
|
|
248
|
+
CURSOR_RULES_DIR,
|
|
249
|
+
CURSOR_RULE_FILES,
|
|
250
|
+
CANONICAL_RULE_NAMES,
|
|
251
|
+
GENERATED_NOTE,
|
|
252
|
+
GENERATED_RULE_MARKER,
|
|
253
|
+
parseFrontmatter,
|
|
254
|
+
listCanonicalRules,
|
|
255
|
+
readCanonicalRule,
|
|
256
|
+
renderCursorRule,
|
|
257
|
+
renderCursorRuleMap,
|
|
258
|
+
renderRulesForHarness,
|
|
259
|
+
checkRulesRenderDeterminism,
|
|
260
|
+
};
|