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,202 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @module config-writer
|
|
5
|
+
*
|
|
6
|
+
* Sparse read-modify-write for the ONE schema-validated config surface,
|
|
7
|
+
* `.forge/config.yaml` (the file `lib/core/runtime-graph.js` reads via
|
|
8
|
+
* `loadRuntimeGraphConfig`). This is the single missing primitive behind the
|
|
9
|
+
* `forge gate` / `forge role` verbs: create-or-update the file, set/remove one
|
|
10
|
+
* nested key, and preserve every other key untouched.
|
|
11
|
+
*
|
|
12
|
+
* Design: docs/work/2026-07-04-kernel-native-skills/extensibility-architecture.md
|
|
13
|
+
* (§9 "the sparse writer"). This is NOT a generalization of the legacy
|
|
14
|
+
* `.forge/adapters.json` review writer — that path stays where it is.
|
|
15
|
+
*
|
|
16
|
+
* Key paths are ARRAYS of segments, not dotted strings, because real keys
|
|
17
|
+
* contain dots (e.g. the gate id `gate.plan-exit`). Splitting on `.` would
|
|
18
|
+
* corrupt them, so callers pass `['workflow', 'gates', 'gate.plan-exit',
|
|
19
|
+
* 'enabled']`.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const fs = require('node:fs');
|
|
23
|
+
const path = require('node:path');
|
|
24
|
+
const YAML = require('yaml');
|
|
25
|
+
const { getPackageRoot } = require('./package-root');
|
|
26
|
+
|
|
27
|
+
/** Root of the installed Forge package (ships the canonical `skills/` dir). */
|
|
28
|
+
const PACKAGE_ROOT = path.join(__dirname, '..');
|
|
29
|
+
|
|
30
|
+
const CONFIG_RELATIVE = path.join('.forge', 'config.yaml');
|
|
31
|
+
|
|
32
|
+
function getConfigPath(projectRoot = process.cwd()) {
|
|
33
|
+
return path.join(projectRoot, '.forge', 'config.yaml');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function assertKeyPath(keyPath) {
|
|
37
|
+
if (!Array.isArray(keyPath) || keyPath.length === 0
|
|
38
|
+
|| keyPath.some(seg => typeof seg !== 'string' || seg === '')) {
|
|
39
|
+
throw new Error('config-writer: keyPath must be a non-empty array of non-empty string segments.');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Load the raw config object (or `{}` if the file is absent/empty). */
|
|
44
|
+
function loadRawConfig(projectRoot = process.cwd()) {
|
|
45
|
+
const configPath = getConfigPath(projectRoot);
|
|
46
|
+
if (!fs.existsSync(configPath)) return {};
|
|
47
|
+
const raw = fs.readFileSync(configPath, 'utf8');
|
|
48
|
+
if (raw.trim() === '') return {};
|
|
49
|
+
const parsed = YAML.parse(raw);
|
|
50
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
51
|
+
throw new Error(`${CONFIG_RELATIVE} root must be an object.`);
|
|
52
|
+
}
|
|
53
|
+
return parsed;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function writeRawConfig(projectRoot, config) {
|
|
57
|
+
const configPath = getConfigPath(projectRoot);
|
|
58
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
59
|
+
fs.writeFileSync(configPath, YAML.stringify(config));
|
|
60
|
+
return configPath;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Set a nested key to `value`, creating intermediate objects as needed and
|
|
65
|
+
* preserving all sibling keys. Creates `.forge/config.yaml` if absent.
|
|
66
|
+
*
|
|
67
|
+
* @param {string} projectRoot
|
|
68
|
+
* @param {string[]} keyPath - Segments, e.g. ['roles', 'plan', 'skill'].
|
|
69
|
+
* @param {*} value
|
|
70
|
+
* @returns {{config: object, configPath: string}}
|
|
71
|
+
*/
|
|
72
|
+
function setConfigOverride(projectRoot, keyPath, value) {
|
|
73
|
+
assertKeyPath(keyPath);
|
|
74
|
+
const config = loadRawConfig(projectRoot);
|
|
75
|
+
let node = config;
|
|
76
|
+
for (let i = 0; i < keyPath.length - 1; i += 1) {
|
|
77
|
+
const key = keyPath[i];
|
|
78
|
+
if (!node[key] || typeof node[key] !== 'object' || Array.isArray(node[key])) {
|
|
79
|
+
node[key] = {};
|
|
80
|
+
}
|
|
81
|
+
node = node[key];
|
|
82
|
+
}
|
|
83
|
+
node[keyPath[keyPath.length - 1]] = value;
|
|
84
|
+
return { config, configPath: writeRawConfig(projectRoot, config) };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Remove a nested key (a "reset" to the profile/default). Prunes ancestor
|
|
89
|
+
* objects that become empty so the surface stays sparse. No-op if the key is
|
|
90
|
+
* absent or the file does not exist.
|
|
91
|
+
*
|
|
92
|
+
* @param {string} projectRoot
|
|
93
|
+
* @param {string[]} keyPath
|
|
94
|
+
* @returns {{config: object, configPath: string, removed: boolean}}
|
|
95
|
+
*/
|
|
96
|
+
function removeConfigOverride(projectRoot, keyPath) {
|
|
97
|
+
assertKeyPath(keyPath);
|
|
98
|
+
const configPath = getConfigPath(projectRoot);
|
|
99
|
+
if (!fs.existsSync(configPath)) {
|
|
100
|
+
return { config: {}, configPath, removed: false };
|
|
101
|
+
}
|
|
102
|
+
const config = loadRawConfig(projectRoot);
|
|
103
|
+
const chain = [config];
|
|
104
|
+
let node = config;
|
|
105
|
+
for (let i = 0; i < keyPath.length - 1; i += 1) {
|
|
106
|
+
const key = keyPath[i];
|
|
107
|
+
if (!node[key] || typeof node[key] !== 'object' || Array.isArray(node[key])) {
|
|
108
|
+
return { config, configPath, removed: false };
|
|
109
|
+
}
|
|
110
|
+
node = node[key];
|
|
111
|
+
chain.push(node);
|
|
112
|
+
}
|
|
113
|
+
const leaf = keyPath[keyPath.length - 1];
|
|
114
|
+
if (!Object.hasOwn(node, leaf)) {
|
|
115
|
+
return { config, configPath, removed: false };
|
|
116
|
+
}
|
|
117
|
+
delete node[leaf];
|
|
118
|
+
// Prune now-empty ancestor objects, deepest first.
|
|
119
|
+
for (let i = chain.length - 1; i > 0; i -= 1) {
|
|
120
|
+
if (Object.keys(chain[i]).length === 0) {
|
|
121
|
+
delete chain[i - 1][keyPath[i - 1]];
|
|
122
|
+
} else {
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return { config, configPath: writeRawConfig(projectRoot, config), removed: true };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Project-local skill dirs (highest precedence), independent of any extraction. */
|
|
130
|
+
function localSkillSearchDirs(projectRoot = process.cwd()) {
|
|
131
|
+
return [path.join(projectRoot, '.skills'), path.join(projectRoot, 'skills')];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* The packaged (shipped-default) skills dir — resolved LAZILY and NON-throwing.
|
|
136
|
+
* In a compiled binary this triggers asset extraction, so it must only run after
|
|
137
|
+
* the project-local dirs miss (and must never abort skill resolution if assets
|
|
138
|
+
* cannot be resolved). Returns null if unavailable.
|
|
139
|
+
*/
|
|
140
|
+
function packagedSkillsDir() {
|
|
141
|
+
try {
|
|
142
|
+
return path.join(getPackageRoot(PACKAGE_ROOT), 'skills');
|
|
143
|
+
} catch {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Directories searched (in precedence order) when resolving a skill name.
|
|
150
|
+
* Mirrors the skills-sync precedence: `.skills/` shadow wins over canonical
|
|
151
|
+
* `skills/`, then the installed package's shipped defaults (resolved lazily).
|
|
152
|
+
*/
|
|
153
|
+
function skillSearchDirs(projectRoot = process.cwd()) {
|
|
154
|
+
const dirs = localSkillSearchDirs(projectRoot);
|
|
155
|
+
const packaged = packagedSkillsDir();
|
|
156
|
+
if (packaged) dirs.push(packaged);
|
|
157
|
+
return dirs;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Resolve a skill NAME to a real `SKILL.md` via `.skills/ > skills/`
|
|
162
|
+
* precedence. Returns null if no such skill exists — the `forge role` verb
|
|
163
|
+
* uses this for write-time validation so an unresolvable skill errors before
|
|
164
|
+
* anything is written (never mid-run).
|
|
165
|
+
*
|
|
166
|
+
* @param {string} projectRoot
|
|
167
|
+
* @param {string} name
|
|
168
|
+
* @returns {{name: string, dir: string, path: string}|null}
|
|
169
|
+
*/
|
|
170
|
+
function resolveSkill(projectRoot, name) {
|
|
171
|
+
if (typeof name !== 'string' || name === ''
|
|
172
|
+
|| name.includes('/') || name.includes('\\') || name.includes('\0')
|
|
173
|
+
|| name === '.' || name === '..') {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
// Check project-local dirs FIRST — these never trigger extraction, so a
|
|
177
|
+
// project-local skill resolves without touching the packaged fallback.
|
|
178
|
+
for (const dir of localSkillSearchDirs(projectRoot)) {
|
|
179
|
+
const skillMd = path.join(dir, name, 'SKILL.md');
|
|
180
|
+
if (fs.existsSync(skillMd)) {
|
|
181
|
+
return { name, dir, path: skillMd };
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// Only now resolve the packaged fallback (lazy, non-throwing).
|
|
185
|
+
const packaged = packagedSkillsDir();
|
|
186
|
+
if (packaged) {
|
|
187
|
+
const skillMd = path.join(packaged, name, 'SKILL.md');
|
|
188
|
+
if (fs.existsSync(skillMd)) {
|
|
189
|
+
return { name, dir: packaged, path: skillMd };
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
module.exports = {
|
|
196
|
+
getConfigPath,
|
|
197
|
+
loadRawConfig,
|
|
198
|
+
setConfigOverride,
|
|
199
|
+
removeConfigOverride,
|
|
200
|
+
resolveSkill,
|
|
201
|
+
skillSearchDirs,
|
|
202
|
+
};
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @module control-plane
|
|
5
|
+
*
|
|
6
|
+
* The single source of truth for the **control-plane guarantee matrix**
|
|
7
|
+
* (docs/reference/control-plane-guarantees.md): it classifies each control
|
|
8
|
+
* surface, names its enforcement-locus, derives the tri-state label, renders the
|
|
9
|
+
* honest read badge, and resolves a `forge control` write-intent into the ONE
|
|
10
|
+
* resolver-enforced field — `workflow.gates.<id>.enabled`.
|
|
11
|
+
*
|
|
12
|
+
* Why this module exists: the cockpit uses a tri-state vocabulary
|
|
13
|
+
* (`mandatory` / `optional` / `permission`), but that vocabulary is only
|
|
14
|
+
* meaningful where Forge can actually DENY at run time — on gates and rails. On
|
|
15
|
+
* MCP servers, rules, and skills there is no run-time deny; "mandatory" there
|
|
16
|
+
* would mean nothing more than *the file is present*. This module draws that
|
|
17
|
+
* line so the UI and the `forge control` command never sell enforcement Forge
|
|
18
|
+
* cannot deliver. Pure + dependency-free so it is trivially testable and shared
|
|
19
|
+
* by the command, `forge options`, and any dashboard snapshot.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/** The closed set of human gates — satisfied by a `gate.approved` kernel EVENT. */
|
|
23
|
+
const HUMAN_GATE_IDS = new Set(['gate.intent', 'gate.plan-approval', 'gate.merge']);
|
|
24
|
+
|
|
25
|
+
const CONTROLLABLE_SURFACES = new Set(['gate', 'human-gate', 'rail']);
|
|
26
|
+
|
|
27
|
+
const GUARANTEE_DOC = 'docs/reference/control-plane-guarantees.md';
|
|
28
|
+
|
|
29
|
+
const VALID_STATES = new Set(['mandatory', 'optional', 'permission']);
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Classify a control id into its surface. The id namespaces are disjoint, so a
|
|
33
|
+
* prefix match is unambiguous; the three human gates are singled out because
|
|
34
|
+
* their enforcement-locus (a human approval event) differs from ordinary gates.
|
|
35
|
+
*
|
|
36
|
+
* @param {string} id
|
|
37
|
+
* @returns {'gate'|'human-gate'|'rail'|'mcp'|'rule'|'skill'|'unknown'}
|
|
38
|
+
*/
|
|
39
|
+
function classifySurface(id) {
|
|
40
|
+
if (typeof id !== 'string') return 'unknown';
|
|
41
|
+
if (HUMAN_GATE_IDS.has(id)) return 'human-gate';
|
|
42
|
+
if (id.startsWith('gate.')) return 'gate';
|
|
43
|
+
if (id.startsWith('rail.')) return 'rail';
|
|
44
|
+
if (id.startsWith('mcp.')) return 'mcp';
|
|
45
|
+
if (id.startsWith('rule.')) return 'rule';
|
|
46
|
+
if (id.startsWith('skill.')) return 'skill';
|
|
47
|
+
return 'unknown';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* True only for surfaces `forge control` can CONFIGURE (gates + rails).
|
|
52
|
+
*
|
|
53
|
+
* NOTE: "controllable" means CLI-configurable, NOT runtime-enforced — the two are
|
|
54
|
+
* deliberately separate. Configuring one of these writes a declared flag; whether
|
|
55
|
+
* anything actually denies on it is a distinct question answered by
|
|
56
|
+
* `enforcementLocus` (today: nothing does). See docs/reference/control-plane-guarantees.md.
|
|
57
|
+
*/
|
|
58
|
+
function isControllable(id) {
|
|
59
|
+
return CONTROLLABLE_SURFACES.has(classifySurface(id));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The honest enforcement-locus — WHERE and WHETHER Forge actually consumes this
|
|
64
|
+
* flag TODAY (as of 2026-07-15). This deliberately does NOT claim `run-time-deny`
|
|
65
|
+
* for the configurable gates/rails, because an adversarial grep of every consumer
|
|
66
|
+
* of the resolved graph found NONE that denies on `workflow.gates.<id>.enabled`:
|
|
67
|
+
*
|
|
68
|
+
* - stage-exit gates + rails: no runtime consumer reads their `.enabled` to
|
|
69
|
+
* refuse. `enforce-stage.js` enforces stage ORDER + kernel COMPLETION, which
|
|
70
|
+
* is independent of these flags. → "registry — declared, not yet enforced".
|
|
71
|
+
* - `gate.issue_verify`: consumed by lib/commands/_issue.js, but WARN-ONLY —
|
|
72
|
+
* a mismatch prints a warning and never overturns the write's `ok`.
|
|
73
|
+
* - human gates: the `forge gate approve`/`check` primitives are real and
|
|
74
|
+
* deny-CAPABLE, but no chokepoint auto-invokes `forge gate check`, so nothing
|
|
75
|
+
* denies on them yet. → "deny-on-check (no chokepoint yet)".
|
|
76
|
+
*
|
|
77
|
+
* Real enforcement today lives elsewhere and is independent of these flags: the
|
|
78
|
+
* B3 lefthook TDD pre-commit hook, B2 fail-closed `validate`/`preflight`, and
|
|
79
|
+
* `enforce-stage` order+completion. See docs/reference/control-plane-guarantees.md.
|
|
80
|
+
*
|
|
81
|
+
* @param {string} id
|
|
82
|
+
*/
|
|
83
|
+
function enforcementLocus(id) {
|
|
84
|
+
const surface = classifySurface(id);
|
|
85
|
+
if (surface === 'human-gate') {
|
|
86
|
+
return 'deny-on-check (deny-capable via forge gate check; no chokepoint invokes it yet)';
|
|
87
|
+
}
|
|
88
|
+
if (id === 'gate.issue_verify') {
|
|
89
|
+
return 'run-time verify (warn-only, never denies)';
|
|
90
|
+
}
|
|
91
|
+
if (surface === 'gate' || surface === 'rail') {
|
|
92
|
+
return 'registry — declared, not yet enforced';
|
|
93
|
+
}
|
|
94
|
+
// mcp / rule / skill / unknown — presence written into harness config only.
|
|
95
|
+
return 'render-time presence-only';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Derive the tri-state label from the resolver-enforced fields, so state is
|
|
100
|
+
* never stored twice (single source of truth = `enabled`).
|
|
101
|
+
*
|
|
102
|
+
* @param {{id: string, enabled?: boolean}} primitive
|
|
103
|
+
* @returns {'mandatory'|'optional'|'permission'|null} null for advisory surfaces.
|
|
104
|
+
*/
|
|
105
|
+
function deriveState(primitive) {
|
|
106
|
+
const surface = classifySurface(primitive.id);
|
|
107
|
+
if (!CONTROLLABLE_SURFACES.has(surface)) return null;
|
|
108
|
+
const enabled = primitive.enabled !== false;
|
|
109
|
+
if (surface === 'human-gate') return enabled ? 'permission' : 'optional';
|
|
110
|
+
return enabled ? 'mandatory' : 'optional';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The read-view badge: reflects the HONEST wiring status TODAY, never the
|
|
115
|
+
* author's intent, so the UI can never imply enforcement a surface lacks.
|
|
116
|
+
* `ENFORCED` is reserved strictly for a wired runtime deny — a set that is
|
|
117
|
+
* EMPTY today for these configurable flags, so this function never returns it.
|
|
118
|
+
* A `locked` primitive appends a `LOCKED` marker (it cannot be lowered).
|
|
119
|
+
*
|
|
120
|
+
* @param {{id: string, enabled?: boolean, locked?: boolean}} primitive
|
|
121
|
+
* @returns {string}
|
|
122
|
+
*/
|
|
123
|
+
function badgeFor(primitive) {
|
|
124
|
+
const surface = classifySurface(primitive.id);
|
|
125
|
+
if (!CONTROLLABLE_SURFACES.has(surface)) return 'PRESENT (advisory)';
|
|
126
|
+
|
|
127
|
+
const enabled = primitive.enabled !== false;
|
|
128
|
+
let badge;
|
|
129
|
+
if (!enabled) {
|
|
130
|
+
badge = 'OFF (optional)';
|
|
131
|
+
} else if (surface === 'human-gate') {
|
|
132
|
+
// Deny-capable via `forge gate check`, but no chokepoint invokes it yet.
|
|
133
|
+
badge = 'DENY-ON-CHECK';
|
|
134
|
+
} else if (primitive.id === 'gate.issue_verify') {
|
|
135
|
+
// Consumed by _issue.js, but warn-only — never overturns a write.
|
|
136
|
+
badge = 'VERIFY (warn-only)';
|
|
137
|
+
} else {
|
|
138
|
+
// stage-exit gates + rails: declared in the registry, no runtime consumer.
|
|
139
|
+
badge = 'DECLARED (no runtime consumer yet)';
|
|
140
|
+
}
|
|
141
|
+
return primitive.locked === true ? `${badge} · LOCKED` : badge;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* The full read record for a primitive — everything a read view / badge needs.
|
|
146
|
+
*
|
|
147
|
+
* @param {{id: string, enabled?: boolean, locked?: boolean}} primitive
|
|
148
|
+
*/
|
|
149
|
+
function describeControl(primitive) {
|
|
150
|
+
const surface = classifySurface(primitive.id);
|
|
151
|
+
return {
|
|
152
|
+
id: primitive.id,
|
|
153
|
+
surface,
|
|
154
|
+
controllable: CONTROLLABLE_SURFACES.has(surface),
|
|
155
|
+
state: deriveState(primitive),
|
|
156
|
+
locus: enforcementLocus(primitive.id),
|
|
157
|
+
badge: badgeFor(primitive),
|
|
158
|
+
locked: primitive.locked === true,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Resolve a `forge control <id> <state>` write-intent into the enabled boolean
|
|
164
|
+
* for `workflow.gates.<id>.enabled` — or an honest refusal. This is the guard
|
|
165
|
+
* that keeps the tri-state vocabulary from over-promising:
|
|
166
|
+
*
|
|
167
|
+
* - advisory surfaces (mcp/rule/skill/unknown) → refused (presence-only).
|
|
168
|
+
* - `permission` is valid ONLY for human gates; elsewhere it has no path.
|
|
169
|
+
* - `mandatory` on a human gate → refused, directed to `permission`.
|
|
170
|
+
* - `optional` on a `locked` primitive → refused (cannot be lowered).
|
|
171
|
+
*
|
|
172
|
+
* @param {{id: string, locked?: boolean}} primitive
|
|
173
|
+
* @param {string} state - requested tri-state
|
|
174
|
+
* @returns {{ok: true, enabled: boolean}|{ok: false, error: string}}
|
|
175
|
+
*/
|
|
176
|
+
function planControl(primitive, state) {
|
|
177
|
+
const surface = classifySurface(primitive.id);
|
|
178
|
+
|
|
179
|
+
if (!CONTROLLABLE_SURFACES.has(surface)) {
|
|
180
|
+
return {
|
|
181
|
+
ok: false,
|
|
182
|
+
error: `${primitive.id} is presence-only, not enforceable — Forge has no `
|
|
183
|
+
+ `run-time deny for this surface. It stays read-only in the cockpit `
|
|
184
|
+
+ `(badge PRESENT (advisory)). See ${GUARANTEE_DOC}.`,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (!VALID_STATES.has(state)) {
|
|
189
|
+
return {
|
|
190
|
+
ok: false,
|
|
191
|
+
error: `Unknown state '${state}'. Use one of: mandatory, optional, permission.`,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const isHuman = surface === 'human-gate';
|
|
196
|
+
|
|
197
|
+
if (state === 'permission' && !isHuman) {
|
|
198
|
+
return {
|
|
199
|
+
ok: false,
|
|
200
|
+
error: `'permission' applies only to a human gate `
|
|
201
|
+
+ `(${[...HUMAN_GATE_IDS].join(', ')}); ${primitive.id} enforces evidence, `
|
|
202
|
+
+ `not approval. Use 'mandatory' or 'optional'. See ${GUARANTEE_DOC}.`,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (state === 'mandatory' && isHuman) {
|
|
207
|
+
return {
|
|
208
|
+
ok: false,
|
|
209
|
+
error: `${primitive.id} is a human gate — it blocks until a human approval `
|
|
210
|
+
+ `event, so use 'permission' (not 'mandatory'). See ${GUARANTEE_DOC}.`,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (state === 'optional' && primitive.locked === true) {
|
|
215
|
+
return {
|
|
216
|
+
ok: false,
|
|
217
|
+
error: `Cannot lower locked ${surface} '${primitive.id}' to optional.`,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// mandatory / permission → enabled; optional → disabled. Written to the ONE
|
|
222
|
+
// field the resolver honors, so enforcement is genuine.
|
|
223
|
+
return { ok: true, enabled: state !== 'optional' };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
module.exports = {
|
|
227
|
+
HUMAN_GATE_IDS,
|
|
228
|
+
GUARANTEE_DOC,
|
|
229
|
+
classifySurface,
|
|
230
|
+
isControllable,
|
|
231
|
+
enforcementLocus,
|
|
232
|
+
deriveState,
|
|
233
|
+
badgeFor,
|
|
234
|
+
describeControl,
|
|
235
|
+
planControl,
|
|
236
|
+
};
|