forge-workflow 0.0.9 → 0.1.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/rules/{greptile-review-process.md → review-process.md} +56 -41
- package/.claude/scripts/{greptile-resolve.sh → review-resolve.sh} +13 -3
- package/.cursor/rules/permissions-guidance.mdc +2 -2
- package/.forge/hooks/check-tdd.js +3 -0
- package/.forge/hooks/forge-native-hook.js +245 -0
- package/.forge/protected-paths.yaml +157 -0
- package/AGENTS.md +151 -61
- package/CHANGELOG.md +681 -0
- package/CLAUDE.md +9 -106
- package/QUICKSTART.md +171 -0
- package/README.md +271 -363
- package/bin/forge-cmd.js +120 -9
- package/bin/forge-preflight.js +26 -5
- package/bin/forge.js +466 -489
- package/docs/INDEX.md +93 -0
- package/docs/PROJECT_DESIGN.md +685 -0
- package/docs/architecture/index.md +66 -0
- package/docs/architecture/notes/README.md +35 -0
- package/docs/architecture/subsystems/README.md +46 -0
- package/docs/{TOOLCHAIN.md → forge/TOOLCHAIN.md} +56 -47
- package/docs/forge/VALIDATION.md +82 -0
- package/docs/{AGENT_INSTALL_PROMPT.md → guides/AGENT_INSTALL_PROMPT.md} +3 -3
- package/docs/guides/BEADS_GITHUB_SYNC.md +32 -0
- package/docs/{ENHANCED_ONBOARDING.md → guides/ENHANCED_ONBOARDING.md} +16 -12
- package/docs/guides/GREPTILE_SETUP.md +46 -0
- package/docs/guides/MANUAL_REVIEW_GUIDE.md +58 -0
- package/docs/guides/MIGRATION.md +56 -0
- package/docs/guides/SETUP.md +118 -0
- package/docs/guides/SUPPORT.md +185 -0
- package/docs/guides/WORKFLOW_TEMPLATES.md +74 -0
- package/docs/guides/memory-backends.md +183 -0
- package/docs/reference/ADAPTERS.md +128 -0
- package/docs/reference/AGENT_SKILL_PARITY.md +175 -0
- package/docs/reference/COMMANDS.md +205 -0
- package/docs/reference/DECISION_DRIFT_GUARDS.md +97 -0
- package/docs/{EXAMPLES.md → reference/EXAMPLES.md} +7 -5
- package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +135 -0
- package/docs/reference/HERMES_INTEGRATION.md +118 -0
- package/docs/reference/INSIGHTS_RECAP.md +63 -0
- package/docs/reference/INSTALL.md +164 -0
- package/docs/reference/KERNEL_TAXONOMY_VALIDATION.md +161 -0
- package/docs/reference/PROTECTED_PATH_MANIFEST.md +25 -0
- package/docs/reference/RELEASE.md +68 -0
- package/docs/reference/RESEARCH_TEMPLATE.md +292 -0
- package/docs/{ROADMAP.md → reference/ROADMAP.md} +12 -9
- package/docs/reference/SKILLS.md +35 -0
- package/docs/reference/STATUS_BOARD.md +80 -0
- package/docs/reference/TEMPLATES.md +106 -0
- package/docs/reference/TOOLCHAIN.md +658 -0
- package/docs/reference/VALIDATION.md +82 -0
- package/docs/reference/agent-permissions.md +169 -0
- package/docs/reference/beads-to-kernel-migration-ux.md +61 -0
- package/docs/reference/control-plane-guarantees.md +125 -0
- package/docs/reference/dependency-chain.md +331 -0
- package/docs/reference/forge-kernel-issue-command-contract.md +161 -0
- package/docs/reference/forge-kernel-schema.md +72 -0
- package/docs/reference/kernel-conflict-evaluators.md +27 -0
- package/docs/reference/patch-md-format.md +77 -0
- package/docs/reference/protected-state-surfaces.md +59 -0
- package/docs/reference/shepherd.md +115 -0
- package/docs/reference/superpowers-analysis.md +320 -0
- package/docs/reference/superpowers-integration-options.md +404 -0
- package/docs/reference/test-environment.md +519 -0
- package/docs/reference/upgrade-safety.md +59 -0
- package/lefthook.yml +18 -0
- package/lib/adapter-cli.js +307 -0
- package/lib/adapters/beads-issue-adapter.js +127 -0
- package/lib/adapters/beads-kernel-compat.js +1042 -0
- package/lib/adapters/greptile-review-adapter.js +141 -0
- package/lib/adapters/kernel-issue-adapter.js +101 -0
- package/lib/adapters/pr-state-adapter.js +484 -0
- package/lib/adoption-profiles.js +126 -0
- package/lib/agents/README.md +2 -6
- package/lib/agents/claude.plugin.json +3 -8
- package/lib/agents/codex.plugin.json +9 -1
- package/lib/agents/cursor.plugin.json +2 -6
- package/lib/agents/hermes.plugin.json +22 -0
- package/lib/agents-config.js +39 -1236
- package/lib/audit-evidence.js +282 -0
- package/lib/beads-setup.js +225 -28
- package/lib/beads-sync-scaffold.js +36 -107
- package/lib/codex-skills.js +51 -1
- package/lib/commands/_issue.js +744 -70
- package/lib/commands/_manifest.js +91 -0
- package/lib/commands/_registry.js +85 -34
- package/lib/commands/_resolve-command-opts.js +261 -0
- package/lib/commands/_serve-security.js +270 -0
- package/lib/commands/adapter.js +12 -0
- package/lib/commands/add.js +118 -0
- package/lib/commands/audit.js +70 -0
- package/lib/commands/blocked.js +5 -0
- package/lib/commands/board.js +64 -0
- package/lib/commands/claim.js +21 -2
- package/lib/commands/claims.js +7 -0
- package/lib/commands/clean.js +485 -75
- package/lib/commands/close.js +2 -2
- package/lib/commands/comment.js +5 -0
- package/lib/commands/control.js +148 -0
- package/lib/commands/create.js +2 -2
- package/lib/commands/dev.js +185 -7
- package/lib/commands/doc-gate.js +336 -0
- package/lib/commands/doctor.js +156 -0
- package/lib/commands/explain.js +15 -0
- package/lib/commands/export.js +237 -0
- package/lib/commands/gate.js +192 -0
- package/lib/commands/hooks.js +242 -0
- package/lib/commands/inbox.js +118 -0
- package/lib/commands/init.js +598 -0
- package/lib/commands/insights.js +79 -0
- package/lib/commands/issue.js +12 -1
- package/lib/commands/issues.js +66 -0
- package/lib/commands/lint.js +5 -0
- package/lib/commands/list.js +2 -2
- package/lib/commands/merge.js +312 -0
- package/lib/commands/migrate.js +523 -0
- package/lib/commands/new.js +12 -0
- package/lib/commands/options.js +241 -0
- package/lib/commands/orient.js +13 -0
- package/lib/commands/orphans.js +5 -0
- package/lib/commands/patch.js +67 -0
- package/lib/commands/plan.js +436 -24
- package/lib/commands/preflight.js +211 -0
- package/lib/commands/prime.js +13 -0
- package/lib/commands/push.js +69 -2
- package/lib/commands/ready.js +2 -2
- package/lib/commands/recall.js +116 -0
- package/lib/commands/recap.js +61 -0
- package/lib/commands/recommend.js +22 -2
- package/lib/commands/release.js +91 -0
- package/lib/commands/remember.js +74 -0
- package/lib/commands/role.js +99 -0
- package/lib/commands/serve.js +581 -0
- package/lib/commands/setup.js +851 -979
- package/lib/commands/shepherd.js +436 -0
- package/lib/commands/ship.js +23 -1
- package/lib/commands/show.js +2 -2
- package/lib/commands/stage.js +192 -0
- package/lib/commands/stale.js +5 -0
- package/lib/commands/status.js +329 -11
- package/lib/commands/sync.js +34 -46
- package/lib/commands/team.js +15 -2
- package/lib/commands/test.js +58 -7
- package/lib/commands/update.js +2 -2
- package/lib/commands/upgrade.js +47 -0
- package/lib/commands/validate.js +56 -25
- package/lib/commands/worktree.js +308 -128
- package/lib/config-writer.js +202 -0
- package/lib/control-plane.js +236 -0
- package/lib/core/runtime-graph.js +946 -0
- package/lib/dep-guard/keyword-ripple.js +184 -0
- package/lib/deprecated-sync-cleanup.js +362 -0
- package/lib/detect-agent.js +2 -28
- package/lib/detect-worktree.js +42 -17
- package/lib/doc-gate/declaration.js +177 -0
- package/lib/doc-gate/detect.js +289 -0
- package/lib/doc-gate/gate.js +375 -0
- package/lib/doc-gate/okf-config.js +128 -0
- package/lib/doc-gate/okf.js +429 -0
- package/lib/docs-command.js +1161 -6
- package/lib/forge-issues.js +697 -0
- package/lib/forge-lock.js +262 -0
- package/lib/gate-events.js +193 -0
- package/lib/global-flags.js +74 -0
- package/lib/greptile-match.js +7 -63
- package/lib/harness-capability-matrix.js +380 -0
- package/lib/hook-global-installer.js +347 -0
- package/lib/hook-renderer.js +451 -0
- package/lib/inbox.js +391 -0
- package/lib/insights.js +397 -0
- package/lib/issue-adapter.js +156 -0
- package/lib/issue-backend.js +145 -0
- package/lib/issue-render.js +220 -0
- package/lib/issue-sync/authority.js +100 -0
- package/lib/issue-sync/github-pull.js +184 -0
- package/lib/issue-sync/import-primitives.js +98 -0
- package/lib/issue-sync/legacy-link-bridge.js +436 -0
- package/lib/issue-sync/link-store.js +292 -0
- package/lib/issue-sync/project-github.js +123 -0
- package/lib/issue-sync/reconcile.js +195 -0
- package/lib/issue-sync/schema.js +126 -0
- package/lib/kernel/backing-issue.js +305 -0
- package/lib/kernel/broker.js +1218 -0
- package/lib/kernel/cli-broker-factory.js +130 -0
- package/lib/kernel/conflict-signal.js +82 -0
- package/lib/kernel/evaluators.js +195 -0
- package/lib/kernel/fs-class.js +495 -0
- package/lib/kernel/issue-command-contract.js +559 -0
- package/lib/kernel/issue-id-resolver.js +186 -0
- package/lib/kernel/lease-enforcer.js +158 -0
- package/lib/kernel/migrations.js +333 -0
- package/lib/kernel/planning-buckets-schema.js +109 -0
- package/lib/kernel/projection-jsonl-writer.js +450 -0
- package/lib/kernel/readiness-model.js +329 -0
- package/lib/kernel/schema.js +356 -0
- package/lib/kernel/sqlite-driver.js +2504 -0
- package/lib/kernel/taxonomy-validator.js +394 -0
- package/lib/lefthook-check.js +8 -4
- package/lib/lefthook-wiring.js +413 -0
- package/lib/mcp-config-renderer.js +288 -0
- package/lib/memory/graphiti-mcp.js +106 -0
- package/lib/memory/router.js +387 -0
- package/lib/memory/typed-api.js +102 -0
- package/lib/memory-digest.js +195 -0
- package/lib/merge-rules.js +395 -0
- package/lib/migrate-dry-run.js +466 -0
- package/lib/orientation.js +863 -0
- package/lib/package-manager-remediation.js +103 -0
- package/lib/package-root.js +381 -0
- package/lib/patch-intent.js +890 -0
- package/lib/plugin-catalog.js +3 -4
- package/lib/plugin-manager.js +0 -5
- package/lib/pr-bundle.js +186 -0
- package/lib/pr-monitor/differ.js +195 -0
- package/lib/pr-monitor/events.js +0 -0
- package/lib/pr-monitor/gather.js +124 -0
- package/lib/pr-monitor/journal.js +299 -0
- package/lib/pr-monitor/monitor.js +146 -0
- package/lib/pr-monitor/render-sticky.js +157 -0
- package/lib/pr-monitor/watch-lifecycle.js +95 -0
- package/lib/pr-monitor/watch.js +247 -0
- package/lib/pr-pull.js +1273 -0
- package/lib/pr-shepherd.js +494 -0
- package/lib/pr-state-validator.js +59 -0
- package/lib/preflight/gates.js +237 -0
- package/lib/preflight/runner.js +116 -0
- package/lib/project-discovery.js +0 -53
- package/lib/project-memory.js +166 -0
- package/lib/protected-path-manifest.js +281 -0
- package/lib/protected-state-surfaces.js +387 -0
- package/lib/release-readiness.js +2089 -0
- package/lib/reset.js +59 -45
- package/lib/review-adapter.js +68 -0
- package/lib/rules-sync.js +260 -0
- package/lib/runtime-health.js +332 -23
- package/lib/safety-config-renderer.js +268 -0
- package/lib/setup-action-log.js +1 -7
- package/lib/setup.js +27 -65
- package/lib/shell-utils.js +76 -6
- package/lib/skills-sync.js +330 -0
- package/lib/smart-status/conflicts.js +205 -0
- package/lib/smart-status/scoring.js +191 -0
- package/lib/status/beads-snapshot.js +145 -0
- package/lib/status/presenter.js +216 -0
- package/lib/status/snapshot.js +186 -0
- package/lib/sync-backend.js +202 -0
- package/lib/untrusted-content.js +52 -0
- package/lib/upgrade-safety.js +199 -0
- package/lib/workflow/enforce-stage.js +298 -47
- package/lib/workflow/stage-transition.js +115 -0
- package/lib/workflow/stages.js +30 -6
- package/lib/workflow/state-manager.js +159 -14
- package/lib/workflow/state.js +23 -1
- package/lib/workflow-profiles.js +17 -5
- package/package.json +46 -36
- package/rules/documentation.md +19 -0
- package/rules/kernel-tracking.md +26 -0
- package/rules/security.md +22 -0
- package/rules/tdd.md +20 -0
- package/rules/workflow.md +27 -0
- package/scripts/auto-backing-issue.js +47 -0
- package/scripts/beads-context.sh +165 -22
- package/scripts/beads-migrate-to-dolt.sh +7 -0
- package/scripts/beads-upgrade-smoke.sh +284 -0
- package/scripts/behavioral-judge.sh +115 -11
- package/scripts/benchmark.js +349 -63
- package/scripts/bootstrap-windows-tools.sh +78 -0
- package/scripts/branch-protection.js +2 -3
- package/scripts/check-agents.js +34 -137
- package/scripts/commitlint.js +3 -1
- package/scripts/conflict-detect.sh +3 -0
- package/scripts/dep-guard-analyze.js +52 -17
- package/scripts/dep-guard-keyword-ripple.js +29 -0
- package/scripts/dep-guard-render-review.js +86 -0
- package/scripts/dep-guard.sh +64 -232
- package/scripts/file-index.sh +3 -0
- package/scripts/forge-team/lib/claim.sh +34 -18
- package/scripts/forge-team/lib/dashboard.sh +61 -86
- package/scripts/forge-team/lib/epic.sh +99 -263
- package/scripts/forge-team/lib/hooks.sh +26 -28
- package/scripts/forge-team/lib/identity.sh +4 -4
- package/scripts/forge-team/lib/sync-github.sh +144 -47
- package/scripts/forge-team/lib/verify.sh +93 -83
- package/scripts/forge-team/lib/workload.sh +41 -65
- package/scripts/forge-team/tests/claim.test.sh +25 -19
- package/scripts/forge-team/tests/dashboard.test.sh +31 -46
- package/scripts/forge-team/tests/epic.test.sh +52 -71
- package/scripts/forge-team/tests/hooks.test.sh +38 -50
- package/scripts/forge-team/tests/identity.test.sh +3 -3
- package/scripts/forge-team/tests/integration.test.sh +44 -66
- package/scripts/forge-team/tests/sync-github.test.sh +183 -79
- package/scripts/forge-team/tests/verify.test.sh +37 -46
- package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
- package/scripts/forge-team/tests/workload.test.sh +32 -66
- package/scripts/gen-command-manifest.js +153 -0
- package/scripts/gen-embedded-assets.mjs +129 -0
- package/scripts/install.ps1 +139 -0
- package/scripts/install.sh +268 -0
- package/scripts/lib/beads-migrate-to-dolt.mjs +503 -0
- package/scripts/lib/release-asset.mjs +84 -0
- package/scripts/parity-check.mjs +145 -0
- package/scripts/parity-check.test.mjs +58 -0
- package/scripts/pin-agentic-workflow-images.js +112 -0
- package/scripts/pr-coordinator.sh +3 -0
- package/scripts/preflight-sonar.eslint.config.mjs +44 -0
- package/scripts/preflight.sh +108 -0
- package/scripts/protected-state-check.js +104 -0
- package/scripts/smart-status-score.js +31 -0
- package/scripts/smart-status-sessions.js +51 -0
- package/scripts/smart-status.sh +117 -369
- package/scripts/spikes/config-race-bench.js +111 -0
- package/scripts/spikes/harness-capability-matrix.js +13 -0
- package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
- package/scripts/spikes/protected-path-manifest.js +20 -0
- package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
- package/scripts/sync-agent-skills.js +62 -0
- package/scripts/sync-agentic-workflow.js +48 -0
- package/scripts/sync-utils.sh +3 -0
- package/scripts/test-ci-shard.js +251 -0
- package/scripts/test-dashboard.js +188 -52
- package/scripts/test-full-suite.js +186 -0
- package/scripts/test-profile.js +278 -0
- package/scripts/test.js +302 -28
- package/scripts/validate.js +143 -0
- package/scripts/validate.sh +18 -1
- package/skills/claim-safety/SKILL.md +102 -0
- package/skills/claim-safety/evals/evals.json +46 -0
- package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +46 -52
- package/skills/dev/evals/evals.json +50 -0
- package/skills/hermes-forge/SKILL.md +185 -0
- package/skills/hermes-forge/evals/evals.json +46 -0
- package/skills/issue-basics/SKILL.md +111 -0
- package/skills/issue-basics/evals/evals.json +46 -0
- package/skills/kernel/SKILL.md +166 -0
- package/skills/kernel/evals/evals.json +50 -0
- package/skills/memory/SKILL.md +102 -0
- package/skills/parallel-deep-research/SKILL.md +14 -11
- package/skills/parallel-deep-research/evals/evals.json +11 -27
- package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +134 -159
- package/skills/plan/evals/evals.json +42 -0
- package/skills/research/SKILL.md +195 -0
- package/skills/research/evals/evals.json +42 -0
- package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
- package/skills/review/evals/evals.json +42 -0
- package/skills/rollback/SKILL.md +110 -0
- package/skills/rollback/evals/evals.json +46 -0
- package/skills/rollback/references/methods.md +204 -0
- package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
- package/skills/shepherd/SKILL.md +66 -0
- package/skills/shepherd/evals/evals.json +42 -0
- package/skills/ship/SKILL.md +251 -0
- package/skills/ship/evals/evals.json +42 -0
- package/skills/smith/SKILL.md +142 -0
- package/skills/smith/evals/evals.json +46 -0
- package/skills/smith/references/autonomy-and-gates.md +94 -0
- package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
- package/skills/sonarcloud/evals/evals.json +46 -0
- package/skills/sonarcloud-analysis/SKILL.md +18 -13
- package/skills/sonarcloud-analysis/evals/evals.json +11 -15
- package/skills/status/SKILL.md +102 -0
- package/skills/status/evals/evals.json +50 -0
- package/skills/triage-ready/SKILL.md +121 -0
- package/skills/triage-ready/evals/evals.json +42 -0
- package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
- package/skills/validate/evals/evals.json +42 -0
- package/skills/verify/SKILL.md +299 -0
- package/skills/verify/evals/evals.json +50 -0
- package/.claude/commands/dev.md +0 -345
- package/.claude/commands/plan.md +0 -566
- package/.claude/commands/premerge.md +0 -186
- package/.claude/commands/research.md +0 -42
- package/.claude/commands/review.md +0 -451
- package/.claude/commands/rollback.md +0 -721
- package/.claude/commands/ship.md +0 -213
- package/.claude/commands/sonarcloud.md +0 -152
- package/.claude/commands/status.md +0 -90
- package/.claude/commands/validate.md +0 -288
- package/.claude/commands/verify.md +0 -269
- package/.claude/rules/workflow.md +0 -121
- package/.cline/workflows/dev.md +0 -342
- package/.cline/workflows/plan.md +0 -563
- package/.cline/workflows/premerge.md +0 -183
- package/.cline/workflows/research.md +0 -39
- package/.cline/workflows/review.md +0 -448
- package/.cline/workflows/rollback.md +0 -718
- package/.cline/workflows/ship.md +0 -210
- package/.cline/workflows/sonarcloud.md +0 -146
- package/.cline/workflows/status.md +0 -87
- package/.cline/workflows/validate.md +0 -285
- package/.cline/workflows/verify.md +0 -266
- package/.codex/config.toml +0 -11
- package/.codex/skills/dev/SKILL.md +0 -345
- package/.codex/skills/plan/SKILL.md +0 -566
- package/.codex/skills/premerge/SKILL.md +0 -186
- package/.codex/skills/research/SKILL.md +0 -42
- package/.codex/skills/review/SKILL.md +0 -451
- package/.codex/skills/rollback/SKILL.md +0 -721
- package/.codex/skills/ship/SKILL.md +0 -213
- package/.codex/skills/sonarcloud/SKILL.md +0 -149
- package/.codex/skills/status/SKILL.md +0 -90
- package/.codex/skills/validate/SKILL.md +0 -288
- package/.codex/skills/verify/SKILL.md +0 -269
- package/.cursor/commands/dev.md +0 -342
- package/.cursor/commands/plan.md +0 -563
- package/.cursor/commands/premerge.md +0 -183
- package/.cursor/commands/research.md +0 -39
- package/.cursor/commands/review.md +0 -448
- package/.cursor/commands/ship.md +0 -210
- package/.cursor/commands/sonarcloud.md +0 -146
- package/.cursor/commands/status.md +0 -87
- package/.cursor/commands/validate.md +0 -285
- package/.cursor/commands/verify.md +0 -266
- package/.cursorrules +0 -149
- package/.github/prompts/premerge.prompt.md +0 -188
- package/.github/prompts/research.prompt.md +0 -44
- package/.github/prompts/rollback.prompt.md +0 -723
- package/.github/prompts/ship.prompt.md +0 -215
- package/.github/prompts/status.prompt.md +0 -92
- package/.github/prompts/verify.prompt.md +0 -271
- package/.github/workflows/beads-to-github.yml +0 -56
- package/.github/workflows/github-to-beads.yml +0 -97
- package/.kilocode/workflows/dev.md +0 -346
- package/.kilocode/workflows/plan.md +0 -567
- package/.kilocode/workflows/premerge.md +0 -187
- package/.kilocode/workflows/research.md +0 -43
- package/.kilocode/workflows/review.md +0 -452
- package/.kilocode/workflows/rollback.md +0 -722
- package/.kilocode/workflows/ship.md +0 -214
- package/.kilocode/workflows/sonarcloud.md +0 -150
- package/.kilocode/workflows/status.md +0 -91
- package/.kilocode/workflows/validate.md +0 -289
- package/.kilocode/workflows/verify.md +0 -270
- package/.opencode/commands/dev.md +0 -345
- package/.opencode/commands/plan.md +0 -566
- package/.opencode/commands/premerge.md +0 -186
- package/.opencode/commands/research.md +0 -42
- package/.opencode/commands/review.md +0 -451
- package/.opencode/commands/rollback.md +0 -721
- package/.opencode/commands/ship.md +0 -213
- package/.opencode/commands/sonarcloud.md +0 -149
- package/.opencode/commands/status.md +0 -90
- package/.opencode/commands/validate.md +0 -288
- package/.opencode/commands/verify.md +0 -269
- package/.roo/commands/dev.md +0 -346
- package/.roo/commands/plan.md +0 -567
- package/.roo/commands/premerge.md +0 -187
- package/.roo/commands/research.md +0 -43
- package/.roo/commands/review.md +0 -452
- package/.roo/commands/rollback.md +0 -722
- package/.roo/commands/ship.md +0 -214
- package/.roo/commands/sonarcloud.md +0 -150
- package/.roo/commands/status.md +0 -91
- package/.roo/commands/validate.md +0 -289
- package/.roo/commands/verify.md +0 -270
- package/docs/BEADS_GITHUB_SYNC.md +0 -255
- package/docs/GREPTILE_SETUP.md +0 -400
- package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
- package/docs/SETUP.md +0 -663
- package/docs/VALIDATION.md +0 -363
- package/lib/agents/cline.plugin.json +0 -29
- package/lib/agents/copilot.plugin.json +0 -24
- package/lib/agents/kilocode.plugin.json +0 -22
- package/lib/agents/opencode.plugin.json +0 -23
- package/lib/agents/roo.plugin.json +0 -30
- package/lib/beads-health-check.js +0 -143
- package/lib/commands/commands-reset.js +0 -147
- package/opencode.json +0 -67
- package/scripts/beads-context.test.js +0 -567
- package/scripts/github-beads-sync/comment.mjs +0 -64
- package/scripts/github-beads-sync/config.mjs +0 -148
- package/scripts/github-beads-sync/github-api.mjs +0 -131
- package/scripts/github-beads-sync/index.mjs +0 -332
- package/scripts/github-beads-sync/label-mapper.mjs +0 -54
- package/scripts/github-beads-sync/mapping.mjs +0 -78
- package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
- package/scripts/github-beads-sync/reverse-sync.mjs +0 -138
- package/scripts/github-beads-sync/run-bd.mjs +0 -161
- package/scripts/github-beads-sync/sanitize.mjs +0 -121
- package/scripts/github-beads-sync.config.json +0 -26
- package/scripts/sync-commands.js +0 -600
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { ISSUE_COMMAND_EXIT_CODES } = require('./issue-command-contract');
|
|
4
|
+
|
|
5
|
+
// Git-style short issue-id resolution (kernel 9556660b). The kernel mints full
|
|
6
|
+
// UUIDs, which are hostile to type by hand; this module resolves an UNAMBIGUOUS
|
|
7
|
+
// hex prefix (>= 6 chars, e.g. `forge show 9556660b`) to the stored full id.
|
|
8
|
+
// It runs ONCE at the broker boundary (runIssueOperation) so every issue
|
|
9
|
+
// subcommand — including the batch-close fan-out and the gate.issue_verify
|
|
10
|
+
// read-back — consumes the RESOLVED id. Resolution is deliberately conservative:
|
|
11
|
+
// * a full UUID never triggers a lookup (byte-identical fast path);
|
|
12
|
+
// * non-hex tokens (legacy `forge-*` / imported Beads ids) pass through untouched;
|
|
13
|
+
// * a prefix with ZERO matches passes through so the downstream not-found
|
|
14
|
+
// error is unchanged;
|
|
15
|
+
// * an EXACT stored id always wins over prefix expansion (a stored short
|
|
16
|
+
// hex id is never mis-expanded or rejected as "too short").
|
|
17
|
+
|
|
18
|
+
const MIN_ISSUE_ID_PREFIX_LENGTH = 6;
|
|
19
|
+
const MAX_AMBIGUOUS_CANDIDATES = 5;
|
|
20
|
+
|
|
21
|
+
const FULL_UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
22
|
+
// A resolvable prefix: at least MIN hex chars, optionally continuing with hex
|
|
23
|
+
// and dashes (so a copied dashed UUID partial like `9556660b-a414` resolves too).
|
|
24
|
+
const HEX_PREFIX_PATTERN = /^[0-9a-f]{6}[0-9a-f-]*$/i;
|
|
25
|
+
// Pure hex but under the minimum length — candidate for the too-short guidance
|
|
26
|
+
// error (unless it exactly matches a stored id).
|
|
27
|
+
const SHORT_HEX_PATTERN = /^[0-9a-f]{1,5}$/i;
|
|
28
|
+
|
|
29
|
+
// The positional slots that carry issue ids, per kernel operation. Positions
|
|
30
|
+
// index the operation's positional tokens under the SAME rule every downstream
|
|
31
|
+
// consumer uses (firstPositionalArg / resolveDependencyEndpoints /
|
|
32
|
+
// buildCommentPayload): a positional is any token not starting with '-'. That
|
|
33
|
+
// keeps the resolved token exactly the one the downstream code reads — e.g.
|
|
34
|
+
// `claim --issue <id>` still resolves because `<id>` is positional 0 under this
|
|
35
|
+
// rule and flags.issue reads the same token. Operations absent from this map
|
|
36
|
+
// (list/ready/search/stats/create/...) take no issue id and pass through with
|
|
37
|
+
// no lookup — a search query is never mistaken for an id.
|
|
38
|
+
const OPERATION_ID_POSITIONS = Object.freeze({
|
|
39
|
+
update: Object.freeze([0]),
|
|
40
|
+
claim: Object.freeze([0]),
|
|
41
|
+
release: Object.freeze([0]),
|
|
42
|
+
comment: Object.freeze([0]),
|
|
43
|
+
close: Object.freeze([0]),
|
|
44
|
+
show: Object.freeze([0]),
|
|
45
|
+
owns: Object.freeze([0]),
|
|
46
|
+
children: Object.freeze([0]),
|
|
47
|
+
'dep.add': Object.freeze([0, 1]),
|
|
48
|
+
'dep.remove': Object.freeze([0, 1]),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// The `--issue=<id>` / `--blocks=<id>` =-joined flag forms carry ids inside a
|
|
52
|
+
// single token, invisible to the positional rule, so they are resolved by name.
|
|
53
|
+
const ID_FLAG_EQUALS_PATTERN = /^--(issue|blocks)=(.+)$/;
|
|
54
|
+
|
|
55
|
+
function tooShortError(token) {
|
|
56
|
+
return {
|
|
57
|
+
error: {
|
|
58
|
+
code: 'FORGE_ISSUE_ID_PREFIX_TOO_SHORT',
|
|
59
|
+
message: `Issue id prefix '${token}' is too short — use at least ${MIN_ISSUE_ID_PREFIX_LENGTH} hex characters `
|
|
60
|
+
+ '(e.g. the first 8 of the id) or the full id.',
|
|
61
|
+
exitCode: ISSUE_COMMAND_EXIT_CODES.validation,
|
|
62
|
+
details: { prefix: token, min_length: MIN_ISSUE_ID_PREFIX_LENGTH },
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function ambiguousError(token, candidates) {
|
|
68
|
+
const shown = candidates.slice(0, MAX_AMBIGUOUS_CANDIDATES);
|
|
69
|
+
const count = candidates.length > MAX_AMBIGUOUS_CANDIDATES
|
|
70
|
+
? `${MAX_AMBIGUOUS_CANDIDATES}+`
|
|
71
|
+
: String(candidates.length);
|
|
72
|
+
const listing = shown
|
|
73
|
+
.map(candidate => `${candidate.id} (${candidate.title ?? 'untitled'})`)
|
|
74
|
+
.join('; ');
|
|
75
|
+
return {
|
|
76
|
+
error: {
|
|
77
|
+
code: 'FORGE_ISSUE_ID_AMBIGUOUS',
|
|
78
|
+
message: `Ambiguous issue id prefix '${token}' — matches ${count} issues: ${listing}. `
|
|
79
|
+
+ 'Use a longer prefix or the full id.',
|
|
80
|
+
exitCode: ISSUE_COMMAND_EXIT_CODES.validation,
|
|
81
|
+
details: {
|
|
82
|
+
prefix: token,
|
|
83
|
+
candidates: shown.map(candidate => ({ id: candidate.id, title: candidate.title ?? null })),
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Resolve one id token. `lookup(prefix, limit)` returns candidate rows
|
|
90
|
+
// ({ id, title }) whose id starts with `prefix`, ordered by id ascending —
|
|
91
|
+
// which guarantees an exact match (shortest id sharing the prefix) sorts first
|
|
92
|
+
// and is never pushed out by the limit. Returns { id } on success (possibly the
|
|
93
|
+
// untouched input) or { error: { code, message, exitCode, details } }.
|
|
94
|
+
// Resolve a hex ref (prefix >= 6, or short hex < 6) against the store. Returns { id } —
|
|
95
|
+
// the resolved full id, or `ref` untouched when nothing matches — or an { error } for the
|
|
96
|
+
// too-short / ambiguous cases. `reportToken` is what user-facing errors name (the original
|
|
97
|
+
// input the caller typed, which may be a handle rather than the bare hex).
|
|
98
|
+
async function resolveHexToken(ref, lookup, reportToken = ref) {
|
|
99
|
+
const isPrefix = HEX_PREFIX_PATTERN.test(ref);
|
|
100
|
+
const isShortHex = !isPrefix && SHORT_HEX_PATTERN.test(ref);
|
|
101
|
+
if (!isPrefix && !isShortHex) return { id: ref };
|
|
102
|
+
|
|
103
|
+
const needle = ref.toLowerCase();
|
|
104
|
+
const candidates = (await lookup(needle, MAX_AMBIGUOUS_CANDIDATES + 1)) || [];
|
|
105
|
+
const exact = candidates.find(
|
|
106
|
+
candidate => candidate && typeof candidate.id === 'string' && candidate.id.toLowerCase() === needle,
|
|
107
|
+
);
|
|
108
|
+
if (exact) return { id: exact.id };
|
|
109
|
+
if (isShortHex) return tooShortError(reportToken);
|
|
110
|
+
if (candidates.length === 0) return { id: ref };
|
|
111
|
+
if (candidates.length === 1) return { id: candidates[0].id };
|
|
112
|
+
return ambiguousError(reportToken, candidates);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function resolveIssueId(token, lookup) {
|
|
116
|
+
if (typeof token !== 'string' || token.length === 0) return { id: token };
|
|
117
|
+
if (FULL_UUID_PATTERN.test(token)) return { id: token };
|
|
118
|
+
|
|
119
|
+
// A display handle is `<title-slug>-<short-id>` (kernel 1db53c60); its short-id is the
|
|
120
|
+
// 8-char UUID prefix, so a handle ends in >= 8 trailing hex. Short legacy/Beads suffixes
|
|
121
|
+
// (`forge-2a3bc9`, 6 hex) are NOT handles and pass through untouched.
|
|
122
|
+
const handleSuffix = (/-([0-9a-f]{8,})$/i.exec(token) || [])[1] || null;
|
|
123
|
+
|
|
124
|
+
if (HEX_PREFIX_PATTERN.test(token) || SHORT_HEX_PATTERN.test(token)) {
|
|
125
|
+
const direct = await resolveHexToken(token, lookup);
|
|
126
|
+
// A handle whose slug is all hex letters (e.g. `facade-decade-fee-add-1a2b3c4d`) also
|
|
127
|
+
// matches the broad hex-prefix pattern; when the whole token matches nothing, retry
|
|
128
|
+
// with its trailing short-id so the handle still resolves (CodeRabbit, PR #335).
|
|
129
|
+
if (direct.id === token && handleSuffix && handleSuffix !== token) {
|
|
130
|
+
const viaHandle = await resolveHexToken(handleSuffix, lookup, token);
|
|
131
|
+
if (viaHandle.error || viaHandle.id !== handleSuffix) return viaHandle;
|
|
132
|
+
}
|
|
133
|
+
return direct;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Non-hex token: a legacy/imported id (pass through so the store resolves it exactly),
|
|
137
|
+
// or a display handle. Prefer an exact whole-token match first so imported handle-shaped
|
|
138
|
+
// ids (`legacy-2a3bc9de`) still resolve; otherwise resolve by the handle's short-id.
|
|
139
|
+
if (!handleSuffix) return { id: token };
|
|
140
|
+
const whole = (await lookup(token.toLowerCase(), 2)) || [];
|
|
141
|
+
const wholeExact = whole.find(
|
|
142
|
+
candidate => candidate && typeof candidate.id === 'string' && candidate.id.toLowerCase() === token.toLowerCase(),
|
|
143
|
+
);
|
|
144
|
+
if (wholeExact) return { id: wholeExact.id };
|
|
145
|
+
return resolveHexToken(handleSuffix, lookup, token);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Resolve every id-carrying token in `args` for `operation`. Returns
|
|
149
|
+
// { args: resolvedArgs } (a copy; the input is never mutated) or the first
|
|
150
|
+
// { error } encountered. Operations without id slots return their args
|
|
151
|
+
// unchanged and never invoke the lookup.
|
|
152
|
+
async function resolveIssueIdArgs(operation, args = [], lookup) {
|
|
153
|
+
const positions = OPERATION_ID_POSITIONS[operation];
|
|
154
|
+
if (!positions) return { args };
|
|
155
|
+
|
|
156
|
+
const resolved = [...args];
|
|
157
|
+
let positionalIndex = 0;
|
|
158
|
+
for (let index = 0; index < resolved.length; index += 1) {
|
|
159
|
+
const token = resolved[index];
|
|
160
|
+
if (typeof token !== 'string') continue;
|
|
161
|
+
if (token.startsWith('-')) {
|
|
162
|
+
const match = ID_FLAG_EQUALS_PATTERN.exec(token);
|
|
163
|
+
if (match) {
|
|
164
|
+
const result = await resolveIssueId(match[2], lookup);
|
|
165
|
+
if (result.error) return result;
|
|
166
|
+
resolved[index] = `--${match[1]}=${result.id}`;
|
|
167
|
+
}
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (positions.includes(positionalIndex)) {
|
|
171
|
+
const result = await resolveIssueId(token, lookup);
|
|
172
|
+
if (result.error) return result;
|
|
173
|
+
resolved[index] = result.id;
|
|
174
|
+
}
|
|
175
|
+
positionalIndex += 1;
|
|
176
|
+
}
|
|
177
|
+
return { args: resolved };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
module.exports = {
|
|
181
|
+
MAX_AMBIGUOUS_CANDIDATES,
|
|
182
|
+
MIN_ISSUE_ID_PREFIX_LENGTH,
|
|
183
|
+
OPERATION_ID_POSITIONS,
|
|
184
|
+
resolveIssueId,
|
|
185
|
+
resolveIssueIdArgs,
|
|
186
|
+
};
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { normalizePayload } = require('./evaluators');
|
|
4
|
+
|
|
5
|
+
// Default lease TTL (kernel d71a824b): how long a claim lease stays live before it
|
|
6
|
+
// is eligible for expiry-reclaim, when the caller does not pin an explicit
|
|
7
|
+
// expires_at (--expires). This is the ONE place the lease duration lives, reused by
|
|
8
|
+
// the CLI boundary (lib/forge-issues.js) to stamp a real expires_at so the dashboard's
|
|
9
|
+
// liveness layer has something to compute against. 8 hours: long enough to outlast a
|
|
10
|
+
// normal agent work session (no heartbeat/renewal exists yet, so a shorter window
|
|
11
|
+
// would risk expiring mid-work and letting another agent reclaim → silent double-work),
|
|
12
|
+
// short enough that a dead agent's lease frees within a working day. Callers that
|
|
13
|
+
// bypass the boundary (broker-direct, tests) carry no ttl and keep the historical
|
|
14
|
+
// null default (never expires).
|
|
15
|
+
const DEFAULT_LEASE_TTL_MS = 8 * 60 * 60 * 1000;
|
|
16
|
+
|
|
17
|
+
// The ECMAScript maximum representable Date, ±8.64e15 ms from the epoch. A time value
|
|
18
|
+
// outside this range makes `new Date(t)` an Invalid Date whose .toISOString() THROWS a
|
|
19
|
+
// RangeError. buildClaimRow calls computeLeaseExpiry synchronously on the claim path, so
|
|
20
|
+
// an uncaught throw here would fail the whole claim.
|
|
21
|
+
const MAX_DATE_MS = 8.64e15;
|
|
22
|
+
|
|
23
|
+
// Compute a lease expires_at from claimed_at (`now`) + a TTL in milliseconds. Returns
|
|
24
|
+
// null (never expires) for an absent/non-positive/non-finite ttl, an unparseable `now`,
|
|
25
|
+
// or a claimed_at+ttl that overflows the representable Date range (a fat-fingered
|
|
26
|
+
// FORGE_LEASE_TTL_MS) — so any of these degrades to the historical null default rather
|
|
27
|
+
// than throwing on the claim path. The result is always the canonical Date#toISOString
|
|
28
|
+
// form isValidExpiresAt requires (…mmmZ), so lexicographic comparison in isLeaseExpired
|
|
29
|
+
// stays chronological.
|
|
30
|
+
function computeLeaseExpiry(now, ttlMs) {
|
|
31
|
+
if (ttlMs === null || ttlMs === undefined) return null;
|
|
32
|
+
const ttl = Number(ttlMs);
|
|
33
|
+
if (!Number.isFinite(ttl) || ttl <= 0) return null;
|
|
34
|
+
const base = Date.parse(now);
|
|
35
|
+
if (Number.isNaN(base)) return null;
|
|
36
|
+
// Guard the sum BEFORE constructing the Date: an overflow (or a non-finite sum) would
|
|
37
|
+
// otherwise reach .toISOString() as an Invalid Date and throw RangeError.
|
|
38
|
+
const expiryMs = base + ttl;
|
|
39
|
+
if (!Number.isFinite(expiryMs) || Math.abs(expiryMs) > MAX_DATE_MS) return null;
|
|
40
|
+
return new Date(expiryMs).toISOString();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Pure claim-lease enforcement helpers (task 9.5.10). No I/O — the broker
|
|
44
|
+
// performs all reads/writes and the DB partial UNIQUE index
|
|
45
|
+
// (idx_kernel_claims_active_lease) is the hard race-safe guarantee. These
|
|
46
|
+
// functions are the optimization + clean error path layered on top, mirroring
|
|
47
|
+
// the pure style of evaluators.js.
|
|
48
|
+
//
|
|
49
|
+
// Ownership model (Slice 1, deliberately conservative): a claim lease is a
|
|
50
|
+
// kernel_claims row with state='active'. A lease is "live" iff it is active and
|
|
51
|
+
// not expired. ANY claim.create against a live lease quarantines as
|
|
52
|
+
// 'claim_conflict' — there is NO silent same-owner renewal, because `actor` is
|
|
53
|
+
// not yet guaranteed distinct per concurrent agent and a renewal branch could
|
|
54
|
+
// let one agent silently steal another's live lease. Legitimate same-key
|
|
55
|
+
// retries never reach here: they are collapsed to duplicate replays by the
|
|
56
|
+
// idempotency-key path before claim planning runs.
|
|
57
|
+
//
|
|
58
|
+
// Claim scope/row/conflict all read from the SAME normalizePayload the evaluator
|
|
59
|
+
// uses to persist the event, so the lease can never describe a different issue
|
|
60
|
+
// than the accepted event/outbox.
|
|
61
|
+
|
|
62
|
+
// A claim lease's expires_at, if present, must be a UTC ISO-8601 timestamp with a
|
|
63
|
+
// trailing 'Z' AND a real calendar date — otherwise lexicographic comparison in
|
|
64
|
+
// isLeaseExpired is meaningless (e.g. 'zzz' sorts after any timestamp and would
|
|
65
|
+
// never expire; other junk sorts before and would look already expired). A
|
|
66
|
+
// null/absent value is valid and means "never expires". The broker quarantines
|
|
67
|
+
// any claim.create whose expires_at fails this check.
|
|
68
|
+
function isValidExpiresAt(value) {
|
|
69
|
+
if (value === null || value === undefined) return true;
|
|
70
|
+
if (typeof value !== 'string') return false;
|
|
71
|
+
const timestamp = Date.parse(value);
|
|
72
|
+
if (Number.isNaN(timestamp)) return false;
|
|
73
|
+
// Require the EXACT canonical Date#toISOString form (YYYY-MM-DDTHH:MM:SS.mmmZ):
|
|
74
|
+
// isLeaseExpired compares lexicographically, so a non-canonical spelling like
|
|
75
|
+
// '...00Z' would sort after the equal instant '...00.000Z' (Z > .) and a reclaim
|
|
76
|
+
// at the expiry instant would wrongly look live. The round-trip also rejects
|
|
77
|
+
// rollover dates Date.parse silently normalises (e.g. 2026-02-31 -> 2026-03-03).
|
|
78
|
+
return new Date(timestamp).toISOString() === value;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// A lease with no expires_at never expires. Timestamps are validated UTC ISO-8601
|
|
82
|
+
// with a trailing 'Z' (see isValidExpiresAt), so lexicographic comparison equals
|
|
83
|
+
// chronological comparison.
|
|
84
|
+
function isLeaseExpired(claim, now) {
|
|
85
|
+
if (!claim?.expires_at) return false;
|
|
86
|
+
return claim.expires_at <= now;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function buildClaimRow(event, now) {
|
|
90
|
+
const payload = normalizePayload(event);
|
|
91
|
+
return {
|
|
92
|
+
id: event.entity_id,
|
|
93
|
+
issue_id: payload.issue_id,
|
|
94
|
+
actor: event.actor,
|
|
95
|
+
state: 'active',
|
|
96
|
+
session_id: event.session_id ?? null,
|
|
97
|
+
worktree_id: event.worktree_id ?? null,
|
|
98
|
+
claimed_at: now,
|
|
99
|
+
// An explicit expires_at (from --expires) wins; otherwise derive from the event's
|
|
100
|
+
// lease_ttl_ms + claimed_at so the CLI boundary can stamp a real expiry. No ttl on
|
|
101
|
+
// the event → null (never expires), preserving the historical broker-direct default.
|
|
102
|
+
expires_at: payload.expires_at ?? computeLeaseExpiry(now, event.lease_ttl_ms),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Decide how a claim.create event should be applied against the issue's current
|
|
107
|
+
// active claim (if any). Returns one of:
|
|
108
|
+
// { action: 'insert', claim } — no active claim
|
|
109
|
+
// { action: 'reclaim', supersede, claim } — active claim has expired
|
|
110
|
+
// { action: 'conflict' } — a live lease blocks the claim
|
|
111
|
+
function planClaimAcquisition({ event, activeClaim, now }) {
|
|
112
|
+
if (!activeClaim) {
|
|
113
|
+
return { action: 'insert', claim: buildClaimRow(event, now) };
|
|
114
|
+
}
|
|
115
|
+
if (isLeaseExpired(activeClaim, now)) {
|
|
116
|
+
return {
|
|
117
|
+
action: 'reclaim',
|
|
118
|
+
supersede: { claimId: activeClaim.id, toState: 'reclaimable' },
|
|
119
|
+
claim: buildClaimRow(event, now),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return { action: 'conflict' };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Build a conflicts-table row for a quarantined claim conflict. Matches the
|
|
126
|
+
// shape produced by evaluators.buildConflict: claims are not revisioned, so the
|
|
127
|
+
// NOT NULL expected_revision / actual_revision columns default to 0. The
|
|
128
|
+
// human-meaningful detail (who owns the live lease, who attempted) lives in
|
|
129
|
+
// payload_json.
|
|
130
|
+
function buildClaimConflict(event, activeClaim, reason = 'claim_conflict') {
|
|
131
|
+
const payload = normalizePayload(event);
|
|
132
|
+
return {
|
|
133
|
+
entity_type: event.entity_type,
|
|
134
|
+
entity_id: event.entity_id,
|
|
135
|
+
expected_revision: 0,
|
|
136
|
+
actual_revision: 0,
|
|
137
|
+
status: 'quarantined',
|
|
138
|
+
reason,
|
|
139
|
+
payload_json: JSON.stringify({
|
|
140
|
+
reason,
|
|
141
|
+
issue_id: payload.issue_id ?? null,
|
|
142
|
+
attempted_by: event.actor,
|
|
143
|
+
current_owner: activeClaim ? activeClaim.actor : null,
|
|
144
|
+
current_claim_id: activeClaim ? activeClaim.id : null,
|
|
145
|
+
}),
|
|
146
|
+
created_at: event.created_at,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
module.exports = {
|
|
151
|
+
DEFAULT_LEASE_TTL_MS,
|
|
152
|
+
buildClaimConflict,
|
|
153
|
+
buildClaimRow,
|
|
154
|
+
computeLeaseExpiry,
|
|
155
|
+
isLeaseExpired,
|
|
156
|
+
isValidExpiresAt,
|
|
157
|
+
planClaimAcquisition,
|
|
158
|
+
};
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
const { getKernelSchema, validateKernelSchema } = require('./schema');
|
|
2
|
+
|
|
3
|
+
function assertIdentifier(identifier, label) {
|
|
4
|
+
if (!/^[a-z][a-z0-9_]*$/.test(identifier)) {
|
|
5
|
+
throw new Error(`Invalid Kernel SQL ${label}: ${identifier}`);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function renderReference(reference) {
|
|
10
|
+
const parts = String(reference || '').split('.');
|
|
11
|
+
if (parts.length !== 2) {
|
|
12
|
+
throw new Error(`Invalid Kernel SQL reference: ${reference}`);
|
|
13
|
+
}
|
|
14
|
+
const [tableName, columnName] = parts;
|
|
15
|
+
assertIdentifier(tableName, 'reference table');
|
|
16
|
+
assertIdentifier(columnName, 'reference column');
|
|
17
|
+
return `REFERENCES kernel_${tableName}(${columnName})`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function renderColumn(field) {
|
|
21
|
+
assertIdentifier(field.name, 'column');
|
|
22
|
+
const parts = [field.name, field.type];
|
|
23
|
+
if (field.notNull) parts.push('NOT NULL');
|
|
24
|
+
if (field.primaryKey) parts.push('PRIMARY KEY');
|
|
25
|
+
if (field.default !== undefined) parts.push(`DEFAULT ${field.default}`);
|
|
26
|
+
if (field.references) parts.push(renderReference(field.references));
|
|
27
|
+
return parts.join(' ');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function renderCreateTable(table) {
|
|
31
|
+
assertIdentifier(table.sqlName, 'table');
|
|
32
|
+
const columns = table.fields.map(field => ` ${renderColumn(field)}`).join(',\n');
|
|
33
|
+
return `CREATE TABLE IF NOT EXISTS ${table.sqlName} (\n${columns}\n);`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function renderCreateIndex(table, index) {
|
|
37
|
+
assertIdentifier(index.name, 'index');
|
|
38
|
+
const unique = index.unique ? 'UNIQUE ' : '';
|
|
39
|
+
const columns = index.columns.map(column => {
|
|
40
|
+
assertIdentifier(column, 'index column');
|
|
41
|
+
return column;
|
|
42
|
+
}).join(', ');
|
|
43
|
+
return `CREATE ${unique}INDEX IF NOT EXISTS ${index.name} ON ${table.sqlName} (${columns});`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function renderDropIndex(index) {
|
|
47
|
+
assertIdentifier(index.name, 'index');
|
|
48
|
+
return `DROP INDEX IF EXISTS ${index.name};`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function renderDropTable(table) {
|
|
52
|
+
assertIdentifier(table.sqlName, 'table');
|
|
53
|
+
return `DROP TABLE IF EXISTS ${table.sqlName};`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Whole tables created by a LATER migration (not by the 001 initial schema). schema.js
|
|
57
|
+
// stays the full current schema; the named tables are filtered out of 001 so they are
|
|
58
|
+
// created exactly once by their dedicated migration (both on a fresh DB and, via the
|
|
59
|
+
// ledger, on an existing DB). KEEP IN SYNC with every new table-creating migration.
|
|
60
|
+
// memories → 005
|
|
61
|
+
const MIGRATION_ADDED_TABLES = ['memories'];
|
|
62
|
+
|
|
63
|
+
function getInitialKernelSchema() {
|
|
64
|
+
const schema = getKernelSchema();
|
|
65
|
+
// Columns added by a later migration MUST be excluded from the initial (001)
|
|
66
|
+
// CREATE TABLE, so a fresh DB doesn't create them before the ALTER … ADD COLUMN
|
|
67
|
+
// migration runs (else "duplicate column name"). schema.js stays the full current
|
|
68
|
+
// schema; this map mirrors which columns each later migration backfills.
|
|
69
|
+
// events.expected_revision → 002 ; issues.design/notes/assignee → 004 ;
|
|
70
|
+
// issues.created_by/closed_at/close_reason/metadata → 006 ;
|
|
71
|
+
// worktrees.issue_id/work_folder → 007
|
|
72
|
+
// KEEP IN SYNC: every new `ALTER TABLE … ADD COLUMN` migration MUST add its
|
|
73
|
+
// column(s) here, or a fresh DB will hit "duplicate column name" on setup.
|
|
74
|
+
const MIGRATION_ADDED_COLUMNS = {
|
|
75
|
+
events: ['expected_revision'],
|
|
76
|
+
issues: ['design', 'notes', 'assignee', 'created_by', 'closed_at', 'close_reason', 'metadata'],
|
|
77
|
+
worktrees: ['issue_id', 'work_folder'],
|
|
78
|
+
};
|
|
79
|
+
return {
|
|
80
|
+
...schema,
|
|
81
|
+
tables: schema.tables
|
|
82
|
+
.filter(table => !MIGRATION_ADDED_TABLES.includes(table.name))
|
|
83
|
+
.map(table => {
|
|
84
|
+
const excluded = MIGRATION_ADDED_COLUMNS[table.name];
|
|
85
|
+
if (!excluded) return table;
|
|
86
|
+
return {
|
|
87
|
+
...table,
|
|
88
|
+
fields: table.fields.filter(field => !excluded.includes(field.name)),
|
|
89
|
+
};
|
|
90
|
+
}),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function buildSchemaMigration(schema = getInitialKernelSchema()) {
|
|
95
|
+
validateKernelSchema(schema);
|
|
96
|
+
|
|
97
|
+
const apply = [];
|
|
98
|
+
for (const table of schema.tables) {
|
|
99
|
+
apply.push(renderCreateTable(table));
|
|
100
|
+
for (const tableIndex of table.indexes) {
|
|
101
|
+
apply.push(renderCreateIndex(table, tableIndex));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const rollback = [];
|
|
106
|
+
for (const table of [...schema.tables].reverse()) {
|
|
107
|
+
for (const tableIndex of [...table.indexes].reverse()) {
|
|
108
|
+
rollback.push(renderDropIndex(tableIndex));
|
|
109
|
+
}
|
|
110
|
+
rollback.push(renderDropTable(table));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
id: '001_kernel_schema',
|
|
115
|
+
apply,
|
|
116
|
+
rollback,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function buildEventExpectedRevisionMigration() {
|
|
121
|
+
return {
|
|
122
|
+
id: '002_kernel_events_expected_revision',
|
|
123
|
+
apply: ['ALTER TABLE kernel_events ADD COLUMN expected_revision INTEGER NOT NULL DEFAULT 0;'],
|
|
124
|
+
rollback: [
|
|
125
|
+
'CREATE TABLE IF NOT EXISTS kernel_events_002_rollback (\n id TEXT NOT NULL PRIMARY KEY,\n entity_type TEXT NOT NULL,\n entity_id TEXT NOT NULL,\n event_type TEXT NOT NULL,\n idempotency_key TEXT NOT NULL,\n actor TEXT NOT NULL,\n origin TEXT NOT NULL,\n payload_json TEXT NOT NULL,\n created_at TEXT NOT NULL\n);',
|
|
126
|
+
'INSERT INTO kernel_events_002_rollback (id, entity_type, entity_id, event_type, idempotency_key, actor, origin, payload_json, created_at) SELECT id, entity_type, entity_id, event_type, idempotency_key, actor, origin, payload_json, created_at FROM kernel_events;',
|
|
127
|
+
'DROP TABLE kernel_events;',
|
|
128
|
+
'ALTER TABLE kernel_events_002_rollback RENAME TO kernel_events;',
|
|
129
|
+
'CREATE INDEX IF NOT EXISTS idx_kernel_events_entity_created ON kernel_events (entity_type, entity_id, created_at);',
|
|
130
|
+
'CREATE UNIQUE INDEX IF NOT EXISTS idx_kernel_events_idempotency ON kernel_events (idempotency_key);',
|
|
131
|
+
],
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function buildClaimActiveLeaseMigration() {
|
|
136
|
+
// DB-enforced claim-lease invariant (task 9.5.10): at most one active claim
|
|
137
|
+
// per issue. A partial UNIQUE index is the only guarantee that survives
|
|
138
|
+
// multi-process races — a pre-read check cannot, because two writers can both
|
|
139
|
+
// read zero active claims before either commits. renderCreateIndex() has no
|
|
140
|
+
// partial-WHERE support, so this is a hand-written apply string (like 002).
|
|
141
|
+
return {
|
|
142
|
+
id: '003_kernel_claims_active_lease',
|
|
143
|
+
apply: [
|
|
144
|
+
"CREATE UNIQUE INDEX IF NOT EXISTS idx_kernel_claims_active_lease ON kernel_claims (issue_id) WHERE state = 'active';",
|
|
145
|
+
],
|
|
146
|
+
rollback: [
|
|
147
|
+
'DROP INDEX IF EXISTS idx_kernel_claims_active_lease;',
|
|
148
|
+
],
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// KAP-10 (acceptance/design/notes) + KAP-11 (assignee): three additive content
|
|
153
|
+
// columns on kernel_issues. acceptance_criteria/estimate already exist; these add
|
|
154
|
+
// the remaining authored fields plus a persistent assignee (distinct from the
|
|
155
|
+
// transient kernel_claims lease). Plain ADD COLUMNs — the 001 initial schema omits
|
|
156
|
+
// these columns (getInitialKernelSchema/MIGRATION_ADDED_COLUMNS) and the broker's
|
|
157
|
+
// per-migration ledger + guarded ADD COLUMN apply them exactly once.
|
|
158
|
+
function buildIssueContentFieldsMigration() {
|
|
159
|
+
return {
|
|
160
|
+
id: '004_kernel_issues_content_fields',
|
|
161
|
+
apply: [
|
|
162
|
+
'ALTER TABLE kernel_issues ADD COLUMN design TEXT;',
|
|
163
|
+
'ALTER TABLE kernel_issues ADD COLUMN notes TEXT;',
|
|
164
|
+
'ALTER TABLE kernel_issues ADD COLUMN assignee TEXT;',
|
|
165
|
+
],
|
|
166
|
+
rollback: [
|
|
167
|
+
'ALTER TABLE kernel_issues DROP COLUMN assignee;',
|
|
168
|
+
'ALTER TABLE kernel_issues DROP COLUMN notes;',
|
|
169
|
+
'ALTER TABLE kernel_issues DROP COLUMN design;',
|
|
170
|
+
],
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// 005: the project-memory read-model table (kernel_memories). Rendered from the
|
|
175
|
+
// schema.js table definition so the DDL never drifts from the registry. Excluded from
|
|
176
|
+
// the 001 initial schema (MIGRATION_ADDED_TABLES), so a fresh DB creates it exactly
|
|
177
|
+
// once here and an existing DB picks it up through the broker's per-migration ledger.
|
|
178
|
+
// CREATE … IF NOT EXISTS keeps a re-run idempotent.
|
|
179
|
+
function buildMemoryProjectionMigration() {
|
|
180
|
+
const memories = getKernelSchema().tables.find(table => table.name === 'memories');
|
|
181
|
+
if (!memories) {
|
|
182
|
+
throw new Error('Kernel schema is missing the memories read-model table');
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
id: '005_kernel_memories',
|
|
186
|
+
apply: [
|
|
187
|
+
renderCreateTable(memories),
|
|
188
|
+
...memories.indexes.map(memoryIndex => renderCreateIndex(memories, memoryIndex)),
|
|
189
|
+
],
|
|
190
|
+
rollback: [
|
|
191
|
+
...[...memories.indexes].reverse().map(memoryIndex => renderDropIndex(memoryIndex)),
|
|
192
|
+
renderDropTable(memories),
|
|
193
|
+
],
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 006: full-fidelity beads import. Four additive ADD COLUMNs on kernel_issues so no
|
|
198
|
+
// beads issue data is dropped — the author (created_by), the close timestamp
|
|
199
|
+
// (closed_at) and raw close reason (close_reason, distinct from the mapped terminal
|
|
200
|
+
// status), plus a verbatim JSON metadata blob (metadata). Plain ADD COLUMNs — the 001
|
|
201
|
+
// initial schema omits these columns (getInitialKernelSchema/MIGRATION_ADDED_COLUMNS)
|
|
202
|
+
// and the broker's per-migration ledger + guarded ADD COLUMN apply them exactly once.
|
|
203
|
+
function buildIssueFidelityColumnsMigration() {
|
|
204
|
+
return {
|
|
205
|
+
id: '006_kernel_issue_fidelity_columns',
|
|
206
|
+
apply: [
|
|
207
|
+
'ALTER TABLE kernel_issues ADD COLUMN created_by TEXT;',
|
|
208
|
+
'ALTER TABLE kernel_issues ADD COLUMN closed_at TEXT;',
|
|
209
|
+
'ALTER TABLE kernel_issues ADD COLUMN close_reason TEXT;',
|
|
210
|
+
'ALTER TABLE kernel_issues ADD COLUMN metadata TEXT;',
|
|
211
|
+
],
|
|
212
|
+
rollback: [
|
|
213
|
+
'ALTER TABLE kernel_issues DROP COLUMN metadata;',
|
|
214
|
+
'ALTER TABLE kernel_issues DROP COLUMN close_reason;',
|
|
215
|
+
'ALTER TABLE kernel_issues DROP COLUMN closed_at;',
|
|
216
|
+
'ALTER TABLE kernel_issues DROP COLUMN created_by;',
|
|
217
|
+
],
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// 007: the kernel linkage backbone. Two additive ADD COLUMNs on kernel_worktrees so a
|
|
222
|
+
// worktree row records the issue it serves (issue_id) and the work-folder that issue
|
|
223
|
+
// owns (work_folder, repo-relative). This is what turns `kernel_worktrees` from a
|
|
224
|
+
// schema-only/never-written table into the queryable issue → worktree → work-folder
|
|
225
|
+
// chain, so orientation resolves the work-folder from the kernel instead of the
|
|
226
|
+
// "most-complete folder" filesystem heuristic. Plain ADD COLUMNs — the 001 initial
|
|
227
|
+
// schema omits these columns (getInitialKernelSchema/MIGRATION_ADDED_COLUMNS) and the
|
|
228
|
+
// broker's per-migration ledger + guarded ADD COLUMN apply them exactly once.
|
|
229
|
+
function buildWorktreeLinkageColumnsMigration() {
|
|
230
|
+
return {
|
|
231
|
+
id: '007_kernel_worktrees_linkage_columns',
|
|
232
|
+
apply: [
|
|
233
|
+
'ALTER TABLE kernel_worktrees ADD COLUMN issue_id TEXT;',
|
|
234
|
+
'ALTER TABLE kernel_worktrees ADD COLUMN work_folder TEXT;',
|
|
235
|
+
],
|
|
236
|
+
rollback: [
|
|
237
|
+
'ALTER TABLE kernel_worktrees DROP COLUMN work_folder;',
|
|
238
|
+
'ALTER TABLE kernel_worktrees DROP COLUMN issue_id;',
|
|
239
|
+
],
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// 008: an FTS5 full-text index over kernel_memories, the shared token-efficient
|
|
244
|
+
// retrieval layer (Section B of the decision-store design). It is an EXTERNAL-CONTENT
|
|
245
|
+
// FTS5 table (content='kernel_memories', content_rowid='rowid'), so the index stores no
|
|
246
|
+
// second copy of the row — it reads the memory text back from kernel_memories by rowid.
|
|
247
|
+
// Three triggers keep the index synchronized with every INSERT/DELETE/UPDATE (the
|
|
248
|
+
// upsert-by-key path fires INSERT on first write and UPDATE on a key collision), and a
|
|
249
|
+
// one-time 'rebuild' backfills any rows written before the index existed (e.g. rows the
|
|
250
|
+
// insights engine wrote through recordMemory). The FTS virtual table lives OUTSIDE
|
|
251
|
+
// schema.js (it is not a kernel authority/read-model table), so it is created only here.
|
|
252
|
+
// CREATE … IF NOT EXISTS + CREATE TRIGGER IF NOT EXISTS keep a re-run idempotent, and the
|
|
253
|
+
// same DDL is reused by the driver's lazy ensureMemorySchema so a synchronous memory write
|
|
254
|
+
// stays indexed without a prior broker.initialize().
|
|
255
|
+
function memoryFtsDdl() {
|
|
256
|
+
return {
|
|
257
|
+
create: "CREATE VIRTUAL TABLE IF NOT EXISTS kernel_memories_fts USING fts5(\n key,\n value_json,\n tags_json,\n content='kernel_memories',\n content_rowid='rowid'\n);",
|
|
258
|
+
triggers: [
|
|
259
|
+
'CREATE TRIGGER IF NOT EXISTS kernel_memories_ai AFTER INSERT ON kernel_memories BEGIN\n INSERT INTO kernel_memories_fts(rowid, key, value_json, tags_json)\n VALUES (new.rowid, new.key, new.value_json, new.tags_json);\nEND;',
|
|
260
|
+
"CREATE TRIGGER IF NOT EXISTS kernel_memories_ad AFTER DELETE ON kernel_memories BEGIN\n INSERT INTO kernel_memories_fts(kernel_memories_fts, rowid, key, value_json, tags_json)\n VALUES ('delete', old.rowid, old.key, old.value_json, old.tags_json);\nEND;",
|
|
261
|
+
"CREATE TRIGGER IF NOT EXISTS kernel_memories_au AFTER UPDATE ON kernel_memories BEGIN\n INSERT INTO kernel_memories_fts(kernel_memories_fts, rowid, key, value_json, tags_json)\n VALUES ('delete', old.rowid, old.key, old.value_json, old.tags_json);\n INSERT INTO kernel_memories_fts(rowid, key, value_json, tags_json)\n VALUES (new.rowid, new.key, new.value_json, new.tags_json);\nEND;",
|
|
262
|
+
],
|
|
263
|
+
rebuild: "INSERT INTO kernel_memories_fts(kernel_memories_fts) VALUES('rebuild');",
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function buildMemoryFtsMigration() {
|
|
268
|
+
const ddl = memoryFtsDdl();
|
|
269
|
+
return {
|
|
270
|
+
id: '008_kernel_memories_fts',
|
|
271
|
+
apply: [ddl.create, ...ddl.triggers, ddl.rebuild],
|
|
272
|
+
rollback: [
|
|
273
|
+
'DROP TRIGGER IF EXISTS kernel_memories_au;',
|
|
274
|
+
'DROP TRIGGER IF EXISTS kernel_memories_ad;',
|
|
275
|
+
'DROP TRIGGER IF EXISTS kernel_memories_ai;',
|
|
276
|
+
'DROP TABLE IF EXISTS kernel_memories_fts;',
|
|
277
|
+
],
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function validateKernelMigrations(migrations) {
|
|
282
|
+
const ids = new Set();
|
|
283
|
+
for (const migration of migrations) {
|
|
284
|
+
if (!migration || !migration.id) {
|
|
285
|
+
throw new Error('Kernel migration is missing an id');
|
|
286
|
+
}
|
|
287
|
+
if (ids.has(migration.id)) {
|
|
288
|
+
throw new Error(`Duplicate Kernel migration id: ${migration.id}`);
|
|
289
|
+
}
|
|
290
|
+
ids.add(migration.id);
|
|
291
|
+
if (!Array.isArray(migration.apply) || migration.apply.length === 0) {
|
|
292
|
+
throw new Error(`Kernel migration ${migration.id} has no apply statements`);
|
|
293
|
+
}
|
|
294
|
+
if (!Array.isArray(migration.rollback) || migration.rollback.length === 0) {
|
|
295
|
+
throw new Error(`Kernel migration ${migration.id} has no rollback statements`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function buildKernelMigrationPlan(migrations = [
|
|
303
|
+
buildSchemaMigration(),
|
|
304
|
+
buildEventExpectedRevisionMigration(),
|
|
305
|
+
buildClaimActiveLeaseMigration(),
|
|
306
|
+
buildIssueContentFieldsMigration(),
|
|
307
|
+
buildMemoryProjectionMigration(),
|
|
308
|
+
buildIssueFidelityColumnsMigration(),
|
|
309
|
+
buildWorktreeLinkageColumnsMigration(),
|
|
310
|
+
buildMemoryFtsMigration(),
|
|
311
|
+
]) {
|
|
312
|
+
validateKernelMigrations(migrations);
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
migrations,
|
|
316
|
+
apply: migrations.flatMap(migration => migration.apply),
|
|
317
|
+
rollback: [...migrations].reverse().flatMap(migration => migration.rollback),
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
module.exports = {
|
|
322
|
+
buildClaimActiveLeaseMigration,
|
|
323
|
+
buildEventExpectedRevisionMigration,
|
|
324
|
+
buildIssueContentFieldsMigration,
|
|
325
|
+
buildIssueFidelityColumnsMigration,
|
|
326
|
+
buildKernelMigrationPlan,
|
|
327
|
+
buildMemoryFtsMigration,
|
|
328
|
+
buildMemoryProjectionMigration,
|
|
329
|
+
buildSchemaMigration,
|
|
330
|
+
buildWorktreeLinkageColumnsMigration,
|
|
331
|
+
memoryFtsDdl,
|
|
332
|
+
validateKernelMigrations,
|
|
333
|
+
};
|