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/commands/worktree.js
CHANGED
|
@@ -6,12 +6,16 @@ const path = require('node:path');
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Forge Worktree Command
|
|
9
|
-
* Manage isolated worktrees
|
|
9
|
+
* Manage isolated worktrees.
|
|
10
10
|
* Uses execFileSync (not execSync) to prevent command injection (OWASP A03).
|
|
11
11
|
*
|
|
12
|
+
* The Kernel issue store lives in the git common dir, which every worktree of a
|
|
13
|
+
* repo shares, so no per-worktree issue-store bootstrap is needed — a new
|
|
14
|
+
* worktree already sees the same kernel.
|
|
15
|
+
*
|
|
12
16
|
* Subcommands:
|
|
13
|
-
* create <slug>
|
|
14
|
-
* remove <slug>
|
|
17
|
+
* create <slug> - Create worktree at .worktrees/<slug> with branch + install
|
|
18
|
+
* remove <slug> - Remove worktree via git worktree remove
|
|
15
19
|
*
|
|
16
20
|
* @module commands/worktree
|
|
17
21
|
*/
|
|
@@ -23,7 +27,10 @@ const path = require('node:path');
|
|
|
23
27
|
* @returns {string|null} Package manager command or null
|
|
24
28
|
*/
|
|
25
29
|
function detectPackageManager(projectRoot, fsApi) {
|
|
30
|
+
// Bun ships two lockfile formats: the legacy binary `bun.lockb` and the current
|
|
31
|
+
// text `bun.lock`. Recognize both so a Bun repo isn't mis-detected as npm.
|
|
26
32
|
if (fsApi.existsSync(path.join(projectRoot, 'bun.lockb'))) return 'bun';
|
|
33
|
+
if (fsApi.existsSync(path.join(projectRoot, 'bun.lock'))) return 'bun';
|
|
27
34
|
if (fsApi.existsSync(path.join(projectRoot, 'pnpm-lock.yaml'))) return 'pnpm';
|
|
28
35
|
if (fsApi.existsSync(path.join(projectRoot, 'yarn.lock'))) return 'yarn';
|
|
29
36
|
if (fsApi.existsSync(path.join(projectRoot, 'package-lock.json'))) return 'npm';
|
|
@@ -31,66 +38,6 @@ function detectPackageManager(projectRoot, fsApi) {
|
|
|
31
38
|
return null;
|
|
32
39
|
}
|
|
33
40
|
|
|
34
|
-
/**
|
|
35
|
-
* Copy .beads/ directory excluding daemon.lock.
|
|
36
|
-
* @param {string} source - Source .beads path
|
|
37
|
-
* @param {string} dest - Destination .beads path
|
|
38
|
-
* @param {object} fsApi - fs module (for DI)
|
|
39
|
-
*/
|
|
40
|
-
function copyBeadsDir(source, dest, fsApi) {
|
|
41
|
-
fsApi.mkdirSync(dest, { recursive: true });
|
|
42
|
-
const entries = fsApi.readdirSync(source);
|
|
43
|
-
for (const entry of entries) {
|
|
44
|
-
if (entry === 'daemon.lock') continue;
|
|
45
|
-
fsApi.cpSync(path.join(source, entry), path.join(dest, entry), { recursive: true });
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Stop a Dolt server running in a worktree to release file locks.
|
|
51
|
-
* Tries graceful `bd dolt stop` first, then falls back to PID-based kill
|
|
52
|
-
* from lock files. Never kills ALL Dolt processes — only the specific PID.
|
|
53
|
-
*
|
|
54
|
-
* @param {string} worktreePath - Absolute path to the worktree
|
|
55
|
-
* @param {object} [opts] - Options for dependency injection
|
|
56
|
-
* @param {Function} [opts._exec] - Override for execFileSync (testing)
|
|
57
|
-
* @param {object} [opts._fs] - Override for fs module (testing)
|
|
58
|
-
* @param {string} [opts._platform] - Override for process.platform (testing)
|
|
59
|
-
* @returns {{ stopped: boolean, method: string, pid?: number }}
|
|
60
|
-
*/
|
|
61
|
-
function stopDolt(worktreePath, opts = {}) {
|
|
62
|
-
const exec = opts._exec || execFileSync;
|
|
63
|
-
const _fs = opts._fs || fs;
|
|
64
|
-
const platform = opts._platform || process.platform;
|
|
65
|
-
|
|
66
|
-
// 1. Try graceful: bd dolt stop
|
|
67
|
-
try {
|
|
68
|
-
exec('bd', ['dolt', 'stop'], { cwd: worktreePath, timeout: 5000, stdio: 'pipe' });
|
|
69
|
-
return { stopped: true, method: 'bd-dolt-stop' };
|
|
70
|
-
} catch (_e) { /* intentional: bd not available, try PID-based kill */ } // NOSONAR S2486
|
|
71
|
-
|
|
72
|
-
// 2. Try PID-based kill from lock file
|
|
73
|
-
const lockPaths = [
|
|
74
|
-
path.join(worktreePath, '.beads', 'dolt-server.lock'),
|
|
75
|
-
path.join(worktreePath, '.beads', 'daemon.lock'),
|
|
76
|
-
];
|
|
77
|
-
for (const lockPath of lockPaths) {
|
|
78
|
-
try {
|
|
79
|
-
const pid = Number.parseInt(_fs.readFileSync(lockPath, 'utf-8').trim(), 10);
|
|
80
|
-
if (!Number.isNaN(pid)) {
|
|
81
|
-
if (platform === 'win32') {
|
|
82
|
-
exec('taskkill', ['/F', '/PID', String(pid)], { stdio: 'pipe' });
|
|
83
|
-
} else {
|
|
84
|
-
process.kill(pid, 'SIGTERM');
|
|
85
|
-
}
|
|
86
|
-
return { stopped: true, method: 'pid-kill', pid };
|
|
87
|
-
}
|
|
88
|
-
} catch (_e) { /* intentional: try next lock file */ } // NOSONAR S2486
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return { stopped: false, method: 'none' };
|
|
92
|
-
}
|
|
93
|
-
|
|
94
41
|
/**
|
|
95
42
|
* Check if a git branch already exists.
|
|
96
43
|
* @param {string} branchName - Branch name to check
|
|
@@ -107,58 +54,189 @@ function branchExists(branchName, runFile) {
|
|
|
107
54
|
}
|
|
108
55
|
|
|
109
56
|
/**
|
|
110
|
-
*
|
|
57
|
+
* True when a link failure is a privilege/support problem (Windows without the
|
|
58
|
+
* symlink privilege, restricted FS) rather than a real error. In that case we
|
|
59
|
+
* degrade to a package install instead of hard-failing.
|
|
60
|
+
* @param {Error} error
|
|
61
|
+
* @returns {boolean}
|
|
62
|
+
*/
|
|
63
|
+
function isLinkPermissionError(error) {
|
|
64
|
+
return ['EPERM', 'EACCES', 'ENOSYS', 'UV_EPERM'].includes(error && error.code);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Run package install in the new worktree if a package manager is detected, and
|
|
69
|
+
* SURFACE failures (spawn error or non-zero exit) instead of swallowing them.
|
|
111
70
|
* @param {string} worktreePath - Absolute path to the new worktree
|
|
112
71
|
* @param {string} projectRoot - Absolute path to the project root
|
|
113
|
-
* @param {
|
|
72
|
+
* @param {Function} spawnFn - spawnSync-compatible function
|
|
114
73
|
* @param {object} fsApi - fs module (for DI)
|
|
115
|
-
* @
|
|
116
|
-
* @
|
|
74
|
+
* @returns {{ linked: boolean, installed: boolean }}
|
|
75
|
+
* @throws {Error} when the install cannot be spawned or exits non-zero.
|
|
117
76
|
*/
|
|
118
|
-
function
|
|
119
|
-
const
|
|
120
|
-
|
|
77
|
+
function runInstall(worktreePath, projectRoot, spawnFn, fsApi) {
|
|
78
|
+
const pkgManager = detectPackageManager(projectRoot, fsApi);
|
|
79
|
+
if (!pkgManager) return { linked: false, installed: false };
|
|
80
|
+
|
|
81
|
+
// shell:true on Windows: npm/pnpm/yarn are .cmd shims that cannot be spawned
|
|
82
|
+
// directly (ENOENT / EINVAL). pkgManager comes from lockfile detection (fixed
|
|
83
|
+
// set) and args are hardcoded, so no user input reaches the shell.
|
|
84
|
+
const result = spawnFn(pkgManager, ['install'], {
|
|
85
|
+
cwd: worktreePath,
|
|
86
|
+
stdio: 'pipe',
|
|
87
|
+
shell: process.platform === 'win32',
|
|
88
|
+
});
|
|
89
|
+
if (result && result.error) {
|
|
90
|
+
throw new Error(`Dependency install failed: could not run '${pkgManager} install' in ${worktreePath}: ${result.error.message}`);
|
|
91
|
+
}
|
|
92
|
+
if (result && typeof result.status === 'number' && result.status !== 0) {
|
|
93
|
+
throw new Error(`Dependency install failed: '${pkgManager} install' exited with code ${result.status} in ${worktreePath}`);
|
|
94
|
+
}
|
|
95
|
+
return { linked: false, installed: true };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Populate the new worktree's node_modules. Fast path: link to the main repo's
|
|
100
|
+
* shared install (a junction on Windows, a directory symlink on POSIX) so a fresh
|
|
101
|
+
* worktree is immediately usable without a full reinstall — this repo's own
|
|
102
|
+
* worktrees follow that pattern. Fallback: run the detected package manager's
|
|
103
|
+
* install when there is no shared install to link. Both paths surface failures.
|
|
104
|
+
* @param {string} worktreePath - Absolute path to the new worktree
|
|
105
|
+
* @param {string} projectRoot - Absolute path to the project root
|
|
106
|
+
* @param {object} deps
|
|
107
|
+
* @param {Function} deps.spawnFn - spawnSync-compatible function
|
|
108
|
+
* @param {object} deps.fsApi - fs module (for DI)
|
|
109
|
+
* @param {string} deps.platform - process.platform value
|
|
110
|
+
* @returns {{ linked: boolean, installed: boolean }}
|
|
111
|
+
* @throws {Error} when linking fails for a non-privilege reason or install fails.
|
|
112
|
+
*/
|
|
113
|
+
function setupWorktreeDeps(worktreePath, projectRoot, { spawnFn, fsApi, platform }) {
|
|
114
|
+
const srcModules = path.join(projectRoot, 'node_modules');
|
|
115
|
+
const destModules = path.join(worktreePath, 'node_modules');
|
|
116
|
+
|
|
117
|
+
// Already populated (e.g. git checkout carried it) — nothing to do.
|
|
118
|
+
if (fsApi.existsSync(destModules)) return { linked: false, installed: false };
|
|
121
119
|
|
|
122
|
-
|
|
123
|
-
|
|
120
|
+
// Fast path: link to the shared install when the main repo has one.
|
|
121
|
+
if (fsApi.existsSync(srcModules)) {
|
|
122
|
+
const linkType = platform === 'win32' ? 'junction' : 'dir';
|
|
123
|
+
try {
|
|
124
|
+
fsApi.symlinkSync(srcModules, destModules, linkType);
|
|
125
|
+
return { linked: true, installed: false };
|
|
126
|
+
} catch (error) {
|
|
127
|
+
// Degrade to a real install only when the link was refused for lack of
|
|
128
|
+
// privilege/support; any other failure is surfaced to the caller.
|
|
129
|
+
if (!isLinkPermissionError(error)) throw error;
|
|
130
|
+
}
|
|
124
131
|
}
|
|
125
132
|
|
|
126
|
-
|
|
133
|
+
return runInstall(worktreePath, projectRoot, spawnFn, fsApi);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Best-effort: record the issue → worktree → work-folder linkage in the Kernel so
|
|
138
|
+
* `forge worktree list` and orientation READ it instead of guessing the work-folder by
|
|
139
|
+
* a filesystem heuristic. Writes a `kernel_worktrees` row keyed by the absolute worktree
|
|
140
|
+
* path (idempotent upsert) and, when an issue + work-folder are given, drops a
|
|
141
|
+
* machine-readable `.forge-issue` marker so a folder resolves to its issue deterministically.
|
|
142
|
+
*
|
|
143
|
+
* Never throws: a repo without a reachable Kernel (or without the 007 linkage columns)
|
|
144
|
+
* must still get a usable worktree — orientation falls back to the folder heuristic.
|
|
145
|
+
*
|
|
146
|
+
* @param {object} params
|
|
147
|
+
* @param {string} params.projectRoot
|
|
148
|
+
* @param {string} params.worktreePath - Absolute worktree path (the row key).
|
|
149
|
+
* @param {string} params.branch
|
|
150
|
+
* @param {string|null} params.issueId
|
|
151
|
+
* @param {string|null} params.workFolder - Repo-relative work-folder path.
|
|
152
|
+
* @param {object} params.opts - DI options (may inject `_kernelDriver`, `_exec`, `_fs`).
|
|
153
|
+
* @returns {Promise<{registered: boolean, issueId: string|null, workFolder: string|null, reason?: string}>}
|
|
154
|
+
*/
|
|
155
|
+
async function registerWorktreeLinkage({ projectRoot, worktreePath, branch, issueId, workFolder, opts }) {
|
|
156
|
+
const runFile = opts._exec || execFileSync;
|
|
157
|
+
const fsApi = opts._fs || fs;
|
|
127
158
|
try {
|
|
128
|
-
if
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
fsApi.
|
|
159
|
+
// Drop the folder → issue marker first — deterministic even if the DB write fails.
|
|
160
|
+
if (workFolder && issueId) {
|
|
161
|
+
const folderAbs = path.resolve(projectRoot, workFolder);
|
|
162
|
+
fsApi.mkdirSync(folderAbs, { recursive: true });
|
|
163
|
+
fsApi.writeFileSync(path.join(folderAbs, '.forge-issue'), `${issueId}\n`, 'utf8');
|
|
132
164
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
165
|
+
|
|
166
|
+
let driver = opts._kernelDriver;
|
|
167
|
+
if (!driver) {
|
|
168
|
+
// No reachable git repo → no shared Kernel; skip quietly (orientation still works
|
|
169
|
+
// off the folder heuristic). Avoids spawning `git` in a non-repo directory.
|
|
170
|
+
if (!fsApi.existsSync(path.join(projectRoot, '.git'))) {
|
|
171
|
+
return { registered: false, issueId: issueId || null, workFolder: workFolder || null, reason: 'no git repository' };
|
|
172
|
+
}
|
|
173
|
+
// Lazy require: keep the worktree command light and avoid paying kernel setup
|
|
174
|
+
// when linkage is not needed. Migrated deps guarantee the 007 columns exist.
|
|
175
|
+
const { buildMigratedKernelIssueDeps } = require('../kernel/cli-broker-factory');
|
|
176
|
+
driver = (await buildMigratedKernelIssueDeps({ projectRoot })).kernelDriver;
|
|
138
177
|
}
|
|
139
|
-
}
|
|
140
178
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
179
|
+
const { resolveGitCommonDir } = require('../kernel/broker');
|
|
180
|
+
const gitCommonDir = resolveGitCommonDir(projectRoot, { execFileSync: runFile });
|
|
181
|
+
|
|
182
|
+
driver.registerWorktree({
|
|
183
|
+
git_common_dir: gitCommonDir,
|
|
184
|
+
path: worktreePath,
|
|
185
|
+
branch,
|
|
186
|
+
actor: process.env.FORGE_ACTOR || null,
|
|
187
|
+
issue_id: issueId || null,
|
|
188
|
+
work_folder: workFolder || null,
|
|
189
|
+
registered_at: new Date().toISOString(),
|
|
190
|
+
state: 'active',
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
return { registered: true, issueId: issueId || null, workFolder: workFolder || null };
|
|
194
|
+
} catch (error) {
|
|
195
|
+
// Non-fatal: keep the worktree usable. Only warn when the caller explicitly asked
|
|
196
|
+
// for linkage (--issue/--work-folder); a plain `worktree create` stays quiet.
|
|
197
|
+
if (issueId || workFolder) {
|
|
198
|
+
process.stderr.write(`forge worktree: kernel linkage not recorded (${error.message})\n`);
|
|
199
|
+
}
|
|
200
|
+
return { registered: false, issueId: issueId || null, workFolder: workFolder || null, reason: error.message };
|
|
146
201
|
}
|
|
147
|
-
|
|
148
|
-
return null;
|
|
149
202
|
}
|
|
150
203
|
|
|
151
204
|
/**
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
205
|
+
* Auto-file rail: guarantee the new worktree's branch has a backing Kernel issue,
|
|
206
|
+
* WITHOUT anyone remembering `forge issue create` (kernel issue a4b8f56f). Best-effort
|
|
207
|
+
* and NON-BLOCKING — the worktree must stay usable even if tracking fails, so any error
|
|
208
|
+
* degrades to a stderr warning and null. Skipped when `--issue` already links a real
|
|
209
|
+
* issue. Idempotent via ensureBackingIssue (deduped by branch), so re-creating a
|
|
210
|
+
* worktree or later `forge push`/pre-push firing never mints a duplicate.
|
|
211
|
+
*
|
|
212
|
+
* @param {object} params
|
|
213
|
+
* @param {string} params.projectRoot
|
|
214
|
+
* @param {string} params.worktreePath
|
|
215
|
+
* @param {string} params.branch
|
|
216
|
+
* @param {string|null} params.issueId - explicit --issue (skip auto-file when set).
|
|
217
|
+
* @param {object} params.opts - DI (may inject `_ensureBackingIssue`, `_kernelDriver`, `_kernelBroker`, `_fs`).
|
|
218
|
+
* @returns {Promise<object|null>} the backing-issue descriptor, or null when skipped/unavailable.
|
|
157
219
|
*/
|
|
158
|
-
function
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
220
|
+
async function autoFileBackingIssue({ projectRoot, worktreePath, branch, issueId, opts }) {
|
|
221
|
+
if (issueId) return null; // explicit --issue already links a real issue.
|
|
222
|
+
const fsApi = opts._fs || fs;
|
|
223
|
+
try {
|
|
224
|
+
// Lazy require INSIDE the try: a module-load failure (missing file / broken
|
|
225
|
+
// sub-dependency) must degrade to null, never crash `forge worktree create`.
|
|
226
|
+
const ensureFn = opts._ensureBackingIssue || require('../kernel/backing-issue').ensureBackingIssue;
|
|
227
|
+
let driver = opts._kernelDriver;
|
|
228
|
+
let broker = opts._kernelBroker;
|
|
229
|
+
if (!driver || !broker) {
|
|
230
|
+
if (!fsApi.existsSync(path.join(projectRoot, '.git'))) return null; // no repo → no kernel
|
|
231
|
+
const { buildMigratedKernelIssueDeps } = require('../kernel/cli-broker-factory');
|
|
232
|
+
const deps = await buildMigratedKernelIssueDeps({ projectRoot });
|
|
233
|
+
driver = driver || deps.kernelDriver;
|
|
234
|
+
broker = broker || deps.kernelBroker;
|
|
235
|
+
}
|
|
236
|
+
return await ensureFn({ branch, projectRoot, worktreePath, driver, broker });
|
|
237
|
+
} catch (error) {
|
|
238
|
+
process.stderr.write(`forge worktree: backing issue not auto-filed (${error.message})\n`);
|
|
239
|
+
return null;
|
|
162
240
|
}
|
|
163
241
|
}
|
|
164
242
|
|
|
@@ -174,18 +252,23 @@ async function handleCreate(slug, flags, projectRoot, opts) {
|
|
|
174
252
|
const runFile = opts._exec || execFileSync;
|
|
175
253
|
const runSpawn = opts._spawn || spawnSync;
|
|
176
254
|
const fsApi = opts._fs || fs;
|
|
177
|
-
const platform = opts._platform || process.platform;
|
|
178
255
|
|
|
179
256
|
const branchName = flags['--branch'] || flags.branch || `feat/${slug}`;
|
|
257
|
+
const issueId = flags['--issue'] || null;
|
|
258
|
+
const workFolder = flags['--work-folder'] || null;
|
|
180
259
|
const worktreesDir = path.resolve(projectRoot, '.worktrees');
|
|
181
260
|
|
|
182
|
-
// Guard: Detect bare repo state
|
|
261
|
+
// Guard: Detect bare repo state - worktrees created from bare repos produce broken branches
|
|
183
262
|
// git rev-parse --show-toplevel throws in a bare repo (no working tree)
|
|
184
263
|
try {
|
|
185
264
|
runFile('git', ['-C', projectRoot, 'rev-parse', '--show-toplevel'], { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
186
|
-
} catch (
|
|
265
|
+
} catch (error) {
|
|
187
266
|
/* intentional: show-toplevel fails in bare repos */
|
|
188
|
-
|
|
267
|
+
const errMsg = String(error?.stderr || error?.message || '').trim();
|
|
268
|
+
if (/must be run in a work tree|this operation must be run in a work tree/i.test(errMsg)) {
|
|
269
|
+
return { success: false, error: 'bare repo detected' };
|
|
270
|
+
}
|
|
271
|
+
return { success: false, error: errMsg || 'git rev-parse --show-toplevel failed' };
|
|
189
272
|
}
|
|
190
273
|
|
|
191
274
|
// Security: Validate slug doesn't contain path traversal (OWASP A01/A03)
|
|
@@ -197,11 +280,23 @@ async function handleCreate(slug, flags, projectRoot, opts) {
|
|
|
197
280
|
|
|
198
281
|
// Step 0: Check if worktree already exists
|
|
199
282
|
if (fsApi.existsSync(worktreePath)) {
|
|
283
|
+
// A pre-existing worktree may be checked out on a different branch than the
|
|
284
|
+
// requested branchName — read the actual HEAD so the kernel row is accurate.
|
|
285
|
+
let existingBranch = branchName;
|
|
286
|
+
try {
|
|
287
|
+
const head = runFile('git', ['-C', worktreePath, 'rev-parse', '--abbrev-ref', 'HEAD'], { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
288
|
+
// A detached worktree reports the literal "HEAD" — don't persist that; keep branchName.
|
|
289
|
+
if (head && head !== 'HEAD') existingBranch = head;
|
|
290
|
+
} catch (_error) { /* not a resolvable worktree HEAD — fall back to branchName */ }
|
|
291
|
+
const linkage = await registerWorktreeLinkage({ projectRoot, worktreePath, branch: existingBranch, issueId, workFolder, opts });
|
|
292
|
+
const backing = await autoFileBackingIssue({ projectRoot, worktreePath, branch: existingBranch, issueId, opts });
|
|
200
293
|
return {
|
|
201
294
|
success: true,
|
|
202
295
|
reused: true,
|
|
203
296
|
message: `Worktree already exists at ${worktreePath}`,
|
|
204
297
|
worktreePath,
|
|
298
|
+
linkage,
|
|
299
|
+
backing,
|
|
205
300
|
};
|
|
206
301
|
}
|
|
207
302
|
|
|
@@ -216,21 +311,52 @@ async function handleCreate(slug, flags, projectRoot, opts) {
|
|
|
216
311
|
runFile('git', ['worktree', 'add', worktreePath, '-b', branchName], { stdio: 'pipe' });
|
|
217
312
|
}
|
|
218
313
|
|
|
219
|
-
// Step 3:
|
|
220
|
-
|
|
314
|
+
// Step 3: Populate node_modules (link to the shared install, else install).
|
|
315
|
+
// No per-worktree issue-store bootstrap is needed — the Kernel DB lives in the
|
|
316
|
+
// shared git common dir, so the new worktree already sees the same kernel.
|
|
317
|
+
let deps;
|
|
318
|
+
try {
|
|
319
|
+
deps = setupWorktreeDeps(worktreePath, projectRoot, {
|
|
320
|
+
spawnFn: runSpawn,
|
|
321
|
+
fsApi,
|
|
322
|
+
platform: opts._platform || process.platform,
|
|
323
|
+
});
|
|
324
|
+
} catch (error) {
|
|
325
|
+
// Surface the failure instead of leaving a worktree with no usable deps.
|
|
326
|
+
return { success: false, error: error.message, worktreePath, branch: branchName };
|
|
327
|
+
}
|
|
221
328
|
|
|
222
|
-
|
|
223
|
-
|
|
329
|
+
const linkage = await registerWorktreeLinkage({ projectRoot, worktreePath, branch: branchName, issueId, workFolder, opts });
|
|
330
|
+
const backing = await autoFileBackingIssue({ projectRoot, worktreePath, branch: branchName, issueId, opts });
|
|
224
331
|
|
|
225
|
-
|
|
332
|
+
return {
|
|
226
333
|
success: true,
|
|
227
334
|
worktreePath,
|
|
228
335
|
branch: branchName,
|
|
336
|
+
depsLinked: deps.linked,
|
|
337
|
+
depsInstalled: deps.installed,
|
|
338
|
+
linkage,
|
|
339
|
+
backing,
|
|
229
340
|
};
|
|
230
|
-
|
|
231
|
-
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Handle the "list" subcommand — read the worktree registry from the Kernel.
|
|
345
|
+
* @param {string} projectRoot - Project root path
|
|
346
|
+
* @param {object} opts - DI options (may inject `_kernelDriver`)
|
|
347
|
+
* @returns {Promise<object>} Result object with a `worktrees` array
|
|
348
|
+
*/
|
|
349
|
+
async function handleList(projectRoot, opts) {
|
|
350
|
+
try {
|
|
351
|
+
let driver = opts._kernelDriver;
|
|
352
|
+
if (!driver) {
|
|
353
|
+
const { buildMigratedKernelIssueDeps } = require('../kernel/cli-broker-factory');
|
|
354
|
+
driver = (await buildMigratedKernelIssueDeps({ projectRoot })).kernelDriver;
|
|
355
|
+
}
|
|
356
|
+
return { success: true, worktrees: driver.listWorktrees({}) };
|
|
357
|
+
} catch (error) {
|
|
358
|
+
return { success: false, error: error.message, worktrees: [] };
|
|
232
359
|
}
|
|
233
|
-
return result;
|
|
234
360
|
}
|
|
235
361
|
|
|
236
362
|
/**
|
|
@@ -247,29 +373,65 @@ async function handleRemove(slug, projectRoot, opts) {
|
|
|
247
373
|
}
|
|
248
374
|
|
|
249
375
|
const runFile = opts._exec || execFileSync;
|
|
250
|
-
const fsModule = opts._fs || fs;
|
|
251
376
|
const worktreePath = path.resolve(projectRoot, '.worktrees', slug);
|
|
252
377
|
|
|
253
|
-
//
|
|
254
|
-
|
|
255
|
-
if (stopResult.stopped) {
|
|
256
|
-
// Brief wait for file locks to release (Windows needs this)
|
|
257
|
-
await new Promise(r => setTimeout(r, 500));
|
|
258
|
-
}
|
|
259
|
-
|
|
378
|
+
// No issue-store server to stop: the Kernel SQLite DB lives in the shared git
|
|
379
|
+
// common dir, so removing a worktree directory never touches it.
|
|
260
380
|
runFile('git', ['worktree', 'remove', worktreePath], { stdio: 'pipe' });
|
|
261
381
|
|
|
262
382
|
return { success: true, removed: worktreePath };
|
|
263
383
|
}
|
|
264
384
|
|
|
385
|
+
// Long flags that take a value, in both `--flag value` and `--flag=value` forms.
|
|
386
|
+
const WORKTREE_VALUE_FLAGS = ['--branch', '--issue', '--work-folder'];
|
|
387
|
+
const WORKTREE_USAGE_HINT = 'Usage: forge worktree create <slug> [--branch <name>] [--issue <id>] [--work-folder <path>]';
|
|
388
|
+
|
|
389
|
+
function parseWorktreeArgs(args, flags) {
|
|
390
|
+
const positional = [];
|
|
391
|
+
const parsedFlags = { ...flags };
|
|
392
|
+
|
|
393
|
+
for (let i = 0; i < args.length; i++) {
|
|
394
|
+
const arg = args[i];
|
|
395
|
+
|
|
396
|
+
const spaceFlag = WORKTREE_VALUE_FLAGS.find(flag => arg === flag);
|
|
397
|
+
if (spaceFlag) {
|
|
398
|
+
const value = args[i + 1];
|
|
399
|
+
if (!value || value.startsWith('--')) {
|
|
400
|
+
return { error: `Missing value for ${spaceFlag}. ${WORKTREE_USAGE_HINT}` };
|
|
401
|
+
}
|
|
402
|
+
parsedFlags[spaceFlag] = value;
|
|
403
|
+
i++;
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const eqFlag = WORKTREE_VALUE_FLAGS.find(flag => arg.startsWith(`${flag}=`));
|
|
408
|
+
if (eqFlag) {
|
|
409
|
+
const value = arg.slice(`${eqFlag}=`.length);
|
|
410
|
+
if (!value) {
|
|
411
|
+
return { error: `Missing value for ${eqFlag}. ${WORKTREE_USAGE_HINT}` };
|
|
412
|
+
}
|
|
413
|
+
parsedFlags[eqFlag] = value;
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
positional.push(arg);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Back-compat alias consumed by handleCreate (flags['--branch'] || flags.branch).
|
|
421
|
+
if (parsedFlags['--branch']) parsedFlags.branch = parsedFlags['--branch'];
|
|
422
|
+
|
|
423
|
+
return { args: positional, flags: parsedFlags };
|
|
424
|
+
}
|
|
425
|
+
|
|
265
426
|
module.exports = {
|
|
266
427
|
name: 'worktree',
|
|
267
|
-
description: 'Manage isolated worktrees
|
|
268
|
-
usage: 'forge worktree <create|remove> <slug>',
|
|
428
|
+
description: 'Manage isolated worktrees',
|
|
429
|
+
usage: 'forge worktree <create|remove|list> <slug>',
|
|
269
430
|
flags: {
|
|
270
431
|
'--branch': 'Custom branch name (default: feat/<slug>)',
|
|
432
|
+
'--issue': 'Kernel issue id to link this worktree to (records issue → worktree)',
|
|
433
|
+
'--work-folder': 'Repo-relative work-folder this issue owns (records worktree → work-folder + drops a .forge-issue marker)',
|
|
271
434
|
},
|
|
272
|
-
stopDolt,
|
|
273
435
|
|
|
274
436
|
/**
|
|
275
437
|
* Main handler for the worktree command.
|
|
@@ -284,27 +446,45 @@ module.exports = {
|
|
|
284
446
|
* @returns {Promise<object>}
|
|
285
447
|
*/
|
|
286
448
|
handler: async (args, flags, projectRoot, opts = {}) => {
|
|
287
|
-
const
|
|
288
|
-
|
|
449
|
+
const parsed = parseWorktreeArgs(args, flags);
|
|
450
|
+
if (parsed.error) {
|
|
451
|
+
return { success: false, error: parsed.error };
|
|
452
|
+
}
|
|
289
453
|
|
|
290
|
-
|
|
454
|
+
const subcommand = parsed.args[0];
|
|
455
|
+
|
|
456
|
+
if (!subcommand || !['create', 'remove', 'list'].includes(subcommand)) {
|
|
291
457
|
return {
|
|
292
458
|
success: false,
|
|
293
|
-
error: 'Missing or invalid subcommand. Usage: forge worktree <create|remove> <slug>',
|
|
459
|
+
error: 'Missing or invalid subcommand. Usage: forge worktree <create|remove|list> <slug>',
|
|
294
460
|
};
|
|
295
461
|
}
|
|
296
462
|
|
|
463
|
+
// `list` reads the Kernel registry and takes no slug.
|
|
464
|
+
if (subcommand === 'list') {
|
|
465
|
+
return handleList(projectRoot, opts);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const slug = parsed.args[1];
|
|
297
469
|
if (!slug) {
|
|
298
470
|
return {
|
|
299
471
|
success: false,
|
|
300
|
-
error: 'Missing slug. Usage: forge worktree <create|remove> <slug>',
|
|
472
|
+
error: 'Missing slug. Usage: forge worktree <create|remove|list> <slug>',
|
|
301
473
|
};
|
|
302
474
|
}
|
|
303
475
|
|
|
304
476
|
if (subcommand === 'create') {
|
|
305
|
-
return handleCreate(slug, flags, projectRoot, opts);
|
|
477
|
+
return handleCreate(slug, parsed.flags, projectRoot, opts);
|
|
306
478
|
}
|
|
307
479
|
|
|
308
480
|
return handleRemove(slug, projectRoot, opts);
|
|
309
481
|
},
|
|
482
|
+
|
|
483
|
+
// Exposed for unit tests; not part of the CLI surface.
|
|
484
|
+
_internal: {
|
|
485
|
+
detectPackageManager,
|
|
486
|
+
setupWorktreeDeps,
|
|
487
|
+
runInstall,
|
|
488
|
+
autoFileBackingIssue,
|
|
489
|
+
},
|
|
310
490
|
};
|