forge-workflow 0.0.10 → 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 +150 -61
- package/CHANGELOG.md +681 -0
- package/CLAUDE.md +9 -118
- 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 +461 -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/forge/TOOLCHAIN.md +670 -0
- 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/{TOOLCHAIN.md → reference/TOOLCHAIN.md} +62 -47
- 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 +121 -0
- package/lib/beads-sync-scaffold.js +25 -101
- package/lib/codex-skills.js +51 -1
- package/lib/commands/_issue.js +741 -77
- 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 +17 -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 +0 -1
- 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 +838 -972
- 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 +158 -21
- package/lib/commands/sync.js +34 -46
- package/lib/commands/team.js +4 -1
- package/lib/commands/test.js +43 -27
- package/lib/commands/update.js +2 -2
- package/lib/commands/upgrade.js +47 -0
- package/lib/commands/validate.js +43 -18
- package/lib/commands/worktree.js +307 -100
- 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 +2 -2
- package/lib/deprecated-sync-cleanup.js +362 -0
- package/lib/detect-agent.js +2 -28
- package/lib/detect-worktree.js +35 -9
- 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 +382 -11
- 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/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 +3 -2
- 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 +99 -497
- 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 +241 -20
- 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/scoring.js +17 -3
- package/lib/status/beads-snapshot.js +45 -2
- package/lib/status/presenter.js +169 -18
- 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 +296 -47
- package/lib/workflow/stage-transition.js +115 -0
- package/lib/workflow/stages.js +30 -6
- package/lib/workflow/state-manager.js +11 -22
- package/lib/workflow/state.js +23 -1
- package/lib/workflow-profiles.js +17 -5
- package/package.json +37 -35
- 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 +81 -57
- package/scripts/beads-upgrade-smoke.sh +24 -3
- 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.sh +22 -3
- 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 +49 -84
- 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 +50 -83
- 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/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 +21 -94
- package/scripts/protected-state-check.js +104 -0
- package/scripts/smart-status.sh +60 -57
- 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-utils.sh +3 -0
- package/scripts/test-ci-shard.js +13 -6
- package/scripts/test.js +95 -12
- 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} +44 -50
- 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} +132 -157
- 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/{.github/prompts/ship.prompt.md → skills/ship/SKILL.md} +81 -45
- 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/{.github/prompts/status.prompt.md → skills/status/SKILL.md} +20 -10
- 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/verify.prompt.md +0 -271
- package/.github/workflows/beads-to-github.yml +0 -89
- package/.github/workflows/github-to-beads.yml +0 -100
- 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 -281
- 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-bootstrap.js +0 -225
- package/lib/beads-health-check.js +0 -188
- package/lib/commands/commands-reset.js +0 -147
- package/opencode.json +0 -67
- package/scripts/beads-context.test.js +0 -584
- 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 -356
- package/scripts/github-beads-sync/label-mapper.mjs +0 -54
- package/scripts/github-beads-sync/mapping.mjs +0 -132
- package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
- package/scripts/github-beads-sync/reverse-sync.mjs +0 -162
- 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,946 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const YAML = require('yaml');
|
|
6
|
+
|
|
7
|
+
const RUNTIME_GRAPH_SCHEMA_VERSION = '0.0.17';
|
|
8
|
+
const CONFIG_SOURCE = '.forge/config.yaml';
|
|
9
|
+
|
|
10
|
+
const RUNTIME_GRAPH_SCHEMA = {
|
|
11
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
12
|
+
$id: 'https://forge.local/schemas/runtime-graph-0.0.17.json',
|
|
13
|
+
title: 'Forge Runtime Graph Artifact',
|
|
14
|
+
type: 'object',
|
|
15
|
+
required: ['schemaVersion', 'kind', 'schema', 'graph'],
|
|
16
|
+
properties: {
|
|
17
|
+
schemaVersion: { const: RUNTIME_GRAPH_SCHEMA_VERSION },
|
|
18
|
+
kind: { const: 'forge.runtimeGraph' },
|
|
19
|
+
schema: {
|
|
20
|
+
type: 'object',
|
|
21
|
+
required: ['$schema', '$id'],
|
|
22
|
+
},
|
|
23
|
+
graph: {
|
|
24
|
+
type: 'object',
|
|
25
|
+
required: [
|
|
26
|
+
'primitives',
|
|
27
|
+
'phases',
|
|
28
|
+
'actions',
|
|
29
|
+
'artifacts',
|
|
30
|
+
'evaluatorRegions',
|
|
31
|
+
'gates',
|
|
32
|
+
'evidence',
|
|
33
|
+
'rails',
|
|
34
|
+
'adapters',
|
|
35
|
+
'edges',
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
function withRuntimeState(primitive, { enabled = true, locked = false, configSource = 'package-defaults' } = {}) {
|
|
42
|
+
return {
|
|
43
|
+
...primitive,
|
|
44
|
+
enabled,
|
|
45
|
+
disabled: enabled === false,
|
|
46
|
+
locked,
|
|
47
|
+
configSource,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function Phase({ id, label, command, actions, artifacts, gates, evidence, enabled = true, locked = false }) {
|
|
52
|
+
return withRuntimeState({ id, label, command, actions, artifacts, gates, evidence }, { enabled, locked });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function Action({ id, kind, command, label, reads = [], writes = [], evidence = [], enabled = true, locked = false }) {
|
|
56
|
+
return withRuntimeState({ id, kind, command, label, reads, writes, evidence }, { enabled, locked });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function Artifact({ id, kind, path, description }) {
|
|
60
|
+
return { id, kind, path, description };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function EvaluatorRegion({ id, label, phase, description }) {
|
|
64
|
+
return { id, label, phase, description };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function Gate({ id, phase, label, requires, enabled = true, locked = false }) {
|
|
68
|
+
return withRuntimeState({ id, phase, label, requires }, { enabled, locked });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function Evidence({ id, kind, source, description }) {
|
|
72
|
+
return { id, kind, source, description };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function Rail({ id, key, label, description, locked = true, enabled = true }) {
|
|
76
|
+
return withRuntimeState({ id, key, label, description, layer: 'L1' }, { enabled, locked });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function Adapter({ id, kind, label, config = {}, enabled = true }) {
|
|
80
|
+
return withRuntimeState({ id, kind, label, config }, { enabled });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function PlanningSubSkill({ id, label, inputs, outputs, evidence, gate, enabled = true }) {
|
|
84
|
+
return withRuntimeState({ id, label, inputs, outputs, evidence, gate }, { enabled });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function Role({ id, role, skill, ideology, configSource = 'package-defaults' }) {
|
|
88
|
+
const primitive = { id, role, skill, configSource };
|
|
89
|
+
if (ideology !== undefined) primitive.ideology = ideology;
|
|
90
|
+
return primitive;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Role -> default skill binding. Role names are the canonical stage ids
|
|
94
|
+
// (lib/workflow/stages.js STAGE_IDS); each ships bound to its like-named skill.
|
|
95
|
+
// This is the closed set of KNOWN roles; the bound skill name is open-world
|
|
96
|
+
// (resolved via .skills/ > skills/), never checked against PLAN_SUBSKILL_IDS.
|
|
97
|
+
// Additive to the graph — see docs/work/2026-07-04-kernel-native-skills/
|
|
98
|
+
// extensibility-architecture.md.
|
|
99
|
+
const ROLE_DEFINITIONS = [
|
|
100
|
+
{ role: 'plan', skill: 'plan' },
|
|
101
|
+
{ role: 'dev', skill: 'dev' },
|
|
102
|
+
{ role: 'validate', skill: 'validate' },
|
|
103
|
+
{ role: 'ship', skill: 'ship' },
|
|
104
|
+
{ role: 'review', skill: 'review' },
|
|
105
|
+
{ role: 'verify', skill: 'verify' },
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
const ROLE_IDS = new Set(ROLE_DEFINITIONS.map(definition => definition.role));
|
|
109
|
+
|
|
110
|
+
function roleFromDefinition(definition) {
|
|
111
|
+
return Role({ id: `role.${definition.role}`, role: definition.role, skill: definition.skill });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const PLAN_SUBSKILL_DEFINITIONS = [
|
|
115
|
+
{
|
|
116
|
+
id: 'plan.intent_capture',
|
|
117
|
+
label: 'Intent capture',
|
|
118
|
+
inputs: ['feature request', 'constraints'],
|
|
119
|
+
outputs: ['purpose', 'success criteria', 'out of scope'],
|
|
120
|
+
actionLabel: 'Capture planning intent',
|
|
121
|
+
reads: [],
|
|
122
|
+
writes: ['artifact.design-doc'],
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
id: 'plan.parallel_research',
|
|
126
|
+
label: 'Parallel research',
|
|
127
|
+
inputs: ['intent capture'],
|
|
128
|
+
outputs: ['technical approach options', 'risk notes'],
|
|
129
|
+
actionLabel: 'Research planning options',
|
|
130
|
+
reads: ['artifact.design-doc'],
|
|
131
|
+
writes: ['artifact.design-doc'],
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: 'plan.parallel_critics',
|
|
135
|
+
label: 'Parallel critics',
|
|
136
|
+
inputs: ['approach options'],
|
|
137
|
+
outputs: ['spec critique', 'risk critique', 'test critique'],
|
|
138
|
+
actionLabel: 'Critique planning options',
|
|
139
|
+
reads: ['artifact.design-doc'],
|
|
140
|
+
writes: ['artifact.design-doc'],
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
id: 'plan.synthesis',
|
|
144
|
+
label: 'Synthesis',
|
|
145
|
+
inputs: ['research', 'critic output'],
|
|
146
|
+
outputs: ['selected approach', 'tradeoffs'],
|
|
147
|
+
actionLabel: 'Synthesize selected plan',
|
|
148
|
+
reads: ['artifact.design-doc'],
|
|
149
|
+
writes: ['artifact.design-doc'],
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
id: 'plan.final_lock',
|
|
153
|
+
label: 'Final lock',
|
|
154
|
+
inputs: ['selected approach'],
|
|
155
|
+
outputs: ['design doc', 'task list'],
|
|
156
|
+
actionLabel: 'Lock planning artifacts',
|
|
157
|
+
reads: ['artifact.design-doc'],
|
|
158
|
+
writes: ['artifact.design-doc', 'artifact.task-list'],
|
|
159
|
+
},
|
|
160
|
+
];
|
|
161
|
+
|
|
162
|
+
const PLAN_SUBSKILL_IDS = new Set(PLAN_SUBSKILL_DEFINITIONS.map(definition => definition.id));
|
|
163
|
+
|
|
164
|
+
function planningSubSkillFromDefinition(definition) {
|
|
165
|
+
return PlanningSubSkill({
|
|
166
|
+
id: definition.id,
|
|
167
|
+
label: definition.label,
|
|
168
|
+
inputs: definition.inputs,
|
|
169
|
+
outputs: definition.outputs,
|
|
170
|
+
evidence: ['evidence.command-docs'],
|
|
171
|
+
gate: 'gate.plan-exit',
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function planningActionFromDefinition(definition) {
|
|
176
|
+
return Action({
|
|
177
|
+
id: `action.${definition.id}`,
|
|
178
|
+
kind: 'plan-subskill',
|
|
179
|
+
command: definition.id,
|
|
180
|
+
label: definition.actionLabel,
|
|
181
|
+
reads: definition.reads,
|
|
182
|
+
writes: definition.writes,
|
|
183
|
+
evidence: ['evidence.command-docs'],
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const RESOLVED_RUNTIME_GRAPH = {
|
|
188
|
+
id: 'forge.runtimeGraph.currentCommandFlow',
|
|
189
|
+
version: RUNTIME_GRAPH_SCHEMA_VERSION,
|
|
190
|
+
description: 'Resolved graph for the current /plan -> /dev -> /validate -> /ship command flow.',
|
|
191
|
+
primitives: {
|
|
192
|
+
Phase: 'phases',
|
|
193
|
+
Action: 'actions',
|
|
194
|
+
PlanningSubSkill: 'planning.subSkills',
|
|
195
|
+
Artifact: 'artifacts',
|
|
196
|
+
EvaluatorRegion: 'evaluatorRegions',
|
|
197
|
+
Gate: 'gates',
|
|
198
|
+
Evidence: 'evidence',
|
|
199
|
+
Rail: 'rails',
|
|
200
|
+
Adapter: 'adapters',
|
|
201
|
+
},
|
|
202
|
+
phases: [
|
|
203
|
+
Phase({
|
|
204
|
+
id: 'plan',
|
|
205
|
+
label: '/plan',
|
|
206
|
+
command: 'plan',
|
|
207
|
+
actions: PLAN_SUBSKILL_DEFINITIONS.map(definition => `action.${definition.id}`),
|
|
208
|
+
artifacts: ['artifact.design-doc', 'artifact.task-list'],
|
|
209
|
+
gates: ['gate.plan-exit'],
|
|
210
|
+
evidence: ['evidence.command-docs'],
|
|
211
|
+
}),
|
|
212
|
+
Phase({
|
|
213
|
+
id: 'dev',
|
|
214
|
+
label: '/dev',
|
|
215
|
+
command: 'dev',
|
|
216
|
+
actions: ['action.dev.command'],
|
|
217
|
+
artifacts: ['artifact.changed-files'],
|
|
218
|
+
gates: ['gate.dev-exit'],
|
|
219
|
+
evidence: ['evidence.tdd-tests'],
|
|
220
|
+
}),
|
|
221
|
+
Phase({
|
|
222
|
+
id: 'validate',
|
|
223
|
+
label: '/validate',
|
|
224
|
+
command: 'validate',
|
|
225
|
+
actions: ['action.validate.command'],
|
|
226
|
+
artifacts: ['artifact.validation-output'],
|
|
227
|
+
gates: ['gate.validate-exit'],
|
|
228
|
+
evidence: ['evidence.validation-output'],
|
|
229
|
+
}),
|
|
230
|
+
Phase({
|
|
231
|
+
id: 'ship',
|
|
232
|
+
label: '/ship',
|
|
233
|
+
command: 'ship',
|
|
234
|
+
actions: ['action.ship.command'],
|
|
235
|
+
artifacts: ['artifact.pull-request'],
|
|
236
|
+
gates: ['gate.ship-entry'],
|
|
237
|
+
evidence: ['evidence.dry-run-report'],
|
|
238
|
+
}),
|
|
239
|
+
],
|
|
240
|
+
planning: {
|
|
241
|
+
template: {
|
|
242
|
+
mode: 'full',
|
|
243
|
+
convergenceThreshold: 0.75,
|
|
244
|
+
criticSet: ['spec', 'risk', 'test'],
|
|
245
|
+
partialInvocation: {
|
|
246
|
+
only: [],
|
|
247
|
+
skip: [],
|
|
248
|
+
},
|
|
249
|
+
configSource: 'package-defaults',
|
|
250
|
+
},
|
|
251
|
+
subSkills: PLAN_SUBSKILL_DEFINITIONS.map(planningSubSkillFromDefinition),
|
|
252
|
+
},
|
|
253
|
+
roles: ROLE_DEFINITIONS.map(roleFromDefinition),
|
|
254
|
+
actions: [
|
|
255
|
+
Action({
|
|
256
|
+
id: 'action.plan.command',
|
|
257
|
+
kind: 'command',
|
|
258
|
+
command: 'plan',
|
|
259
|
+
label: 'Capture design intent and task list',
|
|
260
|
+
writes: ['artifact.design-doc', 'artifact.task-list'],
|
|
261
|
+
evidence: ['evidence.command-docs'],
|
|
262
|
+
}),
|
|
263
|
+
...PLAN_SUBSKILL_DEFINITIONS.map(planningActionFromDefinition),
|
|
264
|
+
Action({
|
|
265
|
+
id: 'action.dev.command',
|
|
266
|
+
kind: 'command',
|
|
267
|
+
command: 'dev',
|
|
268
|
+
label: 'Implement tasks with TDD evidence',
|
|
269
|
+
reads: ['artifact.design-doc', 'artifact.task-list'],
|
|
270
|
+
writes: ['artifact.changed-files'],
|
|
271
|
+
evidence: ['evidence.tdd-tests'],
|
|
272
|
+
}),
|
|
273
|
+
Action({
|
|
274
|
+
id: 'action.validate.command',
|
|
275
|
+
kind: 'command',
|
|
276
|
+
command: 'validate',
|
|
277
|
+
label: 'Run type, lint, test, and security checks',
|
|
278
|
+
reads: ['artifact.changed-files'],
|
|
279
|
+
writes: ['artifact.validation-output'],
|
|
280
|
+
evidence: ['evidence.validation-output'],
|
|
281
|
+
}),
|
|
282
|
+
Action({
|
|
283
|
+
id: 'action.ship.command',
|
|
284
|
+
kind: 'command',
|
|
285
|
+
command: 'ship',
|
|
286
|
+
label: 'Push branch and open a pull request',
|
|
287
|
+
reads: ['artifact.validation-output'],
|
|
288
|
+
writes: ['artifact.pull-request'],
|
|
289
|
+
evidence: ['evidence.dry-run-report'],
|
|
290
|
+
}),
|
|
291
|
+
],
|
|
292
|
+
artifacts: [
|
|
293
|
+
Artifact({
|
|
294
|
+
id: 'artifact.design-doc',
|
|
295
|
+
kind: 'markdown',
|
|
296
|
+
path: 'docs/work/YYYY-MM-DD-<slug>/plan.md',
|
|
297
|
+
description: 'Design intent, constraints, and research.',
|
|
298
|
+
}),
|
|
299
|
+
Artifact({
|
|
300
|
+
id: 'artifact.task-list',
|
|
301
|
+
kind: 'markdown',
|
|
302
|
+
path: 'docs/work/YYYY-MM-DD-<slug>/tasks.md',
|
|
303
|
+
description: 'TDD task list consumed by /dev.',
|
|
304
|
+
}),
|
|
305
|
+
Artifact({
|
|
306
|
+
id: 'artifact.changed-files',
|
|
307
|
+
kind: 'git-diff',
|
|
308
|
+
path: '<worktree>',
|
|
309
|
+
description: 'Source and test changes produced during /dev.',
|
|
310
|
+
}),
|
|
311
|
+
Artifact({
|
|
312
|
+
id: 'artifact.validation-output',
|
|
313
|
+
kind: 'terminal-output',
|
|
314
|
+
path: '<session>',
|
|
315
|
+
description: 'Fresh validation command output.',
|
|
316
|
+
}),
|
|
317
|
+
Artifact({
|
|
318
|
+
id: 'artifact.pull-request',
|
|
319
|
+
kind: 'github-pr',
|
|
320
|
+
path: '<pr-url>',
|
|
321
|
+
description: 'Pull request created by /ship.',
|
|
322
|
+
}),
|
|
323
|
+
],
|
|
324
|
+
evaluatorRegions: [
|
|
325
|
+
EvaluatorRegion({
|
|
326
|
+
id: 'evaluator.plan-entry',
|
|
327
|
+
label: 'Plan entry and exit gates',
|
|
328
|
+
phase: 'plan',
|
|
329
|
+
description: 'Checks worktree isolation, design artifacts, and task handoff.',
|
|
330
|
+
}),
|
|
331
|
+
EvaluatorRegion({
|
|
332
|
+
id: 'evaluator.tdd-loop',
|
|
333
|
+
label: 'TDD implementation loop',
|
|
334
|
+
phase: 'dev',
|
|
335
|
+
description: 'Checks RED/GREEN/REFACTOR evidence and task completion.',
|
|
336
|
+
}),
|
|
337
|
+
EvaluatorRegion({
|
|
338
|
+
id: 'evaluator.validation-suite',
|
|
339
|
+
label: 'Validation suite',
|
|
340
|
+
phase: 'validate',
|
|
341
|
+
description: 'Checks type, lint, tests, and security scan evidence.',
|
|
342
|
+
}),
|
|
343
|
+
EvaluatorRegion({
|
|
344
|
+
id: 'evaluator.ship-readiness',
|
|
345
|
+
label: 'Ship readiness',
|
|
346
|
+
phase: 'ship',
|
|
347
|
+
description: 'Checks branch freshness, PR body, and issue linkage.',
|
|
348
|
+
}),
|
|
349
|
+
],
|
|
350
|
+
gates: [
|
|
351
|
+
Gate({
|
|
352
|
+
id: 'gate.plan-exit',
|
|
353
|
+
phase: 'plan',
|
|
354
|
+
label: '/plan exit',
|
|
355
|
+
requires: ['artifact.design-doc', 'artifact.task-list', 'evidence.command-docs'],
|
|
356
|
+
}),
|
|
357
|
+
Gate({
|
|
358
|
+
id: 'gate.dev-exit',
|
|
359
|
+
phase: 'dev',
|
|
360
|
+
label: '/dev exit',
|
|
361
|
+
requires: ['artifact.changed-files', 'evidence.tdd-tests'],
|
|
362
|
+
}),
|
|
363
|
+
Gate({
|
|
364
|
+
id: 'gate.validate-exit',
|
|
365
|
+
phase: 'validate',
|
|
366
|
+
label: '/validate exit',
|
|
367
|
+
requires: ['artifact.validation-output', 'evidence.validation-output'],
|
|
368
|
+
}),
|
|
369
|
+
Gate({
|
|
370
|
+
id: 'gate.ship-entry',
|
|
371
|
+
phase: 'ship',
|
|
372
|
+
label: '/ship entry',
|
|
373
|
+
requires: ['artifact.validation-output'],
|
|
374
|
+
}),
|
|
375
|
+
// Human gates — satisfied by a `gate.approved` kernel EVENT on the issue, not by
|
|
376
|
+
// an artifact (see lib/gate-events.js + docs/work/2026-07-04-kernel-native-skills/
|
|
377
|
+
// decisions.md). Additive + backward-compatible: they widen the known-gate set so
|
|
378
|
+
// `forge gate approve|reject|status|check` recognize them and each is toggleable
|
|
379
|
+
// via `workflow.gates.<id>.enabled`. `requires: []` because approval, not evidence,
|
|
380
|
+
// is the exit condition. Default three: intent · plan-approval · merge.
|
|
381
|
+
Gate({
|
|
382
|
+
id: 'gate.intent',
|
|
383
|
+
phase: 'plan',
|
|
384
|
+
label: 'Intent approval',
|
|
385
|
+
requires: [],
|
|
386
|
+
}),
|
|
387
|
+
Gate({
|
|
388
|
+
id: 'gate.plan-approval',
|
|
389
|
+
phase: 'plan',
|
|
390
|
+
label: 'Plan approval',
|
|
391
|
+
requires: [],
|
|
392
|
+
}),
|
|
393
|
+
Gate({
|
|
394
|
+
id: 'gate.merge',
|
|
395
|
+
phase: 'ship',
|
|
396
|
+
label: 'Merge approval',
|
|
397
|
+
requires: [],
|
|
398
|
+
}),
|
|
399
|
+
// gate.issue_verify — check-after-write verification on kernel issue mutations
|
|
400
|
+
// (create|update|close|claim|comment) at the lib/commands/_issue.js boundary:
|
|
401
|
+
// after a successful write, re-read through the same broker and confirm the
|
|
402
|
+
// intended delta landed (verified/mismatches in the envelope; warn-only — the
|
|
403
|
+
// write's ok is never overturned). Default ON and UNLOCKED, toggled via
|
|
404
|
+
// `forge gate disable gate.issue_verify` through the same workflow.gates
|
|
405
|
+
// surface as rail.kernel_tracking. Justified by two proven "ok:true lied"
|
|
406
|
+
// bugs: 145d9ad1 (close --reason/closed_at dropped in the projection) and
|
|
407
|
+
// d71a824b (idempotent claim replay telling a losing agent it won). No phase:
|
|
408
|
+
// it guards the issue-command boundary, not a workflow stage exit.
|
|
409
|
+
// `requires: []` — the read-back itself, not evidence, is the check.
|
|
410
|
+
Gate({
|
|
411
|
+
id: 'gate.issue_verify',
|
|
412
|
+
label: 'Issue write verification (check-after-write)',
|
|
413
|
+
requires: [],
|
|
414
|
+
}),
|
|
415
|
+
],
|
|
416
|
+
evidence: [
|
|
417
|
+
Evidence({
|
|
418
|
+
id: 'evidence.command-docs',
|
|
419
|
+
kind: 'checked-in-doc',
|
|
420
|
+
source: 'skills/*/SKILL.md',
|
|
421
|
+
description: 'Skill docs define the compatibility surface.',
|
|
422
|
+
}),
|
|
423
|
+
Evidence({
|
|
424
|
+
id: 'evidence.tdd-tests',
|
|
425
|
+
kind: 'test-output',
|
|
426
|
+
source: 'bun test',
|
|
427
|
+
description: 'Task-level failing then passing test output.',
|
|
428
|
+
}),
|
|
429
|
+
Evidence({
|
|
430
|
+
id: 'evidence.validation-output',
|
|
431
|
+
kind: 'validation-output',
|
|
432
|
+
source: 'bun run typecheck; bun run lint; bun test; bun audit',
|
|
433
|
+
description: 'Fresh validation evidence for the branch.',
|
|
434
|
+
}),
|
|
435
|
+
Evidence({
|
|
436
|
+
id: 'evidence.dry-run-report',
|
|
437
|
+
kind: 'dry-run-output',
|
|
438
|
+
source: 'forge migrate --dry-run',
|
|
439
|
+
description: 'Resolved graph can be printed without side effects.',
|
|
440
|
+
}),
|
|
441
|
+
],
|
|
442
|
+
// Rails are declared as a data table and mapped through Rail() so each entry
|
|
443
|
+
// stays a single line (Rail defaults locked: true; kernel_tracking is the one
|
|
444
|
+
// UNLOCKED, default-ON rail — it ingrains "file every issue/idea/bug/decision
|
|
445
|
+
// to the kernel immediately; nothing discussed goes missing", and a maintainer
|
|
446
|
+
// may opt out via `forge gate disable rail.kernel_tracking`, consumed by the
|
|
447
|
+
// resolver's rail-aware gate loop over workflow.gates.<id>.enabled).
|
|
448
|
+
rails: [
|
|
449
|
+
{ key: 'tdd_intent', label: 'TDD intent evidence', description: 'Source changes require test intent and TDD evidence.' },
|
|
450
|
+
{ key: 'secret_scan', label: 'Secret scan', description: 'Validation must not knowingly ship secrets.' },
|
|
451
|
+
{ key: 'branch_protection', label: 'Branch protection', description: 'Ship through reviewed branches instead of direct protected-branch edits.' },
|
|
452
|
+
{ key: 'signed_commits', label: 'Signed commits', description: 'Preserve commit provenance requirements where configured.' },
|
|
453
|
+
{ key: 'schema_integrity', label: 'Schema integrity', description: 'Keep runtime graph and config schemas internally consistent.' },
|
|
454
|
+
{ key: 'kernel_tracking', label: 'Kernel issue tracking', description: 'Every issue, idea, bug, and decision discussed is filed to the Forge Kernel.', locked: false },
|
|
455
|
+
].map(def => Rail({ id: `rail.${def.key}`, ...def })),
|
|
456
|
+
adapters: [
|
|
457
|
+
Adapter({
|
|
458
|
+
id: 'adapter.issue',
|
|
459
|
+
kind: 'issue',
|
|
460
|
+
label: 'Issue tracking adapter',
|
|
461
|
+
config: { primary: 'beads', mirrors: ['github'] },
|
|
462
|
+
}),
|
|
463
|
+
Adapter({
|
|
464
|
+
id: 'adapter.harness',
|
|
465
|
+
kind: 'harness',
|
|
466
|
+
label: 'Agent harness adapter',
|
|
467
|
+
config: { targets: ['codex', 'claude', 'cursor'] },
|
|
468
|
+
}),
|
|
469
|
+
],
|
|
470
|
+
protectedPaths: [],
|
|
471
|
+
edges: [
|
|
472
|
+
{ from: 'phase.plan', to: 'phase.dev', reason: 'design and task list feed implementation' },
|
|
473
|
+
{ from: 'phase.dev', to: 'phase.validate', reason: 'implementation feeds validation' },
|
|
474
|
+
{ from: 'phase.validate', to: 'phase.ship', reason: 'validated branch can be shipped' },
|
|
475
|
+
],
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
function cloneJson(value) {
|
|
479
|
+
return JSON.parse(JSON.stringify(value));
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function getConfigPath(projectRoot = process.cwd()) {
|
|
483
|
+
return path.join(projectRoot, '.forge', 'config.yaml');
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function loadRuntimeGraphConfig({ projectRoot = process.cwd() } = {}) {
|
|
487
|
+
const configPath = getConfigPath(projectRoot);
|
|
488
|
+
if (!fs.existsSync(configPath)) {
|
|
489
|
+
return { config: {}, configPath, exists: false, errors: [] };
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
try {
|
|
493
|
+
const rawConfig = fs.readFileSync(configPath, 'utf8');
|
|
494
|
+
const parsed = rawConfig.trim() === '' ? {} : YAML.parse(rawConfig);
|
|
495
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
496
|
+
return {
|
|
497
|
+
config: {},
|
|
498
|
+
configPath,
|
|
499
|
+
exists: true,
|
|
500
|
+
errors: [{
|
|
501
|
+
code: 'CONFIG_ROOT_INVALID',
|
|
502
|
+
message: `${CONFIG_SOURCE} root must be an object.`,
|
|
503
|
+
}],
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
return {
|
|
507
|
+
config: parsed,
|
|
508
|
+
configPath,
|
|
509
|
+
exists: true,
|
|
510
|
+
errors: [],
|
|
511
|
+
};
|
|
512
|
+
} catch (err) {
|
|
513
|
+
return {
|
|
514
|
+
config: {},
|
|
515
|
+
configPath,
|
|
516
|
+
exists: true,
|
|
517
|
+
errors: [{
|
|
518
|
+
code: 'CONFIG_PARSE_ERROR',
|
|
519
|
+
message: `Invalid ${CONFIG_SOURCE}: ${err.message}`,
|
|
520
|
+
}],
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function markConfigured(primitive, options) {
|
|
526
|
+
Object.assign(primitive, withRuntimeState(primitive, {
|
|
527
|
+
enabled: options.enabled,
|
|
528
|
+
locked: primitive.locked === true,
|
|
529
|
+
configSource: CONFIG_SOURCE,
|
|
530
|
+
}));
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function readEnabledOption(options, errors, typeName, id) {
|
|
534
|
+
if (!options || typeof options !== 'object' || Array.isArray(options)) {
|
|
535
|
+
errors.push({
|
|
536
|
+
code: 'INVALID_PRIMITIVE_CONFIG',
|
|
537
|
+
message: `${typeName} '${id}' config must be an object.`,
|
|
538
|
+
});
|
|
539
|
+
return { present: false, valid: false, value: undefined };
|
|
540
|
+
}
|
|
541
|
+
if (!Object.hasOwn(options, 'enabled')) {
|
|
542
|
+
return { present: false, valid: true, value: undefined };
|
|
543
|
+
}
|
|
544
|
+
if (typeof options.enabled !== 'boolean') {
|
|
545
|
+
errors.push({
|
|
546
|
+
code: 'INVALID_ENABLED_VALUE',
|
|
547
|
+
message: `${typeName} '${id}' enabled must be a boolean.`,
|
|
548
|
+
});
|
|
549
|
+
return { present: true, valid: false, value: undefined };
|
|
550
|
+
}
|
|
551
|
+
return { present: true, valid: true, value: options.enabled };
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function readConfigSection(parent, key, errors, path) {
|
|
555
|
+
if (!Object.hasOwn(parent, key)) {
|
|
556
|
+
return {};
|
|
557
|
+
}
|
|
558
|
+
const value = parent[key];
|
|
559
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
560
|
+
errors.push({
|
|
561
|
+
code: 'INVALID_CONFIG_SECTION',
|
|
562
|
+
message: `${path} must be an object.`,
|
|
563
|
+
});
|
|
564
|
+
return {};
|
|
565
|
+
}
|
|
566
|
+
return value;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function applyEnabledConfig(collection, id, options, errors, typeName) {
|
|
570
|
+
const primitive = collection.find(candidate => candidate.id === id);
|
|
571
|
+
if (!primitive) {
|
|
572
|
+
errors.push({
|
|
573
|
+
code: `UNKNOWN_${typeName.toUpperCase()}`,
|
|
574
|
+
message: `Unknown ${typeName} '${id}' in ${CONFIG_SOURCE}.`,
|
|
575
|
+
});
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const enabled = readEnabledOption(options, errors, typeName, id);
|
|
580
|
+
if (!enabled.valid) return;
|
|
581
|
+
if (enabled.present) {
|
|
582
|
+
if (enabled.value === false && primitive.locked === true) {
|
|
583
|
+
errors.push({
|
|
584
|
+
code: 'LOCKED_PRIMITIVE_DISABLED',
|
|
585
|
+
message: `Cannot disable locked ${typeName} '${id}'.`,
|
|
586
|
+
});
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
markConfigured(primitive, { enabled: enabled.value });
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function applyRailConfig(graph, config, errors) {
|
|
594
|
+
const rails = readConfigSection(config, 'rails', errors, 'rails');
|
|
595
|
+
for (const [key, options] of Object.entries(rails)) {
|
|
596
|
+
const rail = graph.rails.find(candidate => candidate.key === key || candidate.id === key);
|
|
597
|
+
if (!rail) {
|
|
598
|
+
errors.push({
|
|
599
|
+
code: 'UNKNOWN_RAIL',
|
|
600
|
+
message: `Unknown rail '${key}' in ${CONFIG_SOURCE}.`,
|
|
601
|
+
});
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
const enabled = readEnabledOption(options, errors, 'rail', key);
|
|
605
|
+
if (!enabled.valid) {
|
|
606
|
+
continue;
|
|
607
|
+
}
|
|
608
|
+
if (enabled.value === false) {
|
|
609
|
+
errors.push({
|
|
610
|
+
code: 'LOCKED_L1_RAIL_DISABLED',
|
|
611
|
+
message: `Cannot disable locked L1 rail '${key}'.`,
|
|
612
|
+
});
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
if (options && Object.keys(options).length > 0) {
|
|
616
|
+
rail.configSource = CONFIG_SOURCE;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
function applyWorkflowConfig(graph, config, errors) {
|
|
622
|
+
const workflow = readConfigSection(config, 'workflow', errors, 'workflow');
|
|
623
|
+
const phases = readConfigSection(workflow, 'phases', errors, 'workflow.phases');
|
|
624
|
+
for (const [id, options] of Object.entries(phases)) {
|
|
625
|
+
applyEnabledConfig(graph.phases, id, options, errors, 'stage');
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// The `workflow.gates.<id>.enabled` surface (driven by `forge gate enable|disable`)
|
|
629
|
+
// governs gates AND unlocked toggleable rails (e.g. rail.kernel_tracking): the id
|
|
630
|
+
// namespaces are disjoint (gate.* vs rail.*), so a single combined lookup routes each
|
|
631
|
+
// id to its primitive. applyEnabledConfig still honors each primitive's `locked` flag,
|
|
632
|
+
// so a locked L1 rail cannot be disabled through this surface either.
|
|
633
|
+
const gates = readConfigSection(workflow, 'gates', errors, 'workflow.gates');
|
|
634
|
+
const toggleable = [...graph.gates, ...graph.rails];
|
|
635
|
+
for (const [id, options] of Object.entries(gates)) {
|
|
636
|
+
applyEnabledConfig(toggleable, id, options, errors, 'gate');
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
function applyAdapterConfig(graph, config, errors) {
|
|
641
|
+
const adapters = readConfigSection(config, 'adapters', errors, 'adapters');
|
|
642
|
+
for (const [kind, options] of Object.entries(adapters)) {
|
|
643
|
+
const adapter = graph.adapters.find(candidate => candidate.kind === kind || candidate.id === kind);
|
|
644
|
+
if (!adapter) {
|
|
645
|
+
errors.push({
|
|
646
|
+
code: 'UNKNOWN_ADAPTER',
|
|
647
|
+
message: `Unknown adapter '${kind}' in ${CONFIG_SOURCE}.`,
|
|
648
|
+
});
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
if (!options || typeof options !== 'object' || Array.isArray(options)) {
|
|
652
|
+
errors.push({
|
|
653
|
+
code: 'INVALID_ADAPTER_CONFIG',
|
|
654
|
+
message: `adapter '${kind}' config must be an object.`,
|
|
655
|
+
});
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
const enabled = readEnabledOption(options, errors, 'adapter', kind);
|
|
659
|
+
if (!enabled.valid) {
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
const nextConfig = { ...options };
|
|
663
|
+
delete nextConfig.enabled;
|
|
664
|
+
if (enabled.present) {
|
|
665
|
+
markConfigured(adapter, { enabled: enabled.value });
|
|
666
|
+
}
|
|
667
|
+
adapter.config = { ...adapter.config };
|
|
668
|
+
Object.assign(adapter.config, nextConfig);
|
|
669
|
+
if (enabled.present || Object.keys(nextConfig).length > 0) {
|
|
670
|
+
adapter.configSource = CONFIG_SOURCE;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function readRoleStringField(options, field, role, errors) {
|
|
676
|
+
if (!Object.hasOwn(options, field)) {
|
|
677
|
+
return { present: false, value: undefined };
|
|
678
|
+
}
|
|
679
|
+
const value = options[field];
|
|
680
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
681
|
+
errors.push({
|
|
682
|
+
code: `INVALID_ROLE_${field.toUpperCase()}`,
|
|
683
|
+
message: `role '${role}' ${field} must be a non-empty string.`,
|
|
684
|
+
});
|
|
685
|
+
return { present: true, value: undefined };
|
|
686
|
+
}
|
|
687
|
+
return { present: true, value: value.trim() };
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// Reader for the `roles` section (sibling to `workflow.gates`). Role names are
|
|
691
|
+
// the KNOWN closed set (ROLE_IDS); the bound `skill` / `ideology` names are
|
|
692
|
+
// OPEN-WORLD — validated only as non-empty strings here, never against
|
|
693
|
+
// PLAN_SUBSKILL_IDS (that closed enum stays scoped to
|
|
694
|
+
// planning.template.partialInvocation). Skill EXISTENCE is enforced at write
|
|
695
|
+
// time by the `forge role` verb, not mid-run.
|
|
696
|
+
function applyRoleConfig(graph, config, errors) {
|
|
697
|
+
const roles = readConfigSection(config, 'roles', errors, 'roles');
|
|
698
|
+
for (const [role, options] of Object.entries(roles)) {
|
|
699
|
+
const entry = graph.roles.find(candidate => candidate.role === role || candidate.id === role);
|
|
700
|
+
if (!entry) {
|
|
701
|
+
errors.push({
|
|
702
|
+
code: 'UNKNOWN_ROLE',
|
|
703
|
+
message: `Unknown role '${role}' in ${CONFIG_SOURCE}.`,
|
|
704
|
+
});
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
if (!options || typeof options !== 'object' || Array.isArray(options)) {
|
|
708
|
+
errors.push({
|
|
709
|
+
code: 'INVALID_ROLE_CONFIG',
|
|
710
|
+
message: `role '${role}' config must be an object.`,
|
|
711
|
+
});
|
|
712
|
+
continue;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
let changed = false;
|
|
716
|
+
const skill = readRoleStringField(options, 'skill', role, errors);
|
|
717
|
+
if (skill.present && skill.value !== undefined) {
|
|
718
|
+
entry.skill = skill.value;
|
|
719
|
+
changed = true;
|
|
720
|
+
}
|
|
721
|
+
const ideology = readRoleStringField(options, 'ideology', role, errors);
|
|
722
|
+
if (ideology.present && ideology.value !== undefined) {
|
|
723
|
+
entry.ideology = ideology.value;
|
|
724
|
+
changed = true;
|
|
725
|
+
}
|
|
726
|
+
if (changed) {
|
|
727
|
+
entry.configSource = CONFIG_SOURCE;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
function assertStringList(value, errors, code, message) {
|
|
733
|
+
if (!Array.isArray(value)) {
|
|
734
|
+
errors.push({ code, message });
|
|
735
|
+
return false;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
let valid = true;
|
|
739
|
+
for (const item of value) {
|
|
740
|
+
if (typeof item !== 'string' || item.trim() === '') {
|
|
741
|
+
errors.push({ code, message });
|
|
742
|
+
valid = false;
|
|
743
|
+
break;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
return valid;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
function validatePlanSubSkillList(value, errors, path) {
|
|
750
|
+
if (!assertStringList(
|
|
751
|
+
value,
|
|
752
|
+
errors,
|
|
753
|
+
'INVALID_PLAN_SUBSKILL_LIST',
|
|
754
|
+
`${path} must be a list of planning sub-skill IDs.`
|
|
755
|
+
)) {
|
|
756
|
+
return undefined;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
const normalized = value.map(item => item.trim());
|
|
760
|
+
let hasUnknown = false;
|
|
761
|
+
for (const id of normalized) {
|
|
762
|
+
if (!PLAN_SUBSKILL_IDS.has(id)) {
|
|
763
|
+
hasUnknown = true;
|
|
764
|
+
errors.push({
|
|
765
|
+
code: 'UNKNOWN_PLAN_SUBSKILL',
|
|
766
|
+
message: `Unknown planning sub-skill '${id}' in ${CONFIG_SOURCE}.`,
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
if (hasUnknown) {
|
|
771
|
+
return undefined;
|
|
772
|
+
}
|
|
773
|
+
return normalized;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
function applyPlanningMode(template, nextTemplate, errors) {
|
|
777
|
+
if (!Object.hasOwn(template, 'mode')) return;
|
|
778
|
+
if (!['full', 'partial'].includes(template.mode)) {
|
|
779
|
+
errors.push({
|
|
780
|
+
code: 'INVALID_PLANNING_MODE',
|
|
781
|
+
message: 'planning.template.mode must be full or partial.',
|
|
782
|
+
});
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
nextTemplate.mode = template.mode;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
function applyConvergenceThreshold(template, nextTemplate, errors) {
|
|
789
|
+
if (!Object.hasOwn(template, 'convergenceThreshold')) return;
|
|
790
|
+
if (!Number.isFinite(template.convergenceThreshold)
|
|
791
|
+
|| template.convergenceThreshold < 0
|
|
792
|
+
|| template.convergenceThreshold > 1) {
|
|
793
|
+
errors.push({
|
|
794
|
+
code: 'INVALID_CONVERGENCE_THRESHOLD',
|
|
795
|
+
message: 'planning.template.convergenceThreshold must be a number from 0 to 1.',
|
|
796
|
+
});
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
nextTemplate.convergenceThreshold = template.convergenceThreshold;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function applyCriticSet(template, nextTemplate, errors) {
|
|
803
|
+
if (!Object.hasOwn(template, 'criticSet')) return;
|
|
804
|
+
if (assertStringList(template.criticSet, errors, 'INVALID_CRITIC', 'planning.template.criticSet must contain non-empty strings.')) {
|
|
805
|
+
nextTemplate.criticSet = template.criticSet.map(item => item.trim());
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
function applyPartialInvocation(template, nextTemplate, errors) {
|
|
810
|
+
if (!Object.hasOwn(template, 'partialInvocation')) return;
|
|
811
|
+
const partial = readConfigSection(template, 'partialInvocation', errors, 'planning.template.partialInvocation');
|
|
812
|
+
if (Object.hasOwn(partial, 'only')) {
|
|
813
|
+
const only = validatePlanSubSkillList(partial.only, errors, 'planning.template.partialInvocation.only');
|
|
814
|
+
if (only) nextTemplate.partialInvocation.only = only;
|
|
815
|
+
}
|
|
816
|
+
if (Object.hasOwn(partial, 'skip')) {
|
|
817
|
+
const skip = validatePlanSubSkillList(partial.skip, errors, 'planning.template.partialInvocation.skip');
|
|
818
|
+
if (skip) nextTemplate.partialInvocation.skip = skip;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
function applyPlanningConfig(graph, config, errors) {
|
|
823
|
+
const planning = readConfigSection(config, 'planning', errors, 'planning');
|
|
824
|
+
const template = readConfigSection(planning, 'template', errors, 'planning.template');
|
|
825
|
+
if (Object.keys(template).length === 0) {
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
const nextTemplate = { ...graph.planning.template, partialInvocation: { ...graph.planning.template.partialInvocation } };
|
|
830
|
+
applyPlanningMode(template, nextTemplate, errors);
|
|
831
|
+
applyConvergenceThreshold(template, nextTemplate, errors);
|
|
832
|
+
applyCriticSet(template, nextTemplate, errors);
|
|
833
|
+
applyPartialInvocation(template, nextTemplate, errors);
|
|
834
|
+
nextTemplate.configSource = CONFIG_SOURCE;
|
|
835
|
+
graph.planning.template = nextTemplate;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
function validateProtectedPaths(config, errors) {
|
|
839
|
+
if (!Object.hasOwn(config, 'protectedPaths')) {
|
|
840
|
+
return [];
|
|
841
|
+
}
|
|
842
|
+
if (!Array.isArray(config.protectedPaths)) {
|
|
843
|
+
errors.push({
|
|
844
|
+
code: 'PROTECTED_PATHS_INVALID',
|
|
845
|
+
message: 'protectedPaths must be a list of path patterns.',
|
|
846
|
+
});
|
|
847
|
+
return [];
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
const protectedPaths = [];
|
|
851
|
+
for (const [index, entry] of config.protectedPaths.entries()) {
|
|
852
|
+
if (typeof entry !== 'string' || entry.trim() === '') {
|
|
853
|
+
errors.push({
|
|
854
|
+
code: 'PROTECTED_PATH_INVALID',
|
|
855
|
+
message: `protectedPaths[${index}] must be a non-empty string.`,
|
|
856
|
+
});
|
|
857
|
+
continue;
|
|
858
|
+
}
|
|
859
|
+
const pattern = entry.trim();
|
|
860
|
+
if (['*', '**', '**/*', '.', './', '/'].includes(pattern)) {
|
|
861
|
+
errors.push({
|
|
862
|
+
code: 'PROTECTED_PATH_TOO_BROAD',
|
|
863
|
+
message: `protectedPaths[${index}] is too broad: ${pattern}`,
|
|
864
|
+
});
|
|
865
|
+
continue;
|
|
866
|
+
}
|
|
867
|
+
protectedPaths.push(pattern);
|
|
868
|
+
}
|
|
869
|
+
return protectedPaths;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
function resolveRuntimeGraph({ projectRoot = process.cwd(), throwOnError = true } = {}) {
|
|
873
|
+
const graph = cloneJson(RESOLVED_RUNTIME_GRAPH);
|
|
874
|
+
const loaded = loadRuntimeGraphConfig({ projectRoot });
|
|
875
|
+
const errors = [...loaded.errors];
|
|
876
|
+
|
|
877
|
+
if (loaded.errors.length === 0) {
|
|
878
|
+
applyRailConfig(graph, loaded.config, errors);
|
|
879
|
+
applyWorkflowConfig(graph, loaded.config, errors);
|
|
880
|
+
applyAdapterConfig(graph, loaded.config, errors);
|
|
881
|
+
applyRoleConfig(graph, loaded.config, errors);
|
|
882
|
+
applyPlanningConfig(graph, loaded.config, errors);
|
|
883
|
+
graph.protectedPaths = validateProtectedPaths(loaded.config, errors);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
graph.config = {
|
|
887
|
+
path: CONFIG_SOURCE,
|
|
888
|
+
loaded: loaded.exists,
|
|
889
|
+
errors,
|
|
890
|
+
};
|
|
891
|
+
|
|
892
|
+
if (throwOnError && errors.length > 0) {
|
|
893
|
+
throw new Error(errors.map(error => error.message).join('\n'));
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
return { graph, errors };
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
function getResolvedRuntimeGraph(options = {}) {
|
|
900
|
+
return resolveRuntimeGraph(options).graph;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
function getDefaultRuntimeGraph() {
|
|
904
|
+
return cloneJson(RESOLVED_RUNTIME_GRAPH);
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
function lintRuntimeGraphConfig(options = {}) {
|
|
908
|
+
const { graph, errors } = resolveRuntimeGraph({ ...options, throwOnError: false });
|
|
909
|
+
return {
|
|
910
|
+
ok: errors.length === 0,
|
|
911
|
+
errors,
|
|
912
|
+
warnings: [],
|
|
913
|
+
graph,
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
function buildRuntimeGraphEnvelope(graph = getResolvedRuntimeGraph()) {
|
|
918
|
+
return {
|
|
919
|
+
schemaVersion: RUNTIME_GRAPH_SCHEMA_VERSION,
|
|
920
|
+
kind: 'forge.runtimeGraph',
|
|
921
|
+
schema: cloneJson(RUNTIME_GRAPH_SCHEMA),
|
|
922
|
+
graph: cloneJson(graph),
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
module.exports = {
|
|
927
|
+
RUNTIME_GRAPH_SCHEMA_VERSION,
|
|
928
|
+
RUNTIME_GRAPH_SCHEMA,
|
|
929
|
+
Phase,
|
|
930
|
+
Action,
|
|
931
|
+
Artifact,
|
|
932
|
+
EvaluatorRegion,
|
|
933
|
+
Gate,
|
|
934
|
+
Evidence,
|
|
935
|
+
Rail,
|
|
936
|
+
Adapter,
|
|
937
|
+
PlanningSubSkill,
|
|
938
|
+
Role,
|
|
939
|
+
ROLE_IDS,
|
|
940
|
+
loadRuntimeGraphConfig,
|
|
941
|
+
lintRuntimeGraphConfig,
|
|
942
|
+
resolveRuntimeGraph,
|
|
943
|
+
getDefaultRuntimeGraph,
|
|
944
|
+
buildRuntimeGraphEnvelope,
|
|
945
|
+
getResolvedRuntimeGraph,
|
|
946
|
+
};
|