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,336 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `forge doc-gate` — repo-structure detector command (thin wrapper).
|
|
5
|
+
*
|
|
6
|
+
* Subcommand: `detect [--json]`. Runs the validated doc-gate detector
|
|
7
|
+
* (lib/doc-gate/detect.js) against the cwd repo and prints a human summary by
|
|
8
|
+
* default, or a structured JSON object with `--json`.
|
|
9
|
+
*
|
|
10
|
+
* Exit non-zero ONLY on a real error (not a git repo / no commits, or an
|
|
11
|
+
* unknown subcommand). An ESCALATE-TO-AGENT or MANUAL-CONFIG verdict is a valid
|
|
12
|
+
* detection result and exits 0.
|
|
13
|
+
*
|
|
14
|
+
* This is the first PR of the larger "doc-gate" feature; the CI required-gate,
|
|
15
|
+
* OKF-bundle generation, and agent escalation runtime are deliberately NOT here.
|
|
16
|
+
*
|
|
17
|
+
* @module commands/doc-gate
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const fs = require('node:fs');
|
|
21
|
+
const path = require('node:path');
|
|
22
|
+
const { execFileSync } = require('node:child_process');
|
|
23
|
+
const { detect } = require('../doc-gate/detect');
|
|
24
|
+
const { evaluateGate } = require('../doc-gate/gate');
|
|
25
|
+
const { scaffoldDeclaration, DECLARATION_FILE } = require('../doc-gate/declaration');
|
|
26
|
+
const { loadOkfConfig, setOkfEnabled, isOkfEnabled } = require('../doc-gate/okf-config');
|
|
27
|
+
const { generateBundle, linkAgentsMd } = require('../doc-gate/okf');
|
|
28
|
+
|
|
29
|
+
/** True when `root` is a git working tree with at least one commit (HEAD). */
|
|
30
|
+
function hasGitHead(root) {
|
|
31
|
+
try {
|
|
32
|
+
// NOSONAR S4036 - 'git' is a hardcoded CLI command with no user input; developer-tool context.
|
|
33
|
+
execFileSync('git', ['-C', root, 'rev-parse', '--verify', 'HEAD'], { // NOSONAR S4036
|
|
34
|
+
stdio: ['ignore', 'ignore', 'ignore'],
|
|
35
|
+
});
|
|
36
|
+
return true;
|
|
37
|
+
} catch (_err) {
|
|
38
|
+
// A missing/broken git HEAD simply means "not a usable repo" here. NOSONAR S2486
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Format a single field for the human summary. */
|
|
44
|
+
function fieldLine(label, field, valueKey) {
|
|
45
|
+
let display;
|
|
46
|
+
if (field.escalate) {
|
|
47
|
+
display = `ESCALATE→agent (${field.trigger})`;
|
|
48
|
+
} else {
|
|
49
|
+
const raw = field[valueKey];
|
|
50
|
+
if (raw === null || raw === undefined || (Array.isArray(raw) && raw.length === 0)) {
|
|
51
|
+
display = '(none / abstain)';
|
|
52
|
+
} else {
|
|
53
|
+
display = Array.isArray(raw) ? raw.join(', ') : String(raw);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return ` ${label.padEnd(10)} ${display} [${field.confidence}]`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Render the detector result as a readable summary. */
|
|
60
|
+
function renderHuman(result) {
|
|
61
|
+
const lines = [
|
|
62
|
+
`doc-gate detect — ${result.repo}`,
|
|
63
|
+
`verdict: ${result.verdict}`,
|
|
64
|
+
fieldLine('source', result.source, 'value'),
|
|
65
|
+
fieldLine('toolchain', result.toolchain, 'value'),
|
|
66
|
+
fieldLine('ci', result.ci, 'provider'),
|
|
67
|
+
fieldLine('changelog', result.changelog, 'value'),
|
|
68
|
+
fieldLine('agents', result.agents, 'value'),
|
|
69
|
+
`code-high-confidence: ${result.codeHighConfidence.length ? result.codeHighConfidence.join(', ') : '(none)'}`,
|
|
70
|
+
];
|
|
71
|
+
if (Array.isArray(result.escalate) && result.escalate.length > 0) {
|
|
72
|
+
lines.push('escalate:');
|
|
73
|
+
for (const e of result.escalate) {
|
|
74
|
+
const detail = e.detail ? ` (${e.detail})` : '';
|
|
75
|
+
lines.push(` - ${e.field}: ${e.trigger}${detail}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return lines.join('\n');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const CHECK_USAGE = 'forge doc-gate check --base <ref> --head <ref> [--skip] [--json]';
|
|
82
|
+
const INIT_USAGE = 'forge doc-gate init [--force] [--json]';
|
|
83
|
+
const OKF_USAGE =
|
|
84
|
+
'forge doc-gate okf <status|enable|disable> [--json]\n' +
|
|
85
|
+
' forge doc-gate okf generate [--source <dir>] [--out <dir>] [--json]\n' +
|
|
86
|
+
' forge doc-gate okf link [--out <dir>] [--json]';
|
|
87
|
+
const USAGE = `forge doc-gate detect [--json]\n ${CHECK_USAGE}\n ${INIT_USAGE}\n ${OKF_USAGE}`;
|
|
88
|
+
|
|
89
|
+
/** Standard "not a usable git repo" error result (exit 1). */
|
|
90
|
+
function notAGitRepo(root, sub) {
|
|
91
|
+
return {
|
|
92
|
+
success: false,
|
|
93
|
+
error:
|
|
94
|
+
`forge doc-gate ${sub} must run inside a git repository with at least one ` +
|
|
95
|
+
`commit (HEAD). No readable git HEAD was found at ${root}.`,
|
|
96
|
+
exitCode: 1,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Read a `--name value` or `--name=value` flag from a raw argv array. */
|
|
101
|
+
function readArgValue(argv, name) {
|
|
102
|
+
const prefix = `${name}=`;
|
|
103
|
+
const eq = argv.find(a => a.startsWith(prefix));
|
|
104
|
+
if (eq) return eq.slice(prefix.length);
|
|
105
|
+
const i = argv.indexOf(name);
|
|
106
|
+
if (i >= 0 && i + 1 < argv.length && !argv[i + 1].startsWith('-')) return argv[i + 1];
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Render a gate result as a readable summary. */
|
|
111
|
+
function renderGate(result) {
|
|
112
|
+
const surface = Array.isArray(result.sourceSurface) && result.sourceSurface.length > 0
|
|
113
|
+
? result.sourceSurface.join(', ')
|
|
114
|
+
: '(unresolved)';
|
|
115
|
+
const lines = [
|
|
116
|
+
`doc-gate check — ${result.decision.toUpperCase()}`,
|
|
117
|
+
`verdict: ${result.verdict}`,
|
|
118
|
+
`source surface: ${surface}`,
|
|
119
|
+
`reason: ${result.reason}`,
|
|
120
|
+
];
|
|
121
|
+
if (result.offendingCodeFiles.length > 0) {
|
|
122
|
+
lines.push('code changes without a doc update:');
|
|
123
|
+
for (const f of result.offendingCodeFiles) lines.push(` - ${f}`);
|
|
124
|
+
}
|
|
125
|
+
if (result.docChangesSeen.length > 0) {
|
|
126
|
+
lines.push('doc changes seen:');
|
|
127
|
+
for (const f of result.docChangesSeen) lines.push(` - ${f}`);
|
|
128
|
+
}
|
|
129
|
+
return lines.join('\n');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** `detect` subcommand — repo-structure summary (exit 0 on any verdict). */
|
|
133
|
+
function runDetect(argv, flags, root) {
|
|
134
|
+
if (!hasGitHead(root)) return notAGitRepo(root, 'detect');
|
|
135
|
+
const json = Boolean(flags?.json) || argv.includes('--json');
|
|
136
|
+
const result = detect(root);
|
|
137
|
+
const output = json ? JSON.stringify(result, null, 2) : renderHuman(result);
|
|
138
|
+
return { success: true, output, json };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** `check` subcommand — enforce "a code change requires a doc update". */
|
|
142
|
+
function runCheck(argv, flags, root) {
|
|
143
|
+
if (!hasGitHead(root)) return notAGitRepo(root, 'check');
|
|
144
|
+
const json = Boolean(flags?.json) || argv.includes('--json');
|
|
145
|
+
const skip = Boolean(flags?.skip) || argv.includes('--skip');
|
|
146
|
+
const base = flags?.base ?? readArgValue(argv, '--base');
|
|
147
|
+
const head = flags?.head ?? readArgValue(argv, '--head');
|
|
148
|
+
if (!skip && (!base || !head)) {
|
|
149
|
+
return {
|
|
150
|
+
success: false,
|
|
151
|
+
error: `forge doc-gate check requires --base <ref> and --head <ref>.\nUsage: ${CHECK_USAGE}`,
|
|
152
|
+
exitCode: 1,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const result = evaluateGate({ root, base, head, skip });
|
|
157
|
+
const output = json ? JSON.stringify(result, null, 2) : renderGate(result);
|
|
158
|
+
// Exit 1 ONLY on a hard fail; pass and abstain both exit 0.
|
|
159
|
+
if (result.decision === 'fail') {
|
|
160
|
+
return { success: false, error: `doc-gate: ${result.reason}`, output, exitCode: 1 };
|
|
161
|
+
}
|
|
162
|
+
return { success: true, output, json };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Render an init result as a readable summary. */
|
|
166
|
+
function renderInit(payload) {
|
|
167
|
+
const decl = payload.declaration;
|
|
168
|
+
const lines = [
|
|
169
|
+
`doc-gate init — wrote ${payload.path}${payload.overwritten ? ' (overwritten)' : ''}`,
|
|
170
|
+
`source: ${decl.source.join(', ')}`,
|
|
171
|
+
`toolchain: ${decl.toolchain ?? '(none)'}`,
|
|
172
|
+
`Edit ${payload.path} to declare your repo structure, then commit it (tracked-files-only).`,
|
|
173
|
+
];
|
|
174
|
+
return lines.join('\n');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** `init` subcommand — scaffold a `.docgate.json` from the current detect() result. */
|
|
178
|
+
function runInit(argv, flags, root) {
|
|
179
|
+
if (!hasGitHead(root)) return notAGitRepo(root, 'init');
|
|
180
|
+
const json = Boolean(flags?.json) || argv.includes('--json');
|
|
181
|
+
const force = Boolean(flags?.force) || argv.includes('--force');
|
|
182
|
+
const target = path.join(root, DECLARATION_FILE);
|
|
183
|
+
// lstat (does NOT follow symlinks) detects both existence and a symlink target.
|
|
184
|
+
let stat = null;
|
|
185
|
+
try { stat = fs.lstatSync(target); } catch { /* absent: stat stays null */ }
|
|
186
|
+
// Refuse to write THROUGH a symlink — a checked-in symlink could clobber a file
|
|
187
|
+
// outside the repo. This holds even with --force.
|
|
188
|
+
if (stat?.isSymbolicLink()) {
|
|
189
|
+
return {
|
|
190
|
+
success: false,
|
|
191
|
+
error: `${DECLARATION_FILE} at ${root} is a symlink; refusing to write through it.`,
|
|
192
|
+
exitCode: 1,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
const exists = stat !== null;
|
|
196
|
+
if (exists && !force) {
|
|
197
|
+
return {
|
|
198
|
+
success: false,
|
|
199
|
+
error: `${DECLARATION_FILE} already exists at ${root}. Re-run with --force to overwrite.\nUsage: ${INIT_USAGE}`,
|
|
200
|
+
exitCode: 1,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const declaration = scaffoldDeclaration(detect(root));
|
|
205
|
+
try {
|
|
206
|
+
fs.writeFileSync(target, `${JSON.stringify(declaration, null, 2)}\n`);
|
|
207
|
+
} catch (err) {
|
|
208
|
+
// A write failure (permissions / read-only tree) is a real error — surface it.
|
|
209
|
+
return { success: false, error: `could not write ${DECLARATION_FILE}: ${err.message}`, exitCode: 1 };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const payload = { path: DECLARATION_FILE, overwritten: exists, declaration };
|
|
213
|
+
const output = json ? JSON.stringify(payload, null, 2) : renderInit(payload);
|
|
214
|
+
return { success: true, output, json };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** True when JSON output was requested (parsed flag OR raw `--json`). */
|
|
218
|
+
function wantsJson(argv, flags) {
|
|
219
|
+
return Boolean(flags?.json) || argv.includes('--json');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const OKF_DISABLED_HINT = 'OKF is disabled. Run `forge doc-gate okf enable` to turn it on.';
|
|
223
|
+
|
|
224
|
+
/** `okf status` — report whether OKF generation is enabled. */
|
|
225
|
+
function okfStatus(argv, flags, root) {
|
|
226
|
+
const payload = { enabled: loadOkfConfig(root).okf.enabled };
|
|
227
|
+
const json = wantsJson(argv, flags);
|
|
228
|
+
const output = json ? JSON.stringify(payload, null, 2) : `doc-gate okf: ${payload.enabled ? 'ENABLED' : 'disabled'}`;
|
|
229
|
+
return { success: true, output, json };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** `okf enable|disable` — flip the toggle in `.forge/doc-gate.json`. */
|
|
233
|
+
function okfSetEnabled(argv, flags, root, enabled) {
|
|
234
|
+
let cfg;
|
|
235
|
+
try {
|
|
236
|
+
cfg = setOkfEnabled(root, enabled);
|
|
237
|
+
} catch (err) {
|
|
238
|
+
return { success: false, error: `could not update OKF toggle: ${err.message}`, exitCode: 1 };
|
|
239
|
+
}
|
|
240
|
+
const payload = { enabled: cfg.okf.enabled };
|
|
241
|
+
const json = wantsJson(argv, flags);
|
|
242
|
+
const output = json ? JSON.stringify(payload, null, 2) : `doc-gate okf ${enabled ? 'enabled' : 'disabled'}.`;
|
|
243
|
+
return { success: true, output, json };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** Render a bundle-generation result as a readable summary. */
|
|
247
|
+
function renderOkfGenerate(result) {
|
|
248
|
+
return [
|
|
249
|
+
`doc-gate okf generate — wrote ${result.count} concept${result.count === 1 ? '' : 's'} to ${result.out}/`,
|
|
250
|
+
`source: ${result.source}`,
|
|
251
|
+
`okf_version: ${result.okfVersion}`,
|
|
252
|
+
`root index: ${result.out}/index.md`,
|
|
253
|
+
].join('\n');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** `okf generate` — build the OKF bundle (refuses unless enabled). */
|
|
257
|
+
function okfGenerate(argv, flags, root) {
|
|
258
|
+
if (!hasGitHead(root)) return notAGitRepo(root, 'okf generate');
|
|
259
|
+
if (!isOkfEnabled(root)) return { success: false, error: OKF_DISABLED_HINT, exitCode: 1 };
|
|
260
|
+
const source = flags?.source ?? readArgValue(argv, '--source');
|
|
261
|
+
const out = flags?.out ?? readArgValue(argv, '--out');
|
|
262
|
+
let result;
|
|
263
|
+
try {
|
|
264
|
+
result = generateBundle({ root, source: source ?? undefined, out: out ?? undefined });
|
|
265
|
+
} catch (err) {
|
|
266
|
+
return { success: false, error: `okf generate failed: ${err.message}`, exitCode: 1 };
|
|
267
|
+
}
|
|
268
|
+
if (!result.ok) return { success: false, error: result.error, exitCode: 1 };
|
|
269
|
+
const json = wantsJson(argv, flags);
|
|
270
|
+
const output = json ? JSON.stringify(result, null, 2) : renderOkfGenerate(result);
|
|
271
|
+
return { success: true, output, json };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** `okf link` — write the thin AGENTS.md nav overlay (refuses unless enabled). */
|
|
275
|
+
function okfLink(argv, flags, root) {
|
|
276
|
+
if (!isOkfEnabled(root)) return { success: false, error: OKF_DISABLED_HINT, exitCode: 1 };
|
|
277
|
+
const out = flags?.out ?? readArgValue(argv, '--out');
|
|
278
|
+
let result;
|
|
279
|
+
try {
|
|
280
|
+
result = linkAgentsMd({ root, out: out ?? undefined });
|
|
281
|
+
} catch (err) {
|
|
282
|
+
return { success: false, error: `okf link failed: ${err.message}`, exitCode: 1 };
|
|
283
|
+
}
|
|
284
|
+
const json = wantsJson(argv, flags);
|
|
285
|
+
const output = json
|
|
286
|
+
? JSON.stringify(result, null, 2)
|
|
287
|
+
: `doc-gate okf link — ${result.created ? 'created' : 'updated'} ${result.path} nav → ${result.target}`;
|
|
288
|
+
return { success: true, output, json };
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** `okf` subcommand router. */
|
|
292
|
+
function runOkf(argv, flags, root) {
|
|
293
|
+
const nonFlags = argv.filter(a => a && !a.startsWith('-'));
|
|
294
|
+
const action = nonFlags[1] ?? 'status';
|
|
295
|
+
if (action === 'status') return okfStatus(argv, flags, root);
|
|
296
|
+
if (action === 'enable') return okfSetEnabled(argv, flags, root, true);
|
|
297
|
+
if (action === 'disable') return okfSetEnabled(argv, flags, root, false);
|
|
298
|
+
if (action === 'generate') return okfGenerate(argv, flags, root);
|
|
299
|
+
if (action === 'link') return okfLink(argv, flags, root);
|
|
300
|
+
return {
|
|
301
|
+
success: false,
|
|
302
|
+
error: `Unknown doc-gate okf action '${action}'. Supported: status, enable, disable, generate, link.\nUsage: ${OKF_USAGE}`,
|
|
303
|
+
exitCode: 1,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
module.exports = {
|
|
308
|
+
name: 'doc-gate',
|
|
309
|
+
description: 'Detect a repository\'s structure, or enforce the doc-update gate on a PR',
|
|
310
|
+
usage: USAGE,
|
|
311
|
+
flags: {
|
|
312
|
+
'--json': 'Emit a structured JSON object instead of the human summary.',
|
|
313
|
+
'--base': 'check: base ref/SHA of the pull request.',
|
|
314
|
+
'--head': 'check: head ref/SHA of the pull request.',
|
|
315
|
+
'--skip': 'check: force a pass (e.g. a no-docs-needed label).',
|
|
316
|
+
'--force': 'init: overwrite an existing .docgate.json.',
|
|
317
|
+
'--source': 'okf generate: source docs dir (default: auto — docs/ or tracked markdown).',
|
|
318
|
+
'--out': 'okf generate/link: bundle output dir (default: .okf).',
|
|
319
|
+
},
|
|
320
|
+
|
|
321
|
+
async handler(args, flags, projectRoot, _opts = {}) {
|
|
322
|
+
const argv = Array.isArray(args) ? args : [];
|
|
323
|
+
const sub = argv.find(a => a && !a.startsWith('-')) || 'detect';
|
|
324
|
+
const root = projectRoot || process.cwd();
|
|
325
|
+
|
|
326
|
+
if (sub === 'detect') return runDetect(argv, flags, root);
|
|
327
|
+
if (sub === 'check') return runCheck(argv, flags, root);
|
|
328
|
+
if (sub === 'init') return runInit(argv, flags, root);
|
|
329
|
+
if (sub === 'okf') return runOkf(argv, flags, root);
|
|
330
|
+
return {
|
|
331
|
+
success: false,
|
|
332
|
+
error: `Unknown doc-gate subcommand '${sub}'. Supported subcommands: detect, check, init, okf.\nUsage: ${USAGE}`,
|
|
333
|
+
exitCode: 1,
|
|
334
|
+
};
|
|
335
|
+
},
|
|
336
|
+
};
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// D19 — `forge doctor`: reports the filesystem class of the kernel DB path.
|
|
4
|
+
//
|
|
5
|
+
// Auto-discovered + routed by lib/commands/_registry.js (no bin/forge.js edit).
|
|
6
|
+
// JSON-first, mirrors orient/recap. It is a REPORTER, not the enforcer — the
|
|
7
|
+
// default-on gate lives in broker.initialize(). doctor resolves the exact path
|
|
8
|
+
// the gate guards (via buildLocalBrokerConfig, which does no I/O) and reports
|
|
9
|
+
// whether that path is safe, WITHOUT creating any file.
|
|
10
|
+
|
|
11
|
+
const { buildLocalBrokerConfig } = require('../kernel/broker');
|
|
12
|
+
const { classifyFilesystem, isUnsafeFsOverrideActive, REMEDIATION } = require('../kernel/fs-class');
|
|
13
|
+
const memoryRouter = require('../memory/router');
|
|
14
|
+
|
|
15
|
+
const SCHEMA_VERSION = 1;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Best-effort, NON-FATAL memory-backend check. Reports the resolved backend and,
|
|
19
|
+
* when `graphiti` is selected, whether the opt-in config is coherent and the
|
|
20
|
+
* configured MCP server path exists locally. It never gates the overall report
|
|
21
|
+
* (that stays governed by the filesystem-class check) — a memory misconfig warns
|
|
22
|
+
* but does not make `forge doctor` fail.
|
|
23
|
+
*
|
|
24
|
+
* @param {string} projectRoot
|
|
25
|
+
* @param {object} env
|
|
26
|
+
* @returns {{ id: string, ok: boolean, backend: string, detail: string }}
|
|
27
|
+
*/
|
|
28
|
+
function buildMemoryCheck(projectRoot, env) {
|
|
29
|
+
const fs = require('node:fs');
|
|
30
|
+
try {
|
|
31
|
+
const { backend, graphiti } = memoryRouter.assertMemoryConfigValid({ projectRoot, env });
|
|
32
|
+
if (backend !== 'graphiti') {
|
|
33
|
+
return {
|
|
34
|
+
id: 'memory-backend',
|
|
35
|
+
ok: true,
|
|
36
|
+
backend,
|
|
37
|
+
detail: `memory backend: ${backend} (local kernel store, FTS5-indexed, offline)`,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const serverPath = graphiti.mcpServerPath;
|
|
41
|
+
const serverPathExists = fs.existsSync(serverPath);
|
|
42
|
+
const reach = serverPathExists
|
|
43
|
+
? `mcp_server present at ${serverPath}`
|
|
44
|
+
: `mcp_server path not found locally (${serverPath}); run it before agents can use graph memory`;
|
|
45
|
+
return {
|
|
46
|
+
// Reflect reality in the line symbol: a graphiti backend whose MCP server
|
|
47
|
+
// isn't present yet renders as `!` (warn), not a misleading `✓`. This does
|
|
48
|
+
// NOT affect the overall doctor exit — report.ok is governed solely by the
|
|
49
|
+
// filesystem-class check (see buildDoctorReport), so an uninstalled opt-in
|
|
50
|
+
// memory server warns without failing `forge doctor`.
|
|
51
|
+
id: 'memory-backend',
|
|
52
|
+
ok: serverPathExists,
|
|
53
|
+
backend,
|
|
54
|
+
serverPathExists,
|
|
55
|
+
detail: `memory backend: graphiti — served to agents via the graphiti-memory MCP server; ${reach}`,
|
|
56
|
+
};
|
|
57
|
+
} catch (err) {
|
|
58
|
+
const backend = memoryRouter.resolveMemoryBackend({ projectRoot, env, warn: () => {} });
|
|
59
|
+
return { id: 'memory-backend', ok: false, backend, detail: err.message };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Build the doctor report. Pure-ish: all I/O-bearing deps are injectable.
|
|
65
|
+
* @param {string} projectRoot
|
|
66
|
+
* @param {object} deps - { classifyFilesystem?, env?, gitCommonDir?, execFileSync? }
|
|
67
|
+
*/
|
|
68
|
+
function buildDoctorReport(projectRoot, deps = {}) {
|
|
69
|
+
const env = deps.env || process.env;
|
|
70
|
+
const classify = deps.classifyFilesystem || classifyFilesystem;
|
|
71
|
+
|
|
72
|
+
// Config build does NO I/O (F1): it never opens/creates the DB.
|
|
73
|
+
const config = buildLocalBrokerConfig({
|
|
74
|
+
projectRoot,
|
|
75
|
+
gitCommonDir: deps.gitCommonDir,
|
|
76
|
+
execFileSync: deps.execFileSync,
|
|
77
|
+
});
|
|
78
|
+
const databasePath = config.databasePath;
|
|
79
|
+
|
|
80
|
+
const classification = classify(databasePath, deps);
|
|
81
|
+
const overrideActive = isUnsafeFsOverrideActive(env);
|
|
82
|
+
const isRefuse = classification.riskTier === 'refuse';
|
|
83
|
+
const checkOk = !isRefuse || overrideActive;
|
|
84
|
+
const remediation = isRefuse
|
|
85
|
+
? (REMEDIATION[classification.remediationKey] || REMEDIATION.unknown)
|
|
86
|
+
: null;
|
|
87
|
+
|
|
88
|
+
const check = {
|
|
89
|
+
id: 'filesystem-class',
|
|
90
|
+
ok: checkOk,
|
|
91
|
+
databasePath,
|
|
92
|
+
class: classification.class,
|
|
93
|
+
riskTier: classification.riskTier,
|
|
94
|
+
signal: classification.signal,
|
|
95
|
+
remediation,
|
|
96
|
+
overrideActive,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const memoryCheck = buildMemoryCheck(projectRoot, env);
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
command: 'doctor',
|
|
103
|
+
schemaVersion: SCHEMA_VERSION,
|
|
104
|
+
// Overall ok is governed by the filesystem-class check (the enforcer). The
|
|
105
|
+
// memory-backend check is a NON-FATAL reporter and never fails doctor.
|
|
106
|
+
ok: check.ok,
|
|
107
|
+
checks: [check, memoryCheck],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function formatCheckLine(check) {
|
|
112
|
+
if (check.id === 'memory-backend') {
|
|
113
|
+
return `${check.ok ? '✓' : '!'} ${check.detail}`;
|
|
114
|
+
}
|
|
115
|
+
if (check.riskTier === 'safe') {
|
|
116
|
+
return `✓ filesystem: ${check.class} (safe) — ${check.databasePath}`;
|
|
117
|
+
}
|
|
118
|
+
if (check.riskTier === 'warn') {
|
|
119
|
+
return `! filesystem: ${check.class} (warn) — ${check.databasePath}`;
|
|
120
|
+
}
|
|
121
|
+
// refuse
|
|
122
|
+
const tag = check.overrideActive ? 'REFUSE, override active' : 'REFUSE';
|
|
123
|
+
return `✗ filesystem: ${check.class} (${tag}) — ${check.databasePath}`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function formatReportText(report) {
|
|
127
|
+
const lines = report.checks.map(formatCheckLine);
|
|
128
|
+
for (const check of report.checks) {
|
|
129
|
+
if (check.remediation) {
|
|
130
|
+
lines.push('', check.remediation);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
lines.push('', report.ok ? 'doctor: OK' : 'doctor: FAILED');
|
|
134
|
+
return `${lines.join('\n')}\n`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function handler(args, _flags, projectRoot, deps = {}) {
|
|
138
|
+
const report = buildDoctorReport(projectRoot, deps);
|
|
139
|
+
const output = (args || []).includes('--json')
|
|
140
|
+
? `${JSON.stringify(report, null, 2)}\n`
|
|
141
|
+
: formatReportText(report);
|
|
142
|
+
|
|
143
|
+
const result = { success: report.ok, output };
|
|
144
|
+
if (!report.ok) {
|
|
145
|
+
result.error = `forge doctor found a refuse-class filesystem for the kernel database (${report.checks[0].class}).`;
|
|
146
|
+
}
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
module.exports = {
|
|
151
|
+
name: 'doctor',
|
|
152
|
+
description: 'Report the filesystem class of the kernel database path (cloud/network/local)',
|
|
153
|
+
usage: 'Usage: forge doctor [--json]',
|
|
154
|
+
handler,
|
|
155
|
+
buildDoctorReport,
|
|
156
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const optionsCommand = require('./options');
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
name: 'explain',
|
|
7
|
+
description: 'Explain a runtime graph primitive',
|
|
8
|
+
usage: 'forge explain <id> [--json]',
|
|
9
|
+
flags: {
|
|
10
|
+
'--json': 'Emit machine-readable JSON output',
|
|
11
|
+
},
|
|
12
|
+
async handler(args, flags, projectRoot) {
|
|
13
|
+
return optionsCommand.handler(['why', ...args], flags, projectRoot);
|
|
14
|
+
},
|
|
15
|
+
};
|