@deftai/directive 0.126.2 → 0.128.0
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/bin/deft-hook +14 -0
- package/bin/deft-hook.cmd +14 -0
- package/dist/bin.js +4319 -3
- package/package.json +11 -21
- package/dist/agents-refresh.d.ts +0 -11
- package/dist/agents-refresh.js +0 -90
- package/dist/authz.d.ts +0 -7
- package/dist/authz.js +0 -732
- package/dist/bin.d.ts +0 -3
- package/dist/branch-fixtures.d.ts +0 -45
- package/dist/branch-fixtures.js +0 -156
- package/dist/cache.d.ts +0 -4
- package/dist/cache.js +0 -10
- package/dist/capacity-backfill.d.ts +0 -3
- package/dist/capacity-backfill.js +0 -9
- package/dist/capacity-show.d.ts +0 -3
- package/dist/capacity-show.js +0 -9
- package/dist/change-init.d.ts +0 -3
- package/dist/change-init.js +0 -59
- package/dist/changelog-check.d.ts +0 -3
- package/dist/changelog-check.js +0 -52
- package/dist/check.d.ts +0 -15
- package/dist/check.js +0 -150
- package/dist/cli-package-version.d.ts +0 -3
- package/dist/cli-package-version.js +0 -23
- package/dist/cli-router/index.d.ts +0 -8
- package/dist/cli-router/index.js +0 -44
- package/dist/cli-router/route-argv.d.ts +0 -40
- package/dist/cli-router/route-argv.js +0 -371
- package/dist/codebase-default-extractor.d.ts +0 -3
- package/dist/codebase-default-extractor.js +0 -9
- package/dist/codebase-map-fresh.d.ts +0 -3
- package/dist/codebase-map-fresh.js +0 -9
- package/dist/codebase-map.d.ts +0 -3
- package/dist/codebase-map.js +0 -9
- package/dist/codebase-projection-registry.d.ts +0 -3
- package/dist/codebase-projection-registry.js +0 -9
- package/dist/codebase-provider.d.ts +0 -3
- package/dist/codebase-provider.js +0 -9
- package/dist/commit-lint.d.ts +0 -3
- package/dist/commit-lint.js +0 -44
- package/dist/content-validate-cli/validate-links.d.ts +0 -11
- package/dist/content-validate-cli/validate-links.js +0 -37
- package/dist/content-validate-cli/validate-strategy-output.d.ts +0 -13
- package/dist/content-validate-cli/validate-strategy-output.js +0 -56
- package/dist/content-validate-cli/verify-capacity.d.ts +0 -12
- package/dist/content-validate-cli/verify-capacity.js +0 -54
- package/dist/coverage-hotspots.d.ts +0 -21
- package/dist/coverage-hotspots.js +0 -198
- package/dist/design-critique-spend-resolve.d.ts +0 -18
- package/dist/design-critique-spend-resolve.js +0 -191
- package/dist/directive-finish-loop.d.ts +0 -3
- package/dist/directive-finish-loop.js +0 -10
- package/dist/dispatch.d.ts +0 -193
- package/dist/dispatch.js +0 -3091
- package/dist/doctor.d.ts +0 -19
- package/dist/doctor.js +0 -155
- package/dist/entrypoint.d.ts +0 -6
- package/dist/entrypoint.js +0 -19
- package/dist/escalation-cli.d.ts +0 -4
- package/dist/escalation-cli.js +0 -301
- package/dist/eval-health.d.ts +0 -13
- package/dist/eval-health.js +0 -63
- package/dist/eval-report.d.ts +0 -15
- package/dist/eval-report.js +0 -97
- package/dist/eval-run.d.ts +0 -17
- package/dist/eval-run.js +0 -122
- package/dist/eval-triggers.d.ts +0 -12
- package/dist/eval-triggers.js +0 -56
- package/dist/framework-check-updates.d.ts +0 -10
- package/dist/framework-check-updates.js +0 -68
- package/dist/gates-cli/_helpers.d.ts +0 -24
- package/dist/gates-cli/_helpers.js +0 -115
- package/dist/gates-cli/verify-bridge-drift.d.ts +0 -11
- package/dist/gates-cli/verify-bridge-drift.js +0 -47
- package/dist/gates-cli/verify-go-freeze.d.ts +0 -11
- package/dist/gates-cli/verify-go-freeze.js +0 -48
- package/dist/hook-bin.d.ts +0 -3
- package/dist/hook-bin.js +0 -11
- package/dist/hook-dispatch.d.ts +0 -80
- package/dist/hook-dispatch.js +0 -186
- package/dist/human-presence-mint.d.ts +0 -70
- package/dist/human-presence-mint.js +0 -204
- package/dist/identity-banner.d.ts +0 -13
- package/dist/identity-banner.js +0 -19
- package/dist/index.d.ts +0 -11
- package/dist/index.js +0 -14
- package/dist/init-cli/constants.d.ts +0 -42
- package/dist/init-cli/constants.js +0 -42
- package/dist/init-cli/help.d.ts +0 -9
- package/dist/init-cli/help.js +0 -43
- package/dist/init-cli/init.d.ts +0 -26
- package/dist/init-cli/init.js +0 -80
- package/dist/init-cli/migrate.d.ts +0 -3
- package/dist/init-cli/migrate.js +0 -51
- package/dist/init-cli/resolve-binary.d.ts +0 -34
- package/dist/init-cli/resolve-binary.js +0 -78
- package/dist/init-cli/run-deft-install.d.ts +0 -23
- package/dist/init-cli/run-deft-install.js +0 -71
- package/dist/init-cli/update.d.ts +0 -5
- package/dist/init-cli/update.js +0 -38
- package/dist/install-cli/coverage-map.d.ts +0 -14
- package/dist/install-cli/coverage-map.js +0 -113
- package/dist/install-uninstall.d.ts +0 -3
- package/dist/install-uninstall.js +0 -44
- package/dist/install-upgrade.d.ts +0 -41
- package/dist/install-upgrade.js +0 -86
- package/dist/lifecycle-cli/coverage-map.d.ts +0 -16
- package/dist/lifecycle-cli/coverage-map.js +0 -254
- package/dist/lifecycle-cli/helpers.d.ts +0 -8
- package/dist/lifecycle-cli/helpers.js +0 -20
- package/dist/lifecycle-event.d.ts +0 -11
- package/dist/lifecycle-event.js +0 -45
- package/dist/lifecycle-stats.d.ts +0 -12
- package/dist/lifecycle-stats.js +0 -100
- package/dist/migrate-category-b.d.ts +0 -9
- package/dist/migrate-category-b.js +0 -52
- package/dist/migrate-preflight.d.ts +0 -10
- package/dist/migrate-preflight.js +0 -85
- package/dist/migrate-xbrief.d.ts +0 -11
- package/dist/migrate-xbrief.js +0 -88
- package/dist/occupancy-grant.d.ts +0 -18
- package/dist/occupancy-grant.js +0 -121
- package/dist/occupancy-heartbeat.d.ts +0 -8
- package/dist/occupancy-heartbeat.js +0 -63
- package/dist/occupancy-release.d.ts +0 -23
- package/dist/occupancy-release.js +0 -102
- package/dist/occupancy-steal.d.ts +0 -10
- package/dist/occupancy-steal.js +0 -83
- package/dist/occupancy-unrecognized.d.ts +0 -3
- package/dist/occupancy-unrecognized.js +0 -5
- package/dist/orchestration-cli/coverage-map.d.ts +0 -21
- package/dist/orchestration-cli/coverage-map.js +0 -225
- package/dist/orchestration-cli/helpers.d.ts +0 -11
- package/dist/orchestration-cli/helpers.js +0 -35
- package/dist/ownership-doctor.d.ts +0 -10
- package/dist/ownership-doctor.js +0 -96
- package/dist/ownership-fix.d.ts +0 -10
- package/dist/ownership-fix.js +0 -93
- package/dist/plan-sequence.d.ts +0 -14
- package/dist/plan-sequence.js +0 -258
- package/dist/policy-fixtures.d.ts +0 -33
- package/dist/policy-fixtures.js +0 -118
- package/dist/policy.d.ts +0 -37
- package/dist/policy.js +0 -744
- package/dist/pr-closing-keywords.d.ts +0 -3
- package/dist/pr-closing-keywords.js +0 -10
- package/dist/pr-finish-loop.d.ts +0 -3
- package/dist/pr-finish-loop.js +0 -10
- package/dist/pr-merge-readiness.d.ts +0 -3
- package/dist/pr-merge-readiness.js +0 -10
- package/dist/pr-monitor.d.ts +0 -3
- package/dist/pr-monitor.js +0 -10
- package/dist/pr-protected-issues.d.ts +0 -3
- package/dist/pr-protected-issues.js +0 -10
- package/dist/pr-wait-mergeable.d.ts +0 -3
- package/dist/pr-wait-mergeable.js +0 -10
- package/dist/pr-watch.d.ts +0 -3
- package/dist/pr-watch.js +0 -10
- package/dist/pre-pr-run.d.ts +0 -39
- package/dist/pre-pr-run.js +0 -514
- package/dist/preflight-cache.d.ts +0 -18
- package/dist/preflight-cache.js +0 -113
- package/dist/preflight-gh.d.ts +0 -15
- package/dist/preflight-gh.js +0 -143
- package/dist/probe-session.d.ts +0 -3
- package/dist/probe-session.js +0 -10
- package/dist/release-e2e-fixtures.d.ts +0 -29
- package/dist/release-e2e-fixtures.js +0 -38
- package/dist/release-e2e.d.ts +0 -3
- package/dist/release-e2e.js +0 -10
- package/dist/release-publish.d.ts +0 -3
- package/dist/release-publish.js +0 -10
- package/dist/release-rollback.d.ts +0 -3
- package/dist/release-rollback.js +0 -10
- package/dist/release-wait-npm.d.ts +0 -3
- package/dist/release-wait-npm.js +0 -10
- package/dist/release.d.ts +0 -3
- package/dist/release.js +0 -10
- package/dist/render-cli/deft-ts-runner.d.ts +0 -17
- package/dist/render-cli/deft-ts-runner.js +0 -19
- package/dist/render-cli/export-spec-cli.d.ts +0 -2
- package/dist/render-cli/export-spec-cli.js +0 -6
- package/dist/render-cli/prd-render-cli.d.ts +0 -2
- package/dist/render-cli/prd-render-cli.js +0 -37
- package/dist/render-cli/project-render-cli.d.ts +0 -2
- package/dist/render-cli/project-render-cli.js +0 -6
- package/dist/render-cli/rule-map-cli.d.ts +0 -2
- package/dist/render-cli/rule-map-cli.js +0 -5
- package/dist/render-cli/spec-reconstruct-cli.d.ts +0 -3
- package/dist/render-cli/spec-reconstruct-cli.js +0 -15
- package/dist/render-cli/spec-render-cli.d.ts +0 -2
- package/dist/render-cli/spec-render-cli.js +0 -39
- package/dist/render-cli/spec-validate-cli.d.ts +0 -2
- package/dist/render-cli/spec-validate-cli.js +0 -38
- package/dist/review-monitor-register.d.ts +0 -19
- package/dist/review-monitor-register.js +0 -183
- package/dist/review-monitor-release.d.ts +0 -14
- package/dist/review-monitor-release.js +0 -123
- package/dist/scm-sync-default.d.ts +0 -34
- package/dist/scm-sync-default.js +0 -210
- package/dist/scope-lifecycle.d.ts +0 -4
- package/dist/scope-lifecycle.js +0 -34
- package/dist/scope-record-approved-scope.d.ts +0 -29
- package/dist/scope-record-approved-scope.js +0 -319
- package/dist/scope-record-intent-constraint.d.ts +0 -16
- package/dist/scope-record-intent-constraint.js +0 -202
- package/dist/scope-record-observable-scope.d.ts +0 -16
- package/dist/scope-record-observable-scope.js +0 -204
- package/dist/session-ready.d.ts +0 -15
- package/dist/session-ready.js +0 -144
- package/dist/session-start.d.ts +0 -56
- package/dist/session-start.js +0 -581
- package/dist/slice.d.ts +0 -4
- package/dist/slice.js +0 -34
- package/dist/story-ready-fixtures.d.ts +0 -47
- package/dist/story-ready-fixtures.js +0 -137
- package/dist/subagent-monitor.d.ts +0 -3
- package/dist/subagent-monitor.js +0 -10
- package/dist/subagent-pre-cancel.d.ts +0 -21
- package/dist/subagent-pre-cancel.js +0 -240
- package/dist/subagent-steer-write.d.ts +0 -41
- package/dist/subagent-steer-write.js +0 -333
- package/dist/toolchain-check.d.ts +0 -8
- package/dist/toolchain-check.js +0 -54
- package/dist/triage-actions.d.ts +0 -23
- package/dist/triage-actions.js +0 -233
- package/dist/triage-aux-a-fixtures.d.ts +0 -33
- package/dist/triage-aux-a-fixtures.js +0 -107
- package/dist/triage-aux-b-fixtures.d.ts +0 -30
- package/dist/triage-aux-b-fixtures.js +0 -167
- package/dist/triage-bootstrap-fixtures.d.ts +0 -42
- package/dist/triage-bootstrap-fixtures.js +0 -139
- package/dist/triage-bootstrap.d.ts +0 -24
- package/dist/triage-bootstrap.js +0 -175
- package/dist/triage-bulk.d.ts +0 -17
- package/dist/triage-bulk.js +0 -154
- package/dist/triage-classify-fixtures.d.ts +0 -38
- package/dist/triage-classify-fixtures.js +0 -137
- package/dist/triage-classify.d.ts +0 -45
- package/dist/triage-classify.js +0 -287
- package/dist/triage-evaluate.d.ts +0 -14
- package/dist/triage-evaluate.js +0 -153
- package/dist/triage-help.d.ts +0 -3
- package/dist/triage-help.js +0 -10
- package/dist/triage-queue-fixtures.d.ts +0 -59
- package/dist/triage-queue-fixtures.js +0 -284
- package/dist/triage-queue.d.ts +0 -38
- package/dist/triage-queue.js +0 -391
- package/dist/triage-reconcile.d.ts +0 -11
- package/dist/triage-reconcile.js +0 -76
- package/dist/triage-refresh.d.ts +0 -7
- package/dist/triage-refresh.js +0 -37
- package/dist/triage-scope-drift.d.ts +0 -12
- package/dist/triage-scope-drift.js +0 -117
- package/dist/triage-scope-fixtures.d.ts +0 -32
- package/dist/triage-scope-fixtures.js +0 -131
- package/dist/triage-scope.d.ts +0 -4
- package/dist/triage-scope.js +0 -34
- package/dist/triage-smoketest.d.ts +0 -3
- package/dist/triage-smoketest.js +0 -43
- package/dist/triage-strip-withdrawn.d.ts +0 -21
- package/dist/triage-strip-withdrawn.js +0 -102
- package/dist/triage-subscribe.d.ts +0 -15
- package/dist/triage-subscribe.js +0 -157
- package/dist/triage-summary.d.ts +0 -14
- package/dist/triage-summary.js +0 -83
- package/dist/triage-welcome.d.ts +0 -14
- package/dist/triage-welcome.js +0 -121
- package/dist/ts-check-lane.d.ts +0 -9
- package/dist/ts-check-lane.js +0 -49
- package/dist/umbrella-current-shape.d.ts +0 -9
- package/dist/umbrella-current-shape.js +0 -56
- package/dist/vbrief-activate.d.ts +0 -3
- package/dist/vbrief-activate.js +0 -7
- package/dist/vbrief-build.d.ts +0 -3
- package/dist/vbrief-build.js +0 -8
- package/dist/vbrief-preflight-fixtures.d.ts +0 -32
- package/dist/vbrief-preflight-fixtures.js +0 -79
- package/dist/vbrief-preflight.d.ts +0 -13
- package/dist/vbrief-preflight.js +0 -141
- package/dist/vbrief-reconcile.d.ts +0 -3
- package/dist/vbrief-reconcile.js +0 -7
- package/dist/vbrief-validate.d.ts +0 -3
- package/dist/vbrief-validate.js +0 -8
- package/dist/vbrief-validation.d.ts +0 -3
- package/dist/vbrief-validation.js +0 -8
- package/dist/verify-ac.d.ts +0 -28
- package/dist/verify-ac.js +0 -439
- package/dist/verify-agents-md-advisory.d.ts +0 -19
- package/dist/verify-agents-md-advisory.js +0 -65
- package/dist/verify-agents-md-budget.d.ts +0 -12
- package/dist/verify-agents-md-budget.js +0 -71
- package/dist/verify-branch.d.ts +0 -14
- package/dist/verify-branch.js +0 -81
- package/dist/verify-class-checks.d.ts +0 -11
- package/dist/verify-class-checks.js +0 -71
- package/dist/verify-cohort-review-monitors.d.ts +0 -5
- package/dist/verify-cohort-review-monitors.js +0 -11
- package/dist/verify-completed-tracked.d.ts +0 -19
- package/dist/verify-completed-tracked.js +0 -202
- package/dist/verify-completed-write-guard.d.ts +0 -13
- package/dist/verify-completed-write-guard.js +0 -84
- package/dist/verify-consumer-check-contract.d.ts +0 -12
- package/dist/verify-consumer-check-contract.js +0 -70
- package/dist/verify-consumer-header-placeholder.d.ts +0 -12
- package/dist/verify-consumer-header-placeholder.js +0 -73
- package/dist/verify-consumer-test-lane.d.ts +0 -12
- package/dist/verify-consumer-test-lane.js +0 -57
- package/dist/verify-durable-effect-acquisition.d.ts +0 -13
- package/dist/verify-durable-effect-acquisition.js +0 -70
- package/dist/verify-encoding.d.ts +0 -15
- package/dist/verify-encoding.js +0 -83
- package/dist/verify-eval-health-relocation.d.ts +0 -16
- package/dist/verify-eval-health-relocation.js +0 -93
- package/dist/verify-eval-triggers-relocation.d.ts +0 -14
- package/dist/verify-eval-triggers-relocation.js +0 -74
- package/dist/verify-evaluator-surface.d.ts +0 -15
- package/dist/verify-evaluator-surface.js +0 -87
- package/dist/verify-forward-coverage.d.ts +0 -19
- package/dist/verify-forward-coverage.js +0 -144
- package/dist/verify-hooks-installed.d.ts +0 -24
- package/dist/verify-hooks-installed.js +0 -140
- package/dist/verify-intent-constraint.d.ts +0 -17
- package/dist/verify-intent-constraint.js +0 -110
- package/dist/verify-investigation.d.ts +0 -3
- package/dist/verify-investigation.js +0 -10
- package/dist/verify-judgment-gates.d.ts +0 -3
- package/dist/verify-judgment-gates.js +0 -10
- package/dist/verify-l4-owner.d.ts +0 -15
- package/dist/verify-l4-owner.js +0 -132
- package/dist/verify-lifecycle-visible.d.ts +0 -13
- package/dist/verify-lifecycle-visible.js +0 -68
- package/dist/verify-literal-ac.d.ts +0 -14
- package/dist/verify-literal-ac.js +0 -168
- package/dist/verify-no-task-runtime.d.ts +0 -3
- package/dist/verify-no-task-runtime.js +0 -16
- package/dist/verify-observable-scope.d.ts +0 -17
- package/dist/verify-observable-scope.js +0 -110
- package/dist/verify-operator-scope-limit.d.ts +0 -17
- package/dist/verify-operator-scope-limit.js +0 -285
- package/dist/verify-orphan-active.d.ts +0 -17
- package/dist/verify-orphan-active.js +0 -135
- package/dist/verify-ownership.d.ts +0 -10
- package/dist/verify-ownership.js +0 -90
- package/dist/verify-persisted-planning-narratives.d.ts +0 -12
- package/dist/verify-persisted-planning-narratives.js +0 -82
- package/dist/verify-plan-sequence.d.ts +0 -14
- package/dist/verify-plan-sequence.js +0 -138
- package/dist/verify-pr-closeout-attestable.d.ts +0 -15
- package/dist/verify-pr-closeout-attestable.js +0 -114
- package/dist/verify-presentation-ceiling.d.ts +0 -11
- package/dist/verify-presentation-ceiling.js +0 -71
- package/dist/verify-presentation-coverage.d.ts +0 -14
- package/dist/verify-presentation-coverage.js +0 -90
- package/dist/verify-review-monitor.d.ts +0 -32
- package/dist/verify-review-monitor.js +0 -362
- package/dist/verify-scope-provenance.d.ts +0 -16
- package/dist/verify-scope-provenance.js +0 -91
- package/dist/verify-session-ritual.d.ts +0 -23
- package/dist/verify-session-ritual.js +0 -144
- package/dist/verify-source-cli/rule-ownership-lint.d.ts +0 -12
- package/dist/verify-source-cli/rule-ownership-lint.js +0 -61
- package/dist/verify-source-cli/verify-biome-config.d.ts +0 -11
- package/dist/verify-source-cli/verify-biome-config.js +0 -46
- package/dist/verify-source-cli/verify-closing-keywords.d.ts +0 -35
- package/dist/verify-source-cli/verify-closing-keywords.js +0 -139
- package/dist/verify-source-cli/verify-consumer-hard-stops.d.ts +0 -16
- package/dist/verify-source-cli/verify-consumer-hard-stops.js +0 -99
- package/dist/verify-source-cli/verify-contained-writes.d.ts +0 -13
- package/dist/verify-source-cli/verify-contained-writes.js +0 -67
- package/dist/verify-source-cli/verify-content-manifest.d.ts +0 -12
- package/dist/verify-source-cli/verify-content-manifest.js +0 -61
- package/dist/verify-source-cli/verify-contract-drift.d.ts +0 -11
- package/dist/verify-source-cli/verify-contract-drift.js +0 -47
- package/dist/verify-source-cli/verify-cursor-tier1.d.ts +0 -12
- package/dist/verify-source-cli/verify-cursor-tier1.js +0 -51
- package/dist/verify-source-cli/verify-deposit-closure.d.ts +0 -12
- package/dist/verify-source-cli/verify-deposit-closure.js +0 -52
- package/dist/verify-source-cli/verify-openclaw-tier1.d.ts +0 -12
- package/dist/verify-source-cli/verify-openclaw-tier1.js +0 -51
- package/dist/verify-source-cli/verify-scm-boundary.d.ts +0 -13
- package/dist/verify-source-cli/verify-scm-boundary.js +0 -66
- package/dist/verify-source-cli/verify-semantic-single-source.d.ts +0 -11
- package/dist/verify-source-cli/verify-semantic-single-source.js +0 -49
- package/dist/verify-source-cli/verify-skill-external-fetch-gate.d.ts +0 -11
- package/dist/verify-source-cli/verify-skill-external-fetch-gate.js +0 -46
- package/dist/verify-source-cli/verify-spec-drift.d.ts +0 -3
- package/dist/verify-source-cli/verify-spec-drift.js +0 -15
- package/dist/verify-source-cli/verify-spec-prd-fresh.d.ts +0 -3
- package/dist/verify-source-cli/verify-spec-prd-fresh.js +0 -15
- package/dist/verify-source-cli/verify-stubs.d.ts +0 -11
- package/dist/verify-source-cli/verify-stubs.js +0 -27
- package/dist/verify-source-cli/verify-telemetry-coverage.d.ts +0 -13
- package/dist/verify-source-cli/verify-telemetry-coverage.js +0 -67
- package/dist/verify-source-cli/verify-xbrief-drift.d.ts +0 -15
- package/dist/verify-source-cli/verify-xbrief-drift.js +0 -80
- package/dist/verify-story-ready.d.ts +0 -18
- package/dist/verify-story-ready.js +0 -190
- package/dist/verify-subagent-alive.d.ts +0 -21
- package/dist/verify-subagent-alive.js +0 -219
- package/dist/verify-subagent-steer.d.ts +0 -19
- package/dist/verify-subagent-steer.js +0 -136
- package/dist/verify-test-boundary.d.ts +0 -12
- package/dist/verify-test-boundary.js +0 -80
- package/dist/verify-tools.d.ts +0 -13
- package/dist/verify-tools.js +0 -67
- package/dist/verify-wip-cap.d.ts +0 -13
- package/dist/verify-wip-cap.js +0 -62
- package/dist/windows-bin-ps1.d.ts +0 -39
- package/dist/windows-bin-ps1.js +0 -226
- package/dist/wip-cap-fixtures.d.ts +0 -37
- package/dist/wip-cap-fixtures.js +0 -122
- package/dist/wsl-ownership-guard.d.ts +0 -2
- package/dist/wsl-ownership-guard.js +0 -15
- package/dist/xbrief-create.d.ts +0 -3
- package/dist/xbrief-create.js +0 -9
- package/dist/xbrief-verify.d.ts +0 -3
- package/dist/xbrief-verify.js +0 -9
- package/scripts/remove-windows-ps1-shims.mjs +0 -26
package/dist/dispatch.js
DELETED
|
@@ -1,3091 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unified `directive <verb> [args]` dispatcher (#1828 s0).
|
|
3
|
-
* Routes to ported command modules in packages/cli and packages/core.
|
|
4
|
-
*/
|
|
5
|
-
import { spawnSync } from "node:child_process";
|
|
6
|
-
import { createHash } from "node:crypto";
|
|
7
|
-
import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, readSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
8
|
-
import { homedir, tmpdir } from "node:os";
|
|
9
|
-
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
10
|
-
import { engineInfo, userConfig } from "@deftai/directive-core";
|
|
11
|
-
import { interceptHelp } from "@deftai/directive-core/dist/triage/help/index.js";
|
|
12
|
-
import { parseInitArgv, runInitDepositCli } from "@deftai/directive-core/init-deposit";
|
|
13
|
-
import { appendAuditLog, disclosureLine, migrateLegacyPolicyKey, PLAN_POLICY_KEY, POLICY_AUDIT_NOOP_STDOUT, policyColonInvocation, policySetInvocation, projectDefinitionPath, resolvePolicy, resolveWipCap, setPolicy, stampChangedToken, } from "@deftai/directive-core/policy";
|
|
14
|
-
import { defaultWhich } from "@deftai/directive-core/scm";
|
|
15
|
-
import { KNOWN_SUBAGENT_BACKEND_IDS, probeSubagentBackends, resolveSwarmSubagentBackend, } from "@deftai/directive-core/swarm";
|
|
16
|
-
import { withProjectDefinitionMutation, } from "@deftai/directive-core/vbrief-build";
|
|
17
|
-
import { identityBanner } from "./identity-banner.js";
|
|
18
|
-
const HANDLER_KEYS = [
|
|
19
|
-
"run",
|
|
20
|
-
"main",
|
|
21
|
-
"mainEntry",
|
|
22
|
-
"launchMain",
|
|
23
|
-
"completeCohortMain",
|
|
24
|
-
"readinessMain",
|
|
25
|
-
"verifyReviewCleanMain",
|
|
26
|
-
"worktreesMain",
|
|
27
|
-
];
|
|
28
|
-
/** CLI modules in packages/cli/src (excluding parity harnesses and bin/index). */
|
|
29
|
-
export const CLI_MODULE_VERBS = [
|
|
30
|
-
"agents-refresh",
|
|
31
|
-
"cache",
|
|
32
|
-
"check",
|
|
33
|
-
"capacity-backfill",
|
|
34
|
-
"capacity-show",
|
|
35
|
-
"codebase-default-extractor",
|
|
36
|
-
"codebase-map",
|
|
37
|
-
"codebase-map-fresh",
|
|
38
|
-
"codebase-projection-registry",
|
|
39
|
-
"codebase-provider",
|
|
40
|
-
"doctor",
|
|
41
|
-
"ownership-doctor",
|
|
42
|
-
"ownership-fix",
|
|
43
|
-
"verify-ownership",
|
|
44
|
-
"install-upgrade",
|
|
45
|
-
"install-uninstall",
|
|
46
|
-
"migrate-preflight",
|
|
47
|
-
"migrate-xbrief",
|
|
48
|
-
"migrate-category-b",
|
|
49
|
-
"framework-check-updates",
|
|
50
|
-
"hook-dispatch",
|
|
51
|
-
"umbrella-current-shape",
|
|
52
|
-
"changelog-check",
|
|
53
|
-
"change-init",
|
|
54
|
-
"commit-lint",
|
|
55
|
-
"coverage-hotspots",
|
|
56
|
-
"policy",
|
|
57
|
-
"authz",
|
|
58
|
-
"escalation-cli",
|
|
59
|
-
"pr-closing-keywords",
|
|
60
|
-
"pr-merge-readiness",
|
|
61
|
-
"pr-monitor",
|
|
62
|
-
"pr-protected-issues",
|
|
63
|
-
"pr-wait-mergeable",
|
|
64
|
-
"pr-watch",
|
|
65
|
-
"pr-finish-loop",
|
|
66
|
-
"directive-finish-loop",
|
|
67
|
-
"pre-pr-run",
|
|
68
|
-
"preflight-cache",
|
|
69
|
-
"preflight-gh",
|
|
70
|
-
"probe-session",
|
|
71
|
-
"release",
|
|
72
|
-
"release-e2e",
|
|
73
|
-
"release-publish",
|
|
74
|
-
"release-rollback",
|
|
75
|
-
"release-wait-npm",
|
|
76
|
-
"scope-lifecycle",
|
|
77
|
-
"scope-record-approved-scope",
|
|
78
|
-
"scope-record-observable-scope",
|
|
79
|
-
"scope-record-intent-constraint",
|
|
80
|
-
"scm-sync-default",
|
|
81
|
-
"lifecycle-event",
|
|
82
|
-
"lifecycle-stats",
|
|
83
|
-
"session-start",
|
|
84
|
-
"session-ready",
|
|
85
|
-
"occupancy-steal",
|
|
86
|
-
"occupancy-release",
|
|
87
|
-
"occupancy-heartbeat",
|
|
88
|
-
"occupancy-grant",
|
|
89
|
-
"plan-sequence",
|
|
90
|
-
"slice",
|
|
91
|
-
"subagent-monitor",
|
|
92
|
-
"toolchain-check",
|
|
93
|
-
"triage-actions",
|
|
94
|
-
"triage-bootstrap",
|
|
95
|
-
"triage-bulk",
|
|
96
|
-
"triage-classify",
|
|
97
|
-
"triage-strip-withdrawn",
|
|
98
|
-
"triage-evaluate",
|
|
99
|
-
"triage-help",
|
|
100
|
-
"triage-queue",
|
|
101
|
-
"triage-reconcile",
|
|
102
|
-
"triage-refresh",
|
|
103
|
-
"triage-scope",
|
|
104
|
-
"triage-scope-drift",
|
|
105
|
-
"triage-smoketest",
|
|
106
|
-
"triage-subscribe",
|
|
107
|
-
"triage-summary",
|
|
108
|
-
"triage-welcome",
|
|
109
|
-
"ts-check-lane",
|
|
110
|
-
"vbrief-activate",
|
|
111
|
-
"vbrief-build",
|
|
112
|
-
"vbrief-preflight",
|
|
113
|
-
"vbrief-reconcile",
|
|
114
|
-
"vbrief-validate",
|
|
115
|
-
"vbrief-validation",
|
|
116
|
-
"xbrief-create",
|
|
117
|
-
"xbrief-verify",
|
|
118
|
-
"xbrief-adopt-stored-plan-id",
|
|
119
|
-
"verify-branch",
|
|
120
|
-
"verify-encoding",
|
|
121
|
-
"verify-forward-coverage",
|
|
122
|
-
"verify-test-boundary",
|
|
123
|
-
"verify-class-checks",
|
|
124
|
-
"verify-presentation-coverage",
|
|
125
|
-
"verify-scope-provenance",
|
|
126
|
-
"verify-consumer-check-contract",
|
|
127
|
-
"verify-hooks-installed",
|
|
128
|
-
"verify-investigation",
|
|
129
|
-
"verify-judgment-gates",
|
|
130
|
-
"verify-no-task-runtime",
|
|
131
|
-
"validate-links",
|
|
132
|
-
"validate-strategy-output",
|
|
133
|
-
"verify-biome-config",
|
|
134
|
-
"verify-bridge-drift",
|
|
135
|
-
"verify-capacity",
|
|
136
|
-
"verify-contained-writes",
|
|
137
|
-
"verify-telemetry-coverage",
|
|
138
|
-
"verify-content-manifest",
|
|
139
|
-
"verify-deposit-closure",
|
|
140
|
-
"verify-consumer-hard-stops",
|
|
141
|
-
"verify-closing-keywords",
|
|
142
|
-
"verify-skill-external-fetch-gate",
|
|
143
|
-
"verify-semantic-single-source",
|
|
144
|
-
"verify-contract-drift",
|
|
145
|
-
"verify-cursor-tier1",
|
|
146
|
-
"verify-openclaw-tier1",
|
|
147
|
-
"verify-go-freeze",
|
|
148
|
-
"verify-scm-boundary",
|
|
149
|
-
"verify-session-ritual",
|
|
150
|
-
"verify-plan-sequence",
|
|
151
|
-
"verify-stubs",
|
|
152
|
-
"verify-xbrief-drift",
|
|
153
|
-
"verify-spec-prd-fresh",
|
|
154
|
-
"verify-spec-drift",
|
|
155
|
-
"rule-ownership-lint",
|
|
156
|
-
"verify-story-ready",
|
|
157
|
-
"verify-review-monitor",
|
|
158
|
-
"verify-cohort-review-monitors",
|
|
159
|
-
"verify-l4-owner",
|
|
160
|
-
"verify-subagent-alive",
|
|
161
|
-
"verify-subagent-steer",
|
|
162
|
-
"subagent-pre-cancel",
|
|
163
|
-
"subagent-steer-write",
|
|
164
|
-
"design-critique-spend-resolve",
|
|
165
|
-
"review-monitor-register",
|
|
166
|
-
"review-monitor-release",
|
|
167
|
-
"verify-tools",
|
|
168
|
-
"verify-wip-cap",
|
|
169
|
-
"verify-orphan-active",
|
|
170
|
-
"verify-pr-closeout-attestable",
|
|
171
|
-
"verify-lifecycle-visible",
|
|
172
|
-
"verify-completed-tracked",
|
|
173
|
-
"verify-completed-write-guard",
|
|
174
|
-
"verify-literal-ac",
|
|
175
|
-
"verify-ac",
|
|
176
|
-
"verify-agents-md-budget",
|
|
177
|
-
"verify-consumer-header-placeholder",
|
|
178
|
-
"verify-agents-md-advisory",
|
|
179
|
-
"verify-persisted-planning-narratives",
|
|
180
|
-
"verify-eval-health-relocation",
|
|
181
|
-
"verify-eval-triggers-relocation",
|
|
182
|
-
"verify-evaluator-surface",
|
|
183
|
-
"verify-observable-scope",
|
|
184
|
-
"verify-operator-scope-limit",
|
|
185
|
-
"verify-presentation-ceiling",
|
|
186
|
-
"verify-intent-constraint",
|
|
187
|
-
"verify-durable-effect-acquisition",
|
|
188
|
-
"verify-consumer-test-lane",
|
|
189
|
-
"eval-health",
|
|
190
|
-
"eval-run",
|
|
191
|
-
"eval-report",
|
|
192
|
-
"eval-triggers",
|
|
193
|
-
];
|
|
194
|
-
/** Core-only CLI entrypoints without a packages/cli wrapper. */
|
|
195
|
-
export const CORE_MODULE_VERBS = [
|
|
196
|
-
"scm",
|
|
197
|
-
"scm-readiness",
|
|
198
|
-
"github-auth-modes",
|
|
199
|
-
"github-body",
|
|
200
|
-
"issue-emit",
|
|
201
|
-
"issue-ingest",
|
|
202
|
-
"issue-sync-from-xbrief",
|
|
203
|
-
"reconcile-issues",
|
|
204
|
-
"swarm-launch",
|
|
205
|
-
"swarm-complete-cohort",
|
|
206
|
-
"swarm-finalize-cohort",
|
|
207
|
-
"swarm-finalize-owed",
|
|
208
|
-
"swarm-pre-dispatch",
|
|
209
|
-
"swarm-readiness",
|
|
210
|
-
"swarm-routing-verify",
|
|
211
|
-
"swarm-routing-set",
|
|
212
|
-
"swarm-verify-review-clean",
|
|
213
|
-
"swarm-worktrees",
|
|
214
|
-
"framework-commands",
|
|
215
|
-
"pack-render",
|
|
216
|
-
"packs-slice",
|
|
217
|
-
"prd-render",
|
|
218
|
-
"export-spec",
|
|
219
|
-
"project-render",
|
|
220
|
-
"project-write-narratives",
|
|
221
|
-
"roadmap-render",
|
|
222
|
-
"rule-map",
|
|
223
|
-
"spec-render",
|
|
224
|
-
"spec-reconstruct",
|
|
225
|
-
"spec-validate",
|
|
226
|
-
"code-structure-validate",
|
|
227
|
-
"pack-migrate-skills",
|
|
228
|
-
"pack-migrate-rules",
|
|
229
|
-
"pack-migrate-strategies",
|
|
230
|
-
"pack-migrate-patterns",
|
|
231
|
-
"pack-migrate-swarm-spec",
|
|
232
|
-
"policy-set",
|
|
233
|
-
"setup-ghx",
|
|
234
|
-
"scope-undo",
|
|
235
|
-
"scope-demote",
|
|
236
|
-
"scope-decompose",
|
|
237
|
-
"changelog-resolve-unreleased",
|
|
238
|
-
"architecture-preflight-sor",
|
|
239
|
-
"feedback-file",
|
|
240
|
-
"value-readback",
|
|
241
|
-
"product-signal",
|
|
242
|
-
"freshness-report",
|
|
243
|
-
"decision-write",
|
|
244
|
-
"decision-list",
|
|
245
|
-
"docs-impact",
|
|
246
|
-
"tutorial",
|
|
247
|
-
"migrate-clause-ids",
|
|
248
|
-
"migrate-confidence",
|
|
249
|
-
"rule-to-verb-parity",
|
|
250
|
-
];
|
|
251
|
-
/** Colon aliases for triage-actions (mirrors cli-router SUBCOMMAND_ROUTES). */
|
|
252
|
-
export const TRIAGE_ACTION_ALIAS_SUBCOMMANDS = {
|
|
253
|
-
"triage:accept": "accept",
|
|
254
|
-
"triage:reject": "reject",
|
|
255
|
-
"triage:defer": "defer",
|
|
256
|
-
"triage:needs-ac": "needs-ac",
|
|
257
|
-
"triage:mark-duplicate": "mark-duplicate",
|
|
258
|
-
"triage:status": "status",
|
|
259
|
-
"triage:reset": "reset",
|
|
260
|
-
"triage:history": "history",
|
|
261
|
-
};
|
|
262
|
-
const TRIAGE_ACTION_COLON_ALIASES = Object.fromEntries(Object.keys(TRIAGE_ACTION_ALIAS_SUBCOMMANDS).map((alias) => [alias, "triage-actions"]));
|
|
263
|
-
/** Colon aliases for policy subcommands (mirrors cli-router SUBCOMMAND_ROUTES). */
|
|
264
|
-
export const POLICY_ACTION_ALIAS_SUBCOMMANDS = {
|
|
265
|
-
"policy:show": "show",
|
|
266
|
-
"policy:enforce-branches": "enforce-branches",
|
|
267
|
-
"policy:allow-direct-commits": "allow-direct-commits",
|
|
268
|
-
"policy:allow-destructive-gh-verbs": "allow-destructive-gh-verbs",
|
|
269
|
-
"policy:enforce-destructive-gh-verbs": "enforce-destructive-gh-verbs",
|
|
270
|
-
"policy:allow-bot-merge": "allow-bot-merge",
|
|
271
|
-
"policy:enable-value-feedback": "enable-value-feedback",
|
|
272
|
-
"policy:set-spec-guard-enforcement": "set-spec-guard-enforcement",
|
|
273
|
-
"policy:disable-host-hooks": "disable-host-hooks",
|
|
274
|
-
"policy:set-ceremony-dial": "set-ceremony-dial",
|
|
275
|
-
"policy:clear-value-feedback": "clear-value-feedback",
|
|
276
|
-
"policy:disable-directive": "disable-directive",
|
|
277
|
-
"policy:enable-directive": "enable-directive",
|
|
278
|
-
};
|
|
279
|
-
const POLICY_ACTION_COLON_ALIASES = Object.fromEntries(Object.keys(POLICY_ACTION_ALIAS_SUBCOMMANDS).map((alias) => [alias, "policy"]));
|
|
280
|
-
/** Colon aliases for authz subcommands (#2944). */
|
|
281
|
-
export const AUTHZ_ACTION_ALIAS_SUBCOMMANDS = {
|
|
282
|
-
"authz:show": "show",
|
|
283
|
-
"authz:uat-start": "uat-start",
|
|
284
|
-
"authz:uat-suspend": "uat-suspend",
|
|
285
|
-
"authz:grant": "grant",
|
|
286
|
-
"authz:revoke": "revoke",
|
|
287
|
-
};
|
|
288
|
-
const AUTHZ_ACTION_COLON_ALIASES = Object.fromEntries(Object.keys(AUTHZ_ACTION_ALIAS_SUBCOMMANDS).map((alias) => [alias, "authz"]));
|
|
289
|
-
/** Colon aliases for escalation subcommands (#518). */
|
|
290
|
-
export const ESCALATION_ACTION_ALIAS_SUBCOMMANDS = {
|
|
291
|
-
"escalation:file": "file",
|
|
292
|
-
"escalation:list": "list",
|
|
293
|
-
"escalation:resolve": "resolve",
|
|
294
|
-
"escalation:batch-approve": "batch-approve",
|
|
295
|
-
};
|
|
296
|
-
const ESCALATION_ACTION_COLON_ALIASES = Object.fromEntries(Object.keys(ESCALATION_ACTION_ALIAS_SUBCOMMANDS).map((alias) => [alias, "escalation-cli"]));
|
|
297
|
-
/** Colon aliases for plan-sequence subcommands (#2402). */
|
|
298
|
-
export const PLAN_SEQUENCE_ALIAS_SUBCOMMANDS = {
|
|
299
|
-
"plan-sequence:set": "set",
|
|
300
|
-
"plan-sequence:current": "current",
|
|
301
|
-
"plan-sequence:clear": "clear",
|
|
302
|
-
"plan-sequence:advance": "advance",
|
|
303
|
-
};
|
|
304
|
-
const PLAN_SEQUENCE_COLON_ALIASES = Object.fromEntries(Object.keys(PLAN_SEQUENCE_ALIAS_SUBCOMMANDS).map((alias) => [alias, "plan-sequence"]));
|
|
305
|
-
/** Colon aliases for the Directive Tutorial (#4981). */
|
|
306
|
-
export const TUTORIAL_ALIAS_SUBCOMMANDS = {
|
|
307
|
-
"tutorial:offer": "offer",
|
|
308
|
-
"tutorial:start": "start",
|
|
309
|
-
"tutorial:inspect": "inspect",
|
|
310
|
-
"tutorial:advance": "advance",
|
|
311
|
-
"tutorial:resume": "resume",
|
|
312
|
-
"tutorial:skip": "skip",
|
|
313
|
-
"tutorial:leave": "leave",
|
|
314
|
-
"tutorial:reset": "reset",
|
|
315
|
-
"tutorial:decline": "decline",
|
|
316
|
-
"tutorial:defer": "defer",
|
|
317
|
-
};
|
|
318
|
-
const TUTORIAL_COLON_ALIASES = Object.fromEntries(Object.keys(TUTORIAL_ALIAS_SUBCOMMANDS).map((alias) => [alias, "tutorial"]));
|
|
319
|
-
/** Colon aliases for product-signal subcommands (#2693). */
|
|
320
|
-
export const PRODUCT_SIGNAL_ALIAS_SUBCOMMANDS = {
|
|
321
|
-
"product-signal:status": "status",
|
|
322
|
-
"product-signal:enable": "enable",
|
|
323
|
-
"product-signal:consent": "consent",
|
|
324
|
-
"product-signal:submit": "submit",
|
|
325
|
-
"product-signal:bootstrap-sink": "bootstrap-sink",
|
|
326
|
-
};
|
|
327
|
-
const PRODUCT_SIGNAL_COLON_ALIASES = Object.fromEntries(Object.keys(PRODUCT_SIGNAL_ALIAS_SUBCOMMANDS).map((alias) => [alias, "product-signal"]));
|
|
328
|
-
/** Colon aliases for freshness subcommands (#3117). */
|
|
329
|
-
export const FRESHNESS_ALIAS_SUBCOMMANDS = {
|
|
330
|
-
"freshness:report": "report",
|
|
331
|
-
"freshness:bind": "bind",
|
|
332
|
-
"session:freshness": "report",
|
|
333
|
-
freshness: "report",
|
|
334
|
-
};
|
|
335
|
-
const FRESHNESS_COLON_ALIASES = Object.fromEntries(Object.keys(FRESHNESS_ALIAS_SUBCOMMANDS).map((alias) => [alias, "freshness-report"]));
|
|
336
|
-
/** Task-style aliases (framework_commands / Taskfile names). */
|
|
337
|
-
export const VERB_ALIASES = {
|
|
338
|
-
"pre-pr:run": "pre-pr-run",
|
|
339
|
-
"hook:dispatch": "hook-dispatch",
|
|
340
|
-
"verify:encoding": "verify-encoding",
|
|
341
|
-
"verify:forward-coverage": "verify-forward-coverage",
|
|
342
|
-
"verify:test-boundary": "verify-test-boundary",
|
|
343
|
-
"verify:class-checks": "verify-class-checks",
|
|
344
|
-
"verify:presentation-ceiling": "verify-presentation-ceiling",
|
|
345
|
-
"verify:presentation-coverage": "verify-presentation-coverage",
|
|
346
|
-
"verify:scope-provenance": "verify-scope-provenance",
|
|
347
|
-
"scope:record-approved-scope": "scope-record-approved-scope",
|
|
348
|
-
"scope:record-observable-scope": "scope-record-observable-scope",
|
|
349
|
-
"scope:record-intent-constraint": "scope-record-intent-constraint",
|
|
350
|
-
"verify:consumer-check-contract": "verify-consumer-check-contract",
|
|
351
|
-
"coverage:hotspots": "coverage-hotspots",
|
|
352
|
-
"verify:branch": "verify-branch",
|
|
353
|
-
"verify:vbrief-conformance": "vbrief-validate",
|
|
354
|
-
"verify:wip-cap": "verify-wip-cap",
|
|
355
|
-
"verify:orphan-active": "verify-orphan-active",
|
|
356
|
-
"verify:pr-closeout-attestable": "verify-pr-closeout-attestable",
|
|
357
|
-
"verify:lifecycle-visible": "verify-lifecycle-visible",
|
|
358
|
-
"verify:completed-tracked": "verify-completed-tracked",
|
|
359
|
-
"verify:completed-write-guard": "verify-completed-write-guard",
|
|
360
|
-
"verify:literal-ac": "verify-literal-ac",
|
|
361
|
-
"verify:literal-acceptance": "verify-literal-ac",
|
|
362
|
-
"verify:ac": "verify-ac",
|
|
363
|
-
"verify:acceptance": "verify-ac",
|
|
364
|
-
"verify:agents-md-budget": "verify-agents-md-budget",
|
|
365
|
-
"verify:consumer-header-placeholder": "verify-consumer-header-placeholder",
|
|
366
|
-
"verify:agents-md-advisory": "verify-agents-md-advisory",
|
|
367
|
-
"verify:persisted-planning-narratives": "verify-persisted-planning-narratives",
|
|
368
|
-
"verify:eval-health-relocation": "verify-eval-health-relocation",
|
|
369
|
-
"verify:eval-triggers-relocation": "verify-eval-triggers-relocation",
|
|
370
|
-
"verify:evaluator-surface": "verify-evaluator-surface",
|
|
371
|
-
"verify:observable-scope": "verify-observable-scope",
|
|
372
|
-
"verify:operator-scope-limit": "verify-operator-scope-limit",
|
|
373
|
-
"verify:intent-constraint": "verify-intent-constraint",
|
|
374
|
-
"verify:durable-effect-acquisition": "verify-durable-effect-acquisition",
|
|
375
|
-
"verify:consumer-test-lane": "verify-consumer-test-lane",
|
|
376
|
-
"verify:hooks-installed": "verify-hooks-installed",
|
|
377
|
-
"verify:no-task-runtime": "verify-no-task-runtime",
|
|
378
|
-
"vbrief:validate": "vbrief-validate",
|
|
379
|
-
"vbrief:preflight": "vbrief-preflight",
|
|
380
|
-
// xbrief:-prefixed aliases for the canonical post-#2109 lifecycle spelling.
|
|
381
|
-
// xbrief:validate closes the #3483 gap: the swarm skill named it at MUST
|
|
382
|
-
// level but only vbrief:validate resolved.
|
|
383
|
-
"xbrief:validate": "vbrief-validate",
|
|
384
|
-
"xbrief:activate": "vbrief-activate",
|
|
385
|
-
"xbrief:preflight": "vbrief-preflight",
|
|
386
|
-
"xbrief:create": "xbrief-create",
|
|
387
|
-
"xbrief:verify": "xbrief-verify",
|
|
388
|
-
"xbrief:adopt-stored-plan-id": "xbrief-adopt-stored-plan-id",
|
|
389
|
-
"vbrief:activate": "vbrief-activate",
|
|
390
|
-
"verify:story-ready": "verify-story-ready",
|
|
391
|
-
"verify:review-monitor": "verify-review-monitor",
|
|
392
|
-
"verify:cohort-review-monitors": "verify-cohort-review-monitors",
|
|
393
|
-
"verify:l4-owner": "verify-l4-owner",
|
|
394
|
-
"verify:subagent-alive": "verify-subagent-alive",
|
|
395
|
-
"verify:subagent-steer": "verify-subagent-steer",
|
|
396
|
-
"subagent:pre-cancel": "subagent-pre-cancel",
|
|
397
|
-
"subagent:steer": "subagent-steer-write",
|
|
398
|
-
"design-critique:spend-resolve": "design-critique-spend-resolve",
|
|
399
|
-
"agent:monitor": "subagent-monitor",
|
|
400
|
-
"review-monitor:register": "review-monitor-register",
|
|
401
|
-
"review-monitor:release": "review-monitor-release",
|
|
402
|
-
"verify:tools": "verify-tools",
|
|
403
|
-
"verify:investigation": "verify-investigation",
|
|
404
|
-
"verify:judgment-gates": "verify-judgment-gates",
|
|
405
|
-
"verify:stubs": "verify-stubs",
|
|
406
|
-
"verify:links": "validate-links",
|
|
407
|
-
"validate:links": "validate-links",
|
|
408
|
-
"verify:rule-ownership": "rule-ownership-lint",
|
|
409
|
-
"rule:ownership-lint": "rule-ownership-lint",
|
|
410
|
-
"verify:rule-to-verb-parity": "rule-to-verb-parity",
|
|
411
|
-
"verify:biome-config": "verify-biome-config",
|
|
412
|
-
"verify:contained-writes": "verify-contained-writes",
|
|
413
|
-
"verify:telemetry-coverage": "verify-telemetry-coverage",
|
|
414
|
-
"verify:content-manifest": "verify-content-manifest",
|
|
415
|
-
"verify:deposit-closure": "verify-deposit-closure",
|
|
416
|
-
"verify:consumer-hard-stops": "verify-consumer-hard-stops",
|
|
417
|
-
"verify:closing-keywords": "verify-closing-keywords",
|
|
418
|
-
"verify:docs-impact": "docs-impact",
|
|
419
|
-
"verify:skill-external-fetch-gate": "verify-skill-external-fetch-gate",
|
|
420
|
-
"verify:semantic-single-source": "verify-semantic-single-source",
|
|
421
|
-
"verify:contract-drift": "verify-contract-drift",
|
|
422
|
-
"verify:cursor-tier1": "verify-cursor-tier1",
|
|
423
|
-
"verify:openclaw-tier1": "verify-openclaw-tier1",
|
|
424
|
-
"verify:go-freeze": "verify-go-freeze",
|
|
425
|
-
"verify:bridge-drift": "verify-bridge-drift",
|
|
426
|
-
"verify:scm-boundary": "verify-scm-boundary",
|
|
427
|
-
"verify:xbrief-drift": "verify-xbrief-drift",
|
|
428
|
-
"verify:spec-prd-fresh": "verify-spec-prd-fresh",
|
|
429
|
-
"verify:spec-drift": "verify-spec-drift",
|
|
430
|
-
"verify:capacity": "verify-capacity",
|
|
431
|
-
"verify:session-ritual": "verify-session-ritual",
|
|
432
|
-
"verify:plan-sequence": "verify-plan-sequence",
|
|
433
|
-
...PLAN_SEQUENCE_COLON_ALIASES,
|
|
434
|
-
...TUTORIAL_COLON_ALIASES,
|
|
435
|
-
"verify-strategy-output": "validate-strategy-output",
|
|
436
|
-
"validate:strategy-output": "validate-strategy-output",
|
|
437
|
-
"verify:codebase-map-fresh": "codebase-map-fresh",
|
|
438
|
-
"codebase:map": "codebase-map",
|
|
439
|
-
"triage:welcome": "triage-welcome",
|
|
440
|
-
"triage:bootstrap": "triage-bootstrap",
|
|
441
|
-
"triage:summary": "triage-summary",
|
|
442
|
-
"triage:queue": "triage-queue",
|
|
443
|
-
"triage:evaluate": "triage-evaluate",
|
|
444
|
-
"triage:scope": "triage-scope",
|
|
445
|
-
...TRIAGE_ACTION_COLON_ALIASES,
|
|
446
|
-
...POLICY_ACTION_COLON_ALIASES,
|
|
447
|
-
...AUTHZ_ACTION_COLON_ALIASES,
|
|
448
|
-
...ESCALATION_ACTION_COLON_ALIASES,
|
|
449
|
-
...PRODUCT_SIGNAL_COLON_ALIASES,
|
|
450
|
-
"agents:refresh": "agents-refresh",
|
|
451
|
-
"migrate:preflight": "migrate-preflight",
|
|
452
|
-
"migrate:xbrief": "migrate-xbrief",
|
|
453
|
-
"migrate:category-b": "migrate-category-b",
|
|
454
|
-
"migrate:clause-ids": "migrate-clause-ids",
|
|
455
|
-
"migrate:confidence": "migrate-confidence",
|
|
456
|
-
"framework:check-updates": "framework-check-updates",
|
|
457
|
-
"umbrella:current-shape": "umbrella-current-shape",
|
|
458
|
-
"issue:sync-from-xbrief": "issue-sync-from-xbrief",
|
|
459
|
-
upgrade: "install-upgrade",
|
|
460
|
-
"session:start": "session-start",
|
|
461
|
-
"session:ready": "session-ready",
|
|
462
|
-
"occupancy:steal": "occupancy-steal",
|
|
463
|
-
"occupancy:release": "occupancy-release",
|
|
464
|
-
"occupancy:heartbeat": "occupancy-heartbeat",
|
|
465
|
-
"occupancy:grant": "occupancy-grant",
|
|
466
|
-
"session:end": "occupancy-release",
|
|
467
|
-
...FRESHNESS_COLON_ALIASES,
|
|
468
|
-
"lifecycle:event": "lifecycle-event",
|
|
469
|
-
"lifecycle:stats": "lifecycle-stats",
|
|
470
|
-
"toolchain:check": "toolchain-check",
|
|
471
|
-
"ts:check-lane": "ts-check-lane",
|
|
472
|
-
"spec:validate": "spec-validate",
|
|
473
|
-
"spec:render": "spec-render",
|
|
474
|
-
"spec:reconstruct": "spec-reconstruct",
|
|
475
|
-
"prd:render": "prd-render",
|
|
476
|
-
"project:render": "project-render",
|
|
477
|
-
"project:write-narratives": "project-write-narratives",
|
|
478
|
-
"docs:rule-map": "rule-map",
|
|
479
|
-
"project:export-spec": "export-spec",
|
|
480
|
-
"pr:watch": "pr-watch",
|
|
481
|
-
"pr:finish-loop": "pr-finish-loop",
|
|
482
|
-
"directive:finish-loop": "directive-finish-loop",
|
|
483
|
-
doctor: "doctor",
|
|
484
|
-
"ownership:doctor": "ownership-doctor",
|
|
485
|
-
"ownership:fix": "ownership-fix",
|
|
486
|
-
"verify:ownership": "verify-ownership",
|
|
487
|
-
"deft:ownership:doctor": "ownership-doctor",
|
|
488
|
-
"deft:ownership:fix": "ownership-fix",
|
|
489
|
-
"deft:verify:ownership": "verify-ownership",
|
|
490
|
-
"eval:health": "eval-health",
|
|
491
|
-
"feedback:file": "feedback-file",
|
|
492
|
-
"value:show": "value-readback",
|
|
493
|
-
"triage:metrics": "value-readback",
|
|
494
|
-
"decision:write": "decision-write",
|
|
495
|
-
"decision:list": "decision-list",
|
|
496
|
-
"eval:run": "eval-run",
|
|
497
|
-
"eval:triggers": "eval-triggers",
|
|
498
|
-
"eval:report": "eval-report",
|
|
499
|
-
build: "framework-commands",
|
|
500
|
-
"setup:ghx": "setup-ghx",
|
|
501
|
-
"setup:seed": "setup-seed",
|
|
502
|
-
"scm:status": "scm-readiness",
|
|
503
|
-
"scm:readiness": "scm-readiness",
|
|
504
|
-
"scm:sync-default": "scm-sync-default",
|
|
505
|
-
"release:wait-npm": "release-wait-npm",
|
|
506
|
-
};
|
|
507
|
-
/** CLI modules living under verify-source-cli/ or content-validate-cli/ subdirs. */
|
|
508
|
-
const SUBDIR_CLI_STEMS = {
|
|
509
|
-
"verify-stubs": "verify-source-cli/verify-stubs",
|
|
510
|
-
"rule-ownership-lint": "verify-source-cli/rule-ownership-lint",
|
|
511
|
-
"verify-biome-config": "verify-source-cli/verify-biome-config",
|
|
512
|
-
"verify-contained-writes": "verify-source-cli/verify-contained-writes",
|
|
513
|
-
"verify-telemetry-coverage": "verify-source-cli/verify-telemetry-coverage",
|
|
514
|
-
"verify-content-manifest": "verify-source-cli/verify-content-manifest",
|
|
515
|
-
"verify-deposit-closure": "verify-source-cli/verify-deposit-closure",
|
|
516
|
-
"verify-consumer-hard-stops": "verify-source-cli/verify-consumer-hard-stops",
|
|
517
|
-
"verify-closing-keywords": "verify-source-cli/verify-closing-keywords",
|
|
518
|
-
"verify-skill-external-fetch-gate": "verify-source-cli/verify-skill-external-fetch-gate",
|
|
519
|
-
"verify-semantic-single-source": "verify-source-cli/verify-semantic-single-source",
|
|
520
|
-
"verify-contract-drift": "verify-source-cli/verify-contract-drift",
|
|
521
|
-
"verify-cursor-tier1": "verify-source-cli/verify-cursor-tier1",
|
|
522
|
-
"verify-openclaw-tier1": "verify-source-cli/verify-openclaw-tier1",
|
|
523
|
-
"verify-scm-boundary": "verify-source-cli/verify-scm-boundary",
|
|
524
|
-
"verify-xbrief-drift": "verify-source-cli/verify-xbrief-drift",
|
|
525
|
-
"verify-spec-prd-fresh": "verify-source-cli/verify-spec-prd-fresh",
|
|
526
|
-
"verify-spec-drift": "verify-source-cli/verify-spec-drift",
|
|
527
|
-
"verify-go-freeze": "gates-cli/verify-go-freeze",
|
|
528
|
-
"verify-bridge-drift": "gates-cli/verify-bridge-drift",
|
|
529
|
-
"validate-links": "content-validate-cli/validate-links",
|
|
530
|
-
"verify-capacity": "content-validate-cli/verify-capacity",
|
|
531
|
-
"validate-strategy-output": "content-validate-cli/validate-strategy-output",
|
|
532
|
-
};
|
|
533
|
-
const WRAPPER_CLI_STEMS = new Set([
|
|
534
|
-
"capacity-backfill",
|
|
535
|
-
"capacity-show",
|
|
536
|
-
"codebase-default-extractor",
|
|
537
|
-
"codebase-map",
|
|
538
|
-
"codebase-map-fresh",
|
|
539
|
-
"codebase-projection-registry",
|
|
540
|
-
"codebase-provider",
|
|
541
|
-
"vbrief-activate",
|
|
542
|
-
"vbrief-build",
|
|
543
|
-
"vbrief-reconcile",
|
|
544
|
-
"vbrief-validate",
|
|
545
|
-
"vbrief-validation",
|
|
546
|
-
"xbrief-create",
|
|
547
|
-
"xbrief-verify",
|
|
548
|
-
"xbrief-adopt-stored-plan-id",
|
|
549
|
-
]);
|
|
550
|
-
function emitCliResult(result, io) {
|
|
551
|
-
if (result.stdout)
|
|
552
|
-
io.writeOut(result.stdout);
|
|
553
|
-
if (result.stderr)
|
|
554
|
-
io.writeErr(result.stderr);
|
|
555
|
-
return result.exitCode;
|
|
556
|
-
}
|
|
557
|
-
function resolveHandler(mod) {
|
|
558
|
-
for (const key of HANDLER_KEYS) {
|
|
559
|
-
const fn = mod[key];
|
|
560
|
-
if (typeof fn === "function") {
|
|
561
|
-
return fn;
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
return null;
|
|
565
|
-
}
|
|
566
|
-
async function loadWrapperCliHandler(stem, io) {
|
|
567
|
-
switch (stem) {
|
|
568
|
-
case "capacity-backfill": {
|
|
569
|
-
const { runCapacityBackfillCli } = await import("@deftai/directive-core/capacity");
|
|
570
|
-
return async (argv) => emitCliResult(await runCapacityBackfillCli(argv), io);
|
|
571
|
-
}
|
|
572
|
-
case "capacity-show": {
|
|
573
|
-
const { runCapacityShowCli } = await import("@deftai/directive-core/capacity");
|
|
574
|
-
return (argv) => emitCliResult(runCapacityShowCli(argv), io);
|
|
575
|
-
}
|
|
576
|
-
case "codebase-default-extractor": {
|
|
577
|
-
const { runDefaultExtractorCli } = await import("@deftai/directive-core/codebase");
|
|
578
|
-
return (argv) => emitCliResult(runDefaultExtractorCli(argv), io);
|
|
579
|
-
}
|
|
580
|
-
case "codebase-map": {
|
|
581
|
-
const { runCodebaseMapCli } = await import("@deftai/directive-core/codebase");
|
|
582
|
-
return (argv) => emitCliResult(runCodebaseMapCli(argv), io);
|
|
583
|
-
}
|
|
584
|
-
case "codebase-map-fresh": {
|
|
585
|
-
const { runCodebaseMapFreshCli } = await import("@deftai/directive-core/codebase");
|
|
586
|
-
return (argv) => emitCliResult(runCodebaseMapFreshCli(argv), io);
|
|
587
|
-
}
|
|
588
|
-
case "codebase-projection-registry": {
|
|
589
|
-
const { runProjectionRegistryCli } = await import("@deftai/directive-core/codebase");
|
|
590
|
-
return (argv) => emitCliResult(runProjectionRegistryCli(argv), io);
|
|
591
|
-
}
|
|
592
|
-
case "codebase-provider": {
|
|
593
|
-
const { runProviderCli } = await import("@deftai/directive-core/codebase");
|
|
594
|
-
return (argv) => emitCliResult(runProviderCli(argv), io);
|
|
595
|
-
}
|
|
596
|
-
case "vbrief-activate": {
|
|
597
|
-
const { run } = await import("@deftai/directive-core/vbrief-activate");
|
|
598
|
-
return run;
|
|
599
|
-
}
|
|
600
|
-
case "vbrief-build": {
|
|
601
|
-
const { cmdVbriefBuild } = await import("@deftai/directive-core/vbrief-build");
|
|
602
|
-
return cmdVbriefBuild;
|
|
603
|
-
}
|
|
604
|
-
case "vbrief-reconcile": {
|
|
605
|
-
const { cmdVbriefReconcile } = await import("@deftai/directive-core/vbrief-reconcile");
|
|
606
|
-
return cmdVbriefReconcile;
|
|
607
|
-
}
|
|
608
|
-
case "vbrief-validate": {
|
|
609
|
-
const { cmdVbriefValidate } = await import("@deftai/directive-core/vbrief-validate");
|
|
610
|
-
return cmdVbriefValidate;
|
|
611
|
-
}
|
|
612
|
-
case "vbrief-validation": {
|
|
613
|
-
const { cmdVbriefValidation } = await import("@deftai/directive-core/vbrief-validation");
|
|
614
|
-
return cmdVbriefValidation;
|
|
615
|
-
}
|
|
616
|
-
case "xbrief-create": {
|
|
617
|
-
const { runXbriefCreateCli } = await import("@deftai/directive-core/xbrief");
|
|
618
|
-
return (argv) => emitCliResult(runXbriefCreateCli(argv), io);
|
|
619
|
-
}
|
|
620
|
-
case "xbrief-verify": {
|
|
621
|
-
const { runXbriefVerifyCli } = await import("@deftai/directive-core/xbrief");
|
|
622
|
-
return (argv) => emitCliResult(runXbriefVerifyCli(argv), io);
|
|
623
|
-
}
|
|
624
|
-
case "xbrief-adopt-stored-plan-id": {
|
|
625
|
-
const { runAdoptStoredPlanIdCli } = await import("@deftai/directive-core/xbrief");
|
|
626
|
-
return (argv) => emitCliResult(runAdoptStoredPlanIdCli(argv), io);
|
|
627
|
-
}
|
|
628
|
-
default:
|
|
629
|
-
throw new Error(`no wrapper handler for ${stem}`);
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
async function loadCliModuleHandler(stem, io) {
|
|
633
|
-
if (WRAPPER_CLI_STEMS.has(stem)) {
|
|
634
|
-
return loadWrapperCliHandler(stem, io);
|
|
635
|
-
}
|
|
636
|
-
const subdir = SUBDIR_CLI_STEMS[stem];
|
|
637
|
-
const modulePath = subdir !== undefined ? `./${subdir}.js` : `./${stem}.js`;
|
|
638
|
-
const mod = (await import(modulePath));
|
|
639
|
-
const handler = resolveHandler(mod);
|
|
640
|
-
if (handler === null) {
|
|
641
|
-
throw new Error(`module ${stem} has no command handler export`);
|
|
642
|
-
}
|
|
643
|
-
return handler;
|
|
644
|
-
}
|
|
645
|
-
function resolveDeftRoot() {
|
|
646
|
-
if (process.env.DEFT_ROOT !== undefined && process.env.DEFT_ROOT.length > 0) {
|
|
647
|
-
return resolve(process.env.DEFT_ROOT);
|
|
648
|
-
}
|
|
649
|
-
return resolve(import.meta.dirname, "..", "..", "..");
|
|
650
|
-
}
|
|
651
|
-
function parseCodeStructureArgs(argv) {
|
|
652
|
-
let projectRoot = ".";
|
|
653
|
-
const paths = [];
|
|
654
|
-
let json = false;
|
|
655
|
-
let strict = false;
|
|
656
|
-
let enforce = false;
|
|
657
|
-
for (let i = 0; i < argv.length; i += 1) {
|
|
658
|
-
const arg = argv[i];
|
|
659
|
-
if (arg === "--project-root") {
|
|
660
|
-
const v = argv[i + 1];
|
|
661
|
-
if (v === undefined)
|
|
662
|
-
return {
|
|
663
|
-
projectRoot,
|
|
664
|
-
paths,
|
|
665
|
-
json,
|
|
666
|
-
strict,
|
|
667
|
-
enforce,
|
|
668
|
-
error: "missing --project-root value",
|
|
669
|
-
};
|
|
670
|
-
projectRoot = v;
|
|
671
|
-
i += 1;
|
|
672
|
-
}
|
|
673
|
-
else if (arg?.startsWith("--project-root=")) {
|
|
674
|
-
projectRoot = arg.slice("--project-root=".length);
|
|
675
|
-
}
|
|
676
|
-
else if (arg === "--path") {
|
|
677
|
-
const v = argv[i + 1];
|
|
678
|
-
if (v === undefined)
|
|
679
|
-
return { projectRoot, paths, json, strict, enforce, error: "missing --path value" };
|
|
680
|
-
paths.push(v);
|
|
681
|
-
i += 1;
|
|
682
|
-
}
|
|
683
|
-
else if (arg?.startsWith("--path=")) {
|
|
684
|
-
paths.push(arg.slice("--path=".length));
|
|
685
|
-
}
|
|
686
|
-
else if (arg === "--json") {
|
|
687
|
-
json = true;
|
|
688
|
-
}
|
|
689
|
-
else if (arg === "--strict") {
|
|
690
|
-
strict = true;
|
|
691
|
-
}
|
|
692
|
-
else if (arg === "--enforce") {
|
|
693
|
-
enforce = true;
|
|
694
|
-
}
|
|
695
|
-
else if (arg !== undefined && !arg.startsWith("-")) {
|
|
696
|
-
paths.push(arg);
|
|
697
|
-
}
|
|
698
|
-
else {
|
|
699
|
-
return {
|
|
700
|
-
projectRoot,
|
|
701
|
-
paths,
|
|
702
|
-
json,
|
|
703
|
-
strict,
|
|
704
|
-
enforce,
|
|
705
|
-
error: `unrecognized argument: ${arg}`,
|
|
706
|
-
};
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
return { projectRoot, paths, json, strict, enforce };
|
|
710
|
-
}
|
|
711
|
-
// ===========================================================================
|
|
712
|
-
// Native pack-migrate handlers (#2022 Phase 1).
|
|
713
|
-
//
|
|
714
|
-
// Port of scripts/pack_migrate_{skills,rules,strategies,patterns,swarm_spec}.py
|
|
715
|
-
// to native TypeScript so the pack-render surface no longer shells into bundled
|
|
716
|
-
// Python. Output parity with the Python scripts is exact, including the
|
|
717
|
-
// json.dumps(..., indent=2, ensure_ascii=True) + "\n" serialization, document
|
|
718
|
-
// scanning order, and per-entry field ordering.
|
|
719
|
-
// ===========================================================================
|
|
720
|
-
const PACK_VERSION = "0.1";
|
|
721
|
-
const DEFAULT_SKILL_VERSION = "0.1";
|
|
722
|
-
const SHOULD_NOT_GLYPH = "\u2249";
|
|
723
|
-
const MUST_NOT_GLYPH = "\u2297";
|
|
724
|
-
/** Serialize like Python json.dumps(value, indent=2, ensure_ascii=True) + "\n". */
|
|
725
|
-
function dumpsAsciiJson(value) {
|
|
726
|
-
const base = JSON.stringify(value, null, 2);
|
|
727
|
-
let out = "";
|
|
728
|
-
for (let i = 0; i < base.length; i += 1) {
|
|
729
|
-
const code = base.charCodeAt(i);
|
|
730
|
-
// ensure_ascii escapes every code unit outside the printable ASCII range
|
|
731
|
-
// (0x20-0x7e). JSON.stringify has already escaped control chars (< 0x20)
|
|
732
|
-
// and the structural quote/backslash, so only chars > 0x7e remain literal.
|
|
733
|
-
if (code > 0x7e) {
|
|
734
|
-
out += `\\u${code.toString(16).padStart(4, "0")}`;
|
|
735
|
-
}
|
|
736
|
-
else {
|
|
737
|
-
out += base.charAt(i);
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
return `${out}\n`;
|
|
741
|
-
}
|
|
742
|
-
/** Strip leading/trailing chars in `chars` (Python str.strip(chars)); whitespace when omitted. */
|
|
743
|
-
function pyStrip(value, chars) {
|
|
744
|
-
if (chars === undefined) {
|
|
745
|
-
return value.replace(/^\s+/, "").replace(/\s+$/, "");
|
|
746
|
-
}
|
|
747
|
-
let start = 0;
|
|
748
|
-
let end = value.length;
|
|
749
|
-
while (start < end && chars.includes(value.charAt(start)))
|
|
750
|
-
start += 1;
|
|
751
|
-
while (end > start && chars.includes(value.charAt(end - 1)))
|
|
752
|
-
end -= 1;
|
|
753
|
-
return value.slice(start, end);
|
|
754
|
-
}
|
|
755
|
-
// Python str.splitlines() universal newlines: \n \r \r\n \v \f \x1c \x1d \x1e \x85 \u2028 \u2029.
|
|
756
|
-
// Built from code points (as \uXXXX escape text) so no literal control characters land in the source.
|
|
757
|
-
const LINE_BOUNDARY_CLASS = [0x0a, 0x0d, 0x0b, 0x0c, 0x1c, 0x1d, 0x1e, 0x85, 0x2028, 0x2029]
|
|
758
|
-
.map((code) => `\\u${code.toString(16).padStart(4, "0")}`)
|
|
759
|
-
.join("");
|
|
760
|
-
const LINE_BOUNDARY_RE = new RegExp(`\\r\\n|[${LINE_BOUNDARY_CLASS}]`);
|
|
761
|
-
/** Mirror Python str.splitlines(): split on universal line boundaries, dropping one terminal break. */
|
|
762
|
-
function splitLines(text) {
|
|
763
|
-
if (text === "")
|
|
764
|
-
return [];
|
|
765
|
-
const parts = text.split(LINE_BOUNDARY_RE);
|
|
766
|
-
if (parts.length > 0 && parts[parts.length - 1] === "")
|
|
767
|
-
parts.pop();
|
|
768
|
-
return parts;
|
|
769
|
-
}
|
|
770
|
-
/** Repo-relative POSIX path of `to` measured from `from`. */
|
|
771
|
-
function relPosix(from, to) {
|
|
772
|
-
return relative(from, to).split(/[\\/]/).join("/");
|
|
773
|
-
}
|
|
774
|
-
/** Python Path.stem -- filename minus its final suffix. */
|
|
775
|
-
function stemOf(filePath) {
|
|
776
|
-
const base = basename(filePath);
|
|
777
|
-
const dot = base.lastIndexOf(".");
|
|
778
|
-
return dot > 0 ? base.slice(0, dot) : base;
|
|
779
|
-
}
|
|
780
|
-
/** Slugify a doc stem: lowercase, runs of non-alnum -> '-', trimmed of '-'. */
|
|
781
|
-
function slugify(stem) {
|
|
782
|
-
return pyStrip(stem.toLowerCase().replace(/[^a-z0-9]+/g, "-"), "-");
|
|
783
|
-
}
|
|
784
|
-
function isFileSafe(path) {
|
|
785
|
-
try {
|
|
786
|
-
return statSync(path).isFile();
|
|
787
|
-
}
|
|
788
|
-
catch {
|
|
789
|
-
return false;
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
function isDirSafe(path) {
|
|
793
|
-
try {
|
|
794
|
-
return statSync(path).isDirectory();
|
|
795
|
-
}
|
|
796
|
-
catch {
|
|
797
|
-
return false;
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
/** Sorted SKILL.md paths one directory below skillsDir (Python skills_dir glob of the SKILL.md docs). */
|
|
801
|
-
function globSkillMd(skillsDir) {
|
|
802
|
-
const out = [];
|
|
803
|
-
let names;
|
|
804
|
-
try {
|
|
805
|
-
names = readdirSync(skillsDir);
|
|
806
|
-
}
|
|
807
|
-
catch {
|
|
808
|
-
return out;
|
|
809
|
-
}
|
|
810
|
-
for (const name of names) {
|
|
811
|
-
const dir = join(skillsDir, name);
|
|
812
|
-
if (!isDirSafe(dir))
|
|
813
|
-
continue;
|
|
814
|
-
const candidate = join(dir, "SKILL.md");
|
|
815
|
-
if (isFileSafe(candidate))
|
|
816
|
-
out.push(candidate);
|
|
817
|
-
}
|
|
818
|
-
out.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
819
|
-
return out;
|
|
820
|
-
}
|
|
821
|
-
/** Sorted full paths of `<dir>/*.md` (Python dir.glob("*.md")). */
|
|
822
|
-
function globMd(dir) {
|
|
823
|
-
const out = [];
|
|
824
|
-
let names;
|
|
825
|
-
try {
|
|
826
|
-
names = readdirSync(dir);
|
|
827
|
-
}
|
|
828
|
-
catch {
|
|
829
|
-
return out;
|
|
830
|
-
}
|
|
831
|
-
for (const name of names) {
|
|
832
|
-
if (!name.endsWith(".md"))
|
|
833
|
-
continue;
|
|
834
|
-
const candidate = join(dir, name);
|
|
835
|
-
if (isFileSafe(candidate))
|
|
836
|
-
out.push(candidate);
|
|
837
|
-
}
|
|
838
|
-
out.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
839
|
-
return out;
|
|
840
|
-
}
|
|
841
|
-
const H1_RE = /^#\s+(.+?)\s*$/;
|
|
842
|
-
const CHROME_PREFIXES = [
|
|
843
|
-
"legend ",
|
|
844
|
-
"legend(",
|
|
845
|
-
"**legend",
|
|
846
|
-
`**${"\u26a0\ufe0f"}`,
|
|
847
|
-
"**see also",
|
|
848
|
-
"<!--",
|
|
849
|
-
];
|
|
850
|
-
function isChrome(line) {
|
|
851
|
-
const low = line.replace(/^\s+/, "").toLowerCase();
|
|
852
|
-
if (CHROME_PREFIXES.some((prefix) => low.startsWith(prefix)))
|
|
853
|
-
return true;
|
|
854
|
-
const stripped = line.trim();
|
|
855
|
-
return stripped.length > 0 && [...stripped].every((ch) => ch === "-" || ch === "=");
|
|
856
|
-
}
|
|
857
|
-
/** Index a line array with a defined fallback (`i` is always in range at call sites). */
|
|
858
|
-
function lineAt(lines, i) {
|
|
859
|
-
return lines[i] ?? "";
|
|
860
|
-
}
|
|
861
|
-
function extractTitle(md) {
|
|
862
|
-
for (const line of splitLines(md)) {
|
|
863
|
-
const match = H1_RE.exec(line);
|
|
864
|
-
if (match)
|
|
865
|
-
return (match[1] ?? "").trim();
|
|
866
|
-
}
|
|
867
|
-
return "";
|
|
868
|
-
}
|
|
869
|
-
function extractDescription(md) {
|
|
870
|
-
const lines = splitLines(md);
|
|
871
|
-
const n = lines.length;
|
|
872
|
-
let i = 0;
|
|
873
|
-
while (i < n && !H1_RE.test(lineAt(lines, i)))
|
|
874
|
-
i += 1;
|
|
875
|
-
if (i < n)
|
|
876
|
-
i += 1;
|
|
877
|
-
while (i < n && (lineAt(lines, i).trim() === "" || isChrome(lineAt(lines, i))))
|
|
878
|
-
i += 1;
|
|
879
|
-
const block = [];
|
|
880
|
-
while (i < n &&
|
|
881
|
-
lineAt(lines, i).trim() !== "" &&
|
|
882
|
-
!lineAt(lines, i).replace(/^\s+/, "").startsWith("#")) {
|
|
883
|
-
let stripped = lineAt(lines, i).trim();
|
|
884
|
-
if (stripped.startsWith(">"))
|
|
885
|
-
stripped = stripped.replace(/^>+/, "").trim();
|
|
886
|
-
if (stripped)
|
|
887
|
-
block.push(stripped);
|
|
888
|
-
i += 1;
|
|
889
|
-
}
|
|
890
|
-
return block.join(" ");
|
|
891
|
-
}
|
|
892
|
-
const REDIRECT_MARKERS = [
|
|
893
|
-
"legacy alias",
|
|
894
|
-
"superseded",
|
|
895
|
-
"has been renamed",
|
|
896
|
-
"has moved",
|
|
897
|
-
"deprecated",
|
|
898
|
-
];
|
|
899
|
-
function isRedirectStub(md) {
|
|
900
|
-
const lines = splitLines(md);
|
|
901
|
-
const n = lines.length;
|
|
902
|
-
let i = 0;
|
|
903
|
-
while (i < n && !H1_RE.test(lineAt(lines, i)))
|
|
904
|
-
i += 1;
|
|
905
|
-
if (i < n)
|
|
906
|
-
i += 1;
|
|
907
|
-
while (i < n && (lineAt(lines, i).trim() === "" || isChrome(lineAt(lines, i))))
|
|
908
|
-
i += 1;
|
|
909
|
-
if (i >= n || !lineAt(lines, i).replace(/^\s+/, "").startsWith(">"))
|
|
910
|
-
return false;
|
|
911
|
-
const block = [];
|
|
912
|
-
while (i < n && lineAt(lines, i).replace(/^\s+/, "").startsWith(">")) {
|
|
913
|
-
block.push(lineAt(lines, i).replace(/^\s+/, "").replace(/^>+/, "").trim());
|
|
914
|
-
i += 1;
|
|
915
|
-
}
|
|
916
|
-
const quote = block.join(" ").toLowerCase();
|
|
917
|
-
return REDIRECT_MARKERS.some((marker) => quote.includes(marker));
|
|
918
|
-
}
|
|
919
|
-
const BANNER_OPEN = "<!-- AUTO-GENERATED by task packs:render";
|
|
920
|
-
function stripLeadingBanner(body) {
|
|
921
|
-
const lines = body.split("\n");
|
|
922
|
-
const n = lines.length;
|
|
923
|
-
let i = 0;
|
|
924
|
-
while (i < n && lineAt(lines, i).trim() === "")
|
|
925
|
-
i += 1;
|
|
926
|
-
if (i < n && lineAt(lines, i).startsWith(BANNER_OPEN)) {
|
|
927
|
-
while (i < n && lineAt(lines, i).replace(/^\s+/, "").startsWith("<!--"))
|
|
928
|
-
i += 1;
|
|
929
|
-
while (i < n && lineAt(lines, i).trim() === "")
|
|
930
|
-
i += 1;
|
|
931
|
-
}
|
|
932
|
-
return lines.slice(i).join("\n");
|
|
933
|
-
}
|
|
934
|
-
const FRONTMATTER_RE = /^---\n([\s\S]*?\n)---\n?([\s\S]*)$/;
|
|
935
|
-
function splitFrontmatter(text) {
|
|
936
|
-
if (!text.startsWith("---\n"))
|
|
937
|
-
return [null, text];
|
|
938
|
-
const match = FRONTMATTER_RE.exec(text);
|
|
939
|
-
if (!match)
|
|
940
|
-
return [null, text];
|
|
941
|
-
return [match[1] ?? "", match[2] ?? ""];
|
|
942
|
-
}
|
|
943
|
-
function foldBlock(blockLines) {
|
|
944
|
-
const paragraphs = [];
|
|
945
|
-
let current = [];
|
|
946
|
-
for (const line of blockLines) {
|
|
947
|
-
if (line.trim() === "") {
|
|
948
|
-
if (current.length) {
|
|
949
|
-
paragraphs.push(current.join(" "));
|
|
950
|
-
current = [];
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
else {
|
|
954
|
-
current.push(line.trim());
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
if (current.length)
|
|
958
|
-
paragraphs.push(current.join(" "));
|
|
959
|
-
return paragraphs.join("\n");
|
|
960
|
-
}
|
|
961
|
-
const KEY_RE = /^([A-Za-z_][\w-]*):(.*)$/;
|
|
962
|
-
const BLOCK_INDICATORS = new Set([">", ">-", ">+", "|", "|-", "|+"]);
|
|
963
|
-
function isIndented(line) {
|
|
964
|
-
return line.startsWith(" ") || line.startsWith("\t");
|
|
965
|
-
}
|
|
966
|
-
function parseFrontmatterFields(frontmatter) {
|
|
967
|
-
const lines = frontmatter.split("\n");
|
|
968
|
-
const fields = {};
|
|
969
|
-
const n = lines.length;
|
|
970
|
-
let i = 0;
|
|
971
|
-
while (i < n) {
|
|
972
|
-
const line = lineAt(lines, i);
|
|
973
|
-
const match = KEY_RE.exec(line);
|
|
974
|
-
if (!match || isIndented(line)) {
|
|
975
|
-
i += 1;
|
|
976
|
-
continue;
|
|
977
|
-
}
|
|
978
|
-
const key = match[1] ?? "";
|
|
979
|
-
const value = (match[2] ?? "").trim();
|
|
980
|
-
if (BLOCK_INDICATORS.has(value)) {
|
|
981
|
-
const block = [];
|
|
982
|
-
i += 1;
|
|
983
|
-
while (i < n) {
|
|
984
|
-
const nxt = lineAt(lines, i);
|
|
985
|
-
if (nxt.trim() === "") {
|
|
986
|
-
block.push("");
|
|
987
|
-
i += 1;
|
|
988
|
-
continue;
|
|
989
|
-
}
|
|
990
|
-
if (isIndented(nxt)) {
|
|
991
|
-
block.push(nxt);
|
|
992
|
-
i += 1;
|
|
993
|
-
continue;
|
|
994
|
-
}
|
|
995
|
-
break;
|
|
996
|
-
}
|
|
997
|
-
fields[key] = foldBlock(block);
|
|
998
|
-
continue;
|
|
999
|
-
}
|
|
1000
|
-
if (value === "" || value.startsWith("- ")) {
|
|
1001
|
-
i += 1;
|
|
1002
|
-
while (i < n &&
|
|
1003
|
-
(lineAt(lines, i).replace(/^\s+/, "").startsWith("- ") || isIndented(lineAt(lines, i)))) {
|
|
1004
|
-
i += 1;
|
|
1005
|
-
}
|
|
1006
|
-
if (!(key in fields))
|
|
1007
|
-
fields[key] = "";
|
|
1008
|
-
continue;
|
|
1009
|
-
}
|
|
1010
|
-
fields[key] = pyStrip(pyStrip(value, '"'), "'");
|
|
1011
|
-
i += 1;
|
|
1012
|
-
}
|
|
1013
|
-
return fields;
|
|
1014
|
-
}
|
|
1015
|
-
function extractExtraFrontmatter(frontmatter) {
|
|
1016
|
-
const lines = frontmatter.split("\n");
|
|
1017
|
-
const extra = [];
|
|
1018
|
-
const n = lines.length;
|
|
1019
|
-
let i = 0;
|
|
1020
|
-
while (i < n) {
|
|
1021
|
-
const line = lineAt(lines, i);
|
|
1022
|
-
const match = KEY_RE.exec(line);
|
|
1023
|
-
if (!match || isIndented(line)) {
|
|
1024
|
-
i += 1;
|
|
1025
|
-
continue;
|
|
1026
|
-
}
|
|
1027
|
-
const key = match[1] ?? "";
|
|
1028
|
-
const value = (match[2] ?? "").trim();
|
|
1029
|
-
const block = [line];
|
|
1030
|
-
i += 1;
|
|
1031
|
-
if (BLOCK_INDICATORS.has(value)) {
|
|
1032
|
-
while (i < n && (lineAt(lines, i).trim() === "" || isIndented(lineAt(lines, i)))) {
|
|
1033
|
-
block.push(lineAt(lines, i));
|
|
1034
|
-
i += 1;
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
else if (value === "" || value.startsWith("- ")) {
|
|
1038
|
-
while (i < n &&
|
|
1039
|
-
(lineAt(lines, i).replace(/^\s+/, "").startsWith("- ") || isIndented(lineAt(lines, i)))) {
|
|
1040
|
-
block.push(lineAt(lines, i));
|
|
1041
|
-
i += 1;
|
|
1042
|
-
}
|
|
1043
|
-
}
|
|
1044
|
-
if (key !== "name" && key !== "description")
|
|
1045
|
-
extra.push(...block);
|
|
1046
|
-
}
|
|
1047
|
-
while (extra.length && (extra[extra.length - 1] ?? "").trim() === "")
|
|
1048
|
-
extra.pop();
|
|
1049
|
-
return extra.length ? extra.join("\n") : null;
|
|
1050
|
-
}
|
|
1051
|
-
// Skill trigger keywords are sourced from durable, post-#838 surfaces rather
|
|
1052
|
-
// than the removed AGENTS.md "## Skill Routing" table (#838 / #2152). Priority:
|
|
1053
|
-
// 1. each SKILL.md frontmatter `triggers:` list (the skill's own contract);
|
|
1054
|
-
// 2. the REFERENCES.md "Skills Index" table (the #838 single source of truth).
|
|
1055
|
-
// This decouples the skills pack from AGENTS.md, so adding a skill no longer
|
|
1056
|
-
// requires editing the always-loaded policy file and the trigger map stays
|
|
1057
|
-
// non-empty after #838 removed the heading parseRouting used to read.
|
|
1058
|
-
const SKILLS_INDEX_HEADING_RE = /Skills Index/i;
|
|
1059
|
-
const HEADING_LINE_RE = /^#{1,6}\s/;
|
|
1060
|
-
const SKILL_LINK_RE = /\(([^)]*skills\/[^)]+\/SKILL\.md)\)/;
|
|
1061
|
-
const BACKTICK_TOKEN_RE = /`([^`]+)`/g;
|
|
1062
|
-
/** Normalize a REFERENCES.md skill link path to the `skills/<name>/SKILL.md` key. */
|
|
1063
|
-
function normalizeSkillIndexPath(linkPath) {
|
|
1064
|
-
let p = linkPath.trim();
|
|
1065
|
-
if (p.startsWith("./"))
|
|
1066
|
-
p = p.slice(2);
|
|
1067
|
-
const marker = p.indexOf("skills/");
|
|
1068
|
-
return marker >= 0 ? p.slice(marker) : p;
|
|
1069
|
-
}
|
|
1070
|
-
/**
|
|
1071
|
-
* Parse the REFERENCES.md "Skills Index" table into a `skills/<name>/SKILL.md`
|
|
1072
|
-
* -> triggers map. Skill rows are identified by their SKILL.md link (so the
|
|
1073
|
-
* header and separator rows are skipped); the trigger cell is the last
|
|
1074
|
-
* pipe-delimited column and its keywords are the backtick-quoted tokens.
|
|
1075
|
-
*/
|
|
1076
|
-
function parseSkillsIndexTriggers(referencesMd) {
|
|
1077
|
-
const mapping = new Map();
|
|
1078
|
-
let inSection = false;
|
|
1079
|
-
for (const raw of splitLines(referencesMd)) {
|
|
1080
|
-
const line = raw.trim();
|
|
1081
|
-
if (HEADING_LINE_RE.test(line)) {
|
|
1082
|
-
inSection = SKILLS_INDEX_HEADING_RE.test(line);
|
|
1083
|
-
continue;
|
|
1084
|
-
}
|
|
1085
|
-
if (!inSection || !line.startsWith("|"))
|
|
1086
|
-
continue;
|
|
1087
|
-
const linkMatch = SKILL_LINK_RE.exec(line);
|
|
1088
|
-
if (!linkMatch)
|
|
1089
|
-
continue;
|
|
1090
|
-
const path = normalizeSkillIndexPath(linkMatch[1] ?? "");
|
|
1091
|
-
const cells = line
|
|
1092
|
-
.split("|")
|
|
1093
|
-
.map((cell) => cell.trim())
|
|
1094
|
-
.filter((cell) => cell.length > 0);
|
|
1095
|
-
const triggerCell = cells[cells.length - 1] ?? "";
|
|
1096
|
-
const bucket = mapping.get(path) ?? [];
|
|
1097
|
-
for (const match of triggerCell.matchAll(BACKTICK_TOKEN_RE)) {
|
|
1098
|
-
const keyword = (match[1] ?? "").trim();
|
|
1099
|
-
if (keyword && !bucket.includes(keyword))
|
|
1100
|
-
bucket.push(keyword);
|
|
1101
|
-
}
|
|
1102
|
-
if (bucket.length > 0)
|
|
1103
|
-
mapping.set(path, bucket);
|
|
1104
|
-
}
|
|
1105
|
-
return mapping;
|
|
1106
|
-
}
|
|
1107
|
-
// Split on quoted phrases (single or double) or bare comma-delimited runs, so a
|
|
1108
|
-
// quoted trigger containing a comma (`["what's next, please", other]`) is not
|
|
1109
|
-
// mis-tokenised. All shipped skills use the block-list form today; this keeps
|
|
1110
|
-
// the inline flow-list form correct for future skills.
|
|
1111
|
-
const FLOW_LIST_TOKEN_RE = /(?:"([^"]*)")|(?:'([^']*)')|([^,]+)/g;
|
|
1112
|
-
/** Split an inline YAML flow list (`[a, "b"]`) into trimmed, unquoted tokens. */
|
|
1113
|
-
function parseFlowListTokens(value) {
|
|
1114
|
-
const inner = value.replace(/^\[/, "").replace(/\]$/, "");
|
|
1115
|
-
const out = [];
|
|
1116
|
-
for (const match of inner.matchAll(FLOW_LIST_TOKEN_RE)) {
|
|
1117
|
-
const token = (match[1] ?? match[2] ?? match[3] ?? "").trim();
|
|
1118
|
-
if (token)
|
|
1119
|
-
out.push(token);
|
|
1120
|
-
}
|
|
1121
|
-
return out;
|
|
1122
|
-
}
|
|
1123
|
-
/** Extract a `triggers:` list (block or inline flow form) from SKILL.md frontmatter. */
|
|
1124
|
-
function parseFrontmatterTriggers(frontmatter) {
|
|
1125
|
-
const lines = frontmatter.split("\n");
|
|
1126
|
-
const n = lines.length;
|
|
1127
|
-
for (let i = 0; i < n; i += 1) {
|
|
1128
|
-
const line = lineAt(lines, i);
|
|
1129
|
-
if (isIndented(line))
|
|
1130
|
-
continue;
|
|
1131
|
-
const match = KEY_RE.exec(line);
|
|
1132
|
-
if (!match || (match[1] ?? "") !== "triggers")
|
|
1133
|
-
continue;
|
|
1134
|
-
const value = (match[2] ?? "").trim();
|
|
1135
|
-
if (value.startsWith("["))
|
|
1136
|
-
return parseFlowListTokens(value);
|
|
1137
|
-
const out = [];
|
|
1138
|
-
let j = i + 1;
|
|
1139
|
-
while (j < n) {
|
|
1140
|
-
const nxt = lineAt(lines, j);
|
|
1141
|
-
if (nxt.trim() === "") {
|
|
1142
|
-
j += 1;
|
|
1143
|
-
continue;
|
|
1144
|
-
}
|
|
1145
|
-
if (!isIndented(nxt))
|
|
1146
|
-
break;
|
|
1147
|
-
const item = nxt.trim();
|
|
1148
|
-
if (item.startsWith("- ")) {
|
|
1149
|
-
const token = pyStrip(pyStrip(item.slice(2).trim(), '"'), "'");
|
|
1150
|
-
if (token)
|
|
1151
|
-
out.push(token);
|
|
1152
|
-
}
|
|
1153
|
-
j += 1;
|
|
1154
|
-
}
|
|
1155
|
-
return out;
|
|
1156
|
-
}
|
|
1157
|
-
return [];
|
|
1158
|
-
}
|
|
1159
|
-
function buildSkillEntry(skillMd, skillsDir, indexTriggers, captureBody) {
|
|
1160
|
-
const text = readFileSync(skillMd, "utf8");
|
|
1161
|
-
const [frontmatter, body] = splitFrontmatter(text);
|
|
1162
|
-
if (frontmatter === null)
|
|
1163
|
-
return null;
|
|
1164
|
-
const fields = parseFrontmatterFields(frontmatter);
|
|
1165
|
-
const name = (fields.name ?? "").trim();
|
|
1166
|
-
if (!name)
|
|
1167
|
-
return null;
|
|
1168
|
-
const relPath = relPosix(dirname(resolve(skillsDir)), resolve(skillMd));
|
|
1169
|
-
// Prefer the skill's own frontmatter `triggers:` contract; fall back to the
|
|
1170
|
-
// REFERENCES.md Skills Index (#838 single source of truth) so shipped skills
|
|
1171
|
-
// that carry no frontmatter triggers still get a non-empty trigger list.
|
|
1172
|
-
const frontmatterTriggers = parseFrontmatterTriggers(frontmatter);
|
|
1173
|
-
const triggers = frontmatterTriggers.length > 0 ? frontmatterTriggers : (indexTriggers.get(relPath) ?? []);
|
|
1174
|
-
const version = (fields.version ?? "").trim() || DEFAULT_SKILL_VERSION;
|
|
1175
|
-
return {
|
|
1176
|
-
id: name,
|
|
1177
|
-
description: (fields.description ?? "").trim(),
|
|
1178
|
-
triggers,
|
|
1179
|
-
path: relPath,
|
|
1180
|
-
version,
|
|
1181
|
-
body: captureBody ? stripLeadingBanner(body) : null,
|
|
1182
|
-
frontmatter_extra: extractExtraFrontmatter(frontmatter),
|
|
1183
|
-
};
|
|
1184
|
-
}
|
|
1185
|
-
function buildSkillsPack(skillsDir, referencesMd, proofSkill) {
|
|
1186
|
-
const indexTriggers = parseSkillsIndexTriggers(readFileSync(referencesMd, "utf8"));
|
|
1187
|
-
const captureAll = proofSkill === null;
|
|
1188
|
-
const proofPath = proofSkill !== null ? `skills/${proofSkill}/SKILL.md` : null;
|
|
1189
|
-
const base = dirname(resolve(skillsDir));
|
|
1190
|
-
const skills = [];
|
|
1191
|
-
for (const skillMd of globSkillMd(skillsDir)) {
|
|
1192
|
-
const relPath = relPosix(base, resolve(skillMd));
|
|
1193
|
-
const entry = buildSkillEntry(skillMd, skillsDir, indexTriggers, captureAll || relPath === proofPath);
|
|
1194
|
-
if (entry !== null)
|
|
1195
|
-
skills.push(entry);
|
|
1196
|
-
}
|
|
1197
|
-
return {
|
|
1198
|
-
pack: "skills-pack-0.1",
|
|
1199
|
-
version: PACK_VERSION,
|
|
1200
|
-
generated_from: "skills/*/SKILL.md frontmatter triggers + REFERENCES.md (Skills Index)",
|
|
1201
|
-
skills,
|
|
1202
|
-
};
|
|
1203
|
-
}
|
|
1204
|
-
const GLYPH_TIER = {
|
|
1205
|
-
"!": "MUST",
|
|
1206
|
-
"~": "SHOULD",
|
|
1207
|
-
[SHOULD_NOT_GLYPH]: "SHOULD_NOT",
|
|
1208
|
-
[MUST_NOT_GLYPH]: "MUST_NOT",
|
|
1209
|
-
"?": "MAY",
|
|
1210
|
-
};
|
|
1211
|
-
const MARKER_RE = new RegExp(`^\\s*(?:-\\s+)?([!~?${SHOULD_NOT_GLYPH}${MUST_NOT_GLYPH}])\\s+(\\S.*)$`);
|
|
1212
|
-
const PROSE_TIERS = [
|
|
1213
|
-
["MUST NOT", "MUST_NOT"],
|
|
1214
|
-
["SHOULD NOT", "SHOULD_NOT"],
|
|
1215
|
-
["MUST", "MUST"],
|
|
1216
|
-
["SHOULD", "SHOULD"],
|
|
1217
|
-
["MAY", "MAY"],
|
|
1218
|
-
];
|
|
1219
|
-
function proseTier(text) {
|
|
1220
|
-
for (const [keyword, tier] of PROSE_TIERS) {
|
|
1221
|
-
const pattern = new RegExp(`\\b${keyword.replace(/ /g, "[ ]")}\\b`);
|
|
1222
|
-
if (pattern.test(text))
|
|
1223
|
-
return tier;
|
|
1224
|
-
}
|
|
1225
|
-
return null;
|
|
1226
|
-
}
|
|
1227
|
-
function parseRules(md, domain) {
|
|
1228
|
-
const rules = [];
|
|
1229
|
-
let seq = 0;
|
|
1230
|
-
for (const raw of splitLines(md)) {
|
|
1231
|
-
const line = raw.replace(/\s+$/, "");
|
|
1232
|
-
let tier = null;
|
|
1233
|
-
let text = "";
|
|
1234
|
-
const marker = MARKER_RE.exec(line);
|
|
1235
|
-
if (marker) {
|
|
1236
|
-
tier = GLYPH_TIER[marker[1] ?? ""] ?? null;
|
|
1237
|
-
text = (marker[2] ?? "").trim();
|
|
1238
|
-
}
|
|
1239
|
-
else {
|
|
1240
|
-
const stripped = line.trim();
|
|
1241
|
-
if (!stripped.startsWith("- "))
|
|
1242
|
-
continue;
|
|
1243
|
-
text = stripped.slice(2).trim();
|
|
1244
|
-
tier = text ? proseTier(text) : null;
|
|
1245
|
-
}
|
|
1246
|
-
if (tier === null || text === "")
|
|
1247
|
-
continue;
|
|
1248
|
-
seq += 1;
|
|
1249
|
-
rules.push({ id: `${domain}-${String(seq).padStart(3, "0")}`, tier, domain, text });
|
|
1250
|
-
}
|
|
1251
|
-
return rules;
|
|
1252
|
-
}
|
|
1253
|
-
const MANAGED_SECTION_RE = /<!--\s*deft:managed-section[\s\S]*?<!--\s*\/deft:managed-section\s*-->/g;
|
|
1254
|
-
function stripManagedSection(md) {
|
|
1255
|
-
return md.replace(MANAGED_SECTION_RE, "");
|
|
1256
|
-
}
|
|
1257
|
-
function buildRulesPack(codingDir, extraSources) {
|
|
1258
|
-
const base = dirname(resolve(codingDir));
|
|
1259
|
-
const rules = [];
|
|
1260
|
-
for (const md of globMd(codingDir)) {
|
|
1261
|
-
const relPath = relPosix(base, resolve(md));
|
|
1262
|
-
const domain = slugify(stemOf(md));
|
|
1263
|
-
const text = readFileSync(md, "utf8");
|
|
1264
|
-
const docRules = parseRules(text, domain);
|
|
1265
|
-
docRules.forEach((rule, idx) => {
|
|
1266
|
-
rule.path = relPath;
|
|
1267
|
-
rule.body = idx === 0 ? stripLeadingBanner(text) : null;
|
|
1268
|
-
rules.push(rule);
|
|
1269
|
-
});
|
|
1270
|
-
}
|
|
1271
|
-
for (const src of extraSources) {
|
|
1272
|
-
if (!isFileSafe(src))
|
|
1273
|
-
continue;
|
|
1274
|
-
const candidate = relPosix(base, resolve(src));
|
|
1275
|
-
const relPath = candidate.startsWith("..") || isAbsolute(candidate) ? basename(src) : candidate;
|
|
1276
|
-
const domain = slugify(stemOf(src));
|
|
1277
|
-
let text = readFileSync(src, "utf8");
|
|
1278
|
-
if (basename(src) === "AGENTS.md")
|
|
1279
|
-
text = stripManagedSection(text);
|
|
1280
|
-
for (const rule of parseRules(text, domain)) {
|
|
1281
|
-
rule.path = relPath;
|
|
1282
|
-
rule.body = null;
|
|
1283
|
-
rules.push(rule);
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1286
|
-
return {
|
|
1287
|
-
pack: "rules-pack-0.1",
|
|
1288
|
-
version: PACK_VERSION,
|
|
1289
|
-
generated_from: "coding/*.md + AGENTS.md + main.md (marker-prefixed RFC2119 directives; " +
|
|
1290
|
-
"AGENTS.md managed-section excluded; coding bodies rendered, " +
|
|
1291
|
-
"AGENTS.md/main.md metadata-only)",
|
|
1292
|
-
rules,
|
|
1293
|
-
};
|
|
1294
|
-
}
|
|
1295
|
-
function buildMdEntry(md, dir, captureBody) {
|
|
1296
|
-
const relPath = relPosix(dirname(resolve(dir)), resolve(md));
|
|
1297
|
-
const stemSlug = slugify(stemOf(md));
|
|
1298
|
-
const text = readFileSync(md, "utf8");
|
|
1299
|
-
return {
|
|
1300
|
-
id: stemSlug,
|
|
1301
|
-
title: extractTitle(text),
|
|
1302
|
-
description: extractDescription(text),
|
|
1303
|
-
triggers: stemSlug ? [stemSlug] : [],
|
|
1304
|
-
path: relPath,
|
|
1305
|
-
body: captureBody ? stripLeadingBanner(text) : null,
|
|
1306
|
-
};
|
|
1307
|
-
}
|
|
1308
|
-
function buildStrategiesPack(strategiesDir, proofStrategy) {
|
|
1309
|
-
const base = dirname(resolve(strategiesDir));
|
|
1310
|
-
const captureAll = proofStrategy === null;
|
|
1311
|
-
const strategies = [];
|
|
1312
|
-
for (const md of globMd(strategiesDir)) {
|
|
1313
|
-
const relPath = relPosix(base, resolve(md));
|
|
1314
|
-
const captureBody = captureAll
|
|
1315
|
-
? !isRedirectStub(readFileSync(md, "utf8"))
|
|
1316
|
-
: relPath === proofStrategy;
|
|
1317
|
-
strategies.push(buildMdEntry(md, strategiesDir, captureBody));
|
|
1318
|
-
}
|
|
1319
|
-
return {
|
|
1320
|
-
pack: "strategies-pack-0.1",
|
|
1321
|
-
version: PACK_VERSION,
|
|
1322
|
-
generated_from: "strategies/*.md",
|
|
1323
|
-
strategies,
|
|
1324
|
-
};
|
|
1325
|
-
}
|
|
1326
|
-
function buildPatternsPack(patternsDir, proofPattern) {
|
|
1327
|
-
const base = dirname(resolve(patternsDir));
|
|
1328
|
-
const patterns = [];
|
|
1329
|
-
for (const md of globMd(patternsDir)) {
|
|
1330
|
-
const relPath = relPosix(base, resolve(md));
|
|
1331
|
-
patterns.push(buildMdEntry(md, patternsDir, relPath === proofPattern));
|
|
1332
|
-
}
|
|
1333
|
-
return {
|
|
1334
|
-
pack: "patterns-pack-0.1",
|
|
1335
|
-
version: PACK_VERSION,
|
|
1336
|
-
generated_from: "patterns/*.md",
|
|
1337
|
-
patterns,
|
|
1338
|
-
};
|
|
1339
|
-
}
|
|
1340
|
-
function buildSwarmSpecPack(swarmDir, proofEntry) {
|
|
1341
|
-
const base = dirname(resolve(swarmDir));
|
|
1342
|
-
const entries = [];
|
|
1343
|
-
for (const md of globMd(swarmDir)) {
|
|
1344
|
-
const relPath = relPosix(base, resolve(md));
|
|
1345
|
-
entries.push(buildMdEntry(md, swarmDir, relPath === proofEntry));
|
|
1346
|
-
}
|
|
1347
|
-
return {
|
|
1348
|
-
pack: "swarm-spec-pack-0.1",
|
|
1349
|
-
version: PACK_VERSION,
|
|
1350
|
-
generated_from: "swarm/*.md",
|
|
1351
|
-
entries,
|
|
1352
|
-
};
|
|
1353
|
-
}
|
|
1354
|
-
function writePack(out, pack) {
|
|
1355
|
-
mkdirSync(dirname(out), { recursive: true });
|
|
1356
|
-
writeFileSync(out, dumpsAsciiJson(pack), "utf8");
|
|
1357
|
-
}
|
|
1358
|
-
/** Resolve the shippable content root: <root>/content when present, else <root> (#1875). */
|
|
1359
|
-
function resolveContentRoot() {
|
|
1360
|
-
const root = resolveDeftRoot();
|
|
1361
|
-
const candidate = join(root, "content");
|
|
1362
|
-
return isDirSafe(candidate) ? candidate : root;
|
|
1363
|
-
}
|
|
1364
|
-
/**
|
|
1365
|
-
* Minimal argparse-compatible option reader supporting `--flag value` and
|
|
1366
|
-
* `--flag=value`. `listFlags` accumulate repeats; all flags take a value.
|
|
1367
|
-
*/
|
|
1368
|
-
function parsePackArgs(argv, valueFlags, listFlags = []) {
|
|
1369
|
-
const values = {};
|
|
1370
|
-
const lists = {};
|
|
1371
|
-
const known = new Set([...valueFlags, ...listFlags]);
|
|
1372
|
-
for (let i = 0; i < argv.length; i += 1) {
|
|
1373
|
-
const arg = argv[i];
|
|
1374
|
-
if (arg === undefined)
|
|
1375
|
-
continue;
|
|
1376
|
-
let flag = arg;
|
|
1377
|
-
let inlineValue;
|
|
1378
|
-
if (arg.startsWith("--") && arg.includes("=")) {
|
|
1379
|
-
const eq = arg.indexOf("=");
|
|
1380
|
-
flag = arg.slice(0, eq);
|
|
1381
|
-
inlineValue = arg.slice(eq + 1);
|
|
1382
|
-
}
|
|
1383
|
-
if (!known.has(flag)) {
|
|
1384
|
-
return { values, lists, error: `unrecognized argument: ${arg}` };
|
|
1385
|
-
}
|
|
1386
|
-
let value = inlineValue;
|
|
1387
|
-
if (value === undefined) {
|
|
1388
|
-
i += 1;
|
|
1389
|
-
value = argv[i];
|
|
1390
|
-
}
|
|
1391
|
-
if (value === undefined) {
|
|
1392
|
-
return { values, lists, error: `argument ${flag}: expected one argument` };
|
|
1393
|
-
}
|
|
1394
|
-
if (listFlags.includes(flag)) {
|
|
1395
|
-
const bucket = lists[flag] ?? [];
|
|
1396
|
-
bucket.push(value);
|
|
1397
|
-
lists[flag] = bucket;
|
|
1398
|
-
}
|
|
1399
|
-
else {
|
|
1400
|
-
values[flag] = value;
|
|
1401
|
-
}
|
|
1402
|
-
}
|
|
1403
|
-
return { values, lists };
|
|
1404
|
-
}
|
|
1405
|
-
function runPackMigrateSkills(argv, io) {
|
|
1406
|
-
const contentRoot = resolveContentRoot();
|
|
1407
|
-
const parsed = parsePackArgs(argv, ["--skills-dir", "--references-md", "--proof-skill", "--out"]);
|
|
1408
|
-
if (parsed.error !== undefined) {
|
|
1409
|
-
io.writeErr(`error: ${parsed.error}\n`);
|
|
1410
|
-
return 2;
|
|
1411
|
-
}
|
|
1412
|
-
const skillsDir = parsed.values["--skills-dir"] ?? join(contentRoot, "skills");
|
|
1413
|
-
const referencesMd = parsed.values["--references-md"] ?? join(resolveDeftRoot(), "REFERENCES.md");
|
|
1414
|
-
const proofSkill = parsed.values["--proof-skill"] ?? null;
|
|
1415
|
-
const out = parsed.values["--out"] ?? join(contentRoot, "packs", "skills", "skills-pack-0.1.json");
|
|
1416
|
-
if (!isDirSafe(skillsDir)) {
|
|
1417
|
-
io.writeErr(`error: skills directory not found: ${skillsDir}\n`);
|
|
1418
|
-
return 1;
|
|
1419
|
-
}
|
|
1420
|
-
if (!isFileSafe(referencesMd)) {
|
|
1421
|
-
io.writeErr(`error: REFERENCES.md not found: ${referencesMd}\n`);
|
|
1422
|
-
return 1;
|
|
1423
|
-
}
|
|
1424
|
-
const pack = buildSkillsPack(skillsDir, referencesMd, proofSkill);
|
|
1425
|
-
if (pack.skills.length === 0) {
|
|
1426
|
-
io.writeErr(`error: no skills with frontmatter discovered under ${skillsDir}\n`);
|
|
1427
|
-
return 1;
|
|
1428
|
-
}
|
|
1429
|
-
writePack(out, pack);
|
|
1430
|
-
const bodied = pack.skills.filter((s) => s.body !== null).length;
|
|
1431
|
-
io.writeOut(`Migrated ${pack.skills.length} skills (${bodied} with body) -> ${out}\n`);
|
|
1432
|
-
return 0;
|
|
1433
|
-
}
|
|
1434
|
-
function runPackMigrateRules(argv, io) {
|
|
1435
|
-
const contentRoot = resolveContentRoot();
|
|
1436
|
-
const deftRoot = resolveDeftRoot();
|
|
1437
|
-
const parsed = parsePackArgs(argv, ["--coding-dir", "--out"], ["--extra-source"]);
|
|
1438
|
-
if (parsed.error !== undefined) {
|
|
1439
|
-
io.writeErr(`error: ${parsed.error}\n`);
|
|
1440
|
-
return 2;
|
|
1441
|
-
}
|
|
1442
|
-
const codingDir = parsed.values["--coding-dir"] ?? join(contentRoot, "coding");
|
|
1443
|
-
const extraSources = parsed.lists["--extra-source"] ?? [
|
|
1444
|
-
join(deftRoot, "AGENTS.md"),
|
|
1445
|
-
join(deftRoot, "main.md"),
|
|
1446
|
-
];
|
|
1447
|
-
const out = parsed.values["--out"] ?? join(contentRoot, "packs", "rules", "rules-pack-0.1.json");
|
|
1448
|
-
if (!isDirSafe(codingDir)) {
|
|
1449
|
-
io.writeErr(`error: coding directory not found: ${codingDir}\n`);
|
|
1450
|
-
return 1;
|
|
1451
|
-
}
|
|
1452
|
-
const pack = buildRulesPack(codingDir, extraSources);
|
|
1453
|
-
if (pack.rules.length === 0) {
|
|
1454
|
-
io.writeErr(`error: no directives discovered under ${codingDir}\n`);
|
|
1455
|
-
return 1;
|
|
1456
|
-
}
|
|
1457
|
-
writePack(out, pack);
|
|
1458
|
-
const bodied = pack.rules.filter((r) => r.body != null).length;
|
|
1459
|
-
io.writeOut(`Migrated ${pack.rules.length} rules (${bodied} with body) -> ${out}\n`);
|
|
1460
|
-
return 0;
|
|
1461
|
-
}
|
|
1462
|
-
function runPackMigrateStrategies(argv, io) {
|
|
1463
|
-
const contentRoot = resolveContentRoot();
|
|
1464
|
-
const parsed = parsePackArgs(argv, ["--strategies-dir", "--proof-strategy", "--out"]);
|
|
1465
|
-
if (parsed.error !== undefined) {
|
|
1466
|
-
io.writeErr(`error: ${parsed.error}\n`);
|
|
1467
|
-
return 2;
|
|
1468
|
-
}
|
|
1469
|
-
const strategiesDir = parsed.values["--strategies-dir"] ?? join(contentRoot, "strategies");
|
|
1470
|
-
const proofStrategy = parsed.values["--proof-strategy"] ?? null;
|
|
1471
|
-
const out = parsed.values["--out"] ?? join(contentRoot, "packs", "strategies", "strategies-pack-0.1.json");
|
|
1472
|
-
if (!isDirSafe(strategiesDir)) {
|
|
1473
|
-
io.writeErr(`error: strategies directory not found: ${strategiesDir}\n`);
|
|
1474
|
-
return 1;
|
|
1475
|
-
}
|
|
1476
|
-
const pack = buildStrategiesPack(strategiesDir, proofStrategy);
|
|
1477
|
-
if (pack.strategies.length === 0) {
|
|
1478
|
-
io.writeErr(`error: no strategies discovered under ${strategiesDir}\n`);
|
|
1479
|
-
return 1;
|
|
1480
|
-
}
|
|
1481
|
-
writePack(out, pack);
|
|
1482
|
-
const bodied = pack.strategies.filter((s) => s.body !== null).length;
|
|
1483
|
-
io.writeOut(`Migrated ${pack.strategies.length} strategies (${bodied} with body) -> ${out}\n`);
|
|
1484
|
-
return 0;
|
|
1485
|
-
}
|
|
1486
|
-
function runPackMigratePatterns(argv, io) {
|
|
1487
|
-
const contentRoot = resolveContentRoot();
|
|
1488
|
-
const parsed = parsePackArgs(argv, ["--patterns-dir", "--proof-pattern", "--out"]);
|
|
1489
|
-
if (parsed.error !== undefined) {
|
|
1490
|
-
io.writeErr(`error: ${parsed.error}\n`);
|
|
1491
|
-
return 2;
|
|
1492
|
-
}
|
|
1493
|
-
const patternsDir = parsed.values["--patterns-dir"] ?? join(contentRoot, "patterns");
|
|
1494
|
-
const proofPattern = parsed.values["--proof-pattern"] ?? "patterns/multi-agent.md";
|
|
1495
|
-
const out = parsed.values["--out"] ?? join(contentRoot, "packs", "patterns", "patterns-pack-0.1.json");
|
|
1496
|
-
if (!isDirSafe(patternsDir)) {
|
|
1497
|
-
io.writeErr(`error: patterns directory not found: ${patternsDir}\n`);
|
|
1498
|
-
return 1;
|
|
1499
|
-
}
|
|
1500
|
-
const pack = buildPatternsPack(patternsDir, proofPattern);
|
|
1501
|
-
if (pack.patterns.length === 0) {
|
|
1502
|
-
io.writeErr(`error: no patterns discovered under ${patternsDir}\n`);
|
|
1503
|
-
return 1;
|
|
1504
|
-
}
|
|
1505
|
-
writePack(out, pack);
|
|
1506
|
-
const bodied = pack.patterns.filter((p) => p.body !== null).length;
|
|
1507
|
-
io.writeOut(`Migrated ${pack.patterns.length} patterns (${bodied} with body) -> ${out}\n`);
|
|
1508
|
-
return 0;
|
|
1509
|
-
}
|
|
1510
|
-
function runPackMigrateSwarmSpec(argv, io) {
|
|
1511
|
-
const contentRoot = resolveContentRoot();
|
|
1512
|
-
const parsed = parsePackArgs(argv, ["--swarm-dir", "--proof-entry", "--out"]);
|
|
1513
|
-
if (parsed.error !== undefined) {
|
|
1514
|
-
io.writeErr(`error: ${parsed.error}\n`);
|
|
1515
|
-
return 2;
|
|
1516
|
-
}
|
|
1517
|
-
const swarmDir = parsed.values["--swarm-dir"] ?? join(contentRoot, "swarm");
|
|
1518
|
-
const proofEntry = parsed.values["--proof-entry"] ?? "swarm/swarm.md";
|
|
1519
|
-
const out = parsed.values["--out"] ?? join(contentRoot, "packs", "swarm-spec", "swarm-spec-pack-0.1.json");
|
|
1520
|
-
if (!isDirSafe(swarmDir)) {
|
|
1521
|
-
io.writeErr(`error: swarm directory not found: ${swarmDir}\n`);
|
|
1522
|
-
return 1;
|
|
1523
|
-
}
|
|
1524
|
-
const pack = buildSwarmSpecPack(swarmDir, proofEntry);
|
|
1525
|
-
if (pack.entries.length === 0) {
|
|
1526
|
-
io.writeErr(`error: no swarm-spec docs discovered under ${swarmDir}\n`);
|
|
1527
|
-
return 1;
|
|
1528
|
-
}
|
|
1529
|
-
writePack(out, pack);
|
|
1530
|
-
const bodied = pack.entries.filter((e) => e.body !== null).length;
|
|
1531
|
-
io.writeOut(`Migrated ${pack.entries.length} swarm-spec entries (${bodied} with body) -> ${out}\n`);
|
|
1532
|
-
return 0;
|
|
1533
|
-
}
|
|
1534
|
-
// ===========================================================================
|
|
1535
|
-
// Native setup:ghx handler (#2022 Phase 1; #2178 download-verify-execute).
|
|
1536
|
-
//
|
|
1537
|
-
// Port of scripts/setup_ghx.py to native TypeScript: consent-gated ghx proxy
|
|
1538
|
-
// installer with three-state exit (0 ok / 1 install failure / 2 config error).
|
|
1539
|
-
//
|
|
1540
|
-
// #2178: the installer no longer pipes remote bytes straight into a shell
|
|
1541
|
-
// (`curl | bash` / `irm | iex`). Socket Security's AI-malware heuristic flags
|
|
1542
|
-
// exactly that live-pipe-with-no-integrity-check pattern and blocks every
|
|
1543
|
-
// consumer PR that bumps @deftai/directive (Socket scored the package ~65%
|
|
1544
|
-
// likely malicious, severity 0.78 -- seen on deftai/evolution#1046 / #1047).
|
|
1545
|
-
// Instead: download the installer script to memory, verify it against a
|
|
1546
|
-
// SHA-256 vendored below, write it to a private local temp file, and only
|
|
1547
|
-
// then execute that local file directly. The fetch URL also pins to the
|
|
1548
|
-
// immutable commit SHA that GHX_VERSION resolved to at vendor time (not the
|
|
1549
|
-
// mutable tag name), so a future tag force-move on the upstream repo cannot
|
|
1550
|
-
// swap the fetched bytes out from under the vendored hash without also
|
|
1551
|
-
// failing the hash check.
|
|
1552
|
-
//
|
|
1553
|
-
// Bumping GHX_VERSION (`.github/workflows/ci.yml` env.GHX_VERSION MUST stay
|
|
1554
|
-
// in lockstep):
|
|
1555
|
-
// 1. Resolve the new tag's commit SHA:
|
|
1556
|
-
// gh api repos/brunoborges/ghx/git/refs/tags/<new-version>
|
|
1557
|
-
// Use `object.sha`. If `object.type` is "tag" (an annotated tag, not a
|
|
1558
|
-
// lightweight one), resolve one level further:
|
|
1559
|
-
// gh api repos/brunoborges/ghx/git/tags/<object.sha>
|
|
1560
|
-
// and use THAT response's `object.sha` (the commit, not the tag object).
|
|
1561
|
-
// 2. Refetch both installers at the resolved commit and recompute hashes:
|
|
1562
|
-
// curl -fsSL https://raw.githubusercontent.com/brunoborges/ghx/<sha>/install.sh | sha256sum
|
|
1563
|
-
// curl -fsSL https://raw.githubusercontent.com/brunoborges/ghx/<sha>/install.ps1 | sha256sum
|
|
1564
|
-
// 3. Update GHX_VERSION, GHX_COMMIT_SHA, GHX_INSTALL_SH_SHA256, and
|
|
1565
|
-
// GHX_INSTALL_PS1_SHA256 below IN THE SAME COMMIT as the matching
|
|
1566
|
-
// `.github/workflows/ci.yml` env values -- never let the two drift.
|
|
1567
|
-
// ===========================================================================
|
|
1568
|
-
/** Pinned ghx version (display only) — keep in lockstep with .github/workflows/ci.yml env.GHX_VERSION. */
|
|
1569
|
-
export const GHX_VERSION = "v1.5.1";
|
|
1570
|
-
/**
|
|
1571
|
-
* Immutable commit SHA the GHX_VERSION tag resolved to at vendor time
|
|
1572
|
-
* (2026-07-02, via `gh api repos/brunoborges/ghx/git/refs/tags/v1.5.1`).
|
|
1573
|
-
* Fetch URLs pin to this SHA rather than the mutable tag name so a future
|
|
1574
|
-
* tag force-move on brunoborges/ghx cannot silently swap the fetched bytes
|
|
1575
|
-
* out from under the vendored SHA-256 hashes below (#2178).
|
|
1576
|
-
*/
|
|
1577
|
-
export const GHX_COMMIT_SHA = "aa4a2786660e27392b0d3e8886f140e0a0261a0c";
|
|
1578
|
-
export const INSTALL_PS1_URL = `https://raw.githubusercontent.com/brunoborges/ghx/${GHX_COMMIT_SHA}/install.ps1`;
|
|
1579
|
-
export const INSTALL_SH_URL = `https://raw.githubusercontent.com/brunoborges/ghx/${GHX_COMMIT_SHA}/install.sh`;
|
|
1580
|
-
/**
|
|
1581
|
-
* SHA-256 of the installer scripts at GHX_COMMIT_SHA, vendored so the
|
|
1582
|
-
* download-verify-execute pipeline below can refuse to run tampered bytes.
|
|
1583
|
-
* Matches `.github/workflows/ci.yml` env.GHX_INSTALL_SH_SHA256 /
|
|
1584
|
-
* GHX_INSTALL_PS1_SHA256 (#1070 / #1328) — keep both in lockstep (#2178).
|
|
1585
|
-
*/
|
|
1586
|
-
export const GHX_INSTALL_SH_SHA256 = "08c768feb6d2bc485079898f7e76c2b07576cbb1188a356acf99dac0fc55d1cb";
|
|
1587
|
-
export const GHX_INSTALL_PS1_SHA256 = "5f67eab68970ecc55bb0fc1b8399ba6f3ce4b2aadeee39255d628e96d187a5ed";
|
|
1588
|
-
async function defaultGhxDownload(url) {
|
|
1589
|
-
const res = await fetch(url);
|
|
1590
|
-
if (!res.ok) {
|
|
1591
|
-
throw new Error(`download failed: HTTP ${res.status} ${res.statusText} for ${url}`);
|
|
1592
|
-
}
|
|
1593
|
-
return Buffer.from(await res.arrayBuffer());
|
|
1594
|
-
}
|
|
1595
|
-
/** True when `buf`'s SHA-256 (hex) matches `expectedHex`, case- and whitespace-insensitive. */
|
|
1596
|
-
export function verifyGhxSha256(buf, expectedHex) {
|
|
1597
|
-
const actual = createHash("sha256").update(buf).digest("hex");
|
|
1598
|
-
return actual.toLowerCase() === expectedHex.trim().toLowerCase();
|
|
1599
|
-
}
|
|
1600
|
-
export function resolveGhxInstallerAsset(host) {
|
|
1601
|
-
if (host === "windows") {
|
|
1602
|
-
return { url: INSTALL_PS1_URL, sha256: GHX_INSTALL_PS1_SHA256, fileExt: "ps1" };
|
|
1603
|
-
}
|
|
1604
|
-
if (host === "darwin" || host === "linux") {
|
|
1605
|
-
return { url: INSTALL_SH_URL, sha256: GHX_INSTALL_SH_SHA256, fileExt: "sh" };
|
|
1606
|
-
}
|
|
1607
|
-
throw new Error(`no upstream ghx installer available for host '${host}'; ` +
|
|
1608
|
-
"see https://github.com/brunoborges/ghx#install for manual options");
|
|
1609
|
-
}
|
|
1610
|
-
function parseSetupGhxArgs(argv) {
|
|
1611
|
-
let yes = false;
|
|
1612
|
-
let check = false;
|
|
1613
|
-
for (const arg of argv) {
|
|
1614
|
-
if (arg === "--yes") {
|
|
1615
|
-
yes = true;
|
|
1616
|
-
}
|
|
1617
|
-
else if (arg === "--check") {
|
|
1618
|
-
check = true;
|
|
1619
|
-
}
|
|
1620
|
-
else {
|
|
1621
|
-
return { yes, check, error: `unrecognized argument: ${arg}` };
|
|
1622
|
-
}
|
|
1623
|
-
}
|
|
1624
|
-
return { yes, check };
|
|
1625
|
-
}
|
|
1626
|
-
export function ghxPresent(whichFn = defaultWhich) {
|
|
1627
|
-
return whichFn("ghx") !== null;
|
|
1628
|
-
}
|
|
1629
|
-
export function detectSetupGhxHost() {
|
|
1630
|
-
if (process.platform === "win32")
|
|
1631
|
-
return "windows";
|
|
1632
|
-
if (process.platform === "darwin")
|
|
1633
|
-
return "darwin";
|
|
1634
|
-
if (process.platform === "linux")
|
|
1635
|
-
return "linux";
|
|
1636
|
-
return process.platform;
|
|
1637
|
-
}
|
|
1638
|
-
function readConsentLineFromStdin() {
|
|
1639
|
-
const buf = Buffer.alloc(256);
|
|
1640
|
-
try {
|
|
1641
|
-
const n = readSync(0, buf, 0, 256, null);
|
|
1642
|
-
if (n === null || n <= 0)
|
|
1643
|
-
return "";
|
|
1644
|
-
return buf.toString("utf8", 0, n);
|
|
1645
|
-
}
|
|
1646
|
-
catch {
|
|
1647
|
-
return "";
|
|
1648
|
-
}
|
|
1649
|
-
}
|
|
1650
|
-
export function promptSetupGhxConsent(io, readLine = readConsentLineFromStdin) {
|
|
1651
|
-
io.writeOut("\n[setup_ghx] ghx is the recommended GitHub CLI cache proxy for deft " +
|
|
1652
|
-
"maintainers (prevents rate-limiting in multi-agent swarms; speeds up " +
|
|
1653
|
-
"scm:* calls). Consumer projects only require gh.\n");
|
|
1654
|
-
io.writeOut(`[setup_ghx] Upstream: https://github.com/brunoborges/ghx (${GHX_VERSION})\n`);
|
|
1655
|
-
io.writeOut("[setup_ghx] Install ghx via the upstream installer? [y/N]: ");
|
|
1656
|
-
const answer = readLine().trim().toLowerCase();
|
|
1657
|
-
return answer === "y" || answer === "yes";
|
|
1658
|
-
}
|
|
1659
|
-
function ghxTempFileName(fileExt) {
|
|
1660
|
-
return `ghx-install-${GHX_VERSION}.${fileExt}`;
|
|
1661
|
-
}
|
|
1662
|
-
/**
|
|
1663
|
-
* Downloads `asset`, verifies it against its vendored SHA-256, and writes it
|
|
1664
|
-
* to a private local temp file. Returns the local path, ready for direct
|
|
1665
|
-
* local-file execution (never piped into a shell). Throws -- without
|
|
1666
|
-
* writing or executing anything -- on a hash mismatch (#2178). Split out
|
|
1667
|
-
* from `fetchAndVerifyGhxInstaller` so tests can exercise the download ->
|
|
1668
|
-
* verify -> write pipeline against a synthetic asset/hash without depending
|
|
1669
|
-
* on the real vendored constants or the network.
|
|
1670
|
-
*/
|
|
1671
|
-
export async function fetchAndVerifyGhxInstallerAsset(asset, downloadFn = defaultGhxDownload) {
|
|
1672
|
-
const bytes = await downloadFn(asset.url);
|
|
1673
|
-
if (!verifyGhxSha256(bytes, asset.sha256)) {
|
|
1674
|
-
const actual = createHash("sha256").update(bytes).digest("hex");
|
|
1675
|
-
throw new Error(`ghx installer SHA-256 mismatch for ${asset.url} ` +
|
|
1676
|
-
`(expected ${asset.sha256}, got ${actual}); refusing to execute. ` +
|
|
1677
|
-
"The pinned commit's bytes may have changed, or the download was tampered with.");
|
|
1678
|
-
}
|
|
1679
|
-
const dir = mkdtempSync(join(tmpdir(), "deft-ghx-"));
|
|
1680
|
-
const installerPath = join(dir, ghxTempFileName(asset.fileExt));
|
|
1681
|
-
writeFileSync(installerPath, bytes, { mode: 0o700 });
|
|
1682
|
-
return installerPath;
|
|
1683
|
-
}
|
|
1684
|
-
/**
|
|
1685
|
-
* Downloads the pinned installer for `host`, verifies it against the
|
|
1686
|
-
* vendored SHA-256, and writes it to a private local temp file. Returns the
|
|
1687
|
-
* local path, ready for direct local-file execution (never piped into a
|
|
1688
|
-
* shell). Throws -- without writing or executing anything -- on a hash
|
|
1689
|
-
* mismatch (#2178).
|
|
1690
|
-
*/
|
|
1691
|
-
export async function fetchAndVerifyGhxInstaller(host, downloadFn = defaultGhxDownload) {
|
|
1692
|
-
return fetchAndVerifyGhxInstallerAsset(resolveGhxInstallerAsset(host), downloadFn);
|
|
1693
|
-
}
|
|
1694
|
-
/**
|
|
1695
|
-
* Executes an already-downloaded, hash-verified installer from its local
|
|
1696
|
-
* temp path. No live pipe (`curl | bash` / `irm | iex`) and no
|
|
1697
|
-
* `-ExecutionPolicy Bypass` -- the file is written by Node, so it never
|
|
1698
|
-
* carries a Windows Mark-of-the-Web zone identifier the way a browser or
|
|
1699
|
-
* `Invoke-WebRequest` download would; `RemoteSigned` treats it as a local,
|
|
1700
|
-
* unsigned-but-trusted script (#2178).
|
|
1701
|
-
*/
|
|
1702
|
-
export function executeVerifiedGhxInstaller(host, installerPath, whichFn = defaultWhich, runner = spawnSync) {
|
|
1703
|
-
const cmd = host === "windows"
|
|
1704
|
-
? [
|
|
1705
|
-
whichFn("pwsh") ?? whichFn("powershell") ?? "powershell",
|
|
1706
|
-
"-NoProfile",
|
|
1707
|
-
"-ExecutionPolicy",
|
|
1708
|
-
"RemoteSigned",
|
|
1709
|
-
"-File",
|
|
1710
|
-
installerPath,
|
|
1711
|
-
]
|
|
1712
|
-
: ["bash", installerPath];
|
|
1713
|
-
const proc = runner(cmd[0] ?? "", cmd.slice(1), {
|
|
1714
|
-
env: { ...process.env, GHX_VERSION },
|
|
1715
|
-
stdio: "inherit",
|
|
1716
|
-
});
|
|
1717
|
-
return proc.status ?? 1;
|
|
1718
|
-
}
|
|
1719
|
-
/**
|
|
1720
|
-
* Downloads, hash-verifies, and executes `asset` for `host`. Cleans up the
|
|
1721
|
-
* temp file (and its containing directory) regardless of outcome. Split out
|
|
1722
|
-
* from `installSetupGhx` so tests can exercise the full download -> verify
|
|
1723
|
-
* -> execute -> cleanup pipeline against a synthetic asset without depending
|
|
1724
|
-
* on the real vendored constants or the network (#2178).
|
|
1725
|
-
*/
|
|
1726
|
-
export async function installVerifiedGhxAsset(asset, host, whichFn = defaultWhich, runner = spawnSync, downloadFn = defaultGhxDownload) {
|
|
1727
|
-
const installerPath = await fetchAndVerifyGhxInstallerAsset(asset, downloadFn);
|
|
1728
|
-
try {
|
|
1729
|
-
return executeVerifiedGhxInstaller(host, installerPath, whichFn, runner);
|
|
1730
|
-
}
|
|
1731
|
-
finally {
|
|
1732
|
-
try {
|
|
1733
|
-
rmSync(dirname(installerPath), { recursive: true, force: true });
|
|
1734
|
-
}
|
|
1735
|
-
catch {
|
|
1736
|
-
// Best-effort cleanup; a leftover temp file is not fatal.
|
|
1737
|
-
}
|
|
1738
|
-
}
|
|
1739
|
-
}
|
|
1740
|
-
/**
|
|
1741
|
-
* Downloads, hash-verifies, and executes the ghx installer for `host`.
|
|
1742
|
-
* Cleans up the temp file (and its containing directory) regardless of
|
|
1743
|
-
* outcome (#2178).
|
|
1744
|
-
*/
|
|
1745
|
-
export async function installSetupGhx(host, whichFn = defaultWhich, runner = spawnSync, downloadFn = defaultGhxDownload) {
|
|
1746
|
-
return installVerifiedGhxAsset(resolveGhxInstallerAsset(host), host, whichFn, runner, downloadFn);
|
|
1747
|
-
}
|
|
1748
|
-
/** Native `setup:ghx` handler (replaces scripts/setup_ghx.py shell-out, #2022 Phase 1). */
|
|
1749
|
-
export async function runSetupGhx(argv, io, deps = {}) {
|
|
1750
|
-
const args = parseSetupGhxArgs(argv);
|
|
1751
|
-
if (args.error !== undefined) {
|
|
1752
|
-
io.writeErr(`setup-ghx: ${args.error}\n`);
|
|
1753
|
-
return 2;
|
|
1754
|
-
}
|
|
1755
|
-
if (args.yes && args.check) {
|
|
1756
|
-
io.writeErr("[setup_ghx] error: --yes and --check are mutually exclusive.\n");
|
|
1757
|
-
return 2;
|
|
1758
|
-
}
|
|
1759
|
-
const whichFn = deps.whichFn ?? defaultWhich;
|
|
1760
|
-
if (ghxPresent(whichFn)) {
|
|
1761
|
-
io.writeOut("[setup_ghx] ghx already on PATH -- skipping install.\n");
|
|
1762
|
-
return 0;
|
|
1763
|
-
}
|
|
1764
|
-
if (args.check) {
|
|
1765
|
-
if (whichFn("gh") !== null) {
|
|
1766
|
-
io.writeOut("[setup_ghx] gh is on PATH but ghx is not; ghx is the recommended GitHub CLI " +
|
|
1767
|
-
"cache proxy. Install with `directive setup:ghx` (or `task setup:ghx`). Refs #884.\n");
|
|
1768
|
-
}
|
|
1769
|
-
else {
|
|
1770
|
-
io.writeOut("[setup_ghx] ghx not on PATH; recommended for speed -- run `directive setup:ghx` " +
|
|
1771
|
-
"to opt in. Consumer projects only require gh. Refs #884.\n");
|
|
1772
|
-
}
|
|
1773
|
-
return 0;
|
|
1774
|
-
}
|
|
1775
|
-
let consent;
|
|
1776
|
-
if (args.yes) {
|
|
1777
|
-
consent = true;
|
|
1778
|
-
io.writeOut("[setup_ghx] --yes provided; skipping interactive consent prompt.\n");
|
|
1779
|
-
}
|
|
1780
|
-
else {
|
|
1781
|
-
const skip = (process.env.DEFT_SETUP_GHX_SKIP ?? "").trim();
|
|
1782
|
-
if (skip === "1" || skip.toLowerCase() === "true" || skip.toLowerCase() === "yes") {
|
|
1783
|
-
io.writeOut("[setup_ghx] DEFT_SETUP_GHX_SKIP set; skipping ghx install. Refs #884.\n");
|
|
1784
|
-
return 0;
|
|
1785
|
-
}
|
|
1786
|
-
const readLine = deps.readConsentLine ?? readConsentLineFromStdin;
|
|
1787
|
-
consent = promptSetupGhxConsent(io, readLine);
|
|
1788
|
-
}
|
|
1789
|
-
if (!consent) {
|
|
1790
|
-
io.writeOut("[setup_ghx] Skipping ghx install. ghx is recommended for speed for maintainers and " +
|
|
1791
|
-
"swarm runs; consumer projects only require gh " +
|
|
1792
|
-
"(see https://github.com/brunoborges/ghx, #884).\n");
|
|
1793
|
-
return 0;
|
|
1794
|
-
}
|
|
1795
|
-
const host = detectSetupGhxHost();
|
|
1796
|
-
const runInstall = deps.runInstall ??
|
|
1797
|
-
((h) => installSetupGhx(h, whichFn, deps.runner, deps.downloadFn));
|
|
1798
|
-
try {
|
|
1799
|
-
const rc = await runInstall(host);
|
|
1800
|
-
if (rc !== 0) {
|
|
1801
|
-
io.writeErr(`[setup_ghx] error: upstream installer exited ${rc}. ` +
|
|
1802
|
-
"See https://github.com/brunoborges/ghx#install for manual options.\n");
|
|
1803
|
-
return 1;
|
|
1804
|
-
}
|
|
1805
|
-
io.writeOut("[setup_ghx] ghx installed. Open a fresh shell so the updated PATH takes effect, " +
|
|
1806
|
-
"then re-run `task setup` to verify.\n");
|
|
1807
|
-
return 0;
|
|
1808
|
-
}
|
|
1809
|
-
catch (err) {
|
|
1810
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1811
|
-
io.writeErr(`[setup_ghx] error: ${message}\n`);
|
|
1812
|
-
return 1;
|
|
1813
|
-
}
|
|
1814
|
-
}
|
|
1815
|
-
// ===========================================================================
|
|
1816
|
-
// Native directive bootstrap launcher (#2022 Phase 4).
|
|
1817
|
-
//
|
|
1818
|
-
// Thin operator entry: deposit-if-absent, carry phase intent + deliberate
|
|
1819
|
-
// re-entry signal, then hand off to deft-directive-setup (agent-driven).
|
|
1820
|
-
// ===========================================================================
|
|
1821
|
-
export const SETUP_SKILL_REL_PATH = ".deft/core/skills/deft-directive-setup/SKILL.md";
|
|
1822
|
-
function bootstrapPhaseLabel(phase) {
|
|
1823
|
-
if (phase === 1)
|
|
1824
|
-
return "user";
|
|
1825
|
-
if (phase === 2)
|
|
1826
|
-
return "project";
|
|
1827
|
-
return "spec";
|
|
1828
|
-
}
|
|
1829
|
-
/**
|
|
1830
|
-
* Bootstrap USER.md path resolver (#2271). Delegates to the shared first-hit-
|
|
1831
|
-
* wins resolver so the CLI bootstrap, session-start, and doctor share one
|
|
1832
|
-
* source of truth. DEFT_USER_PATH precedence is preserved by the shared
|
|
1833
|
-
* resolver (rung 1). `projectRoot` scopes the workspace-local rung so a bridged
|
|
1834
|
-
* `<projectRoot>/.deft/USER.md` resolves without a manual DEFT_USER_PATH.
|
|
1835
|
-
*/
|
|
1836
|
-
function resolveBootstrapUserMdPath(projectRoot) {
|
|
1837
|
-
return userConfig.resolveUserMdPath(projectRoot !== undefined ? { projectRoot } : {}).path;
|
|
1838
|
-
}
|
|
1839
|
-
function defaultBootstrapDeps() {
|
|
1840
|
-
return {
|
|
1841
|
-
deftCorePresent: (projectRoot) => isDirSafe(join(resolve(projectRoot), ".deft", "core")),
|
|
1842
|
-
userMdPresent: (projectRoot) => isFileSafe(resolveBootstrapUserMdPath(projectRoot)),
|
|
1843
|
-
projectDefPresent: (projectRoot) => isFileSafe(projectDefinitionPath(projectRoot)),
|
|
1844
|
-
runInitDeposit: async (projectRoot, io) => {
|
|
1845
|
-
const initArgs = parseInitArgv(["--yes", "--repo-root", projectRoot, "--json"], []);
|
|
1846
|
-
return runInitDepositCli({
|
|
1847
|
-
...initArgs,
|
|
1848
|
-
writeOut: io.writeOut,
|
|
1849
|
-
writeErr: io.writeErr,
|
|
1850
|
-
});
|
|
1851
|
-
},
|
|
1852
|
-
};
|
|
1853
|
-
}
|
|
1854
|
-
/** Parse `directive bootstrap` argv (#2022 Phase 4). */
|
|
1855
|
-
export function parseDirectiveBootstrapArgs(argv) {
|
|
1856
|
-
let projectRoot = ".";
|
|
1857
|
-
let jumpProject = false;
|
|
1858
|
-
let strategy = null;
|
|
1859
|
-
let reconfigure = false;
|
|
1860
|
-
let force = false;
|
|
1861
|
-
let json = false;
|
|
1862
|
-
for (let i = 0; i < argv.length; i += 1) {
|
|
1863
|
-
const arg = argv[i];
|
|
1864
|
-
if (arg === "--project") {
|
|
1865
|
-
jumpProject = true;
|
|
1866
|
-
}
|
|
1867
|
-
else if (arg === "--reconfigure") {
|
|
1868
|
-
reconfigure = true;
|
|
1869
|
-
}
|
|
1870
|
-
else if (arg === "--force") {
|
|
1871
|
-
force = true;
|
|
1872
|
-
}
|
|
1873
|
-
else if (arg === "--json") {
|
|
1874
|
-
json = true;
|
|
1875
|
-
}
|
|
1876
|
-
else if (arg === "--project-root") {
|
|
1877
|
-
const value = argv[i + 1];
|
|
1878
|
-
if (value === undefined) {
|
|
1879
|
-
return {
|
|
1880
|
-
projectRoot,
|
|
1881
|
-
jumpProject,
|
|
1882
|
-
strategy,
|
|
1883
|
-
reconfigure,
|
|
1884
|
-
force,
|
|
1885
|
-
json,
|
|
1886
|
-
error: "argument --project-root: expected one argument",
|
|
1887
|
-
};
|
|
1888
|
-
}
|
|
1889
|
-
projectRoot = value;
|
|
1890
|
-
i += 1;
|
|
1891
|
-
}
|
|
1892
|
-
else if (arg?.startsWith("--project-root=")) {
|
|
1893
|
-
projectRoot = arg.slice("--project-root=".length);
|
|
1894
|
-
}
|
|
1895
|
-
else if (arg === "--strategy") {
|
|
1896
|
-
const value = argv[i + 1];
|
|
1897
|
-
if (value === undefined) {
|
|
1898
|
-
return {
|
|
1899
|
-
projectRoot,
|
|
1900
|
-
jumpProject,
|
|
1901
|
-
strategy,
|
|
1902
|
-
reconfigure,
|
|
1903
|
-
force,
|
|
1904
|
-
json,
|
|
1905
|
-
error: "argument --strategy: expected one argument",
|
|
1906
|
-
};
|
|
1907
|
-
}
|
|
1908
|
-
strategy = value;
|
|
1909
|
-
i += 1;
|
|
1910
|
-
}
|
|
1911
|
-
else if (arg?.startsWith("--strategy=")) {
|
|
1912
|
-
strategy = arg.slice("--strategy=".length);
|
|
1913
|
-
}
|
|
1914
|
-
else if (arg === "--help" || arg === "-h") {
|
|
1915
|
-
return {
|
|
1916
|
-
projectRoot,
|
|
1917
|
-
jumpProject,
|
|
1918
|
-
strategy,
|
|
1919
|
-
reconfigure,
|
|
1920
|
-
force,
|
|
1921
|
-
json,
|
|
1922
|
-
error: "__help__",
|
|
1923
|
-
};
|
|
1924
|
-
}
|
|
1925
|
-
else {
|
|
1926
|
-
return {
|
|
1927
|
-
projectRoot,
|
|
1928
|
-
jumpProject,
|
|
1929
|
-
strategy,
|
|
1930
|
-
reconfigure,
|
|
1931
|
-
force,
|
|
1932
|
-
json,
|
|
1933
|
-
error: `unrecognized argument: ${arg}`,
|
|
1934
|
-
};
|
|
1935
|
-
}
|
|
1936
|
-
}
|
|
1937
|
-
return {
|
|
1938
|
-
projectRoot: resolve(projectRoot),
|
|
1939
|
-
jumpProject,
|
|
1940
|
-
strategy,
|
|
1941
|
-
reconfigure,
|
|
1942
|
-
force,
|
|
1943
|
-
json,
|
|
1944
|
-
};
|
|
1945
|
-
}
|
|
1946
|
-
/** Resolve phase intent and deliberate re-entry signal from parsed args + on-disk state. */
|
|
1947
|
-
export function resolveDirectiveBootstrapPlan(args, deps) {
|
|
1948
|
-
let phase;
|
|
1949
|
-
if (args.jumpProject) {
|
|
1950
|
-
phase = 2;
|
|
1951
|
-
}
|
|
1952
|
-
else if (args.strategy !== null) {
|
|
1953
|
-
phase = 3;
|
|
1954
|
-
}
|
|
1955
|
-
else if (!deps.userMdPresent(args.projectRoot)) {
|
|
1956
|
-
phase = 1;
|
|
1957
|
-
}
|
|
1958
|
-
else if (!deps.projectDefPresent(args.projectRoot)) {
|
|
1959
|
-
phase = 2;
|
|
1960
|
-
}
|
|
1961
|
-
else {
|
|
1962
|
-
phase = 3;
|
|
1963
|
-
}
|
|
1964
|
-
let reEntry = "none";
|
|
1965
|
-
if (args.force) {
|
|
1966
|
-
reEntry = "force";
|
|
1967
|
-
}
|
|
1968
|
-
else if (args.reconfigure) {
|
|
1969
|
-
reEntry = "reconfigure";
|
|
1970
|
-
}
|
|
1971
|
-
else {
|
|
1972
|
-
const artifactExists = phase === 1
|
|
1973
|
-
? deps.userMdPresent(args.projectRoot)
|
|
1974
|
-
: phase === 2
|
|
1975
|
-
? deps.projectDefPresent(args.projectRoot)
|
|
1976
|
-
: deps.projectDefPresent(args.projectRoot);
|
|
1977
|
-
if (artifactExists) {
|
|
1978
|
-
reEntry = "prompt";
|
|
1979
|
-
}
|
|
1980
|
-
}
|
|
1981
|
-
return {
|
|
1982
|
-
phase,
|
|
1983
|
-
phaseLabel: bootstrapPhaseLabel(phase),
|
|
1984
|
-
reEntry,
|
|
1985
|
-
strategy: args.strategy,
|
|
1986
|
-
};
|
|
1987
|
-
}
|
|
1988
|
-
function printDirectiveBootstrapHelp(io) {
|
|
1989
|
-
io.writeOut("Usage: directive bootstrap [--project-root <path>] [--project] [--strategy <name>] [--reconfigure] [--force] [--json]\n\n" +
|
|
1990
|
-
"Deposit the framework when absent, then hand off to deft-directive-setup.\n\n" +
|
|
1991
|
-
" --project Jump to Phase 2 (project configuration)\n" +
|
|
1992
|
-
" --strategy Jump to Phase 3 (scope vBRIEF / spec interview)\n" +
|
|
1993
|
-
" --reconfigure Deliberate re-entry — agent should reconfigure existing artifacts\n" +
|
|
1994
|
-
" --force Skip reconfigure-or-keep prompt; overwrite allowed\n" +
|
|
1995
|
-
" --json Emit structured handoff JSON on stdout\n");
|
|
1996
|
-
}
|
|
1997
|
-
function emitBootstrapHandoff(handoff, io, json) {
|
|
1998
|
-
if (json) {
|
|
1999
|
-
io.writeOut(`${JSON.stringify(handoff, null, 2)}\n`);
|
|
2000
|
-
return;
|
|
2001
|
-
}
|
|
2002
|
-
io.writeOut("[directive bootstrap] Setup launcher — hand off to deft-directive-setup\n\n");
|
|
2003
|
-
io.writeOut(`project_root: ${handoff.project_root}\n`);
|
|
2004
|
-
io.writeOut(`deposited: ${handoff.deposited}\n`);
|
|
2005
|
-
io.writeOut(`phase: ${handoff.phase} (${handoff.phase_label})\n`);
|
|
2006
|
-
io.writeOut(`re_entry: ${handoff.re_entry}\n`);
|
|
2007
|
-
if (handoff.strategy !== null) {
|
|
2008
|
-
io.writeOut(`strategy: ${handoff.strategy}\n`);
|
|
2009
|
-
}
|
|
2010
|
-
io.writeOut(`\nNext: Read and follow ${handoff.skill_path}\n`);
|
|
2011
|
-
}
|
|
2012
|
-
/** Native `directive bootstrap` handler (#2022 Phase 4). */
|
|
2013
|
-
export async function runDirectiveBootstrap(argv, io, deps = {}) {
|
|
2014
|
-
const merged = { ...defaultBootstrapDeps(), ...deps };
|
|
2015
|
-
const args = parseDirectiveBootstrapArgs(argv);
|
|
2016
|
-
if (args.error === "__help__") {
|
|
2017
|
-
printDirectiveBootstrapHelp(io);
|
|
2018
|
-
return 0;
|
|
2019
|
-
}
|
|
2020
|
-
if (args.error !== undefined) {
|
|
2021
|
-
io.writeErr(`directive bootstrap: ${args.error}\n`);
|
|
2022
|
-
return 2;
|
|
2023
|
-
}
|
|
2024
|
-
const plan = resolveDirectiveBootstrapPlan(args, merged);
|
|
2025
|
-
let deposited = false;
|
|
2026
|
-
if (!merged.deftCorePresent(args.projectRoot)) {
|
|
2027
|
-
const initCode = await merged.runInitDeposit(args.projectRoot, io);
|
|
2028
|
-
if (initCode !== 0) {
|
|
2029
|
-
return initCode;
|
|
2030
|
-
}
|
|
2031
|
-
deposited = true;
|
|
2032
|
-
}
|
|
2033
|
-
const handoff = {
|
|
2034
|
-
handoff: "deft-directive-setup",
|
|
2035
|
-
skill_path: SETUP_SKILL_REL_PATH,
|
|
2036
|
-
project_root: args.projectRoot,
|
|
2037
|
-
deposited,
|
|
2038
|
-
phase: plan.phase,
|
|
2039
|
-
phase_label: plan.phaseLabel,
|
|
2040
|
-
re_entry: plan.reEntry,
|
|
2041
|
-
strategy: plan.strategy,
|
|
2042
|
-
};
|
|
2043
|
-
emitBootstrapHandoff(handoff, io, args.json);
|
|
2044
|
-
return 0;
|
|
2045
|
-
}
|
|
2046
|
-
// ===========================================================================
|
|
2047
|
-
// Native policy-set handler (#2022 Phase 1).
|
|
2048
|
-
//
|
|
2049
|
-
// Port of scripts/policy_set.py to native TypeScript so the typed-policy write
|
|
2050
|
-
// path (enforce-branches / allow-direct-commits / wip-cap / subagent-backend)
|
|
2051
|
-
// and the subagent-backends probe surface no longer shell into bundled Python.
|
|
2052
|
-
// Behaviour parity with the Python script is preserved: the audit row appended
|
|
2053
|
-
// to meta/policy-changes.log, the json.dumps(..., indent=2, ensure_ascii=False)
|
|
2054
|
-
// + "\n" serialization, the disclosure text, and the exit codes
|
|
2055
|
-
// (0 success / 1 refusal / 2 config-or-parse error).
|
|
2056
|
-
// ===========================================================================
|
|
2057
|
-
const POLICY_CAPABILITY_COST_DISCLOSURE = "\u26a0 Capability-cost disclosure -- enabling direct commits to the default " +
|
|
2058
|
-
"branch turns OFF the deft branch-protection policy.\n" +
|
|
2059
|
-
" \u2022 Pre-commit + pre-push hooks will no longer block default-branch " +
|
|
2060
|
-
"commits.\n" +
|
|
2061
|
-
" \u2022 verify:branch will pass on the default branch.\n" +
|
|
2062
|
-
" \u2022 The CI sanity check (head_ref != base_ref) is still independent and " +
|
|
2063
|
-
"will continue to flag master->master PRs.\n" +
|
|
2064
|
-
" \u2022 This change is reversible: run `" +
|
|
2065
|
-
policyColonInvocation("enforce-branches") +
|
|
2066
|
-
"` to " +
|
|
2067
|
-
"re-enable the gate.\n" +
|
|
2068
|
-
" \u2022 The change is recorded to meta/policy-changes.log for auditability.";
|
|
2069
|
-
const POLICY_WIP_CAP_DISCLOSURE = "\u26a0 Capability-cost disclosure -- changing plan.policy.wipCap " +
|
|
2070
|
-
"alters the refusal threshold on task scope:promote (#1124 / D4 of #1119).\n" +
|
|
2071
|
-
" \u2022 Raising the cap lets more vBRIEFs sit in pending/+active/ " +
|
|
2072
|
-
"before promotion is refused.\n" +
|
|
2073
|
-
" \u2022 Lowering the cap may put the project over cap immediately; " +
|
|
2074
|
-
"use `task scope:demote` / `task scope:demote --batch --older-than-days 30` " +
|
|
2075
|
-
"to drain.\n" +
|
|
2076
|
-
" \u2022 cap=0 freezes promotion entirely (useful for code-freeze " +
|
|
2077
|
-
"windows; restore by setting a positive value).\n" +
|
|
2078
|
-
" \u2022 This change is reversible and recorded to " +
|
|
2079
|
-
"meta/policy-changes.log for auditability.";
|
|
2080
|
-
const POLICY_SET_COMMANDS = [
|
|
2081
|
-
"enforce-branches",
|
|
2082
|
-
"allow-direct-commits",
|
|
2083
|
-
"wip-cap",
|
|
2084
|
-
"subagent-backend",
|
|
2085
|
-
"subagent-backends",
|
|
2086
|
-
];
|
|
2087
|
-
/** Flags each subcommand accepts (mirrors the policy_set.py argparse subparsers). */
|
|
2088
|
-
const POLICY_SET_ALLOWED_FLAGS = {
|
|
2089
|
-
"enforce-branches": new Set(["--actor", "--note", "--project-root"]),
|
|
2090
|
-
"allow-direct-commits": new Set(["--confirm", "--actor", "--note", "--project-root"]),
|
|
2091
|
-
"wip-cap": new Set(["--set", "--confirm", "--actor", "--note", "--project-root"]),
|
|
2092
|
-
"subagent-backend": new Set(["--set", "--actor", "--note", "--project-root"]),
|
|
2093
|
-
"subagent-backends": new Set(["--format", "--project-root"]),
|
|
2094
|
-
};
|
|
2095
|
-
/** Custom error so write helpers can distinguish a missing file from a config fault. */
|
|
2096
|
-
class PolicySetError extends Error {
|
|
2097
|
-
kind;
|
|
2098
|
-
constructor(message, kind) {
|
|
2099
|
-
super(message);
|
|
2100
|
-
this.name = "PolicySetError";
|
|
2101
|
-
this.kind = kind;
|
|
2102
|
-
}
|
|
2103
|
-
}
|
|
2104
|
-
/** Python repr() for the audit-trail `previous=` field (None / 'str' / int / bool). */
|
|
2105
|
-
function pyRepr(value) {
|
|
2106
|
-
if (value === undefined || value === null)
|
|
2107
|
-
return "None";
|
|
2108
|
-
if (typeof value === "string")
|
|
2109
|
-
return `'${value}'`;
|
|
2110
|
-
if (typeof value === "boolean")
|
|
2111
|
-
return value ? "True" : "False";
|
|
2112
|
-
return String(value);
|
|
2113
|
-
}
|
|
2114
|
-
/** Strip newlines so an audit note stays a single log line (mirrors policy_set.py). */
|
|
2115
|
-
function sanitizeNote(note) {
|
|
2116
|
-
return note.replace(/\n/g, " ").replace(/\r/g, " ");
|
|
2117
|
-
}
|
|
2118
|
-
function defaultPolicySetActor(cmd) {
|
|
2119
|
-
switch (cmd) {
|
|
2120
|
-
case "enforce-branches":
|
|
2121
|
-
return policyColonInvocation("enforce-branches");
|
|
2122
|
-
case "allow-direct-commits":
|
|
2123
|
-
return policyColonInvocation("allow-direct-commits");
|
|
2124
|
-
case "wip-cap":
|
|
2125
|
-
return policySetInvocation("wip-cap");
|
|
2126
|
-
case "subagent-backend":
|
|
2127
|
-
return policySetInvocation("subagent-backend");
|
|
2128
|
-
case "subagent-backends":
|
|
2129
|
-
return policySetInvocation("subagent-backends");
|
|
2130
|
-
}
|
|
2131
|
-
}
|
|
2132
|
-
/** Mirror Python `Path(...).expanduser()` for a leading `~` / `~/` segment. */
|
|
2133
|
-
function expandUser(p) {
|
|
2134
|
-
if (p === "~")
|
|
2135
|
-
return homedir();
|
|
2136
|
-
if (p.startsWith("~/") || p.startsWith("~\\")) {
|
|
2137
|
-
return join(homedir(), p.slice(2));
|
|
2138
|
-
}
|
|
2139
|
-
return p;
|
|
2140
|
-
}
|
|
2141
|
-
function policySetError(message) {
|
|
2142
|
-
return {
|
|
2143
|
-
cmd: "enforce-branches",
|
|
2144
|
-
confirm: false,
|
|
2145
|
-
actor: "",
|
|
2146
|
-
note: "",
|
|
2147
|
-
projectRoot: ".",
|
|
2148
|
-
format: "text",
|
|
2149
|
-
error: message,
|
|
2150
|
-
};
|
|
2151
|
-
}
|
|
2152
|
-
/** Parse `policy-set <cmd> [flags]` (mirrors the policy_set.py argparse surface). */
|
|
2153
|
-
function parsePolicySetArgs(argv) {
|
|
2154
|
-
const cmd = argv[0];
|
|
2155
|
-
if (cmd === undefined) {
|
|
2156
|
-
return policySetError("the following arguments are required: cmd");
|
|
2157
|
-
}
|
|
2158
|
-
if (!POLICY_SET_COMMANDS.includes(cmd)) {
|
|
2159
|
-
return policySetError(`argument cmd: invalid choice: '${cmd}'`);
|
|
2160
|
-
}
|
|
2161
|
-
const command = cmd;
|
|
2162
|
-
const allowed = POLICY_SET_ALLOWED_FLAGS[command];
|
|
2163
|
-
const args = {
|
|
2164
|
-
cmd: command,
|
|
2165
|
-
confirm: false,
|
|
2166
|
-
actor: defaultPolicySetActor(command),
|
|
2167
|
-
note: "",
|
|
2168
|
-
projectRoot: ".",
|
|
2169
|
-
format: "text",
|
|
2170
|
-
};
|
|
2171
|
-
const rest = argv.slice(1);
|
|
2172
|
-
for (let i = 0; i < rest.length; i += 1) {
|
|
2173
|
-
const token = rest[i];
|
|
2174
|
-
if (token === undefined)
|
|
2175
|
-
continue;
|
|
2176
|
-
let flag = token;
|
|
2177
|
-
let inlineValue;
|
|
2178
|
-
if (token.startsWith("--") && token.includes("=")) {
|
|
2179
|
-
const eq = token.indexOf("=");
|
|
2180
|
-
flag = token.slice(0, eq);
|
|
2181
|
-
inlineValue = token.slice(eq + 1);
|
|
2182
|
-
}
|
|
2183
|
-
if (!allowed.has(flag)) {
|
|
2184
|
-
return policySetError(`unrecognized arguments: ${token}`);
|
|
2185
|
-
}
|
|
2186
|
-
const takeValue = () => {
|
|
2187
|
-
if (inlineValue !== undefined)
|
|
2188
|
-
return inlineValue;
|
|
2189
|
-
i += 1;
|
|
2190
|
-
return rest[i];
|
|
2191
|
-
};
|
|
2192
|
-
if (flag === "--confirm") {
|
|
2193
|
-
args.confirm = true;
|
|
2194
|
-
continue;
|
|
2195
|
-
}
|
|
2196
|
-
const value = takeValue();
|
|
2197
|
-
if (value === undefined) {
|
|
2198
|
-
return policySetError(`argument ${flag}: expected one argument`);
|
|
2199
|
-
}
|
|
2200
|
-
if (flag === "--actor") {
|
|
2201
|
-
args.actor = value;
|
|
2202
|
-
}
|
|
2203
|
-
else if (flag === "--note") {
|
|
2204
|
-
args.note = value;
|
|
2205
|
-
}
|
|
2206
|
-
else if (flag === "--project-root") {
|
|
2207
|
-
args.projectRoot = expandUser(value);
|
|
2208
|
-
}
|
|
2209
|
-
else if (flag === "--format") {
|
|
2210
|
-
if (value !== "text" && value !== "json") {
|
|
2211
|
-
return policySetError(`argument --format: invalid choice: '${value}'`);
|
|
2212
|
-
}
|
|
2213
|
-
args.format = value;
|
|
2214
|
-
}
|
|
2215
|
-
else if (flag === "--set") {
|
|
2216
|
-
if (command === "wip-cap") {
|
|
2217
|
-
// Python int() strips surrounding whitespace, so "--set ' 5'" parsed
|
|
2218
|
-
// cleanly; trim before the integer check to preserve that contract.
|
|
2219
|
-
const capText = value.trim();
|
|
2220
|
-
if (!/^[+-]?\d+$/.test(capText)) {
|
|
2221
|
-
return policySetError(`argument --set: invalid int value: '${value}'`);
|
|
2222
|
-
}
|
|
2223
|
-
args.cap = Number.parseInt(capText, 10);
|
|
2224
|
-
}
|
|
2225
|
-
else {
|
|
2226
|
-
// subagent-backend --set <choice>
|
|
2227
|
-
if (!KNOWN_SUBAGENT_BACKEND_IDS.has(value)) {
|
|
2228
|
-
const choices = [...KNOWN_SUBAGENT_BACKEND_IDS].sort().join(", ");
|
|
2229
|
-
return policySetError(`argument --set: invalid choice: '${value}' (choose from ${choices})`);
|
|
2230
|
-
}
|
|
2231
|
-
args.backendId = value;
|
|
2232
|
-
}
|
|
2233
|
-
}
|
|
2234
|
-
}
|
|
2235
|
-
if (command === "wip-cap" && args.cap === undefined) {
|
|
2236
|
-
return policySetError("the following arguments are required: --set");
|
|
2237
|
-
}
|
|
2238
|
-
if (command === "subagent-backend" && args.backendId === undefined) {
|
|
2239
|
-
return policySetError("the following arguments are required: --set");
|
|
2240
|
-
}
|
|
2241
|
-
return args;
|
|
2242
|
-
}
|
|
2243
|
-
/**
|
|
2244
|
-
* Load PROJECT-DEFINITION for an in-place typed-field write (mirrors the
|
|
2245
|
-
* .setdefault chain). Reads the artifact the mutation lock captured (#3796) so
|
|
2246
|
-
* these CLI writers can no longer resolve, read, and write a PROJECT-DEFINITION
|
|
2247
|
-
* outside the shared mutation lock.
|
|
2248
|
-
*/
|
|
2249
|
-
function loadProjectDefinitionForWrite(mutation) {
|
|
2250
|
-
const path = mutation.artifactPath;
|
|
2251
|
-
if (!existsSync(path)) {
|
|
2252
|
-
throw new PolicySetError(`PROJECT-DEFINITION not found at ${mutation.artifactLabel}`, "not-found");
|
|
2253
|
-
}
|
|
2254
|
-
let data;
|
|
2255
|
-
try {
|
|
2256
|
-
// The shared loader rejects unreadable, malformed, and non-object top-level
|
|
2257
|
-
// payloads; every one of those is a config error for the CLI writers.
|
|
2258
|
-
data = mutation.load();
|
|
2259
|
-
}
|
|
2260
|
-
catch (err) {
|
|
2261
|
-
throw new PolicySetError(err instanceof Error ? err.message : String(err), "config");
|
|
2262
|
-
}
|
|
2263
|
-
let plan = data.plan;
|
|
2264
|
-
if (plan === undefined) {
|
|
2265
|
-
plan = {};
|
|
2266
|
-
data.plan = plan;
|
|
2267
|
-
}
|
|
2268
|
-
if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
|
|
2269
|
-
throw new PolicySetError("PROJECT-DEFINITION 'plan' is not an object", "config");
|
|
2270
|
-
}
|
|
2271
|
-
const planObj = plan;
|
|
2272
|
-
const legacyMigrated = migrateLegacyPolicyKey(planObj);
|
|
2273
|
-
let policy = planObj[PLAN_POLICY_KEY];
|
|
2274
|
-
if (policy === undefined) {
|
|
2275
|
-
policy = {};
|
|
2276
|
-
planObj[PLAN_POLICY_KEY] = policy;
|
|
2277
|
-
}
|
|
2278
|
-
if (typeof policy !== "object" || policy === null || Array.isArray(policy)) {
|
|
2279
|
-
throw new PolicySetError("plan.policy is not an object", "config");
|
|
2280
|
-
}
|
|
2281
|
-
return { data, policyBlock: policy, legacyMigrated };
|
|
2282
|
-
}
|
|
2283
|
-
/** Write plan.policy.wipCap in place + append the audit row (mirrors set_wip_cap). */
|
|
2284
|
-
function writeWipCap(projectRoot, cap, actor, note) {
|
|
2285
|
-
return withProjectDefinitionMutation(projectRoot, (mutation) => {
|
|
2286
|
-
const { data, policyBlock, legacyMigrated } = loadProjectDefinitionForWrite(mutation);
|
|
2287
|
-
const previous = policyBlock.wipCap;
|
|
2288
|
-
policyBlock.wipCap = cap;
|
|
2289
|
-
const changed = previous !== cap || legacyMigrated;
|
|
2290
|
-
const parts = [`actor=${actor}`, `wipCap=${cap}`, `previous=${pyRepr(previous)}`];
|
|
2291
|
-
if (note)
|
|
2292
|
-
parts.push(`note=${sanitizeNote(note)}`);
|
|
2293
|
-
const auditEntry = stampChangedToken(parts.join(" "), changed);
|
|
2294
|
-
if (changed) {
|
|
2295
|
-
mutation.persist(data);
|
|
2296
|
-
}
|
|
2297
|
-
appendAuditLog(projectRoot, auditEntry, changed);
|
|
2298
|
-
return { changed, auditEntry };
|
|
2299
|
-
});
|
|
2300
|
-
}
|
|
2301
|
-
/** Write plan.policy.swarmSubagentBackend in place + append the audit row. */
|
|
2302
|
-
function writeSubagentBackend(projectRoot, backendId, actor, note) {
|
|
2303
|
-
return withProjectDefinitionMutation(projectRoot, (mutation) => {
|
|
2304
|
-
const { data, policyBlock, legacyMigrated } = loadProjectDefinitionForWrite(mutation);
|
|
2305
|
-
const previous = policyBlock.swarmSubagentBackend;
|
|
2306
|
-
policyBlock.swarmSubagentBackend = backendId;
|
|
2307
|
-
const changed = previous !== backendId || legacyMigrated;
|
|
2308
|
-
const parts = [
|
|
2309
|
-
`actor=${actor}`,
|
|
2310
|
-
`swarmSubagentBackend=${backendId}`,
|
|
2311
|
-
`previous=${pyRepr(previous)}`,
|
|
2312
|
-
];
|
|
2313
|
-
if (note)
|
|
2314
|
-
parts.push(`note=${sanitizeNote(note)}`);
|
|
2315
|
-
const auditEntry = stampChangedToken(parts.join(" "), changed);
|
|
2316
|
-
if (changed) {
|
|
2317
|
-
mutation.persist(data);
|
|
2318
|
-
}
|
|
2319
|
-
appendAuditLog(projectRoot, auditEntry, changed);
|
|
2320
|
-
return { changed, auditEntry };
|
|
2321
|
-
});
|
|
2322
|
-
}
|
|
2323
|
-
/** Serialise the probe output for `subagent-backends --format json`. */
|
|
2324
|
-
function subagentBackendsToJson(backends) {
|
|
2325
|
-
const payload = {
|
|
2326
|
-
backends: backends.map((entry) => ({
|
|
2327
|
-
id: entry.backend_id,
|
|
2328
|
-
display_name: entry.display_name,
|
|
2329
|
-
roles: [...entry.roles],
|
|
2330
|
-
available: entry.available,
|
|
2331
|
-
})),
|
|
2332
|
-
};
|
|
2333
|
-
return JSON.stringify(payload, null, 2);
|
|
2334
|
-
}
|
|
2335
|
-
/** Map a write-path error to its fail-closed message + exit code (mirrors the except blocks). */
|
|
2336
|
-
function reportPolicyWriteError(err, io) {
|
|
2337
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2338
|
-
if (err instanceof PolicySetError && err.kind === "not-found") {
|
|
2339
|
-
io.writeErr(`\u274c ${message}\n`);
|
|
2340
|
-
io.writeErr(" Recovery: run `task setup` to generate vbrief/PROJECT-DEFINITION.vbrief.json.\n");
|
|
2341
|
-
return 2;
|
|
2342
|
-
}
|
|
2343
|
-
io.writeErr(`\u274c Config error: ${message}\n`);
|
|
2344
|
-
return 2;
|
|
2345
|
-
}
|
|
2346
|
-
function applyBranchPolicy(args, io) {
|
|
2347
|
-
let target;
|
|
2348
|
-
if (args.cmd === "enforce-branches") {
|
|
2349
|
-
target = false;
|
|
2350
|
-
}
|
|
2351
|
-
else {
|
|
2352
|
-
if (!args.confirm) {
|
|
2353
|
-
io.writeOut(`${POLICY_CAPABILITY_COST_DISCLOSURE}\n`);
|
|
2354
|
-
io.writeOut("\n");
|
|
2355
|
-
io.writeOut(`Re-run with --confirm to apply: ${policyColonInvocation("allow-direct-commits", " -- --confirm")}\n`);
|
|
2356
|
-
return 1;
|
|
2357
|
-
}
|
|
2358
|
-
target = true;
|
|
2359
|
-
}
|
|
2360
|
-
let result;
|
|
2361
|
-
try {
|
|
2362
|
-
result = setPolicy(args.projectRoot, {
|
|
2363
|
-
allowDirectCommits: target,
|
|
2364
|
-
actor: args.actor,
|
|
2365
|
-
note: args.note,
|
|
2366
|
-
});
|
|
2367
|
-
}
|
|
2368
|
-
catch (err) {
|
|
2369
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2370
|
-
if (message.includes("PROJECT-DEFINITION not found")) {
|
|
2371
|
-
io.writeErr(`\u274c ${message}\n`);
|
|
2372
|
-
io.writeErr(" Recovery: run `task setup` to generate vbrief/PROJECT-DEFINITION.vbrief.json.\n");
|
|
2373
|
-
return 2;
|
|
2374
|
-
}
|
|
2375
|
-
io.writeErr(`\u274c Config error: ${message}\n`);
|
|
2376
|
-
return 2;
|
|
2377
|
-
}
|
|
2378
|
-
const state = target ? "OFF" : "ON";
|
|
2379
|
-
io.writeOut(`\u2713 plan.policy.allowDirectCommitsToMaster=${target ? "true" : "false"} ` +
|
|
2380
|
-
`(branch-protection ${state}).\n`);
|
|
2381
|
-
if (result.changed) {
|
|
2382
|
-
io.writeOut(` audit: meta/policy-changes.log :: ${result.auditEntry}\n`);
|
|
2383
|
-
}
|
|
2384
|
-
else {
|
|
2385
|
-
io.writeOut(`${POLICY_AUDIT_NOOP_STDOUT}\n`);
|
|
2386
|
-
}
|
|
2387
|
-
io.writeOut(`${disclosureLine(resolvePolicy(args.projectRoot))}\n`);
|
|
2388
|
-
return 0;
|
|
2389
|
-
}
|
|
2390
|
-
function applyWipCap(args, io) {
|
|
2391
|
-
const cap = args.cap ?? 0;
|
|
2392
|
-
if (cap < 0) {
|
|
2393
|
-
io.writeErr(`\u274c --set must be >= 0; got ${cap}.\n`);
|
|
2394
|
-
return 1;
|
|
2395
|
-
}
|
|
2396
|
-
if (!args.confirm) {
|
|
2397
|
-
io.writeOut(`${POLICY_WIP_CAP_DISCLOSURE}\n`);
|
|
2398
|
-
io.writeOut("\n");
|
|
2399
|
-
io.writeOut(`Re-run with --confirm to apply: ${policySetInvocation("wip-cap", ` -- --set ${cap} --confirm`)}\n`);
|
|
2400
|
-
return 1;
|
|
2401
|
-
}
|
|
2402
|
-
let res;
|
|
2403
|
-
try {
|
|
2404
|
-
res = writeWipCap(args.projectRoot, cap, args.actor, args.note);
|
|
2405
|
-
}
|
|
2406
|
-
catch (err) {
|
|
2407
|
-
return reportPolicyWriteError(err, io);
|
|
2408
|
-
}
|
|
2409
|
-
io.writeOut(`\u2713 plan.policy.wipCap=${cap}.\n`);
|
|
2410
|
-
if (res.changed) {
|
|
2411
|
-
io.writeOut(` audit: meta/policy-changes.log :: ${res.auditEntry}\n`);
|
|
2412
|
-
}
|
|
2413
|
-
else {
|
|
2414
|
-
io.writeOut(`${POLICY_AUDIT_NOOP_STDOUT}\n`);
|
|
2415
|
-
}
|
|
2416
|
-
const result = resolveWipCap(args.projectRoot);
|
|
2417
|
-
io.writeOut(`[deft policy] plan.policy.wipCap=${result.cap} (source: ${result.source}).\n`);
|
|
2418
|
-
return 0;
|
|
2419
|
-
}
|
|
2420
|
-
function applySubagentBackend(args, io) {
|
|
2421
|
-
const backendId = args.backendId ?? "";
|
|
2422
|
-
let res;
|
|
2423
|
-
try {
|
|
2424
|
-
res = writeSubagentBackend(args.projectRoot, backendId, args.actor, args.note);
|
|
2425
|
-
}
|
|
2426
|
-
catch (err) {
|
|
2427
|
-
return reportPolicyWriteError(err, io);
|
|
2428
|
-
}
|
|
2429
|
-
io.writeOut(`\u2713 plan.policy.swarmSubagentBackend=${backendId}.\n`);
|
|
2430
|
-
if (res.changed) {
|
|
2431
|
-
io.writeOut(` audit: meta/policy-changes.log :: ${res.auditEntry}\n`);
|
|
2432
|
-
}
|
|
2433
|
-
else {
|
|
2434
|
-
io.writeOut(`${POLICY_AUDIT_NOOP_STDOUT}\n`);
|
|
2435
|
-
}
|
|
2436
|
-
const result = resolveSwarmSubagentBackend(args.projectRoot);
|
|
2437
|
-
io.writeOut(`[deft policy] plan.policy.swarmSubagentBackend=${pyRepr(result.backend_id)} ` +
|
|
2438
|
-
`(source: ${result.source}).\n`);
|
|
2439
|
-
return 0;
|
|
2440
|
-
}
|
|
2441
|
-
function applySubagentBackends(args, io) {
|
|
2442
|
-
const entries = probeSubagentBackends();
|
|
2443
|
-
if (args.format === "json") {
|
|
2444
|
-
io.writeOut(`${subagentBackendsToJson(entries)}\n`);
|
|
2445
|
-
return 0;
|
|
2446
|
-
}
|
|
2447
|
-
for (const entry of entries) {
|
|
2448
|
-
const roles = entry.roles.join(", ");
|
|
2449
|
-
const avail = entry.available ? "available" : "unavailable";
|
|
2450
|
-
io.writeOut(`${entry.backend_id}\t${entry.display_name}\troles=[${roles}]\t${avail}\n`);
|
|
2451
|
-
}
|
|
2452
|
-
return 0;
|
|
2453
|
-
}
|
|
2454
|
-
/** Native `policy-set` dispatcher (replaces the policy_set.py shell-out, #2022 Phase 1). */
|
|
2455
|
-
export function runPolicySet(argv, io) {
|
|
2456
|
-
const args = parsePolicySetArgs(argv);
|
|
2457
|
-
if (args.error !== undefined) {
|
|
2458
|
-
io.writeErr(`policy-set: ${args.error}\n`);
|
|
2459
|
-
return 2;
|
|
2460
|
-
}
|
|
2461
|
-
switch (args.cmd) {
|
|
2462
|
-
case "enforce-branches":
|
|
2463
|
-
case "allow-direct-commits":
|
|
2464
|
-
return applyBranchPolicy(args, io);
|
|
2465
|
-
case "wip-cap":
|
|
2466
|
-
return applyWipCap(args, io);
|
|
2467
|
-
case "subagent-backend":
|
|
2468
|
-
return applySubagentBackend(args, io);
|
|
2469
|
-
case "subagent-backends":
|
|
2470
|
-
return applySubagentBackends(args, io);
|
|
2471
|
-
}
|
|
2472
|
-
}
|
|
2473
|
-
async function loadCoreModuleHandler(verb, io) {
|
|
2474
|
-
switch (verb) {
|
|
2475
|
-
case "scm": {
|
|
2476
|
-
const { main } = await import("@deftai/directive-core/dist/scm/main.js");
|
|
2477
|
-
return (argv) => main(argv);
|
|
2478
|
-
}
|
|
2479
|
-
case "scm-readiness": {
|
|
2480
|
-
const { mainEntry } = await import("@deftai/directive-core/dist/scm/readiness-cli.js");
|
|
2481
|
-
return mainEntry;
|
|
2482
|
-
}
|
|
2483
|
-
case "github-auth-modes": {
|
|
2484
|
-
const { mainEntry } = await import("@deftai/directive-core/dist/intake/github-auth-modes-cli.js");
|
|
2485
|
-
return mainEntry;
|
|
2486
|
-
}
|
|
2487
|
-
case "github-body": {
|
|
2488
|
-
const { mainEntry } = await import("@deftai/directive-core/dist/intake/github-body-cli.js");
|
|
2489
|
-
return mainEntry;
|
|
2490
|
-
}
|
|
2491
|
-
case "rule-to-verb-parity": {
|
|
2492
|
-
const { mainEntry } = await import("@deftai/directive-core/dist/check/rule-to-verb-parity.js");
|
|
2493
|
-
return mainEntry;
|
|
2494
|
-
}
|
|
2495
|
-
case "issue-emit": {
|
|
2496
|
-
const { mainEntry } = await import("@deftai/directive-core/dist/intake/issue-emit-cli.js");
|
|
2497
|
-
return mainEntry;
|
|
2498
|
-
}
|
|
2499
|
-
case "issue-ingest": {
|
|
2500
|
-
const { mainEntry } = await import("@deftai/directive-core/dist/intake/issue-ingest-cli.js");
|
|
2501
|
-
return mainEntry;
|
|
2502
|
-
}
|
|
2503
|
-
case "issue-sync-from-xbrief": {
|
|
2504
|
-
const { mainEntry } = await import("@deftai/directive-core/dist/issue-sync/sync-from-xbrief-cli.js");
|
|
2505
|
-
return mainEntry;
|
|
2506
|
-
}
|
|
2507
|
-
case "reconcile-issues": {
|
|
2508
|
-
const { mainEntry } = await import("@deftai/directive-core/dist/intake/reconcile-issues-cli.js");
|
|
2509
|
-
return mainEntry;
|
|
2510
|
-
}
|
|
2511
|
-
case "swarm-launch": {
|
|
2512
|
-
const { launchMain } = await import("@deftai/directive-core/dist/swarm/launch-cli.js");
|
|
2513
|
-
return launchMain;
|
|
2514
|
-
}
|
|
2515
|
-
case "swarm-complete-cohort": {
|
|
2516
|
-
const { completeCohortMain } = await import("@deftai/directive-core/dist/swarm/complete-cohort-cli.js");
|
|
2517
|
-
return completeCohortMain;
|
|
2518
|
-
}
|
|
2519
|
-
case "swarm-finalize-cohort": {
|
|
2520
|
-
const { finalizeCohortMain } = await import("@deftai/directive-core/dist/swarm/finalize-cohort-cli.js");
|
|
2521
|
-
return finalizeCohortMain;
|
|
2522
|
-
}
|
|
2523
|
-
case "swarm-finalize-owed": {
|
|
2524
|
-
const { finalizeOwedMain } = await import("@deftai/directive-core/dist/swarm/finalize-owed-cli.js");
|
|
2525
|
-
return finalizeOwedMain;
|
|
2526
|
-
}
|
|
2527
|
-
case "swarm-pre-dispatch": {
|
|
2528
|
-
const { preDispatchMain } = await import("@deftai/directive-core/dist/swarm/pre-dispatch-cli.js");
|
|
2529
|
-
return preDispatchMain;
|
|
2530
|
-
}
|
|
2531
|
-
case "swarm-readiness": {
|
|
2532
|
-
const { readinessMain } = await import("@deftai/directive-core/dist/swarm/readiness-cli.js");
|
|
2533
|
-
return readinessMain;
|
|
2534
|
-
}
|
|
2535
|
-
case "swarm-routing-verify": {
|
|
2536
|
-
const { routingVerifyMain } = await import("@deftai/directive-core/dist/swarm/routing-verify-cli.js");
|
|
2537
|
-
return routingVerifyMain;
|
|
2538
|
-
}
|
|
2539
|
-
case "swarm-routing-set": {
|
|
2540
|
-
const { routingSetMain } = await import("@deftai/directive-core/dist/swarm/routing-set-cli.js");
|
|
2541
|
-
return routingSetMain;
|
|
2542
|
-
}
|
|
2543
|
-
case "swarm-verify-review-clean": {
|
|
2544
|
-
const { verifyReviewCleanMain } = await import("@deftai/directive-core/dist/swarm/verify-review-clean-cli.js");
|
|
2545
|
-
return verifyReviewCleanMain;
|
|
2546
|
-
}
|
|
2547
|
-
case "swarm-worktrees": {
|
|
2548
|
-
const { worktreesMain } = await import("@deftai/directive-core/dist/swarm/worktrees-cli.js");
|
|
2549
|
-
return worktreesMain;
|
|
2550
|
-
}
|
|
2551
|
-
case "framework-commands": {
|
|
2552
|
-
const { frameworkCommandsMain } = await import("@deftai/directive-core/render");
|
|
2553
|
-
return (argv) => frameworkCommandsMain(argv);
|
|
2554
|
-
}
|
|
2555
|
-
case "pack-render": {
|
|
2556
|
-
const { main } = await import("@deftai/directive-core/dist/packs/pack-render.js");
|
|
2557
|
-
return (argv) => main([...argv]);
|
|
2558
|
-
}
|
|
2559
|
-
case "packs-slice": {
|
|
2560
|
-
const { main } = await import("@deftai/directive-core/dist/packs/packs-slice.js");
|
|
2561
|
-
return (argv) => main([...argv]);
|
|
2562
|
-
}
|
|
2563
|
-
case "roadmap-render": {
|
|
2564
|
-
const { main } = await import("@deftai/directive-core/dist/render/roadmap-render.js");
|
|
2565
|
-
return (argv) => main(argv);
|
|
2566
|
-
}
|
|
2567
|
-
case "spec-validate": {
|
|
2568
|
-
const { runSpecValidateCli } = await import("./render-cli/spec-validate-cli.js");
|
|
2569
|
-
return (argv) => runSpecValidateCli(argv);
|
|
2570
|
-
}
|
|
2571
|
-
case "spec-reconstruct": {
|
|
2572
|
-
const { runSpecReconstructCliEntry } = await import("./render-cli/spec-reconstruct-cli.js");
|
|
2573
|
-
return (argv) => runSpecReconstructCliEntry(argv);
|
|
2574
|
-
}
|
|
2575
|
-
case "spec-render": {
|
|
2576
|
-
const { runSpecRenderCli } = await import("./render-cli/spec-render-cli.js");
|
|
2577
|
-
return (argv) => runSpecRenderCli(argv);
|
|
2578
|
-
}
|
|
2579
|
-
case "prd-render": {
|
|
2580
|
-
const { runPrdRenderCli } = await import("./render-cli/prd-render-cli.js");
|
|
2581
|
-
return (argv) => runPrdRenderCli(argv);
|
|
2582
|
-
}
|
|
2583
|
-
case "project-render": {
|
|
2584
|
-
const { runProjectRenderCli } = await import("./render-cli/project-render-cli.js");
|
|
2585
|
-
return (argv) => runProjectRenderCli(argv);
|
|
2586
|
-
}
|
|
2587
|
-
case "project-write-narratives": {
|
|
2588
|
-
const { writePhase2NarrativesMain } = await import("@deftai/directive-core/dist/render/phase2-narratives-cli.js");
|
|
2589
|
-
return (argv) => writePhase2NarrativesMain(argv);
|
|
2590
|
-
}
|
|
2591
|
-
case "setup-seed": {
|
|
2592
|
-
const { seedFromDesignMain } = await import("@deftai/directive-core/dist/setup/seed-from-design.js");
|
|
2593
|
-
return (argv) => seedFromDesignMain(argv);
|
|
2594
|
-
}
|
|
2595
|
-
case "rule-map": {
|
|
2596
|
-
const { runRuleMapCli } = await import("./render-cli/rule-map-cli.js");
|
|
2597
|
-
return (argv) => runRuleMapCli(argv);
|
|
2598
|
-
}
|
|
2599
|
-
case "export-spec": {
|
|
2600
|
-
const { runExportSpecCli } = await import("./render-cli/export-spec-cli.js");
|
|
2601
|
-
return (argv) => runExportSpecCli(argv);
|
|
2602
|
-
}
|
|
2603
|
-
case "code-structure-validate": {
|
|
2604
|
-
const { evaluateCodeStructure } = await import("@deftai/directive-core/verify-source");
|
|
2605
|
-
return (argv) => {
|
|
2606
|
-
const parsed = parseCodeStructureArgs(argv);
|
|
2607
|
-
if (parsed.error !== undefined) {
|
|
2608
|
-
io.writeErr(`code_structure_validate: ${parsed.error}\n`);
|
|
2609
|
-
return 2;
|
|
2610
|
-
}
|
|
2611
|
-
const result = evaluateCodeStructure(parsed.projectRoot, {
|
|
2612
|
-
paths: parsed.paths.length > 0 ? parsed.paths : undefined,
|
|
2613
|
-
json: parsed.json,
|
|
2614
|
-
strict: parsed.strict,
|
|
2615
|
-
enforce: parsed.enforce,
|
|
2616
|
-
});
|
|
2617
|
-
if (result.stdout)
|
|
2618
|
-
io.writeOut(result.stdout);
|
|
2619
|
-
if (result.stderr)
|
|
2620
|
-
io.writeErr(result.stderr);
|
|
2621
|
-
return result.code;
|
|
2622
|
-
};
|
|
2623
|
-
}
|
|
2624
|
-
case "pack-migrate-skills":
|
|
2625
|
-
return (argv) => runPackMigrateSkills(argv, io);
|
|
2626
|
-
case "pack-migrate-rules":
|
|
2627
|
-
return (argv) => runPackMigrateRules(argv, io);
|
|
2628
|
-
case "pack-migrate-strategies":
|
|
2629
|
-
return (argv) => runPackMigrateStrategies(argv, io);
|
|
2630
|
-
case "pack-migrate-patterns":
|
|
2631
|
-
return (argv) => runPackMigratePatterns(argv, io);
|
|
2632
|
-
case "pack-migrate-swarm-spec":
|
|
2633
|
-
return (argv) => runPackMigrateSwarmSpec(argv, io);
|
|
2634
|
-
case "policy-set":
|
|
2635
|
-
return (argv) => runPolicySet(argv, io);
|
|
2636
|
-
case "setup-ghx":
|
|
2637
|
-
return (argv) => runSetupGhx(argv, io);
|
|
2638
|
-
case "scope-undo": {
|
|
2639
|
-
const { undoMain } = await import("@deftai/directive-core/dist/scope/main.js");
|
|
2640
|
-
return undoMain;
|
|
2641
|
-
}
|
|
2642
|
-
case "scope-demote": {
|
|
2643
|
-
const { demoteMain } = await import("@deftai/directive-core/dist/scope/main.js");
|
|
2644
|
-
return demoteMain;
|
|
2645
|
-
}
|
|
2646
|
-
case "scope-decompose": {
|
|
2647
|
-
const { decomposeMain } = await import("@deftai/directive-core/dist/scope/decompose.js");
|
|
2648
|
-
return decomposeMain;
|
|
2649
|
-
}
|
|
2650
|
-
case "changelog-resolve-unreleased": {
|
|
2651
|
-
const { changelogResolveUnreleasedMain } = await import("@deftai/directive-core/dist/platform/changelog-cli.js");
|
|
2652
|
-
return changelogResolveUnreleasedMain;
|
|
2653
|
-
}
|
|
2654
|
-
case "architecture-preflight-sor": {
|
|
2655
|
-
const { architecturePreflightSorMain } = await import("@deftai/directive-core/dist/architecture/sor-preflight.js");
|
|
2656
|
-
return architecturePreflightSorMain;
|
|
2657
|
-
}
|
|
2658
|
-
case "feedback-file": {
|
|
2659
|
-
const { mainEntry } = await import("@deftai/directive-core/dist/value/feedback-file.js");
|
|
2660
|
-
return mainEntry;
|
|
2661
|
-
}
|
|
2662
|
-
case "value-readback": {
|
|
2663
|
-
const { mainEntry } = await import("@deftai/directive-core/dist/value/readback.js");
|
|
2664
|
-
return mainEntry;
|
|
2665
|
-
}
|
|
2666
|
-
case "product-signal": {
|
|
2667
|
-
const { mainEntry } = await import("@deftai/directive-core/dist/product-signal/submit.js");
|
|
2668
|
-
return mainEntry;
|
|
2669
|
-
}
|
|
2670
|
-
case "freshness-report": {
|
|
2671
|
-
const { mainEntry } = await import("@deftai/directive-core/dist/freshness/cli.js");
|
|
2672
|
-
return mainEntry;
|
|
2673
|
-
}
|
|
2674
|
-
case "decision-write": {
|
|
2675
|
-
const { writeMainEntry } = await import("@deftai/directive-core/dist/decision/index.js");
|
|
2676
|
-
return writeMainEntry;
|
|
2677
|
-
}
|
|
2678
|
-
case "decision-list": {
|
|
2679
|
-
const { listMainEntry } = await import("@deftai/directive-core/dist/decision/index.js");
|
|
2680
|
-
return listMainEntry;
|
|
2681
|
-
}
|
|
2682
|
-
case "docs-impact": {
|
|
2683
|
-
const { docsImpactMain } = await import("@deftai/directive-core/dist/docs/docs-impact.js");
|
|
2684
|
-
return (argv) => docsImpactMain(argv);
|
|
2685
|
-
}
|
|
2686
|
-
case "tutorial": {
|
|
2687
|
-
const { tutorialMain } = await import("@deftai/directive-core/dist/tutorial/cli.js");
|
|
2688
|
-
return tutorialMain;
|
|
2689
|
-
}
|
|
2690
|
-
case "migrate-clause-ids": {
|
|
2691
|
-
const { mainEntry } = await import("@deftai/directive-core/dist/xbrief-migrate/clause-ids.js");
|
|
2692
|
-
return (argv) => mainEntry(argv);
|
|
2693
|
-
}
|
|
2694
|
-
case "migrate-confidence": {
|
|
2695
|
-
const { mainEntry } = await import("@deftai/directive-core/dist/xbrief-migrate/confidence.js");
|
|
2696
|
-
return (argv) => mainEntry(argv);
|
|
2697
|
-
}
|
|
2698
|
-
default:
|
|
2699
|
-
throw new Error(`unknown core verb: ${verb}`);
|
|
2700
|
-
}
|
|
2701
|
-
}
|
|
2702
|
-
const handlerCache = new Map();
|
|
2703
|
-
function loadHandler(canonical, io) {
|
|
2704
|
-
let pending = handlerCache.get(canonical);
|
|
2705
|
-
if (pending === undefined) {
|
|
2706
|
-
pending = CLI_MODULE_VERBS.includes(canonical)
|
|
2707
|
-
? loadCliModuleHandler(canonical, io)
|
|
2708
|
-
: loadCoreModuleHandler(canonical, io);
|
|
2709
|
-
handlerCache.set(canonical, pending);
|
|
2710
|
-
}
|
|
2711
|
-
return pending;
|
|
2712
|
-
}
|
|
2713
|
-
function defaultIo() {
|
|
2714
|
-
return {
|
|
2715
|
-
writeOut: (text) => {
|
|
2716
|
-
process.stdout.write(text);
|
|
2717
|
-
},
|
|
2718
|
-
writeErr: (text) => {
|
|
2719
|
-
process.stderr.write(text);
|
|
2720
|
-
},
|
|
2721
|
-
};
|
|
2722
|
-
}
|
|
2723
|
-
/** Resolve a user-facing verb to its canonical handler key. */
|
|
2724
|
-
export function resolveCanonicalVerb(verb) {
|
|
2725
|
-
if (CLI_MODULE_VERBS.includes(verb))
|
|
2726
|
-
return verb;
|
|
2727
|
-
if (CORE_MODULE_VERBS.includes(verb))
|
|
2728
|
-
return verb;
|
|
2729
|
-
const alias = VERB_ALIASES[verb];
|
|
2730
|
-
if (alias !== undefined)
|
|
2731
|
-
return alias;
|
|
2732
|
-
return null;
|
|
2733
|
-
}
|
|
2734
|
-
/** Sorted list of all registered verb names (canonical + aliases). */
|
|
2735
|
-
export function registeredVerbs() {
|
|
2736
|
-
const names = new Set([
|
|
2737
|
-
...CLI_MODULE_VERBS,
|
|
2738
|
-
...CORE_MODULE_VERBS,
|
|
2739
|
-
...Object.keys(VERB_ALIASES),
|
|
2740
|
-
]);
|
|
2741
|
-
return [...names].sort();
|
|
2742
|
-
}
|
|
2743
|
-
/** Top-level UX commands routed before the flat dispatcher (#1670). */
|
|
2744
|
-
const TOP_LEVEL_COMMAND_NAMES = [
|
|
2745
|
-
"init",
|
|
2746
|
-
"update",
|
|
2747
|
-
"migrate",
|
|
2748
|
-
"bootstrap",
|
|
2749
|
-
"doctor",
|
|
2750
|
-
"check",
|
|
2751
|
-
];
|
|
2752
|
-
/** Scope lifecycle verbs exposed as scope:<verb> in help (#2172). */
|
|
2753
|
-
const SCOPE_COMMAND_NAMES = [
|
|
2754
|
-
"scope:promote",
|
|
2755
|
-
"scope:activate",
|
|
2756
|
-
"scope:complete",
|
|
2757
|
-
"scope:demote",
|
|
2758
|
-
"scope:undo",
|
|
2759
|
-
"scope:record-approved-scope",
|
|
2760
|
-
"scope:record-observable-scope",
|
|
2761
|
-
"scope:record-intent-constraint",
|
|
2762
|
-
];
|
|
2763
|
-
/**
|
|
2764
|
-
* Static dual-invoke set for scm:body:* (#5521). Mirror of
|
|
2765
|
-
* packages/cli/src/cli-router/route-argv.ts SCM_BODY_COLON_VERBS — kept here
|
|
2766
|
-
* so dispatch does not import route-argv (route-argv already imports dispatch).
|
|
2767
|
-
*/
|
|
2768
|
-
const SCM_BODY_DUAL_INVOKE_VERBS = new Set([
|
|
2769
|
-
"scm:body:issue:create",
|
|
2770
|
-
"scm:body:issue:edit",
|
|
2771
|
-
"scm:body:issue:fetch",
|
|
2772
|
-
"scm:body:issue:lint",
|
|
2773
|
-
"scm:body:comment:create",
|
|
2774
|
-
"scm:body:comment:edit",
|
|
2775
|
-
"scm:body:pr:edit",
|
|
2776
|
-
"scm:body:pr:lint",
|
|
2777
|
-
]);
|
|
2778
|
-
/**
|
|
2779
|
-
* Deduplicated command names for `directive commands`, preferring colon-style
|
|
2780
|
-
* task verbs over dash-style canonical stems when both exist (#2172).
|
|
2781
|
-
*/
|
|
2782
|
-
export function preferredCommandNames() {
|
|
2783
|
-
const aliasKeys = Object.keys(VERB_ALIASES);
|
|
2784
|
-
const aliasedCanonicals = new Set(Object.values(VERB_ALIASES));
|
|
2785
|
-
const unaliasedCanonicals = [...CLI_MODULE_VERBS, ...CORE_MODULE_VERBS].filter((verb) => !aliasedCanonicals.has(verb));
|
|
2786
|
-
return [
|
|
2787
|
-
...new Set([
|
|
2788
|
-
...TOP_LEVEL_COMMAND_NAMES,
|
|
2789
|
-
...SCOPE_COMMAND_NAMES,
|
|
2790
|
-
...aliasKeys,
|
|
2791
|
-
...SCM_BODY_DUAL_INVOKE_VERBS,
|
|
2792
|
-
...unaliasedCanonicals,
|
|
2793
|
-
]),
|
|
2794
|
-
].sort();
|
|
2795
|
-
}
|
|
2796
|
-
/** Major.minor label for the curated help title (#2172). */
|
|
2797
|
-
export function helpVersionLabel() {
|
|
2798
|
-
const version = engineInfo().version;
|
|
2799
|
-
const match = /^(\d+\.\d+)/.exec(version);
|
|
2800
|
-
return match ? `v${match[1]}` : `v${version}`;
|
|
2801
|
-
}
|
|
2802
|
-
const CURATED_HELP_GROUPS = [
|
|
2803
|
-
{
|
|
2804
|
-
title: "Getting started",
|
|
2805
|
-
commands: [
|
|
2806
|
-
{ name: "init", summary: "Set up Directive in the current project (first-time setup)" },
|
|
2807
|
-
{ name: "update", summary: "Refresh an existing install and self-heal the engine" },
|
|
2808
|
-
{ name: "doctor", summary: "Diagnose the install and print the one next step" },
|
|
2809
|
-
],
|
|
2810
|
-
},
|
|
2811
|
-
{
|
|
2812
|
-
title: "Session & ritual",
|
|
2813
|
-
commands: [
|
|
2814
|
-
{
|
|
2815
|
-
name: "session:start",
|
|
2816
|
-
summary: "Claim occupancy and record ritual state for one session owner",
|
|
2817
|
-
},
|
|
2818
|
-
{
|
|
2819
|
-
name: "session:ready",
|
|
2820
|
-
summary: "One-shot recovery to gated write-ready (session + ritual + cache)",
|
|
2821
|
-
},
|
|
2822
|
-
{
|
|
2823
|
-
name: "occupancy:steal",
|
|
2824
|
-
summary: "Replace only the live lease; use session:start --steal to align ritual",
|
|
2825
|
-
},
|
|
2826
|
-
{
|
|
2827
|
-
name: "occupancy:release",
|
|
2828
|
-
summary: "Release this worktree occupancy lease (owner live, or expired residue)",
|
|
2829
|
-
},
|
|
2830
|
-
{
|
|
2831
|
-
name: "occupancy:heartbeat",
|
|
2832
|
-
summary: "Refresh your own live lease during long quiet stretches (never claims)",
|
|
2833
|
-
},
|
|
2834
|
-
{
|
|
2835
|
-
name: "occupancy:grant",
|
|
2836
|
-
summary: "Admit a dispatched child to your lease for writes (--revoke withdraws)",
|
|
2837
|
-
},
|
|
2838
|
-
{
|
|
2839
|
-
name: "session:end",
|
|
2840
|
-
summary: "End this mutation session by releasing occupancy (same as occupancy:release)",
|
|
2841
|
-
},
|
|
2842
|
-
{
|
|
2843
|
-
name: "freshness:report",
|
|
2844
|
-
summary: "Bound vs live deposit generation (current|stale_soft|stale_hard)",
|
|
2845
|
-
},
|
|
2846
|
-
{
|
|
2847
|
-
name: "freshness:bind",
|
|
2848
|
-
summary: "Bind live deposit generation into this session (no host restart)",
|
|
2849
|
-
},
|
|
2850
|
-
{
|
|
2851
|
-
name: "scm:status",
|
|
2852
|
-
summary: "Probe gh/ghx + auth readiness in this execution env (#2275)",
|
|
2853
|
-
},
|
|
2854
|
-
{
|
|
2855
|
-
name: "lifecycle:event",
|
|
2856
|
-
summary: "Record review-cycle plan:approved approval events",
|
|
2857
|
-
},
|
|
2858
|
-
{
|
|
2859
|
-
name: "lifecycle:stats",
|
|
2860
|
-
summary: "Local xBRIEF lifecycle folder counts for process rollups",
|
|
2861
|
-
},
|
|
2862
|
-
],
|
|
2863
|
-
},
|
|
2864
|
-
{
|
|
2865
|
-
title: "Quality & gates",
|
|
2866
|
-
commands: [{ name: "check", summary: "Run install and lifecycle quality gates" }],
|
|
2867
|
-
},
|
|
2868
|
-
{
|
|
2869
|
-
title: "Work queue & triage",
|
|
2870
|
-
commands: [
|
|
2871
|
-
{ name: "triage:welcome", summary: "Session orientation and triage one-liner" },
|
|
2872
|
-
{ name: "triage:queue", summary: "Ranked work queue for what to do next" },
|
|
2873
|
-
],
|
|
2874
|
-
},
|
|
2875
|
-
{
|
|
2876
|
-
title: "Scope lifecycle",
|
|
2877
|
-
commands: [{ name: "scope:promote", summary: "Promote a scope xBRIEF to pending" }],
|
|
2878
|
-
},
|
|
2879
|
-
{
|
|
2880
|
-
title: "xBRIEF create/verify (not lifecycle)",
|
|
2881
|
-
commands: [
|
|
2882
|
-
{
|
|
2883
|
-
name: "xbrief:create",
|
|
2884
|
-
summary: "Write a dense xBRIEF artifact at --out (requires --format); not scope:*",
|
|
2885
|
-
},
|
|
2886
|
-
{
|
|
2887
|
-
name: "xbrief:verify",
|
|
2888
|
-
summary: "Verify a dense xBRIEF artifact at --out (requires --format); not scope:*",
|
|
2889
|
-
},
|
|
2890
|
-
{
|
|
2891
|
-
name: "xbrief:adopt-stored-plan-id",
|
|
2892
|
-
summary: "Set plan.id from the stored plan-id binding; refuse if another artifact has it",
|
|
2893
|
-
},
|
|
2894
|
-
],
|
|
2895
|
-
},
|
|
2896
|
-
{
|
|
2897
|
-
title: "Project artifacts",
|
|
2898
|
-
commands: [
|
|
2899
|
-
{ name: "project:render", summary: "Render PROJECT-DEFINITION projection" },
|
|
2900
|
-
{
|
|
2901
|
-
name: "project:write-narratives",
|
|
2902
|
-
summary: "Store Phase 2 narratives in PROJECT-DEFINITION without policy keys",
|
|
2903
|
-
},
|
|
2904
|
-
{
|
|
2905
|
-
name: "setup:seed",
|
|
2906
|
-
summary: "Draft Phase 2 narratives from --from design.md or --answers file (confirm before write)",
|
|
2907
|
-
},
|
|
2908
|
-
{ name: "spec:render", summary: "Render specification projection" },
|
|
2909
|
-
],
|
|
2910
|
-
},
|
|
2911
|
-
];
|
|
2912
|
-
function formatHelpCommand(command) {
|
|
2913
|
-
const padding = " ".repeat(Math.max(1, 22 - command.name.length));
|
|
2914
|
-
return ` ${command.name}${padding}${command.summary}\n`;
|
|
2915
|
-
}
|
|
2916
|
-
/**
|
|
2917
|
-
* Print the exhaustive registered-command list for `directive commands` (#2172).
|
|
2918
|
-
* Lists deduplicated preferred names (colon-style when available).
|
|
2919
|
-
*/
|
|
2920
|
-
export function printCommandsList(io = defaultIo()) {
|
|
2921
|
-
io.writeOut("Registered commands:\n");
|
|
2922
|
-
for (const name of preferredCommandNames()) {
|
|
2923
|
-
io.writeOut(` ${name}\n`);
|
|
2924
|
-
}
|
|
2925
|
-
}
|
|
2926
|
-
/**
|
|
2927
|
-
* Print curated top-level help: title/version, usage, common options, grouped
|
|
2928
|
-
* common commands, and a pointer to `directive commands` for the full list
|
|
2929
|
-
* (#2172). Preserves the init/update/doctor first-run guidance from #2273.
|
|
2930
|
-
*/
|
|
2931
|
-
export function printHelp(io = defaultIo()) {
|
|
2932
|
-
io.writeOut(`Directive ${helpVersionLabel()}\n`);
|
|
2933
|
-
io.writeOut("AI development framework CLI for project lifecycle, scope, and quality gates.\n\n");
|
|
2934
|
-
io.writeOut("Usage:\n");
|
|
2935
|
-
io.writeOut(" directive <command> [options]\n");
|
|
2936
|
-
io.writeOut(" directive help\n");
|
|
2937
|
-
io.writeOut(" directive commands List every registered command\n\n");
|
|
2938
|
-
io.writeOut("Options:\n");
|
|
2939
|
-
io.writeOut(" -h, --help Show this help\n");
|
|
2940
|
-
io.writeOut(" -V, --version Print version information\n\n");
|
|
2941
|
-
for (const group of CURATED_HELP_GROUPS) {
|
|
2942
|
-
io.writeOut(`${group.title}:\n`);
|
|
2943
|
-
for (const command of group.commands) {
|
|
2944
|
-
io.writeOut(formatHelpCommand(command));
|
|
2945
|
-
}
|
|
2946
|
-
io.writeOut("\n");
|
|
2947
|
-
}
|
|
2948
|
-
io.writeOut("Commands use colon style (e.g. triage:queue); dash-style aliases remain supported.\n" +
|
|
2949
|
-
"Run `directive commands` for the full registered-command list.\n\n" +
|
|
2950
|
-
"First run? From the project root:\n" +
|
|
2951
|
-
" 1. npm i -g @deftai/directive (Node >= 20)\n" +
|
|
2952
|
-
" (pnpm: pnpm add -g @deftai/directive -- ensure PNPM_HOME is on PATH, run `pnpm setup` if needed)\n" +
|
|
2953
|
-
" 2. directive init\n" +
|
|
2954
|
-
" 3. directive doctor\n" +
|
|
2955
|
-
" 4. directive toolchain:check --consumer\n" +
|
|
2956
|
-
"New clone where `directive` will not run? Read the Cold-start bootstrap block at the top of README.md.\n");
|
|
2957
|
-
}
|
|
2958
|
-
async function invokeHandler(handler, argv) {
|
|
2959
|
-
const code = await handler(argv);
|
|
2960
|
-
return typeof code === "number" ? code : 0;
|
|
2961
|
-
}
|
|
2962
|
-
const PLAN_SEQUENCE_VERBS = "set|current|clear|advance";
|
|
2963
|
-
/**
|
|
2964
|
-
* Unknown-colon-verb remediation (#3439 / #2652 / #5521).
|
|
2965
|
-
* Never re-suggest the failing `deft <verb>` spelling. Prefer Taskfile
|
|
2966
|
-
* dual-invoke forms when known; demote hyphen stems; no `task --list` shell-out.
|
|
2967
|
-
*/
|
|
2968
|
-
export function unknownColonVerbHint(verb, context = "unknown") {
|
|
2969
|
-
if (verb.startsWith("plan-sequence:") && !(verb in PLAN_SEQUENCE_ALIAS_SUBCOMMANDS)) {
|
|
2970
|
-
return (`hint: plan-sequence verbs are ${PLAN_SEQUENCE_VERBS} ` +
|
|
2971
|
-
`(e.g. \`deft plan-sequence:current\`). '${verb}' is not a verb. ` +
|
|
2972
|
-
`Do not run bare \`task ${verb}\` -- consumer include-only Taskfiles cannot resolve it.\n`);
|
|
2973
|
-
}
|
|
2974
|
-
const hyphen = verb.replaceAll(":", "-");
|
|
2975
|
-
const hyphenNote = `Hyphen stem \`${hyphen}\` is not primary recovery.`;
|
|
2976
|
-
const listing = "List registered CLI verbs with `deft commands`.";
|
|
2977
|
-
if (SCM_BODY_DUAL_INVOKE_VERBS.has(verb)) {
|
|
2978
|
-
if (context === "include-only") {
|
|
2979
|
-
return (`hint: try \`task deft:${verb}\` (consumer include Taskfile). ` +
|
|
2980
|
-
`Bare \`task ${verb}\` is framework-only. ${hyphenNote} ${listing}\n`);
|
|
2981
|
-
}
|
|
2982
|
-
if (context === "no-taskfile") {
|
|
2983
|
-
return (`hint: Taskfile recovery is unavailable here. ` +
|
|
2984
|
-
`On a framework checkout with go-task: \`task ${verb}\`; ` +
|
|
2985
|
-
`on an include-only deposit: \`task deft:${verb}\`. ${hyphenNote} ${listing}\n`);
|
|
2986
|
-
}
|
|
2987
|
-
if (context === "framework") {
|
|
2988
|
-
return (`hint: try \`task ${verb}\` (framework Taskfile) or \`task deft:${verb}\` (consumer include). ` +
|
|
2989
|
-
`${hyphenNote} ${listing}\n`);
|
|
2990
|
-
}
|
|
2991
|
-
return (`hint: if go-task is available, try \`task ${verb}\` (framework) or ` +
|
|
2992
|
-
`\`task deft:${verb}\` (consumer include) — Taskfile recovery is conditional. ` +
|
|
2993
|
-
`${hyphenNote} ${listing}\n`);
|
|
2994
|
-
}
|
|
2995
|
-
if (context === "include-only") {
|
|
2996
|
-
return (`hint: do not retry the same \`deft ${verb}\` spelling. ` +
|
|
2997
|
-
`If a Taskfile task exists, try \`task deft:${verb}\` (include-only). ` +
|
|
2998
|
-
`${hyphenNote} ${listing}\n`);
|
|
2999
|
-
}
|
|
3000
|
-
if (context === "no-taskfile") {
|
|
3001
|
-
return (`hint: do not retry the same \`deft ${verb}\` spelling. ` +
|
|
3002
|
-
`Taskfile recovery is unavailable here. ${hyphenNote} ${listing}\n`);
|
|
3003
|
-
}
|
|
3004
|
-
if (context === "framework") {
|
|
3005
|
-
return (`hint: do not retry the same \`deft ${verb}\` spelling. ` +
|
|
3006
|
-
`If a Taskfile task exists, try \`task ${verb}\` (framework) or \`task deft:${verb}\` (include). ` +
|
|
3007
|
-
`${hyphenNote} ${listing}\n`);
|
|
3008
|
-
}
|
|
3009
|
-
return (`hint: do not retry the same \`deft ${verb}\` spelling. ` +
|
|
3010
|
-
`If a Taskfile task exists, try \`task ${verb}\` (framework) or \`task deft:${verb}\` (include) — ` +
|
|
3011
|
-
`Taskfile recovery is conditional. ${hyphenNote} ${listing}\n`);
|
|
3012
|
-
}
|
|
3013
|
-
function versionBanner() {
|
|
3014
|
-
return `${identityBanner()}\n`;
|
|
3015
|
-
}
|
|
3016
|
-
/** Dispatch argv to a registered verb; returns the handler exit code. */
|
|
3017
|
-
export async function dispatch(argv, io = defaultIo()) {
|
|
3018
|
-
if (argv[0] === "--version" || argv[0] === "-V") {
|
|
3019
|
-
io.writeOut(versionBanner());
|
|
3020
|
-
return 0;
|
|
3021
|
-
}
|
|
3022
|
-
if (argv.length === 0 || argv[0] === "--help" || argv[0] === "-h" || argv[0] === "help") {
|
|
3023
|
-
printHelp(io);
|
|
3024
|
-
return 0;
|
|
3025
|
-
}
|
|
3026
|
-
const [verb, ...rest] = argv;
|
|
3027
|
-
if (verb === "commands") {
|
|
3028
|
-
printCommandsList(io);
|
|
3029
|
-
return 0;
|
|
3030
|
-
}
|
|
3031
|
-
const canonical = resolveCanonicalVerb(verb ?? "");
|
|
3032
|
-
if (canonical === null) {
|
|
3033
|
-
io.writeErr(`directive: unknown verb '${verb}'\n`);
|
|
3034
|
-
if (verb?.includes(":")) {
|
|
3035
|
-
io.writeErr(unknownColonVerbHint(verb));
|
|
3036
|
-
}
|
|
3037
|
-
return 1;
|
|
3038
|
-
}
|
|
3039
|
-
try {
|
|
3040
|
-
const triageSubcommand = verb !== undefined ? TRIAGE_ACTION_ALIAS_SUBCOMMANDS[verb] : undefined;
|
|
3041
|
-
const policySubcommand = verb !== undefined ? POLICY_ACTION_ALIAS_SUBCOMMANDS[verb] : undefined;
|
|
3042
|
-
const authzSubcommand = verb !== undefined ? AUTHZ_ACTION_ALIAS_SUBCOMMANDS[verb] : undefined;
|
|
3043
|
-
const escalationSubcommand = verb !== undefined ? ESCALATION_ACTION_ALIAS_SUBCOMMANDS[verb] : undefined;
|
|
3044
|
-
const planSequenceSubcommand = verb !== undefined ? PLAN_SEQUENCE_ALIAS_SUBCOMMANDS[verb] : undefined;
|
|
3045
|
-
const tutorialSubcommand = verb !== undefined ? TUTORIAL_ALIAS_SUBCOMMANDS[verb] : undefined;
|
|
3046
|
-
const productSignalSubcommand = verb !== undefined ? PRODUCT_SIGNAL_ALIAS_SUBCOMMANDS[verb] : undefined;
|
|
3047
|
-
const freshnessSubcommand = verb !== undefined ? FRESHNESS_ALIAS_SUBCOMMANDS[verb] : undefined;
|
|
3048
|
-
const vbriefValidateSubcommand = verb === "verify:vbrief-conformance" && canonical === "vbrief-validate"
|
|
3049
|
-
? "conformance"
|
|
3050
|
-
: undefined;
|
|
3051
|
-
const handlerArgv = canonical === "framework-commands" && verb !== undefined && verb !== canonical
|
|
3052
|
-
? [verb, ...rest]
|
|
3053
|
-
: triageSubcommand !== undefined && canonical === "triage-actions"
|
|
3054
|
-
? [triageSubcommand, ...rest]
|
|
3055
|
-
: policySubcommand !== undefined && canonical === "policy"
|
|
3056
|
-
? [policySubcommand, ...rest]
|
|
3057
|
-
: authzSubcommand !== undefined && canonical === "authz"
|
|
3058
|
-
? [authzSubcommand, ...rest]
|
|
3059
|
-
: escalationSubcommand !== undefined && canonical === "escalation-cli"
|
|
3060
|
-
? [escalationSubcommand, ...rest]
|
|
3061
|
-
: planSequenceSubcommand !== undefined && canonical === "plan-sequence"
|
|
3062
|
-
? [planSequenceSubcommand, ...rest]
|
|
3063
|
-
: tutorialSubcommand !== undefined && canonical === "tutorial"
|
|
3064
|
-
? [tutorialSubcommand, ...rest]
|
|
3065
|
-
: productSignalSubcommand !== undefined && canonical === "product-signal"
|
|
3066
|
-
? [productSignalSubcommand, ...rest]
|
|
3067
|
-
: freshnessSubcommand !== undefined && canonical === "freshness-report"
|
|
3068
|
-
? [freshnessSubcommand, ...rest]
|
|
3069
|
-
: vbriefValidateSubcommand !== undefined
|
|
3070
|
-
? [vbriefValidateSubcommand, ...rest]
|
|
3071
|
-
: rest;
|
|
3072
|
-
const helpRc = interceptHelp(canonical.replaceAll("-", "_"), handlerArgv, {
|
|
3073
|
-
write: io.writeOut,
|
|
3074
|
-
});
|
|
3075
|
-
if (helpRc !== null) {
|
|
3076
|
-
return helpRc;
|
|
3077
|
-
}
|
|
3078
|
-
const handler = await loadHandler(canonical, io);
|
|
3079
|
-
return await invokeHandler(handler, handlerArgv);
|
|
3080
|
-
}
|
|
3081
|
-
catch (err) {
|
|
3082
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
3083
|
-
io.writeErr(`directive: ${message}\n`);
|
|
3084
|
-
return 2;
|
|
3085
|
-
}
|
|
3086
|
-
}
|
|
3087
|
-
/** Test seam: reset lazy handler cache between cases. */
|
|
3088
|
-
export function resetHandlerCacheForTests() {
|
|
3089
|
-
handlerCache.clear();
|
|
3090
|
-
}
|
|
3091
|
-
//# sourceMappingURL=dispatch.js.map
|