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,237 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Gate composition + real sub-runners for `forge preflight`.
|
|
5
|
+
*
|
|
6
|
+
* Each real runner takes an injectable `spawn` (defaults to spawnSync) so the
|
|
7
|
+
* composition and the runners are unit-testable without touching the shell.
|
|
8
|
+
* The four gates give DETERMINISTIC-GATE PARITY with CI on the blast radius of
|
|
9
|
+
* a change, without the slow full matrix:
|
|
10
|
+
*
|
|
11
|
+
* 1. lint — eslint --max-warnings 0 on changed files (or all with --all)
|
|
12
|
+
* 2. drift — skills-mirror + registry-sync + agentic-workflow structural asserts
|
|
13
|
+
* 3. sonar — eslint-plugin-sonarjs (cognitive-complexity=15 + parity rules)
|
|
14
|
+
* 4. affected — only the tests mapped from the changed files
|
|
15
|
+
*
|
|
16
|
+
* @module preflight/gates
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const path = require('node:path');
|
|
20
|
+
const { spawnSync } = require('node:child_process');
|
|
21
|
+
const { execFileSync } = require('node:child_process');
|
|
22
|
+
const nodeFs = require('node:fs');
|
|
23
|
+
const { getAffectedTestFiles } = require('../commands/test');
|
|
24
|
+
|
|
25
|
+
const IS_WINDOWS = process.platform === 'win32';
|
|
26
|
+
|
|
27
|
+
/** Filter a changed-file list down to the JS files eslint should look at. */
|
|
28
|
+
function lintableFiles(files) {
|
|
29
|
+
if (!Array.isArray(files)) return [];
|
|
30
|
+
return files.filter((f) => /\.(mjs|cjs|js)$/.test(f) && !f.includes('node_modules'));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Short, bounded summary for a spawn result. */
|
|
34
|
+
function statusSummary(result, okMsg, failMsg) {
|
|
35
|
+
return result && result.status === 0
|
|
36
|
+
? { ok: true, summary: okMsg }
|
|
37
|
+
: { ok: false, summary: failMsg };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Gate 1 — ESLint on the change blast radius (or the whole tree with --all).
|
|
42
|
+
* `files === null` means "lint everything" (`eslint .`).
|
|
43
|
+
*/
|
|
44
|
+
function runEslint(files, { projectRoot, spawn = spawnSync } = {}) {
|
|
45
|
+
const lintAll = files === null;
|
|
46
|
+
const targets = lintAll ? ['.'] : lintableFiles(files);
|
|
47
|
+
if (targets.length === 0) {
|
|
48
|
+
return { ok: true, summary: 'no changed JS files' };
|
|
49
|
+
}
|
|
50
|
+
const result = spawn(
|
|
51
|
+
'npx',
|
|
52
|
+
['eslint', '--max-warnings', '0', ...targets],
|
|
53
|
+
{ cwd: projectRoot, stdio: 'inherit', shell: IS_WINDOWS },
|
|
54
|
+
);
|
|
55
|
+
return statusSummary(
|
|
56
|
+
result,
|
|
57
|
+
lintAll ? 'whole tree clean' : `${targets.length} changed file(s) clean`,
|
|
58
|
+
'eslint reported errors/warnings',
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Gate 2 — deterministic structural asserts that CI enforces:
|
|
64
|
+
* - skills mirror drift (test/structural/skills-sync-drift.test.js)
|
|
65
|
+
* - SUBCOMMANDS<->ISSUE_COMMANDS registry sync (test/commands/_resolve-command-opts.test.js)
|
|
66
|
+
* - committed agentic-workflow mirror (scripts/sync-agentic-workflow.js --check)
|
|
67
|
+
*/
|
|
68
|
+
function runStructural({ projectRoot, spawn = spawnSync, fs = nodeFs } = {}) {
|
|
69
|
+
const exists = (rel) => {
|
|
70
|
+
try {
|
|
71
|
+
return fs.existsSync(path.join(projectRoot, rel));
|
|
72
|
+
} catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// These are Forge's OWN internal structural asserts. A consumer repo does not
|
|
78
|
+
// contain them and can NEVER satisfy them, so only run the ones that actually
|
|
79
|
+
// exist in this project. If none do, the gate is not applicable here — report
|
|
80
|
+
// an explicit SKIP rather than a false pass or an impossible failure.
|
|
81
|
+
const structuralTests = [
|
|
82
|
+
'test/structural/skills-sync-drift.test.js',
|
|
83
|
+
'test/commands/_resolve-command-opts.test.js',
|
|
84
|
+
].filter(exists);
|
|
85
|
+
const agenticScript = 'scripts/sync-agentic-workflow.js';
|
|
86
|
+
const hasAgentic = exists(agenticScript);
|
|
87
|
+
|
|
88
|
+
if (structuralTests.length === 0 && !hasAgentic) {
|
|
89
|
+
return {
|
|
90
|
+
ok: true,
|
|
91
|
+
skipped: true,
|
|
92
|
+
summary: 'no Forge-internal structural checks in this repo (consumer context)',
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (structuralTests.length > 0) {
|
|
97
|
+
const bun = spawn(
|
|
98
|
+
'bun',
|
|
99
|
+
['test', '--timeout', '15000', ...structuralTests],
|
|
100
|
+
{ cwd: projectRoot, stdio: 'inherit', shell: IS_WINDOWS },
|
|
101
|
+
);
|
|
102
|
+
if (!bun || bun.status !== 0) {
|
|
103
|
+
return { ok: false, summary: 'skills-mirror / registry-sync asserts failed' };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!hasAgentic) {
|
|
108
|
+
return { ok: true, summary: 'skills + registry in sync (no agentic-workflow mirror here)' };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const agentic = spawn(
|
|
112
|
+
'node',
|
|
113
|
+
[agenticScript, '--check'],
|
|
114
|
+
{ cwd: projectRoot, stdio: 'inherit', shell: IS_WINDOWS },
|
|
115
|
+
);
|
|
116
|
+
return statusSummary(
|
|
117
|
+
agentic,
|
|
118
|
+
'skills + registry + agentic-workflow in sync',
|
|
119
|
+
'agentic-workflow mirror drift (run: node scripts/sync-agentic-workflow.js)',
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Gate 3 — SonarCloud parity via eslint-plugin-sonarjs, using an isolated flat
|
|
125
|
+
* config (cognitive-complexity pinned to 15 + the parity rules) so it never
|
|
126
|
+
* merges with the repo's main eslint config.
|
|
127
|
+
* `files === null` means "scan everything" (whole-tree `--all` mode) — mirrors
|
|
128
|
+
* runEslint so `--all` never leaves sonar as a vacuous "no changed files" pass.
|
|
129
|
+
*/
|
|
130
|
+
function runSonar(files, { projectRoot, spawn = spawnSync } = {}) {
|
|
131
|
+
const scanAll = files === null;
|
|
132
|
+
const targets = scanAll ? ['.'] : lintableFiles(files);
|
|
133
|
+
if (targets.length === 0) {
|
|
134
|
+
return { ok: true, summary: 'no changed JS files' };
|
|
135
|
+
}
|
|
136
|
+
const config = path.join(projectRoot, 'scripts', 'preflight-sonar.eslint.config.mjs');
|
|
137
|
+
const result = spawn(
|
|
138
|
+
'npx',
|
|
139
|
+
['eslint', '--no-config-lookup', '--config', config, '--max-warnings', '0', ...targets],
|
|
140
|
+
{ cwd: projectRoot, stdio: 'inherit', shell: IS_WINDOWS },
|
|
141
|
+
);
|
|
142
|
+
return statusSummary(
|
|
143
|
+
result,
|
|
144
|
+
scanAll ? 'sonarjs clean (whole tree)' : `sonarjs clean (${targets.length} file(s))`,
|
|
145
|
+
'sonarjs parity violations (e.g. cognitive-complexity > 15)',
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Gate 4 — run ONLY the tests mapped from the changed files (reuses the
|
|
151
|
+
* pre-push affected-test mapping). No affected tests → fast-lane pass.
|
|
152
|
+
*/
|
|
153
|
+
function runAffectedTests({
|
|
154
|
+
projectRoot,
|
|
155
|
+
changedFiles: _changedFiles,
|
|
156
|
+
spawn = spawnSync,
|
|
157
|
+
resolveTests,
|
|
158
|
+
} = {}) {
|
|
159
|
+
const resolver = typeof resolveTests === 'function'
|
|
160
|
+
? resolveTests
|
|
161
|
+
// strict:true so a failed `git diff` THROWS here instead of returning [] —
|
|
162
|
+
// otherwise a git error would masquerade as "no affected tests" (fast-lane
|
|
163
|
+
// green), a fail-OPEN. The catch below turns that throw into a closed gate.
|
|
164
|
+
: () => getAffectedTestFiles(projectRoot, execFileSync, nodeFs, { strict: true });
|
|
165
|
+
let targets;
|
|
166
|
+
try {
|
|
167
|
+
targets = resolver() || [];
|
|
168
|
+
} catch (err) {
|
|
169
|
+
// A resolver ERROR must NOT masquerade as "no affected tests" (green).
|
|
170
|
+
// We could not determine what to run, so fail closed — never a vacuous pass.
|
|
171
|
+
const reason = err && err.message ? err.message : String(err);
|
|
172
|
+
return { ok: false, summary: `affected-test resolution failed — fail-closed (${reason})` };
|
|
173
|
+
}
|
|
174
|
+
if (targets.length === 0) {
|
|
175
|
+
return { ok: true, summary: 'no affected tests resolved (fast lane)' };
|
|
176
|
+
}
|
|
177
|
+
const result = spawn(
|
|
178
|
+
'bun',
|
|
179
|
+
['test', '--timeout', '15000', ...targets],
|
|
180
|
+
{ cwd: projectRoot, stdio: 'inherit', shell: IS_WINDOWS },
|
|
181
|
+
);
|
|
182
|
+
return statusSummary(
|
|
183
|
+
result,
|
|
184
|
+
`${targets.length} affected test file(s) passed`,
|
|
185
|
+
'affected tests failed',
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Compose the ordered gate list. Each gate delegates to a sub-runner that can
|
|
191
|
+
* be injected via `deps` for testing; defaults wire the real runners above.
|
|
192
|
+
*
|
|
193
|
+
* @param {Object} args
|
|
194
|
+
* @param {string} args.projectRoot
|
|
195
|
+
* @param {string[]} args.changedFiles
|
|
196
|
+
* @param {boolean} [args.runAll] - lint/scan whole tree instead of changed files
|
|
197
|
+
* @param {Object} [args.deps] - { eslint, structural, sonar, affected }
|
|
198
|
+
* @returns {{ name: string, run: () => Promise<{ok:boolean, summary?:string}> }[]}
|
|
199
|
+
*/
|
|
200
|
+
function buildGates({ projectRoot, changedFiles = [], runAll = false, deps = {} }) {
|
|
201
|
+
const eslint = deps.eslint || ((files) => runEslint(files, { projectRoot }));
|
|
202
|
+
const structural = deps.structural || (() => runStructural({ projectRoot }));
|
|
203
|
+
const sonar = deps.sonar || ((files) => runSonar(files, { projectRoot }));
|
|
204
|
+
const affected = deps.affected || (() => runAffectedTests({ projectRoot, changedFiles }));
|
|
205
|
+
|
|
206
|
+
// Under --all, scope BOTH lint and sonar to the whole tree (null). Otherwise
|
|
207
|
+
// sonar would receive changedFiles=[] and report a vacuous "no changed files"
|
|
208
|
+
// pass while lint scanned everything — a fail-open hole on the remedy path.
|
|
209
|
+
const scanTargets = runAll ? null : changedFiles;
|
|
210
|
+
|
|
211
|
+
// Affected-tests maps from the change set, which is empty under --all. Running
|
|
212
|
+
// the WHOLE suite here defeats preflight's fast purpose (and hangs on Windows),
|
|
213
|
+
// so mark it explicitly not-run — never a vacuous green. CI runs the full suite.
|
|
214
|
+
const affectedGate = runAll
|
|
215
|
+
? async () => ({
|
|
216
|
+
ok: true,
|
|
217
|
+
skipped: true,
|
|
218
|
+
summary: 'whole-tree mode (--all): affected-test mapping N/A — run the full suite (CI does)',
|
|
219
|
+
})
|
|
220
|
+
: async () => affected();
|
|
221
|
+
|
|
222
|
+
return [
|
|
223
|
+
{ name: 'lint', run: async () => eslint(scanTargets) },
|
|
224
|
+
{ name: 'drift/registry/mirror', run: async () => structural() },
|
|
225
|
+
{ name: 'sonar (cognitive-complexity=15)', run: async () => sonar(scanTargets) },
|
|
226
|
+
{ name: 'affected-tests', run: affectedGate },
|
|
227
|
+
];
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
module.exports = {
|
|
231
|
+
buildGates,
|
|
232
|
+
lintableFiles,
|
|
233
|
+
runEslint,
|
|
234
|
+
runStructural,
|
|
235
|
+
runSonar,
|
|
236
|
+
runAffectedTests,
|
|
237
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Fast-fail gate runner for `forge preflight`.
|
|
5
|
+
*
|
|
6
|
+
* Executes an ordered list of gates one at a time. The first gate to fail
|
|
7
|
+
* short-circuits the run: every later gate is reported as `skipped` and never
|
|
8
|
+
* executed. This keeps preflight FAST — an agent fixes the first broken gate
|
|
9
|
+
* and re-runs rather than waiting for the whole matrix.
|
|
10
|
+
*
|
|
11
|
+
* A gate is `{ name: string, run: () => Promise<{ ok: boolean, summary?: string }> }`.
|
|
12
|
+
* The sub-runner behind each `run` is injected by the composer (see gates.js),
|
|
13
|
+
* so this module stays pure and trivially testable.
|
|
14
|
+
*
|
|
15
|
+
* @module preflight/runner
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {Object} GateOutcome
|
|
20
|
+
* @property {boolean} ok - Whether the gate passed.
|
|
21
|
+
* @property {string} [summary] - Short human-readable result line.
|
|
22
|
+
*
|
|
23
|
+
* @typedef {Object} Gate
|
|
24
|
+
* @property {string} name
|
|
25
|
+
* @property {() => Promise<GateOutcome>} run
|
|
26
|
+
*
|
|
27
|
+
* @typedef {Object} GateResult
|
|
28
|
+
* @property {string} name
|
|
29
|
+
* @property {boolean|null} ok - true/false, or null when skipped.
|
|
30
|
+
* @property {boolean} skipped
|
|
31
|
+
* @property {string} summary
|
|
32
|
+
* @property {number} [durationMs]
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Execute a single gate, normalizing thrown errors into a failed outcome.
|
|
37
|
+
*
|
|
38
|
+
* @param {Gate} gate
|
|
39
|
+
* @returns {Promise<GateResult>}
|
|
40
|
+
*/
|
|
41
|
+
async function executeGate(gate) {
|
|
42
|
+
const started = Date.now();
|
|
43
|
+
let outcome;
|
|
44
|
+
try {
|
|
45
|
+
outcome = await gate.run();
|
|
46
|
+
} catch (err) {
|
|
47
|
+
const message = err && err.message ? err.message : String(err);
|
|
48
|
+
outcome = { ok: false, summary: `error: ${message}` };
|
|
49
|
+
}
|
|
50
|
+
const durationMs = Date.now() - started;
|
|
51
|
+
const skipped = !!outcome?.skipped;
|
|
52
|
+
// A skipped gate is a pass-through: it is neither a pass nor a failure, so it
|
|
53
|
+
// must never short-circuit later gates (see runGates fast-fail). BUT a skip
|
|
54
|
+
// must never MASK an explicit failure — a gate that reports {ok:false} has
|
|
55
|
+
// failed regardless of a skipped flag, so it stays failed and short-circuits.
|
|
56
|
+
const explicitOk = !!outcome?.ok;
|
|
57
|
+
const ok = skipped ? outcome?.ok !== false : explicitOk;
|
|
58
|
+
// Contract: an explicit ok:false can never normalize to a pass. Guards against
|
|
59
|
+
// a future edit reintroducing the skip-masks-failure bug (4b73b6bf).
|
|
60
|
+
if (outcome && outcome.ok === false && ok === true) {
|
|
61
|
+
throw new Error('executeGate contract violation: skipped must not override an explicit ok:false');
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
name: gate.name,
|
|
65
|
+
ok,
|
|
66
|
+
skipped,
|
|
67
|
+
summary: outcome?.summary || '',
|
|
68
|
+
durationMs,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Live-log label for a gate result — SKIP must never read as PASS. */
|
|
73
|
+
function gateLogMark(result) {
|
|
74
|
+
if (result.skipped) return 'SKIP';
|
|
75
|
+
return result.ok ? 'PASS' : 'FAIL';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Run gates in order, fast-failing at the first failure.
|
|
80
|
+
*
|
|
81
|
+
* @param {Gate[]} gates
|
|
82
|
+
* @param {{ log?: (line: string) => void }} [options]
|
|
83
|
+
* @returns {Promise<{ ok: boolean, results: GateResult[], failedIndex: number }>}
|
|
84
|
+
*/
|
|
85
|
+
async function runGates(gates, options = {}) {
|
|
86
|
+
const log = typeof options.log === 'function' ? options.log : () => {};
|
|
87
|
+
const results = [];
|
|
88
|
+
let ok = true;
|
|
89
|
+
let failedIndex = -1;
|
|
90
|
+
|
|
91
|
+
for (const gate of gates) {
|
|
92
|
+
if (!ok) {
|
|
93
|
+
results.push({
|
|
94
|
+
name: gate.name,
|
|
95
|
+
ok: null,
|
|
96
|
+
skipped: true,
|
|
97
|
+
summary: 'skipped (earlier gate failed)',
|
|
98
|
+
});
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const result = await executeGate(gate);
|
|
103
|
+
results.push(result);
|
|
104
|
+
const suffix = result.summary ? ` — ${result.summary}` : '';
|
|
105
|
+
log(`${gateLogMark(result)} ${gate.name}${suffix}`);
|
|
106
|
+
|
|
107
|
+
if (!result.ok) {
|
|
108
|
+
ok = false;
|
|
109
|
+
failedIndex = results.length - 1;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return { ok, results, failedIndex };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
module.exports = { runGates };
|
package/lib/project-discovery.js
CHANGED
|
@@ -263,16 +263,6 @@ async function detectInstalledAgents(projectPath) {
|
|
|
263
263
|
}
|
|
264
264
|
]
|
|
265
265
|
},
|
|
266
|
-
{
|
|
267
|
-
name: 'copilot',
|
|
268
|
-
checks: [
|
|
269
|
-
// .github/copilot-instructions.md
|
|
270
|
-
async () => {
|
|
271
|
-
const copilotFile = path.join(projectPath, '.github', 'copilot-instructions.md');
|
|
272
|
-
return fs.existsSync(copilotFile);
|
|
273
|
-
}
|
|
274
|
-
]
|
|
275
|
-
},
|
|
276
266
|
{
|
|
277
267
|
name: 'cursor',
|
|
278
268
|
checks: [
|
|
@@ -283,39 +273,6 @@ async function detectInstalledAgents(projectPath) {
|
|
|
283
273
|
}
|
|
284
274
|
]
|
|
285
275
|
},
|
|
286
|
-
{
|
|
287
|
-
name: 'cline',
|
|
288
|
-
checks: [
|
|
289
|
-
async () => fs.existsSync(path.join(projectPath, '.clinerules')),
|
|
290
|
-
async () => {
|
|
291
|
-
const clineDir = path.join(projectPath, '.cline');
|
|
292
|
-
return fs.existsSync(clineDir) && (await fs.promises.stat(clineDir)).isDirectory();
|
|
293
|
-
}
|
|
294
|
-
]
|
|
295
|
-
},
|
|
296
|
-
{
|
|
297
|
-
name: 'kilocode',
|
|
298
|
-
checks: [
|
|
299
|
-
async () => {
|
|
300
|
-
const kilocodeDir = path.join(projectPath, '.kilocode');
|
|
301
|
-
return fs.existsSync(kilocodeDir) && (await fs.promises.stat(kilocodeDir)).isDirectory();
|
|
302
|
-
},
|
|
303
|
-
async () => fs.existsSync(path.join(projectPath, '.kilocode', 'workflows', 'forge-workflow.md')),
|
|
304
|
-
async () => fs.existsSync(path.join(projectPath, '.kilocode', 'rules', 'workflow.md')),
|
|
305
|
-
async () => fs.existsSync(path.join(projectPath, '.kilocode', 'skills', 'forge-workflow', 'SKILL.md'))
|
|
306
|
-
]
|
|
307
|
-
},
|
|
308
|
-
{
|
|
309
|
-
name: 'roo',
|
|
310
|
-
checks: [
|
|
311
|
-
async () => fs.existsSync(path.join(projectPath, '.roorules')),
|
|
312
|
-
async () => {
|
|
313
|
-
const rooDir = path.join(projectPath, '.roo');
|
|
314
|
-
return fs.existsSync(rooDir) && (await fs.promises.stat(rooDir)).isDirectory();
|
|
315
|
-
},
|
|
316
|
-
async () => fs.existsSync(path.join(projectPath, '.roo', 'rules'))
|
|
317
|
-
]
|
|
318
|
-
},
|
|
319
276
|
{
|
|
320
277
|
name: 'codex',
|
|
321
278
|
checks: [
|
|
@@ -325,16 +282,6 @@ async function detectInstalledAgents(projectPath) {
|
|
|
325
282
|
return fs.existsSync(codexDir) && (await fs.promises.stat(codexDir)).isDirectory();
|
|
326
283
|
}
|
|
327
284
|
]
|
|
328
|
-
},
|
|
329
|
-
{
|
|
330
|
-
name: 'opencode',
|
|
331
|
-
checks: [
|
|
332
|
-
// opencode.json file
|
|
333
|
-
async () => {
|
|
334
|
-
const opencodeJson = path.join(projectPath, 'opencode.json');
|
|
335
|
-
return fs.existsSync(opencodeJson);
|
|
336
|
-
}
|
|
337
|
-
]
|
|
338
285
|
}
|
|
339
286
|
];
|
|
340
287
|
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { resolveKernelDatabasePath } = require('./kernel/cli-broker-factory');
|
|
4
|
+
const { createBuiltinSQLiteDriver } = require('./kernel/sqlite-driver');
|
|
5
|
+
|
|
6
|
+
// Project memory is a Forge read model persisted in the kernel store (kernel_memories),
|
|
7
|
+
// written DIRECTLY rather than through the issue CAS/guarded-event path. The store seam
|
|
8
|
+
// (options.store) is a driver-like object — { recordMemory, loadMemory, searchMemories,
|
|
9
|
+
// listMemories } — so hermetic tests stay in-memory. The default seam resolves the
|
|
10
|
+
// per-repo kernel database path and reuses one driver per database path (the CLI process
|
|
11
|
+
// is short-lived, so the connection closing on exit is sufficient cleanup).
|
|
12
|
+
|
|
13
|
+
const storeCache = new Map();
|
|
14
|
+
|
|
15
|
+
function defaultStore(projectRoot, options = {}) {
|
|
16
|
+
const databasePath = resolveKernelDatabasePath({
|
|
17
|
+
projectRoot,
|
|
18
|
+
gitCommonDir: options.gitCommonDir,
|
|
19
|
+
databasePath: options.databasePath,
|
|
20
|
+
});
|
|
21
|
+
let store = storeCache.get(databasePath);
|
|
22
|
+
if (!store) {
|
|
23
|
+
store = createBuiltinSQLiteDriver({ databasePath });
|
|
24
|
+
storeCache.set(databasePath, store);
|
|
25
|
+
}
|
|
26
|
+
return store;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function resolveStore(projectRoot, options = {}) {
|
|
30
|
+
return options.store ?? defaultStore(projectRoot, options);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function assertEntryObject(entry) {
|
|
34
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
|
|
35
|
+
throw new TypeError('project memory entry must be an object');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function assertRequiredString(value, fieldName) {
|
|
40
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
41
|
+
throw new TypeError(`project memory entry ${fieldName} is required`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function assertStringArray(value, fieldName) {
|
|
46
|
+
if (!Array.isArray(value) || value.some(item => typeof item !== 'string')) {
|
|
47
|
+
throw new TypeError(`project memory entry ${fieldName} must be an array of strings`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function assertOptionalConfidence(value) {
|
|
52
|
+
if (value !== undefined && (!Number.isFinite(value) || value < 0 || value > 1)) {
|
|
53
|
+
throw new TypeError('project memory entry confidence must be a number from 0 to 1');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function validateEntry(entry) {
|
|
58
|
+
assertEntryObject(entry);
|
|
59
|
+
assertRequiredString(entry.key, 'key');
|
|
60
|
+
if (!Object.hasOwn(entry, 'value') || entry.value === undefined) {
|
|
61
|
+
throw new TypeError('project memory entry value is required');
|
|
62
|
+
}
|
|
63
|
+
assertRequiredString(entry.sourceAgent || entry['source-agent'], 'sourceAgent');
|
|
64
|
+
if (entry.timestamp !== undefined
|
|
65
|
+
&& (typeof entry.timestamp !== 'string' || Number.isNaN(Date.parse(entry.timestamp)))) {
|
|
66
|
+
throw new TypeError('project memory entry timestamp must be an ISO timestamp string');
|
|
67
|
+
}
|
|
68
|
+
if (entry.scope !== undefined && (typeof entry.scope !== 'string' || entry.scope.trim() === '')) {
|
|
69
|
+
throw new TypeError('project memory entry scope must be a non-empty string');
|
|
70
|
+
}
|
|
71
|
+
assertOptionalConfidence(entry.confidence);
|
|
72
|
+
if (entry.tags !== undefined) assertStringArray(entry.tags, 'tags');
|
|
73
|
+
if (entry.supersedes !== undefined) assertStringArray(entry.supersedes, 'supersedes');
|
|
74
|
+
if (entry.beadsRefs !== undefined) assertStringArray(entry.beadsRefs, 'beadsRefs');
|
|
75
|
+
if (entry['beads-refs'] !== undefined) assertStringArray(entry['beads-refs'], 'beads-refs');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Canonicalize an entry for storage: resolve the snake-case input aliases, default the
|
|
79
|
+
// timestamp and tags, and carry the optional fields only when present (so the persisted
|
|
80
|
+
// shape matches the legacy entry exactly).
|
|
81
|
+
function normalizeEntry(key, entry) {
|
|
82
|
+
const normalized = {
|
|
83
|
+
key,
|
|
84
|
+
value: entry.value,
|
|
85
|
+
sourceAgent: entry.sourceAgent || entry['source-agent'],
|
|
86
|
+
tags: Array.isArray(entry.tags) ? [...entry.tags] : [],
|
|
87
|
+
timestamp: entry.timestamp ?? new Date().toISOString(),
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
if (entry.scope !== undefined) normalized.scope = entry.scope;
|
|
91
|
+
if (entry.confidence !== undefined) normalized.confidence = entry.confidence;
|
|
92
|
+
if (entry.supersedes !== undefined) normalized.supersedes = [...entry.supersedes];
|
|
93
|
+
if (entry.beadsRefs !== undefined || entry['beads-refs'] !== undefined) {
|
|
94
|
+
normalized.beadsRefs = [...(entry.beadsRefs || entry['beads-refs'])];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return normalized;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function write(projectRoot, entry, options = {}) {
|
|
101
|
+
validateEntry(entry);
|
|
102
|
+
const normalized = normalizeEntry(entry.key.trim(), entry);
|
|
103
|
+
resolveStore(projectRoot, options).recordMemory(normalized);
|
|
104
|
+
return normalized;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function read(projectRoot, key, options = {}) {
|
|
108
|
+
assertRequiredString(key, 'read key');
|
|
109
|
+
return resolveStore(projectRoot, options).loadMemory(key.trim());
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function search(projectRoot, query, options = {}) {
|
|
113
|
+
if (typeof query !== 'string' || query.trim() === '') {
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
return resolveStore(projectRoot, options).searchMemories(query.trim());
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function list(projectRoot, options = {}) {
|
|
120
|
+
return resolveStore(projectRoot, options).listMemories();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// The newest `limit` entries (default recall with no query). Delegates to the FTS-backed
|
|
124
|
+
// driver so recall never loads and re-sorts the whole table. `options.agents` (a
|
|
125
|
+
// source_agent allow-list) scopes the read, e.g. to human `remember` notes only.
|
|
126
|
+
function recent(projectRoot, limit, options = {}) {
|
|
127
|
+
return resolveStore(projectRoot, options).recentMemories(limit, { agents: options.agents });
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Total stored memories (optionally scoped by `options.agents`) — paired with `recent` so
|
|
131
|
+
// recall can report "showing N of TOTAL".
|
|
132
|
+
function count(projectRoot, options = {}) {
|
|
133
|
+
return resolveStore(projectRoot, options).countMemories({ agents: options.agents });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// BM25 top-N recall over the FTS5 index (token-AND). Unlike `search` (the legacy LIKE
|
|
137
|
+
// helper) this does not short-circuit an empty query — the driver falls back to recent so
|
|
138
|
+
// recall stays capped either way.
|
|
139
|
+
function searchRanked(projectRoot, query, limit, options = {}) {
|
|
140
|
+
return resolveStore(projectRoot, options).searchMemoriesRanked(query, limit);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Close and forget every cached default store. The CLI process is short-lived (the OS
|
|
144
|
+
// closes the handle on exit), so this is mainly a lifecycle helper for long-lived hosts and
|
|
145
|
+
// tests — it releases the SQLite/WAL handle before a temp dir is removed.
|
|
146
|
+
function closeAll() {
|
|
147
|
+
for (const store of storeCache.values()) {
|
|
148
|
+
try {
|
|
149
|
+
if (store && typeof store.close === 'function') store.close();
|
|
150
|
+
} catch {
|
|
151
|
+
// best-effort close
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
storeCache.clear();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
module.exports = {
|
|
158
|
+
read,
|
|
159
|
+
write,
|
|
160
|
+
search,
|
|
161
|
+
list,
|
|
162
|
+
recent,
|
|
163
|
+
count,
|
|
164
|
+
searchRanked,
|
|
165
|
+
closeAll,
|
|
166
|
+
};
|