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
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { repairWorkflowRuntimeAssets } = require('../commands/setup');
|
|
4
4
|
const { checkRuntimeHealth } = require('../runtime-health');
|
|
5
|
+
const { resolveIssueBackend } = require('../issue-backend');
|
|
5
6
|
const { normalizeStageId } = require('./stages');
|
|
6
7
|
const {
|
|
7
8
|
getAllowedTransitionsForWorkflowState,
|
|
@@ -10,6 +11,173 @@ const {
|
|
|
10
11
|
} = require('./state');
|
|
11
12
|
const { loadState, WORKFLOW_STATE_FILENAME } = require('./state-manager');
|
|
12
13
|
|
|
14
|
+
const STATELESS_ENTRY_STAGES = new Set(['plan', 'dev', 'validate', 'verify']);
|
|
15
|
+
|
|
16
|
+
const UUID_RE = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i;
|
|
17
|
+
|
|
18
|
+
// Canonical full-ladder path (critical superset). Used to derive a stage's
|
|
19
|
+
// immediate predecessor for the kernel completion gate.
|
|
20
|
+
const STAGE_PATH = Object.freeze(['plan', 'dev', 'validate', 'ship', 'review', 'verify']);
|
|
21
|
+
|
|
22
|
+
function stagePredecessor(stageId) {
|
|
23
|
+
const index = STAGE_PATH.indexOf(stageId);
|
|
24
|
+
return index > 0 ? STAGE_PATH[index - 1] : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Emit a warning to stderr. Writes even under FORGE_JSON=1 on purpose: stderr
|
|
28
|
+
// never pollutes machine-readable stdout, and a dropped kernel write must always
|
|
29
|
+
// leave a trace ("never silent") so a JSON-mode agent can see it.
|
|
30
|
+
function defaultStageWarn(message) {
|
|
31
|
+
process.stderr.write(`${message}\n`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Best-effort kernel write (action 'start' | 'complete') at the stage
|
|
35
|
+
// chokepoint. Idempotent per (issue_id, stage). A failure warns to stderr but
|
|
36
|
+
// never throws — ship's gate stays tolerant of whatever IS durably recorded.
|
|
37
|
+
function recordStageRunSafe(driver, issueId, stageId, action, warn = defaultStageWarn) {
|
|
38
|
+
if (!driver || !issueId || !stageId || typeof driver.recordStageRun !== 'function') {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
driver.recordStageRun({ issue_id: issueId, stage: stageId, action }, {});
|
|
44
|
+
return true;
|
|
45
|
+
} catch (error) {
|
|
46
|
+
warn(`[forge] could not record stage '${stageId}' (${action}) for ${issueId} in the kernel: ${error.message}`);
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Latest stage-run row for a specific (issue, stage), or null.
|
|
52
|
+
function findLatestStageRun(driver, issueId, stage) {
|
|
53
|
+
if (!driver || typeof driver.listStageRuns !== 'function') {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const runs = driver.listStageRuns({ issue_id: issueId }, {}) || [];
|
|
59
|
+
let latest = null;
|
|
60
|
+
for (const run of runs) {
|
|
61
|
+
if (run?.stage === stage) {
|
|
62
|
+
latest = run;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return latest;
|
|
66
|
+
} catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Decide whether entering stageId is allowed given ONLY the kernel's recorded
|
|
72
|
+
// stage history (used when no inline/file workflow state exists):
|
|
73
|
+
// - Stateless stages (plan/dev/validate/verify) are always re-entrant, so the
|
|
74
|
+
// dev<->validate rework loop never dead-ends.
|
|
75
|
+
// - A gated stage (ship/review) requires its immediate predecessor to be
|
|
76
|
+
// COMPLETED (status 'done'); merely ENTERING validate does not unlock ship.
|
|
77
|
+
// - When nothing is recorded for the predecessor, returns { kernelEmpty } so
|
|
78
|
+
// the caller falls through to the fail-closed hard block.
|
|
79
|
+
function evaluateKernelStageGate(driver, issueId, stageId) {
|
|
80
|
+
if (STATELESS_ENTRY_STAGES.has(stageId)) {
|
|
81
|
+
return { allowed: true };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const predecessor = stagePredecessor(stageId);
|
|
85
|
+
const predecessorRun = predecessor ? findLatestStageRun(driver, issueId, predecessor) : null;
|
|
86
|
+
if (predecessorRun?.status === 'done') {
|
|
87
|
+
return { allowed: true };
|
|
88
|
+
}
|
|
89
|
+
if (predecessorRun) {
|
|
90
|
+
return {
|
|
91
|
+
allowed: false,
|
|
92
|
+
reason: `Stage ${stageId} requires ${predecessor} to be completed first (currently ${predecessorRun.status}).`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
return { allowed: false, kernelEmpty: true };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Verify a kernel issue exists before binding stage state to it (F4a: a UUID
|
|
99
|
+
// parsed from a branch name must not bind to a phantom issue).
|
|
100
|
+
async function kernelIssueExists(driver, issueId) {
|
|
101
|
+
if (!driver || typeof driver.findIssueIdsByPrefix !== 'function') {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
// findIssueIdsByPrefix returns rows ({ id, title }); tolerate plain-id shapes too.
|
|
107
|
+
const matches = await driver.findIssueIdsByPrefix(issueId, 6, {}, {});
|
|
108
|
+
return Array.isArray(matches) && matches.some(row => (row?.id ?? row) === issueId);
|
|
109
|
+
} catch {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Resolve the kernel issue that THIS worktree/branch is working on, so stage
|
|
115
|
+
// state can be read and written without an explicit issue argument on `forge
|
|
116
|
+
// ship`. Prefers the branch->issue linkage registry (authoritative); falls back
|
|
117
|
+
// to a UUID encoded in the branch name, but ONLY after verifying it exists.
|
|
118
|
+
async function resolveActiveIssueId(driver, branch) {
|
|
119
|
+
if (!driver || !branch) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
if (typeof driver.listWorktrees === 'function') {
|
|
125
|
+
const rows = driver.listWorktrees() || [];
|
|
126
|
+
const match = rows.find(row => row && row.branch === branch && row.issue_id);
|
|
127
|
+
if (match) {
|
|
128
|
+
return match.issue_id;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
} catch {
|
|
132
|
+
// Fall through to branch-name parsing.
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const encoded = UUID_RE.exec(String(branch));
|
|
136
|
+
if (encoded && await kernelIssueExists(driver, encoded[0])) {
|
|
137
|
+
return encoded[0];
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Lazily build a kernel driver from the project root (the real CLI path).
|
|
143
|
+
// Best-effort: returns null when the kernel is unavailable so the caller
|
|
144
|
+
// degrades to legacy file/beads state instead of crashing a stage command.
|
|
145
|
+
async function buildKernelDriver(projectRoot) {
|
|
146
|
+
if (!projectRoot) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
const { buildMigratedKernelIssueDeps } = require('../kernel/cli-broker-factory');
|
|
152
|
+
const deps = await buildMigratedKernelIssueDeps({ projectRoot });
|
|
153
|
+
return deps.kernelDriver || null;
|
|
154
|
+
} catch {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function detectBranchName(projectRoot) {
|
|
160
|
+
try {
|
|
161
|
+
const { detectWorktree } = require('../detect-worktree');
|
|
162
|
+
const info = detectWorktree(projectRoot || process.cwd());
|
|
163
|
+
return info?.branch || null;
|
|
164
|
+
} catch {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Resolve { driver, issueId } for kernel stage-state: build the driver from the
|
|
170
|
+
// project root and resolve the active issue from the branch when not explicitly
|
|
171
|
+
// injected. Best-effort — returns nulls when the kernel is absent.
|
|
172
|
+
async function resolveKernelContext({ kernelDriver, activeIssueId, branch, projectRoot }) {
|
|
173
|
+
const driver = kernelDriver || await buildKernelDriver(projectRoot);
|
|
174
|
+
let issueId = activeIssueId || null;
|
|
175
|
+
if (driver && !issueId) {
|
|
176
|
+
issueId = await resolveActiveIssueId(driver, branch || detectBranchName(projectRoot));
|
|
177
|
+
}
|
|
178
|
+
return { driver, issueId };
|
|
179
|
+
}
|
|
180
|
+
|
|
13
181
|
function getOverrideInput(flags = {}) {
|
|
14
182
|
if (Object.hasOwn(flags, 'overrideStage')) {
|
|
15
183
|
return flags.overrideStage;
|
|
@@ -101,55 +269,18 @@ function formatDiagnostics(diagnostics = []) {
|
|
|
101
269
|
.join('; ');
|
|
102
270
|
}
|
|
103
271
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
return { allowed: true };
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (projectRoot) {
|
|
111
|
-
repairWorkflowRuntimeAssets(projectRoot);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
let runtimeHealth = health || checkRuntimeHealth(projectRoot);
|
|
115
|
-
if (runtimeHealth.hardStop && typeof repairRuntime === 'function') {
|
|
116
|
-
const repairedHealth = await repairRuntime({
|
|
117
|
-
commandName,
|
|
118
|
-
flags,
|
|
119
|
-
projectRoot,
|
|
120
|
-
workflowState,
|
|
121
|
-
health: runtimeHealth,
|
|
122
|
-
});
|
|
123
|
-
if (repairedHealth) {
|
|
124
|
-
runtimeHealth = repairedHealth;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
if (runtimeHealth.hardStop) {
|
|
128
|
-
throw new Error(`Stage ${stageId} blocked by runtime prerequisites: ${formatDiagnostics(runtimeHealth.diagnostics)}`);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const stateInput = resolveWorkflowStateInput(workflowState, flags, args, projectRoot);
|
|
132
|
-
const currentState = readWorkflowStateInput(stateInput);
|
|
133
|
-
if (!currentState) {
|
|
134
|
-
if (stageId === 'plan') {
|
|
135
|
-
return { allowed: true, stage: stageId, workflowState: null };
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
throw new Error(
|
|
139
|
-
`Stage ${stageId} requires authoritative workflow state. ` +
|
|
140
|
-
`Provide --workflow-state or restore ${WORKFLOW_STATE_FILENAME} before continuing.`
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
|
|
272
|
+
// Enforce a stage entry against authoritative FILE/inline workflow state
|
|
273
|
+
// (unchanged legacy behavior: normal path-transition + override rules).
|
|
274
|
+
function enforceWithFileState(currentState, stageId, flags, args, finish) {
|
|
144
275
|
const currentStage = currentState.currentStage;
|
|
145
276
|
const classification = currentState.workflowDecisions?.classification;
|
|
146
277
|
if (!currentStage || !classification || stageId === currentStage) {
|
|
147
|
-
return { allowed: true, stage: stageId, workflowState: currentState };
|
|
278
|
+
return finish({ allowed: true, stage: stageId, workflowState: currentState });
|
|
148
279
|
}
|
|
149
280
|
|
|
150
281
|
const allowedTransitions = getAllowedTransitionsForWorkflowState(currentState);
|
|
151
282
|
if (allowedTransitions.includes(stageId)) {
|
|
152
|
-
return { allowed: true, stage: stageId, workflowState: currentState };
|
|
283
|
+
return finish({ allowed: true, stage: stageId, workflowState: currentState });
|
|
153
284
|
}
|
|
154
285
|
|
|
155
286
|
const override = parseOverride(flags, args);
|
|
@@ -166,12 +297,128 @@ async function enforceStageEntry({ commandName, args = [], flags = {}, projectRo
|
|
|
166
297
|
);
|
|
167
298
|
}
|
|
168
299
|
|
|
169
|
-
return {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
300
|
+
return finish({ allowed: true, stage: stageId, workflowState: currentState, override });
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function isInlineStateProvided(workflowState, flags, args) {
|
|
304
|
+
return Boolean(
|
|
305
|
+
workflowState || flags.workflowState || flags['--workflow-state'] || getCliFlagValue('--workflow-state', args)
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Enforce a stage entry against kernel-recorded stage state (completion gate).
|
|
310
|
+
// Returns a decided enforcement result, or null to fall through to the
|
|
311
|
+
// stateless / hard-block rules.
|
|
312
|
+
function enforceWithKernelState(driver, issueId, stageId, finish) {
|
|
313
|
+
const gate = evaluateKernelStageGate(driver, issueId, stageId);
|
|
314
|
+
if (gate.allowed) {
|
|
315
|
+
return finish({ allowed: true, stage: stageId, workflowState: null });
|
|
316
|
+
}
|
|
317
|
+
if (!gate.kernelEmpty) {
|
|
318
|
+
throw new Error(gate.reason);
|
|
319
|
+
}
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async function resolveStageRuntimeHealth({ health, checkHealth, projectRoot, issueBackend, commandName, flags, workflowState, repairRuntime }) {
|
|
324
|
+
const runHealthCheck = checkHealth || checkRuntimeHealth;
|
|
325
|
+
let runtimeHealth = health || runHealthCheck(projectRoot, { issueBackend });
|
|
326
|
+
if (runtimeHealth.hardStop && typeof repairRuntime === 'function') {
|
|
327
|
+
const repaired = await repairRuntime({ commandName, flags, projectRoot, workflowState, health: runtimeHealth });
|
|
328
|
+
if (repaired) {
|
|
329
|
+
runtimeHealth = repaired;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return runtimeHealth;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async function enforceStageEntry({
|
|
336
|
+
commandName,
|
|
337
|
+
args = [],
|
|
338
|
+
flags = {},
|
|
339
|
+
projectRoot,
|
|
340
|
+
workflowState,
|
|
341
|
+
health,
|
|
342
|
+
repairRuntime,
|
|
343
|
+
checkHealth,
|
|
344
|
+
// B1 — kernel stage-state authority. Injectable for tests; the real CLI sets
|
|
345
|
+
// autoResolveKernel:true so the driver + active issue are resolved from the
|
|
346
|
+
// worktree. When neither a driver nor autoResolveKernel is provided, the
|
|
347
|
+
// kernel path is inert and behavior matches the legacy file/beads state.
|
|
348
|
+
kernelDriver,
|
|
349
|
+
activeIssueId,
|
|
350
|
+
branch,
|
|
351
|
+
autoResolveKernel = false,
|
|
352
|
+
warn = defaultStageWarn,
|
|
353
|
+
} = {}) {
|
|
354
|
+
const stageId = normalizeStageId(commandName);
|
|
355
|
+
if (!stageId) {
|
|
356
|
+
return { allowed: true };
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (projectRoot) {
|
|
360
|
+
repairWorkflowRuntimeAssets(projectRoot);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Resolve the active issue backend (env > .forge/config.yaml > default 'kernel') so
|
|
364
|
+
// the runtime gate only treats bd as a hard prerequisite for the beads backend. The
|
|
365
|
+
// kernel default needs no bd, so stages must run without it.
|
|
366
|
+
const issueBackend = resolveIssueBackend({ deps: {}, env: process.env, projectRoot, warn: () => {} });
|
|
367
|
+
const runtimeHealth = await resolveStageRuntimeHealth({
|
|
368
|
+
health, checkHealth, projectRoot, issueBackend, commandName, flags, workflowState, repairRuntime,
|
|
369
|
+
});
|
|
370
|
+
if (runtimeHealth.hardStop) {
|
|
371
|
+
throw new Error(`Stage ${stageId} blocked by runtime prerequisites: ${formatDiagnostics(runtimeHealth.diagnostics)}`);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const stateInput = resolveWorkflowStateInput(workflowState, flags, args, projectRoot);
|
|
375
|
+
|
|
376
|
+
// Kernel stage-state authority is active when a driver is injected (tests) or
|
|
377
|
+
// the caller opts in (real CLI via autoResolveKernel). Inline/flag state
|
|
378
|
+
// disables it: the caller is explicitly driving state, so no kernel side
|
|
379
|
+
// effects should occur.
|
|
380
|
+
const kernelEnabled = !isInlineStateProvided(workflowState, flags, args)
|
|
381
|
+
&& (Boolean(kernelDriver) || autoResolveKernel === true);
|
|
382
|
+
const { driver, issueId } = kernelEnabled
|
|
383
|
+
? await resolveKernelContext({ kernelDriver, activeIssueId, branch, projectRoot })
|
|
384
|
+
: { driver: null, issueId: null };
|
|
385
|
+
const kernelActive = Boolean(kernelEnabled && driver && issueId);
|
|
386
|
+
|
|
387
|
+
// On an allowed entry, record the stage as started (active) AND return a
|
|
388
|
+
// recordCompletion() the command runner calls after the handler SUCCEEDS — so
|
|
389
|
+
// a stage only counts as 'done' when its command actually passed (the ship
|
|
390
|
+
// gate below requires the predecessor to be done, not merely entered).
|
|
391
|
+
const finish = (result) => {
|
|
392
|
+
if (kernelActive) {
|
|
393
|
+
recordStageRunSafe(driver, issueId, stageId, 'start', warn);
|
|
394
|
+
result.recordCompletion = () => recordStageRunSafe(driver, issueId, stageId, 'complete', warn);
|
|
395
|
+
}
|
|
396
|
+
return result;
|
|
174
397
|
};
|
|
398
|
+
|
|
399
|
+
const currentState = readWorkflowStateInput(stateInput);
|
|
400
|
+
if (currentState) {
|
|
401
|
+
return enforceWithFileState(currentState, stageId, flags, args, finish);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// No inline/file state: the kernel is authoritative. Gate on recorded stage
|
|
405
|
+
// completions (tolerant read) so `ship` is reachable from a pure-CLI
|
|
406
|
+
// plan->dev->validate progression with no .forge-state.json.
|
|
407
|
+
if (kernelActive) {
|
|
408
|
+
const decided = enforceWithKernelState(driver, issueId, stageId, finish);
|
|
409
|
+
if (decided) {
|
|
410
|
+
return decided;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (STATELESS_ENTRY_STAGES.has(stageId)) {
|
|
415
|
+
return finish({ allowed: true, stage: stageId, workflowState: null });
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
throw new Error(
|
|
419
|
+
`Stage ${stageId} requires authoritative workflow state. ` +
|
|
420
|
+
`Provide --workflow-state or restore ${WORKFLOW_STATE_FILENAME} before continuing.`
|
|
421
|
+
);
|
|
175
422
|
}
|
|
176
423
|
|
|
177
424
|
module.exports = {
|
|
@@ -180,4 +427,8 @@ module.exports = {
|
|
|
180
427
|
parseOverride,
|
|
181
428
|
resolveWorkflowStateInput,
|
|
182
429
|
readWorkflowStateFile,
|
|
430
|
+
resolveActiveIssueId,
|
|
431
|
+
evaluateKernelStageGate,
|
|
432
|
+
recordStageRunSafe,
|
|
433
|
+
stagePredecessor,
|
|
183
434
|
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stage-transition auto-recording (5a5ba3a6).
|
|
5
|
+
*
|
|
6
|
+
* PR #348 (f61601ab) added the stage_runs record/read capability but nothing
|
|
7
|
+
* populated it automatically — `current_stage` stayed unknown. The Descriptive
|
|
8
|
+
* Context Convention (AGENTS.md) already has agents record each stage boundary as
|
|
9
|
+
* a kernel issue comment shaped:
|
|
10
|
+
*
|
|
11
|
+
* stage: <from> -> <to>
|
|
12
|
+
* summary: ...
|
|
13
|
+
*
|
|
14
|
+
* This module turns that existing, already-followed convention into a structured
|
|
15
|
+
* stage_run WITHOUT any new manual verb call: parse the `stage:` line and, best
|
|
16
|
+
* effort, complete the from-stage and start the to-stage. Recording is strictly
|
|
17
|
+
* non-blocking — a stage_run write failure must NEVER break the comment that
|
|
18
|
+
* triggered it.
|
|
19
|
+
*
|
|
20
|
+
* @module workflow/stage-transition
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const { normalizeStageId } = require('./stages');
|
|
24
|
+
|
|
25
|
+
// `stage: <from> -> <to>` on its own line, case-insensitive, arrow spacing optional.
|
|
26
|
+
const STAGE_LINE = /^\s*stage:\s*([a-z]+)\s*->\s*([a-z]+)\s*$/im;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Parse a stage-transition line out of a comment body.
|
|
30
|
+
*
|
|
31
|
+
* @param {string} body - Comment body (may be multi-line).
|
|
32
|
+
* @returns {{from: string, to: string}|null} Normalized stages, or null when the
|
|
33
|
+
* body has no valid `stage: X -> Y` line (both tokens must be canonical stages).
|
|
34
|
+
*/
|
|
35
|
+
function parseStageTransition(body) {
|
|
36
|
+
if (typeof body !== 'string' || body.length === 0) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const match = body.match(STAGE_LINE);
|
|
40
|
+
if (!match) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
// STAGE_LINE is case-insensitive, so an uppercase token (e.g. `stage: DEV ->
|
|
44
|
+
// VALIDATE`) matches. normalizeStageId only knows lowercase canonical ids, so
|
|
45
|
+
// lowercase both captures first or an uppercase-but-valid stage is wrongly rejected.
|
|
46
|
+
const from = normalizeStageId(match[1].toLowerCase());
|
|
47
|
+
const to = normalizeStageId(match[2].toLowerCase());
|
|
48
|
+
if (!from || !to) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
return { from, to };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Best-effort record of a stage transition into stage_runs. Parses the comment
|
|
56
|
+
* body; on a valid transition it completes the from-stage and starts the to-stage.
|
|
57
|
+
*
|
|
58
|
+
* The write must be ATOMIC: completing `from` and starting `to` are one logical
|
|
59
|
+
* transition, so a failure partway through must leave neither persisted (otherwise
|
|
60
|
+
* `current_stage` reflects a half-transition — from marked done, to never started).
|
|
61
|
+
* The preferred path is a single transactional `driver.recordStageTransition` that
|
|
62
|
+
* wraps both writes in one transaction; a driver that only exposes `recordStageRun`
|
|
63
|
+
* falls back to two sequential writes (non-atomic, tolerated only because the whole
|
|
64
|
+
* operation is non-blocking).
|
|
65
|
+
*
|
|
66
|
+
* ANY failure (parse miss, missing driver, DB error) is swallowed and reported as
|
|
67
|
+
* `{ recorded: false }` — this function never throws.
|
|
68
|
+
*
|
|
69
|
+
* @param {object} params
|
|
70
|
+
* @param {object} [params.driver] - Kernel driver exposing recordStageTransition
|
|
71
|
+
* (preferred) and/or recordStageRun(input, config).
|
|
72
|
+
* @param {string} params.issueId - Full kernel issue id the comment targets.
|
|
73
|
+
* @param {string} params.body - The comment body just written.
|
|
74
|
+
* @param {object} [params.config] - Driver config (e.g. { databasePath }).
|
|
75
|
+
* @returns {{recorded: boolean, from?: string, to?: string}}
|
|
76
|
+
*/
|
|
77
|
+
function recordStageTransition({ driver, issueId, body, config = {} } = {}) {
|
|
78
|
+
try {
|
|
79
|
+
const transition = parseStageTransition(body);
|
|
80
|
+
if (!transition || !issueId || !driver) {
|
|
81
|
+
return { recorded: false };
|
|
82
|
+
}
|
|
83
|
+
// Preferred: one atomic driver op — complete(from) + start(to) inside a single
|
|
84
|
+
// transaction, so a mid-transition failure rolls back and never persists a
|
|
85
|
+
// wrong `current_stage`.
|
|
86
|
+
if (typeof driver.recordStageTransition === 'function') {
|
|
87
|
+
driver.recordStageTransition(
|
|
88
|
+
{ issue_id: issueId, from: transition.from, to: transition.to },
|
|
89
|
+
config,
|
|
90
|
+
);
|
|
91
|
+
return { from: transition.from, to: transition.to, recorded: true };
|
|
92
|
+
}
|
|
93
|
+
// Fallback for a minimal driver without the atomic op: two sequential writes.
|
|
94
|
+
if (typeof driver.recordStageRun === 'function') {
|
|
95
|
+
driver.recordStageRun(
|
|
96
|
+
{ issue_id: issueId, stage: transition.from, action: 'complete' },
|
|
97
|
+
config,
|
|
98
|
+
);
|
|
99
|
+
driver.recordStageRun(
|
|
100
|
+
{ issue_id: issueId, stage: transition.to, action: 'start' },
|
|
101
|
+
config,
|
|
102
|
+
);
|
|
103
|
+
return { from: transition.from, to: transition.to, recorded: true };
|
|
104
|
+
}
|
|
105
|
+
return { recorded: false };
|
|
106
|
+
} catch {
|
|
107
|
+
// Non-blocking by contract: never let a stage_run write break the comment.
|
|
108
|
+
return { recorded: false };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
module.exports = {
|
|
113
|
+
parseStageTransition,
|
|
114
|
+
recordStageTransition,
|
|
115
|
+
};
|
package/lib/workflow/stages.js
CHANGED
|
@@ -15,7 +15,6 @@ const STAGE_IDS = Object.freeze([
|
|
|
15
15
|
'validate',
|
|
16
16
|
'ship',
|
|
17
17
|
'review',
|
|
18
|
-
'premerge',
|
|
19
18
|
'verify',
|
|
20
19
|
]);
|
|
21
20
|
|
|
@@ -25,7 +24,6 @@ const STAGE_LABELS = Object.freeze({
|
|
|
25
24
|
validate: 'Validate',
|
|
26
25
|
ship: 'Ship',
|
|
27
26
|
review: 'Review',
|
|
28
|
-
premerge: 'Premerge',
|
|
29
27
|
verify: 'Verify',
|
|
30
28
|
});
|
|
31
29
|
|
|
@@ -35,14 +33,13 @@ const STAGE_COMMANDS = Object.freeze({
|
|
|
35
33
|
validate: '/validate',
|
|
36
34
|
ship: '/ship',
|
|
37
35
|
review: '/review',
|
|
38
|
-
premerge: '/premerge',
|
|
39
36
|
verify: '/verify',
|
|
40
37
|
});
|
|
41
38
|
|
|
42
39
|
const WORKFLOW_STAGE_MATRIX = Object.freeze({
|
|
43
|
-
critical: Object.freeze(['plan', 'dev', 'validate', 'ship', 'review', '
|
|
44
|
-
standard: Object.freeze(['plan', 'dev', 'validate', 'ship', 'review'
|
|
45
|
-
refactor: Object.freeze(['plan', 'dev', 'validate', 'ship'
|
|
40
|
+
critical: Object.freeze(['plan', 'dev', 'validate', 'ship', 'review', 'verify']),
|
|
41
|
+
standard: Object.freeze(['plan', 'dev', 'validate', 'ship', 'review']),
|
|
42
|
+
refactor: Object.freeze(['plan', 'dev', 'validate', 'ship']),
|
|
46
43
|
simple: Object.freeze(['dev', 'validate', 'ship']),
|
|
47
44
|
hotfix: Object.freeze(['dev', 'validate', 'ship']),
|
|
48
45
|
// Docs-only work intentionally reuses /verify as a pre-ship content check to
|
|
@@ -51,6 +48,18 @@ const WORKFLOW_STAGE_MATRIX = Object.freeze({
|
|
|
51
48
|
docs: Object.freeze(['verify', 'ship']),
|
|
52
49
|
});
|
|
53
50
|
|
|
51
|
+
// Pre-merge is a task-type gate/checkpoint embedded inside existing stages
|
|
52
|
+
// (the doc-completion + PR-handoff checks that run before merge), not a
|
|
53
|
+
// standalone universal workflow stage. It is keyed with a hyphen ('pre-merge')
|
|
54
|
+
// so it never re-enters the stage model. `enabledFor` lists the classifications
|
|
55
|
+
// that run the gate; `embeddedIn` names the stages where it fires.
|
|
56
|
+
const WORKFLOW_GATES = Object.freeze({
|
|
57
|
+
'pre-merge': Object.freeze({
|
|
58
|
+
embeddedIn: Object.freeze(['ship', 'review']),
|
|
59
|
+
enabledFor: Object.freeze(['critical', 'standard', 'refactor']),
|
|
60
|
+
}),
|
|
61
|
+
});
|
|
62
|
+
|
|
54
63
|
const WORKFLOW_TERMINAL_STAGES = Object.freeze(Object.entries(WORKFLOW_STAGE_MATRIX).reduce((accumulator, [classification, path]) => {
|
|
55
64
|
accumulator[classification] = path.at(-1);
|
|
56
65
|
return accumulator;
|
|
@@ -77,6 +86,19 @@ function getWorkflowPath(classification) {
|
|
|
77
86
|
return normalized ? WORKFLOW_STAGE_MATRIX[normalized] : Object.freeze([]);
|
|
78
87
|
}
|
|
79
88
|
|
|
89
|
+
function getGatesForClassification(classification) {
|
|
90
|
+
const normalized = normalizeClassification(classification);
|
|
91
|
+
if (!normalized) {
|
|
92
|
+
return Object.freeze([]);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return Object.freeze(
|
|
96
|
+
Object.entries(WORKFLOW_GATES)
|
|
97
|
+
.filter(([, gate]) => gate.enabledFor.includes(normalized))
|
|
98
|
+
.map(([gateId]) => gateId),
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
80
102
|
function getStageWorkflow(stageId, classification) {
|
|
81
103
|
const normalizedStage = normalizeStageId(stageId);
|
|
82
104
|
const normalizedClassification = normalizeClassification(classification);
|
|
@@ -186,6 +208,7 @@ module.exports = {
|
|
|
186
208
|
STAGE_LABELS,
|
|
187
209
|
STAGE_COMMANDS,
|
|
188
210
|
WORKFLOW_STAGE_MATRIX,
|
|
211
|
+
WORKFLOW_GATES,
|
|
189
212
|
WORKFLOW_TERMINAL_STAGES,
|
|
190
213
|
STAGE_TRANSITIONS,
|
|
191
214
|
STAGE_MODEL,
|
|
@@ -193,6 +216,7 @@ module.exports = {
|
|
|
193
216
|
normalizeStageId,
|
|
194
217
|
isCanonicalStageId,
|
|
195
218
|
getWorkflowPath,
|
|
219
|
+
getGatesForClassification,
|
|
196
220
|
getStageWorkflow,
|
|
197
221
|
getAllowedTransitions,
|
|
198
222
|
canTransition,
|