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
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
const fs = require('node:fs');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
const readline = require('node:readline');
|
|
4
|
+
const YAML = require('yaml');
|
|
5
|
+
|
|
6
|
+
const {
|
|
7
|
+
ADOPTION_PROFILE_NAMES,
|
|
8
|
+
buildAdoptionConfig,
|
|
9
|
+
renderAdoptionConfigYaml,
|
|
10
|
+
} = require('../adoption-profiles');
|
|
11
|
+
|
|
12
|
+
const PROFILE_REQUIRED_ERROR = '--profile requires a non-empty value: minimal, standard, or full.';
|
|
13
|
+
const CLASSIFICATION_REQUIRED_ERROR = '--classification requires a non-empty value: critical, standard, or refactor.';
|
|
14
|
+
const PROFILE_SHORTCUTS = Object.freeze({
|
|
15
|
+
'--minimal': 'minimal',
|
|
16
|
+
'--standard': 'standard',
|
|
17
|
+
'--full': 'full',
|
|
18
|
+
});
|
|
19
|
+
const CLASSIFICATIONS = Object.freeze(['critical', 'standard', 'refactor']);
|
|
20
|
+
const DETECTABLE_HARNESS_TARGETS = Object.freeze(['claude', 'cursor', 'codex']);
|
|
21
|
+
const HARNESS_TARGETS = Object.freeze(['claude', 'cursor', 'codex']);
|
|
22
|
+
const HARNESS_REQUIRED_ERROR = `--harness requires a non-empty value: ${HARNESS_TARGETS.join(', ')}.`;
|
|
23
|
+
|
|
24
|
+
/** Return the command help text shown by the registry. */
|
|
25
|
+
function usage() {
|
|
26
|
+
return [
|
|
27
|
+
'Usage: forge init [--profile minimal|standard|full] [--classification critical|standard|refactor] [--harness claude,cursor,codex] [--yes] [--force] [--dry-run]',
|
|
28
|
+
'',
|
|
29
|
+
'Initialize the day-one .forge skeleton for a fresh repository.',
|
|
30
|
+
].join('\n');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Normalize a profile flag value while preserving empty-string validation. */
|
|
34
|
+
function normalizeProfile(value) {
|
|
35
|
+
if (typeof value !== 'string') return value ?? null;
|
|
36
|
+
const trimmed = value.trim();
|
|
37
|
+
return trimmed.length > 0 ? trimmed : '';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Apply a parsed adoption profile value to the mutable parse result. */
|
|
41
|
+
function setProfile(parsed, value) {
|
|
42
|
+
const profile = normalizeProfile(value);
|
|
43
|
+
if (profile === '') {
|
|
44
|
+
parsed.error = PROFILE_REQUIRED_ERROR;
|
|
45
|
+
parsed.profile = null;
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
parsed.profile = profile;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Normalize a classification flag or wizard answer. */
|
|
52
|
+
function normalizeClassification(value) {
|
|
53
|
+
if (typeof value !== 'string') return value ?? null;
|
|
54
|
+
const trimmed = value.trim().toLowerCase();
|
|
55
|
+
return trimmed.length > 0 ? trimmed : '';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Validate and apply a default workflow classification. */
|
|
59
|
+
function setClassification(parsed, value) {
|
|
60
|
+
const classification = normalizeClassification(value);
|
|
61
|
+
if (classification === '') {
|
|
62
|
+
parsed.error = CLASSIFICATION_REQUIRED_ERROR;
|
|
63
|
+
parsed.classification = null;
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (!CLASSIFICATIONS.includes(classification)) {
|
|
67
|
+
parsed.error = `Unknown classification '${classification}'. Choose one of: ${CLASSIFICATIONS.join(', ')}.`;
|
|
68
|
+
parsed.classification = null;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
parsed.classification = classification;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Convert comma-separated or array harness input into normalized target names. */
|
|
75
|
+
function normalizeHarnessTargets(value) {
|
|
76
|
+
if (Array.isArray(value)) {
|
|
77
|
+
return value.flatMap(item => normalizeHarnessTargets(item));
|
|
78
|
+
}
|
|
79
|
+
if (typeof value !== 'string') return [];
|
|
80
|
+
return value
|
|
81
|
+
.split(',')
|
|
82
|
+
.map(item => item.trim().toLowerCase())
|
|
83
|
+
.filter(Boolean);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Validate and apply selected harness targets. */
|
|
87
|
+
function setHarnessTargets(parsed, value) {
|
|
88
|
+
const targets = [...new Set(normalizeHarnessTargets(value))];
|
|
89
|
+
if (targets.length === 0) {
|
|
90
|
+
parsed.error = HARNESS_REQUIRED_ERROR;
|
|
91
|
+
parsed.harnessTargets = null;
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const unknown = targets.find(target => !HARNESS_TARGETS.includes(target));
|
|
95
|
+
if (unknown) {
|
|
96
|
+
parsed.error = `Unknown harness target '${unknown}'. Choose from: ${HARNESS_TARGETS.join(', ')}.`;
|
|
97
|
+
parsed.harnessTargets = null;
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
parsed.harnessTargets = targets;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Consume --profile syntax from argv and return the consumed index. */
|
|
104
|
+
function consumeProfileArg(args, index, parsed) {
|
|
105
|
+
const arg = args[index];
|
|
106
|
+
if (arg === '--profile') {
|
|
107
|
+
const value = args[index + 1];
|
|
108
|
+
if (typeof value === 'string' && !value.startsWith('--')) {
|
|
109
|
+
setProfile(parsed, value);
|
|
110
|
+
return index + 1;
|
|
111
|
+
}
|
|
112
|
+
parsed.error = PROFILE_REQUIRED_ERROR;
|
|
113
|
+
return index;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
setProfile(parsed, arg.slice('--profile='.length));
|
|
117
|
+
return index;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Consume --classification syntax from argv and return the consumed index. */
|
|
121
|
+
function consumeClassificationArg(args, index, parsed) {
|
|
122
|
+
const arg = args[index];
|
|
123
|
+
if (arg === '--classification') {
|
|
124
|
+
const value = args[index + 1];
|
|
125
|
+
if (typeof value === 'string' && !value.startsWith('--')) {
|
|
126
|
+
setClassification(parsed, value);
|
|
127
|
+
return index + 1;
|
|
128
|
+
}
|
|
129
|
+
parsed.error = CLASSIFICATION_REQUIRED_ERROR;
|
|
130
|
+
return index;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
setClassification(parsed, arg.slice('--classification='.length));
|
|
134
|
+
return index;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Consume --harness syntax from argv and return the consumed index. */
|
|
138
|
+
function consumeHarnessArg(args, index, parsed) {
|
|
139
|
+
const arg = args[index];
|
|
140
|
+
if (arg === '--harness') {
|
|
141
|
+
const value = args[index + 1];
|
|
142
|
+
if (typeof value === 'string' && !value.startsWith('--')) {
|
|
143
|
+
setHarnessTargets(parsed, value);
|
|
144
|
+
return index + 1;
|
|
145
|
+
}
|
|
146
|
+
parsed.error = HARNESS_REQUIRED_ERROR;
|
|
147
|
+
return index;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
setHarnessTargets(parsed, arg.slice('--harness='.length));
|
|
151
|
+
return index;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Apply profile shortcut flags such as --minimal and --full. */
|
|
155
|
+
function applyProfileShortcut(arg, parsed) {
|
|
156
|
+
const profile = PROFILE_SHORTCUTS[arg];
|
|
157
|
+
if (profile) {
|
|
158
|
+
parsed.profile = profile;
|
|
159
|
+
parsed.yes = true;
|
|
160
|
+
}
|
|
161
|
+
return Boolean(profile);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Parse init-specific flags after global CLI flag handling. */
|
|
165
|
+
function parseInitFlags(args = [], flags = {}) {
|
|
166
|
+
const parsed = {
|
|
167
|
+
profile: null,
|
|
168
|
+
error: null,
|
|
169
|
+
yes: Boolean(flags.yes || flags.nonInteractive),
|
|
170
|
+
force: Boolean(flags.force),
|
|
171
|
+
dryRun: Boolean(flags.dryRun),
|
|
172
|
+
classification: null,
|
|
173
|
+
harnessTargets: null,
|
|
174
|
+
railsConfirmed: Boolean(flags.yes || flags.nonInteractive),
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
if (Object.hasOwn(flags, 'profile')) {
|
|
178
|
+
setProfile(parsed, flags.profile);
|
|
179
|
+
}
|
|
180
|
+
if (Object.hasOwn(flags, 'classification')) {
|
|
181
|
+
setClassification(parsed, flags.classification);
|
|
182
|
+
}
|
|
183
|
+
if (Object.hasOwn(flags, 'harness')) {
|
|
184
|
+
setHarnessTargets(parsed, flags.harness);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
let index = 0;
|
|
188
|
+
while (index < args.length) {
|
|
189
|
+
const arg = args[index];
|
|
190
|
+
if (arg === '--profile' || arg.startsWith('--profile=')) {
|
|
191
|
+
index = consumeProfileArg(args, index, parsed) + 1;
|
|
192
|
+
continue;
|
|
193
|
+
} else if (arg === '--classification' || arg.startsWith('--classification=')) {
|
|
194
|
+
index = consumeClassificationArg(args, index, parsed) + 1;
|
|
195
|
+
continue;
|
|
196
|
+
} else if (arg === '--harness' || arg.startsWith('--harness=')) {
|
|
197
|
+
index = consumeHarnessArg(args, index, parsed) + 1;
|
|
198
|
+
continue;
|
|
199
|
+
} else if (applyProfileShortcut(arg, parsed)) {
|
|
200
|
+
index += 1;
|
|
201
|
+
continue;
|
|
202
|
+
} else if (arg === '--yes' || arg === '-y') {
|
|
203
|
+
parsed.yes = true;
|
|
204
|
+
parsed.railsConfirmed = true;
|
|
205
|
+
} else if (arg === '--force') {
|
|
206
|
+
parsed.force = true;
|
|
207
|
+
} else if (arg === '--dry-run') {
|
|
208
|
+
parsed.dryRun = true;
|
|
209
|
+
}
|
|
210
|
+
index += 1;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return parsed;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Create one readline prompt interface for a multi-question wizard. */
|
|
217
|
+
function makePromptInterface() {
|
|
218
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
219
|
+
return {
|
|
220
|
+
ask(question) {
|
|
221
|
+
return new Promise((resolve) => {
|
|
222
|
+
rl.question(question, (answer) => {
|
|
223
|
+
resolve(answer);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
},
|
|
227
|
+
close() {
|
|
228
|
+
rl.close();
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** Resolve the adoption profile while preserving non-interactive defaults. */
|
|
234
|
+
async function resolveProfile(parsed) {
|
|
235
|
+
if (parsed.profile) {
|
|
236
|
+
return parsed.profile;
|
|
237
|
+
}
|
|
238
|
+
return 'standard';
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** Validate a resolved adoption profile name before prompting the user. */
|
|
242
|
+
function validateProfile(profile) {
|
|
243
|
+
if (ADOPTION_PROFILE_NAMES.includes(profile)) {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
return `Unknown adoption profile '${profile}'. Available profiles: ${ADOPTION_PROFILE_NAMES.join(', ')}`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** Return true only when a path exists and is a directory. */
|
|
250
|
+
function isDirectory(filePath) {
|
|
251
|
+
try {
|
|
252
|
+
return fs.statSync(filePath).isDirectory();
|
|
253
|
+
} catch (_err) {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** Detect active harnesses from day-one filesystem marker directories. */
|
|
259
|
+
function detectHarnessTargets(projectRoot = process.cwd()) {
|
|
260
|
+
return DETECTABLE_HARNESS_TARGETS.filter(target => isDirectory(path.join(projectRoot, `.${target}`)));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** Read profile-defined harness targets for explicit profile selections. */
|
|
264
|
+
function getProfileHarnessTargets(profile) {
|
|
265
|
+
const targets = buildAdoptionConfig(profile).adapters?.harness?.targets;
|
|
266
|
+
return Array.isArray(targets) ? targets : [];
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** Render the empty patch intent scaffold. */
|
|
270
|
+
function renderPatchMd() {
|
|
271
|
+
return [
|
|
272
|
+
'# Forge Patch Intent',
|
|
273
|
+
'',
|
|
274
|
+
'<!--',
|
|
275
|
+
'This file is intentionally empty for day-one setup.',
|
|
276
|
+
'Use it later to document local Forge overrides before applying them.',
|
|
277
|
+
'-->',
|
|
278
|
+
'',
|
|
279
|
+
].join('\n');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** Append an existing file or directory glob to the protected-path manifest. */
|
|
283
|
+
function addExistingPath(entries, projectRoot, protectedPath, reason) {
|
|
284
|
+
const absolutePath = projectRoot ? path.join(projectRoot, protectedPath) : null;
|
|
285
|
+
if (!absolutePath || !fs.existsSync(absolutePath)) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const manifestPath = isDirectory(absolutePath) ? `${protectedPath}/**` : protectedPath;
|
|
289
|
+
entries.push({ path: manifestPath.replace(/\\/g, '/'), reason });
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/** Add only selected and existing harness paths to the manifest. */
|
|
293
|
+
function addSelectedHarnessPaths(entries, { projectRoot, harnessTargets }) {
|
|
294
|
+
if (!projectRoot) return;
|
|
295
|
+
if (fs.existsSync(path.join(projectRoot, 'AGENTS.md'))) {
|
|
296
|
+
entries.push({ path: 'AGENTS.md', reason: 'Agent workflow instructions' });
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (harnessTargets.includes('claude')) {
|
|
300
|
+
addExistingPath(entries, projectRoot, 'CLAUDE.md', 'Claude harness instructions');
|
|
301
|
+
addExistingPath(entries, projectRoot, '.claude', 'Claude harness directory');
|
|
302
|
+
}
|
|
303
|
+
if (harnessTargets.includes('cursor')) {
|
|
304
|
+
addExistingPath(entries, projectRoot, '.cursor', 'Cursor harness directory');
|
|
305
|
+
}
|
|
306
|
+
if (harnessTargets.includes('codex')) {
|
|
307
|
+
addExistingPath(entries, projectRoot, '.codex', 'Codex harness directory');
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** Render the protected-path manifest scaffold without enforcing it. */
|
|
312
|
+
function renderProtectedPathsYaml({ classification, harnessTargets, projectRoot = null }) {
|
|
313
|
+
const paths = [
|
|
314
|
+
{ path: '.forge/config.yaml', reason: 'Forge runtime configuration' },
|
|
315
|
+
{ path: '.forge/patch.md', reason: 'Local patch intent scaffold' },
|
|
316
|
+
{ path: '.forge/protected-paths.yaml', reason: 'Protected path manifest scaffold' },
|
|
317
|
+
];
|
|
318
|
+
addSelectedHarnessPaths(paths, { projectRoot, harnessTargets });
|
|
319
|
+
|
|
320
|
+
return YAML.stringify({
|
|
321
|
+
kind: 'forge.protectedPaths',
|
|
322
|
+
version: 1,
|
|
323
|
+
classification,
|
|
324
|
+
harness: {
|
|
325
|
+
targets: harnessTargets,
|
|
326
|
+
},
|
|
327
|
+
paths,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** Render config.yaml by layering day-one choices over an adoption profile. */
|
|
332
|
+
function renderDayOneConfigYaml({ profile, classification, harnessTargets, railsConfirmed }) {
|
|
333
|
+
const config = YAML.parse(renderAdoptionConfigYaml(profile));
|
|
334
|
+
config.workflow = config.workflow || {};
|
|
335
|
+
config.workflow.classification = { default: classification };
|
|
336
|
+
config.layer1Rails = {
|
|
337
|
+
confirmed: railsConfirmed,
|
|
338
|
+
rails: [
|
|
339
|
+
'tdd_intent',
|
|
340
|
+
'secret_scan',
|
|
341
|
+
'branch_protection',
|
|
342
|
+
'signed_commits',
|
|
343
|
+
'schema_integrity',
|
|
344
|
+
],
|
|
345
|
+
};
|
|
346
|
+
config.adapters = config.adapters || {};
|
|
347
|
+
config.adapters.harness = {
|
|
348
|
+
enabled: harnessTargets.length > 0,
|
|
349
|
+
targets: harnessTargets,
|
|
350
|
+
};
|
|
351
|
+
config.protectedPaths = [
|
|
352
|
+
...new Set([
|
|
353
|
+
...(Array.isArray(config.protectedPaths) ? config.protectedPaths : []),
|
|
354
|
+
'.forge/config.yaml',
|
|
355
|
+
'.forge/patch.md',
|
|
356
|
+
'.forge/protected-paths.yaml',
|
|
357
|
+
]),
|
|
358
|
+
];
|
|
359
|
+
return YAML.stringify(config);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** Format generated file paths relative to the target project root. */
|
|
363
|
+
function relativeForgePath(projectRoot, filePath) {
|
|
364
|
+
return path.relative(projectRoot, filePath).replace(/\\/g, '/');
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/** Build the complete set of day-one generated files. */
|
|
368
|
+
function buildGeneratedFiles(projectRoot, choices) {
|
|
369
|
+
const forgeDir = path.join(projectRoot, '.forge');
|
|
370
|
+
return [
|
|
371
|
+
{
|
|
372
|
+
path: path.join(forgeDir, 'config.yaml'),
|
|
373
|
+
content: renderDayOneConfigYaml(choices),
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
path: path.join(forgeDir, 'patch.md'),
|
|
377
|
+
content: renderPatchMd(),
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
path: path.join(forgeDir, 'protected-paths.yaml'),
|
|
381
|
+
content: renderProtectedPathsYaml({ ...choices, projectRoot }),
|
|
382
|
+
},
|
|
383
|
+
];
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/** Filter generated files down to paths that already exist. */
|
|
387
|
+
function existingGeneratedFiles(files) {
|
|
388
|
+
return files.filter(file => fs.existsSync(file.path));
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/** Build a no-clobber repair message for existing generated files. */
|
|
392
|
+
function formatNoClobberError(projectRoot, files) {
|
|
393
|
+
const names = files.map(file => relativeForgePath(projectRoot, file.path)).join(', ');
|
|
394
|
+
const plural = files.length !== 1;
|
|
395
|
+
return `${names} already ${plural ? 'exist' : 'exists'}. Re-run with --force to overwrite ${plural ? 'them' : 'it'}, or move the existing ${plural ? 'files' : 'file'} and run forge init again.`;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/** Resolve the workflow classification from flags, wizard input, or defaults. */
|
|
399
|
+
async function resolveClassificationChoice(parsed, interactive, prompt) {
|
|
400
|
+
if (parsed.classification) {
|
|
401
|
+
return { classification: parsed.classification };
|
|
402
|
+
}
|
|
403
|
+
if (!interactive) {
|
|
404
|
+
return { classification: 'standard' };
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const answer = await prompt(`Select default classification (${CLASSIFICATIONS.join('/')}), default standard: `);
|
|
408
|
+
setClassification(parsed, answer.trim() || 'standard');
|
|
409
|
+
if (parsed.error) {
|
|
410
|
+
return { error: parsed.error };
|
|
411
|
+
}
|
|
412
|
+
return { classification: parsed.classification };
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/** Resolve Layer 1 rail confirmation from flags, wizard input, or defaults. */
|
|
416
|
+
async function resolveRailsConfirmation(parsed, interactive, prompt) {
|
|
417
|
+
if (parsed.railsConfirmed) {
|
|
418
|
+
return { railsConfirmed: true };
|
|
419
|
+
}
|
|
420
|
+
if (!interactive) {
|
|
421
|
+
return { railsConfirmed: true };
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const answer = await prompt('Confirm Layer 1 rails are required for this repo? (Y/n): ');
|
|
425
|
+
const normalized = answer.trim().toLowerCase();
|
|
426
|
+
const railsConfirmed = normalized === '' || normalized === 'y' || normalized === 'yes';
|
|
427
|
+
if (railsConfirmed) {
|
|
428
|
+
return { railsConfirmed: true };
|
|
429
|
+
}
|
|
430
|
+
return {
|
|
431
|
+
error: 'Layer 1 rails must be confirmed before initialization. Re-run forge init and answer yes to continue.',
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/** Resolve harness targets from flags, filesystem detection, wizard input, or defaults. */
|
|
436
|
+
async function resolveHarnessTargetChoice(parsed, interactive, prompt, projectRoot, profile) {
|
|
437
|
+
if (parsed.harnessTargets) {
|
|
438
|
+
return { harnessTargets: parsed.harnessTargets };
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const detectedHarnessTargets = detectHarnessTargets(projectRoot);
|
|
442
|
+
const profileHarnessTargets = parsed.profile ? getProfileHarnessTargets(profile) : [];
|
|
443
|
+
const fallbackHarnessTargets = profileHarnessTargets.length > 0 || parsed.profile ? profileHarnessTargets : ['codex'];
|
|
444
|
+
const defaultHarnessTargets = detectedHarnessTargets.length > 0
|
|
445
|
+
? detectedHarnessTargets
|
|
446
|
+
: fallbackHarnessTargets;
|
|
447
|
+
if (!interactive) {
|
|
448
|
+
return { harnessTargets: defaultHarnessTargets };
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const defaultValue = defaultHarnessTargets.join(',');
|
|
452
|
+
const answer = await prompt(`Select harness targets (${HARNESS_TARGETS.join(',')}), default ${defaultValue}: `);
|
|
453
|
+
if (answer.trim().length === 0) {
|
|
454
|
+
return { harnessTargets: defaultHarnessTargets };
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
setHarnessTargets(parsed, answer.trim());
|
|
458
|
+
if (parsed.error) {
|
|
459
|
+
return { error: parsed.error };
|
|
460
|
+
}
|
|
461
|
+
return { harnessTargets: parsed.harnessTargets };
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/** Resolve profile, classification, L1 confirmation, and harness targets. */
|
|
465
|
+
async function resolveChoices(parsed, projectRoot, deps = {}, profile = null) {
|
|
466
|
+
const stdinIsTTY = deps.stdinIsTTY ?? process.stdin.isTTY;
|
|
467
|
+
const interactive = !parsed.yes && stdinIsTTY;
|
|
468
|
+
const promptInterface = !deps.prompt && interactive ? makePromptInterface() : null;
|
|
469
|
+
const prompt = deps.prompt || (promptInterface ? promptInterface.ask : null);
|
|
470
|
+
const resolvedProfile = profile || await resolveProfile(parsed);
|
|
471
|
+
|
|
472
|
+
try {
|
|
473
|
+
const classificationChoice = await resolveClassificationChoice(parsed, interactive, prompt);
|
|
474
|
+
if (classificationChoice.error) {
|
|
475
|
+
return { error: classificationChoice.error };
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const railsChoice = await resolveRailsConfirmation(parsed, interactive, prompt);
|
|
479
|
+
if (railsChoice.error) {
|
|
480
|
+
return { error: railsChoice.error };
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const harnessChoice = await resolveHarnessTargetChoice(parsed, interactive, prompt, projectRoot, resolvedProfile);
|
|
484
|
+
if (harnessChoice.error) {
|
|
485
|
+
return { error: harnessChoice.error };
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return {
|
|
489
|
+
profile: resolvedProfile,
|
|
490
|
+
classification: classificationChoice.classification,
|
|
491
|
+
railsConfirmed: railsChoice.railsConfirmed,
|
|
492
|
+
harnessTargets: harnessChoice.harnessTargets,
|
|
493
|
+
};
|
|
494
|
+
} finally {
|
|
495
|
+
if (promptInterface) {
|
|
496
|
+
promptInterface.close();
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/** Execute forge init for the target project root. */
|
|
502
|
+
async function handler(args, flags, projectRoot = process.cwd(), deps = {}) {
|
|
503
|
+
const parsed = parseInitFlags(args, flags);
|
|
504
|
+
if (parsed.error) {
|
|
505
|
+
return { success: false, error: parsed.error };
|
|
506
|
+
}
|
|
507
|
+
const profile = await resolveProfile(parsed);
|
|
508
|
+
const profileError = validateProfile(profile);
|
|
509
|
+
if (profileError) {
|
|
510
|
+
return {
|
|
511
|
+
success: false,
|
|
512
|
+
error: profileError,
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
const choices = await resolveChoices(parsed, projectRoot, deps, profile);
|
|
516
|
+
if (choices.error) {
|
|
517
|
+
return { success: false, error: choices.error };
|
|
518
|
+
}
|
|
519
|
+
const forgeDir = path.join(projectRoot, '.forge');
|
|
520
|
+
const files = buildGeneratedFiles(projectRoot, choices);
|
|
521
|
+
|
|
522
|
+
if (parsed.dryRun) {
|
|
523
|
+
const previewFile = files[0];
|
|
524
|
+
return {
|
|
525
|
+
success: true,
|
|
526
|
+
...choices,
|
|
527
|
+
files: [relativeForgePath(projectRoot, previewFile.path)],
|
|
528
|
+
output: previewFile.content,
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const existingFiles = existingGeneratedFiles(files);
|
|
533
|
+
if (existingFiles.length > 0 && !parsed.force) {
|
|
534
|
+
return {
|
|
535
|
+
success: false,
|
|
536
|
+
error: formatNoClobberError(projectRoot, existingFiles),
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
fs.mkdirSync(forgeDir, { recursive: true });
|
|
541
|
+
for (const file of files) {
|
|
542
|
+
fs.writeFileSync(file.path, file.content, 'utf8');
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Close the onboarding loop that a bare `forge init` used to leave open:
|
|
546
|
+
// 1. install git hooks so stage commands are not HOOKS_NOT_ACTIVE-blocked, and
|
|
547
|
+
// 2. import any existing Beads store into the Kernel.
|
|
548
|
+
// Both reuse setup's real implementations (overridable via deps for tests) and
|
|
549
|
+
// degrade to a warning rather than failing init.
|
|
550
|
+
const installHooks = deps.installHooks
|
|
551
|
+
|| ((root) => require('./setup').ensureGitHooksInstalled(root));
|
|
552
|
+
const autoMigrateBeads = deps.autoMigrateBeads
|
|
553
|
+
|| ((root) => require('./migrate').autoMigrateBeadsIfPresent(root));
|
|
554
|
+
try {
|
|
555
|
+
await installHooks(projectRoot);
|
|
556
|
+
} catch (err) {
|
|
557
|
+
console.warn(`Warning: git hook installation skipped: ${err.message}`);
|
|
558
|
+
}
|
|
559
|
+
try {
|
|
560
|
+
await autoMigrateBeads(projectRoot);
|
|
561
|
+
} catch (err) {
|
|
562
|
+
console.warn(`Warning: Beads → Kernel migration skipped: ${err.message}`);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
console.log(`Initialized Forge adoption profile '${profile}' at .forge/`);
|
|
566
|
+
console.log(`Classification: ${choices.classification}`);
|
|
567
|
+
console.log(`Harness targets: ${choices.harnessTargets.join(', ')}`);
|
|
568
|
+
console.log('Inspect with: forge options lint && forge options diff');
|
|
569
|
+
|
|
570
|
+
return {
|
|
571
|
+
success: true,
|
|
572
|
+
...choices,
|
|
573
|
+
files: files.map(file => relativeForgePath(projectRoot, file.path)),
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
module.exports = {
|
|
578
|
+
name: 'init',
|
|
579
|
+
description: 'Initialize Forge adoption config in a fresh repository',
|
|
580
|
+
usage: usage(),
|
|
581
|
+
flags: {
|
|
582
|
+
'--profile <name>': 'Adoption profile: minimal, standard, or full',
|
|
583
|
+
'--minimal': 'Shortcut for --profile minimal --yes',
|
|
584
|
+
'--standard': 'Shortcut for --profile standard --yes',
|
|
585
|
+
'--full': 'Shortcut for --profile full --yes',
|
|
586
|
+
'--classification <name>': 'Default workflow classification: critical, standard, or refactor',
|
|
587
|
+
'--harness <targets>': 'Comma-separated harness targets: claude, cursor, codex',
|
|
588
|
+
'--yes': 'Use the standard profile when no profile is provided',
|
|
589
|
+
'--force': 'Overwrite existing day-one .forge files',
|
|
590
|
+
'--dry-run': 'Print the generated config YAML without writing files',
|
|
591
|
+
},
|
|
592
|
+
handler,
|
|
593
|
+
detectHarnessTargets,
|
|
594
|
+
parseInitFlags,
|
|
595
|
+
renderDayOneConfigYaml,
|
|
596
|
+
renderPatchMd,
|
|
597
|
+
renderProtectedPathsYaml,
|
|
598
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
analyzeInsights,
|
|
5
|
+
formatInsightsText,
|
|
6
|
+
recordInsightDecision,
|
|
7
|
+
} = require('../insights');
|
|
8
|
+
|
|
9
|
+
const usage = [
|
|
10
|
+
'Usage: forge insights [--limit N] [--min-count N] [--since YYYY-MM-DD] [--json]',
|
|
11
|
+
' forge insights accept <candidate-id> [--note text]',
|
|
12
|
+
' forge insights reject <candidate-id> [--note text]',
|
|
13
|
+
].join('\n');
|
|
14
|
+
|
|
15
|
+
function readOption(args, name, fallback) {
|
|
16
|
+
const equals = args.find(arg => arg.startsWith(`${name}=`));
|
|
17
|
+
if (equals) return equals.slice(name.length + 1);
|
|
18
|
+
const index = args.indexOf(name);
|
|
19
|
+
if (index >= 0 && args[index + 1] && !args[index + 1].startsWith('-')) return args[index + 1];
|
|
20
|
+
return fallback;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function positionals(args) {
|
|
24
|
+
const values = [];
|
|
25
|
+
const flagsWithValues = new Set(['--limit', '--min-count', '--since', '--note', '--path', '-p']);
|
|
26
|
+
for (let index = 0; index < args.length; index++) {
|
|
27
|
+
const arg = args[index];
|
|
28
|
+
if (arg.startsWith('--') && arg.includes('=')) continue;
|
|
29
|
+
if (flagsWithValues.has(arg)) {
|
|
30
|
+
index += 1;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (arg.startsWith('-')) continue;
|
|
34
|
+
values.push(arg);
|
|
35
|
+
}
|
|
36
|
+
return values;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function handler(args, flags, projectRoot) {
|
|
40
|
+
const commandFlags = flags ?? {};
|
|
41
|
+
const [subcommand, candidateId] = positionals(args);
|
|
42
|
+
if (subcommand === 'accept' || subcommand === 'reject') {
|
|
43
|
+
if (!candidateId) {
|
|
44
|
+
return { success: false, error: usage };
|
|
45
|
+
}
|
|
46
|
+
const status = subcommand === 'accept' ? 'accepted' : 'rejected';
|
|
47
|
+
const decision = recordInsightDecision(projectRoot, candidateId, status, {
|
|
48
|
+
note: readOption(args, '--note', ''),
|
|
49
|
+
memory: commandFlags.memory,
|
|
50
|
+
});
|
|
51
|
+
return {
|
|
52
|
+
success: true,
|
|
53
|
+
output: `Insight ${candidateId} ${status}. Decision recorded at ${decision.key}.\n`,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const result = analyzeInsights(projectRoot, {
|
|
58
|
+
limit: readOption(args, '--limit', undefined),
|
|
59
|
+
minCount: readOption(args, '--min-count', undefined),
|
|
60
|
+
since: readOption(args, '--since', undefined),
|
|
61
|
+
});
|
|
62
|
+
if (args.includes('--review-feedback')) {
|
|
63
|
+
result.limitations = [
|
|
64
|
+
'Compatibility note: --review-feedback now reads Beads interactions and issue evidence; external review-provider comments are not inferred.',
|
|
65
|
+
...result.limitations,
|
|
66
|
+
];
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
success: true,
|
|
70
|
+
output: args.includes('--json') ? `${JSON.stringify(result, null, 2)}\n` : formatInsightsText(result),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = {
|
|
75
|
+
name: 'insights',
|
|
76
|
+
description: 'Detect recurring evidence patterns and suggest conservative workflow follow-ups',
|
|
77
|
+
usage,
|
|
78
|
+
handler,
|
|
79
|
+
};
|
package/lib/commands/issue.js
CHANGED
|
@@ -2,4 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
const { createIssueCommand } = require('./_issue');
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// Static registry export for `forge issue`. The handler delegates to the issue
|
|
6
|
+
// surface built by createIssueCommand() in _issue.js (called inline so the
|
|
7
|
+
// delegation is visible to the release-readiness surface check), which dispatches
|
|
8
|
+
// each subcommand through the shared backend abstraction.
|
|
9
|
+
module.exports = {
|
|
10
|
+
name: 'issue',
|
|
11
|
+
description: 'Manage issues through the Forge command surface',
|
|
12
|
+
usage: 'forge issue <create|update|claim|release|close|show|list|ready|search|stats|dep> [...]',
|
|
13
|
+
flags: {},
|
|
14
|
+
handler: (args, flags, projectRoot, opts = {}) =>
|
|
15
|
+
createIssueCommand().handler(args, flags, projectRoot, opts),
|
|
16
|
+
};
|