mandrel 1.57.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/.agents/README.md +954 -0
- package/.agents/docs/SDLC.md +1420 -0
- package/.agents/docs/agentrc-reference.json +278 -0
- package/.agents/docs/configuration.md +1040 -0
- package/.agents/docs/workflows.md +59 -0
- package/.agents/instructions.md +384 -0
- package/.agents/personas/architect.md +107 -0
- package/.agents/personas/devops-engineer.md +36 -0
- package/.agents/personas/engineer-mobile.md +119 -0
- package/.agents/personas/engineer-web.md +110 -0
- package/.agents/personas/engineer.md +90 -0
- package/.agents/personas/product.md +88 -0
- package/.agents/personas/project-manager.md +110 -0
- package/.agents/personas/qa-engineer.md +91 -0
- package/.agents/personas/refactorer.md +110 -0
- package/.agents/personas/security-engineer.md +112 -0
- package/.agents/personas/sre.md +86 -0
- package/.agents/personas/technical-writer.md +100 -0
- package/.agents/personas/ux-designer.md +95 -0
- package/.agents/rules/api-conventions.md +75 -0
- package/.agents/rules/changelog-style.md +238 -0
- package/.agents/rules/gherkin-standards.md +146 -0
- package/.agents/rules/git-conventions.md +146 -0
- package/.agents/rules/orchestration-error-handling.md +35 -0
- package/.agents/rules/security-baseline.md +92 -0
- package/.agents/rules/shell-conventions.md +70 -0
- package/.agents/rules/test-seams.md +59 -0
- package/.agents/rules/testing-standards.md +177 -0
- package/.agents/runtime-deps.json +18 -0
- package/.agents/schemas/acceptance-eval-verdict.schema.json +93 -0
- package/.agents/schemas/agentrc.schema.json +1583 -0
- package/.agents/schemas/audit-results.schema.json +69 -0
- package/.agents/schemas/audit-rules.json +134 -0
- package/.agents/schemas/audit-rules.schema.json +69 -0
- package/.agents/schemas/baselines/baseline-envelope.schema.json +44 -0
- package/.agents/schemas/baselines/bundle-size.schema.json +47 -0
- package/.agents/schemas/baselines/coverage.schema.json +50 -0
- package/.agents/schemas/baselines/crap.schema.json +52 -0
- package/.agents/schemas/baselines/duplication.schema.json +62 -0
- package/.agents/schemas/baselines/lighthouse.schema.json +59 -0
- package/.agents/schemas/baselines/lint.schema.json +47 -0
- package/.agents/schemas/baselines/maintainability.schema.json +71 -0
- package/.agents/schemas/baselines/mutation.schema.json +52 -0
- package/.agents/schemas/crap-baseline.schema.json +57 -0
- package/.agents/schemas/crap-report.schema.json +102 -0
- package/.agents/schemas/dispatch-manifest.json +232 -0
- package/.agents/schemas/epic-perf-report.schema.json +89 -0
- package/.agents/schemas/epic-spec.schema.json +183 -0
- package/.agents/schemas/friction-event.schema.json +56 -0
- package/.agents/schemas/lifecycle/README.md +18 -0
- package/.agents/schemas/lifecycle/acceptance.reconcile.failed.schema.json +13 -0
- package/.agents/schemas/lifecycle/acceptance.reconcile.ok.schema.json +13 -0
- package/.agents/schemas/lifecycle/acceptance.reconcile.skipped.schema.json +13 -0
- package/.agents/schemas/lifecycle/acceptance.reconcile.start.schema.json +12 -0
- package/.agents/schemas/lifecycle/acceptance.reconcile.waived.schema.json +13 -0
- package/.agents/schemas/lifecycle/checkpoint.written.schema.json +13 -0
- package/.agents/schemas/lifecycle/close-validate.end.schema.json +18 -0
- package/.agents/schemas/lifecycle/close-validate.start.schema.json +13 -0
- package/.agents/schemas/lifecycle/code-review.end.schema.json +30 -0
- package/.agents/schemas/lifecycle/code-review.start.schema.json +12 -0
- package/.agents/schemas/lifecycle/epic.automerge.end.schema.json +14 -0
- package/.agents/schemas/lifecycle/epic.automerge.start.schema.json +13 -0
- package/.agents/schemas/lifecycle/epic.blocked.schema.json +13 -0
- package/.agents/schemas/lifecycle/epic.cleanup.end.schema.json +12 -0
- package/.agents/schemas/lifecycle/epic.cleanup.start.schema.json +12 -0
- package/.agents/schemas/lifecycle/epic.close.end.schema.json +12 -0
- package/.agents/schemas/lifecycle/epic.complete.schema.json +13 -0
- package/.agents/schemas/lifecycle/epic.finalize.end.schema.json +13 -0
- package/.agents/schemas/lifecycle/epic.finalize.start.schema.json +12 -0
- package/.agents/schemas/lifecycle/epic.merge.armed.schema.json +13 -0
- package/.agents/schemas/lifecycle/epic.merge.blocked.schema.json +14 -0
- package/.agents/schemas/lifecycle/epic.merge.confirmed.schema.json +17 -0
- package/.agents/schemas/lifecycle/epic.merge.ready.schema.json +15 -0
- package/.agents/schemas/lifecycle/epic.plan.end.schema.json +18 -0
- package/.agents/schemas/lifecycle/epic.plan.start.schema.json +12 -0
- package/.agents/schemas/lifecycle/epic.snapshot.end.schema.json +16 -0
- package/.agents/schemas/lifecycle/epic.snapshot.start.schema.json +12 -0
- package/.agents/schemas/lifecycle/epic.watch.end.schema.json +28 -0
- package/.agents/schemas/lifecycle/epic.watch.start.schema.json +16 -0
- package/.agents/schemas/lifecycle/intervention.recorded.schema.json +15 -0
- package/.agents/schemas/lifecycle/ledger-record.schema.json +59 -0
- package/.agents/schemas/lifecycle/notification.emitted.schema.json +18 -0
- package/.agents/schemas/lifecycle/pr.created.schema.json +14 -0
- package/.agents/schemas/lifecycle/retro.end.schema.json +16 -0
- package/.agents/schemas/lifecycle/retro.start.schema.json +12 -0
- package/.agents/schemas/lifecycle/story.blocked.schema.json +13 -0
- package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +17 -0
- package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +15 -0
- package/.agents/schemas/lifecycle/story.heartbeat.schema.json +20 -0
- package/.agents/schemas/lifecycle/story.merged.schema.json +13 -0
- package/.agents/schemas/mi-report.schema.json +58 -0
- package/.agents/schemas/model-attribution.schema.json +49 -0
- package/.agents/schemas/qa-finding.schema.json +133 -0
- package/.agents/schemas/qa-ledger.schema.json +89 -0
- package/.agents/schemas/risk-verdict.schema.json +53 -0
- package/.agents/schemas/signal-event.schema.json +58 -0
- package/.agents/schemas/skill.schema.json +31 -0
- package/.agents/schemas/skills-index.schema.json +81 -0
- package/.agents/schemas/story-perf-summary.schema.json +73 -0
- package/.agents/schemas/validation-evidence.schema.json +78 -0
- package/.agents/scripts/README.md +93 -0
- package/.agents/scripts/acceptance-eval.js +284 -0
- package/.agents/scripts/acceptance-spec-reconciler.js +556 -0
- package/.agents/scripts/agents-bootstrap-github.js +634 -0
- package/.agents/scripts/analyze-execution.js +369 -0
- package/.agents/scripts/assert-branch.js +83 -0
- package/.agents/scripts/audit-labels-bootstrap.js +253 -0
- package/.agents/scripts/audit-to-stories.js +257 -0
- package/.agents/scripts/bootstrap.js +1378 -0
- package/.agents/scripts/check-baselines.js +81 -0
- package/.agents/scripts/check-dead-exports.js +311 -0
- package/.agents/scripts/check-doc-links.js +401 -0
- package/.agents/scripts/check-gherkin-placeholders.js +663 -0
- package/.agents/scripts/check-lifecycle-doc-drift.js +402 -0
- package/.agents/scripts/check-lifecycle-lint.js +379 -0
- package/.agents/scripts/check-prepush-recovery.js +90 -0
- package/.agents/scripts/check-windows-git-perf.js +138 -0
- package/.agents/scripts/cleanup-repo-test-temp.js +67 -0
- package/.agents/scripts/coverage-capture.js +112 -0
- package/.agents/scripts/detect-merges.js +111 -0
- package/.agents/scripts/diagnose-friction.js +257 -0
- package/.agents/scripts/diagnose.js +240 -0
- package/.agents/scripts/dispatcher.js +295 -0
- package/.agents/scripts/drain-pending-cleanup.js +147 -0
- package/.agents/scripts/epic-audit-prepare.js +419 -0
- package/.agents/scripts/epic-audit-recheck.js +241 -0
- package/.agents/scripts/epic-deliver-note-intervention.js +192 -0
- package/.agents/scripts/epic-deliver-preflight.js +407 -0
- package/.agents/scripts/epic-deliver-prepare.js +383 -0
- package/.agents/scripts/epic-execute-record-wave.js +463 -0
- package/.agents/scripts/epic-plan-clarity.js +201 -0
- package/.agents/scripts/epic-plan-decompose.js +79 -0
- package/.agents/scripts/epic-plan-healthcheck.js +363 -0
- package/.agents/scripts/epic-plan-spec-validate.js +111 -0
- package/.agents/scripts/epic-plan-spec.js +198 -0
- package/.agents/scripts/epic-reconcile.js +637 -0
- package/.agents/scripts/evidence-gate.js +235 -0
- package/.agents/scripts/generate-config-docs.js +516 -0
- package/.agents/scripts/generate-lifecycle-docs.js +224 -0
- package/.agents/scripts/generate-skills-index.js +252 -0
- package/.agents/scripts/generate-workflows-doc.js +168 -0
- package/.agents/scripts/git-cleanup.js +124 -0
- package/.agents/scripts/git-pr-quality-gate.js +203 -0
- package/.agents/scripts/git-rebase-and-resolve.js +234 -0
- package/.agents/scripts/hierarchy-gate.js +176 -0
- package/.agents/scripts/hydrate-context.js +179 -0
- package/.agents/scripts/install-matrix-assert.js +282 -0
- package/.agents/scripts/lib/Graph.js +326 -0
- package/.agents/scripts/lib/ITicketingProvider.js +349 -0
- package/.agents/scripts/lib/Logger.js +194 -0
- package/.agents/scripts/lib/audit-suite/cli.js +64 -0
- package/.agents/scripts/lib/audit-suite/findings.js +164 -0
- package/.agents/scripts/lib/audit-suite/frontmatter-lint.js +32 -0
- package/.agents/scripts/lib/audit-suite/frontmatter.js +110 -0
- package/.agents/scripts/lib/audit-suite/index.js +22 -0
- package/.agents/scripts/lib/audit-suite/runner.js +233 -0
- package/.agents/scripts/lib/audit-suite/selector.js +235 -0
- package/.agents/scripts/lib/audit-suite/substitutions.js +124 -0
- package/.agents/scripts/lib/audit-suite/workflow-loader.js +49 -0
- package/.agents/scripts/lib/audit-to-stories/build-story-body.js +130 -0
- package/.agents/scripts/lib/audit-to-stories/dedupe-against-github.js +114 -0
- package/.agents/scripts/lib/audit-to-stories/finding-adapter.js +93 -0
- package/.agents/scripts/lib/audit-to-stories/group-findings.js +265 -0
- package/.agents/scripts/lib/audit-to-stories/parse-audit-md.js +246 -0
- package/.agents/scripts/lib/audit-to-stories/seed-epic-from-findings.js +160 -0
- package/.agents/scripts/lib/auto-refresh-baselines.js +308 -0
- package/.agents/scripts/lib/baseline-loader.js +0 -0
- package/.agents/scripts/lib/baseline-schema-registry.js +69 -0
- package/.agents/scripts/lib/baseline-snapshot.js +716 -0
- package/.agents/scripts/lib/baselines/component-matcher.js +21 -0
- package/.agents/scripts/lib/baselines/components.js +126 -0
- package/.agents/scripts/lib/baselines/diff-scope-cli.js +203 -0
- package/.agents/scripts/lib/baselines/duplication-scanner.js +220 -0
- package/.agents/scripts/lib/baselines/env-overrides.js +129 -0
- package/.agents/scripts/lib/baselines/envelope.js +368 -0
- package/.agents/scripts/lib/baselines/exit-codes.js +89 -0
- package/.agents/scripts/lib/baselines/git-base.js +0 -0
- package/.agents/scripts/lib/baselines/kernel.js +111 -0
- package/.agents/scripts/lib/baselines/kinds/_shared-metric.js +220 -0
- package/.agents/scripts/lib/baselines/kinds/bundle-size.js +157 -0
- package/.agents/scripts/lib/baselines/kinds/coverage.js +194 -0
- package/.agents/scripts/lib/baselines/kinds/crap.js +555 -0
- package/.agents/scripts/lib/baselines/kinds/duplication.js +197 -0
- package/.agents/scripts/lib/baselines/kinds/lighthouse.js +185 -0
- package/.agents/scripts/lib/baselines/kinds/lint.js +172 -0
- package/.agents/scripts/lib/baselines/kinds/maintainability.js +340 -0
- package/.agents/scripts/lib/baselines/kinds/mutation.js +153 -0
- package/.agents/scripts/lib/baselines/path-canon.js +279 -0
- package/.agents/scripts/lib/baselines/preview-gates.js +298 -0
- package/.agents/scripts/lib/baselines/reader.js +321 -0
- package/.agents/scripts/lib/baselines/refresh-service.js +733 -0
- package/.agents/scripts/lib/baselines/scope.js +291 -0
- package/.agents/scripts/lib/baselines/writer.js +312 -0
- package/.agents/scripts/lib/bdd-runner-detect.js +417 -0
- package/.agents/scripts/lib/bdd-scenario-scanner.js +310 -0
- package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +202 -0
- package/.agents/scripts/lib/bootstrap/branch-protection.js +222 -0
- package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +171 -0
- package/.agents/scripts/lib/bootstrap/commit-push.js +146 -0
- package/.agents/scripts/lib/bootstrap/gh-list.js +153 -0
- package/.agents/scripts/lib/bootstrap/gh-preflight.js +306 -0
- package/.agents/scripts/lib/bootstrap/hitl-confirm.js +89 -0
- package/.agents/scripts/lib/bootstrap/install-ledger.js +174 -0
- package/.agents/scripts/lib/bootstrap/manifest.js +272 -0
- package/.agents/scripts/lib/bootstrap/merge-methods.js +108 -0
- package/.agents/scripts/lib/bootstrap/preflight.js +195 -0
- package/.agents/scripts/lib/bootstrap/project-bootstrap.js +801 -0
- package/.agents/scripts/lib/bootstrap/prompt.js +480 -0
- package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +370 -0
- package/.agents/scripts/lib/bootstrap/summary.js +75 -0
- package/.agents/scripts/lib/bootstrap/workflow-audit.js +256 -0
- package/.agents/scripts/lib/branch-name-guard.js +98 -0
- package/.agents/scripts/lib/c8-cli-path.js +21 -0
- package/.agents/scripts/lib/changed-files.js +184 -0
- package/.agents/scripts/lib/checks/baseline-drift-main-checkout.js +104 -0
- package/.agents/scripts/lib/checks/core-bare-clean.js +48 -0
- package/.agents/scripts/lib/checks/epic-merge-lock-stale.js +54 -0
- package/.agents/scripts/lib/checks/index.js +288 -0
- package/.agents/scripts/lib/checks/push-hook-parity.js +106 -0
- package/.agents/scripts/lib/checks/stale-origin-epic.js +49 -0
- package/.agents/scripts/lib/checks/state.js +558 -0
- package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +186 -0
- package/.agents/scripts/lib/checks/subagent-agent-tool-required.js +182 -0
- package/.agents/scripts/lib/checks/windows-coverage-noise-floor.js +92 -0
- package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +81 -0
- package/.agents/scripts/lib/checks/worktree-residue-biome.js +55 -0
- package/.agents/scripts/lib/cli/parse-numeric.js +60 -0
- package/.agents/scripts/lib/cli/standard-args.js +351 -0
- package/.agents/scripts/lib/cli-args.js +286 -0
- package/.agents/scripts/lib/cli-utils.js +69 -0
- package/.agents/scripts/lib/close-validation/projections/head-sha.js +44 -0
- package/.agents/scripts/lib/close-validation/projections/inputs.js +86 -0
- package/.agents/scripts/lib/close-validation/projections/maintainability.js +286 -0
- package/.agents/scripts/lib/close-validation.js +897 -0
- package/.agents/scripts/lib/codebase-snapshot.js +513 -0
- package/.agents/scripts/lib/command-header.js +33 -0
- package/.agents/scripts/lib/config/acceptance-eval.js +95 -0
- package/.agents/scripts/lib/config/baselines.js +60 -0
- package/.agents/scripts/lib/config/ci.js +30 -0
- package/.agents/scripts/lib/config/commands.js +36 -0
- package/.agents/scripts/lib/config/defaults.js +119 -0
- package/.agents/scripts/lib/config/explain.js +348 -0
- package/.agents/scripts/lib/config/gates/bundle-size.schema.js +23 -0
- package/.agents/scripts/lib/config/gates/coverage.schema.js +18 -0
- package/.agents/scripts/lib/config/gates/crap.schema.js +33 -0
- package/.agents/scripts/lib/config/gates/duplication.schema.js +26 -0
- package/.agents/scripts/lib/config/gates/index.js +36 -0
- package/.agents/scripts/lib/config/gates/lighthouse.schema.js +23 -0
- package/.agents/scripts/lib/config/gates/lint.schema.js +9 -0
- package/.agents/scripts/lib/config/gates/maintainability.schema.js +20 -0
- package/.agents/scripts/lib/config/gates/mutation.schema.js +12 -0
- package/.agents/scripts/lib/config/gates/shared.js +117 -0
- package/.agents/scripts/lib/config/github.js +122 -0
- package/.agents/scripts/lib/config/lifecycle.js +40 -0
- package/.agents/scripts/lib/config/limits.js +211 -0
- package/.agents/scripts/lib/config/paths.js +73 -0
- package/.agents/scripts/lib/config/preflight.js +58 -0
- package/.agents/scripts/lib/config/quality.js +665 -0
- package/.agents/scripts/lib/config/retro.js +77 -0
- package/.agents/scripts/lib/config/runners.js +105 -0
- package/.agents/scripts/lib/config/runtime.js +167 -0
- package/.agents/scripts/lib/config/shared.js +46 -0
- package/.agents/scripts/lib/config/sync-agentrc.js +243 -0
- package/.agents/scripts/lib/config/temp-paths.js +373 -0
- package/.agents/scripts/lib/config/validate-orchestration.js +81 -0
- package/.agents/scripts/lib/config/worktree-isolation.js +80 -0
- package/.agents/scripts/lib/config-resolver.js +298 -0
- package/.agents/scripts/lib/config-schema-shared.js +32 -0
- package/.agents/scripts/lib/config-schema.js +20 -0
- package/.agents/scripts/lib/config-settings-schema-delivery.js +332 -0
- package/.agents/scripts/lib/config-settings-schema-quality.js +165 -0
- package/.agents/scripts/lib/config-settings-schema.js +420 -0
- package/.agents/scripts/lib/coverage-baseline.js +352 -0
- package/.agents/scripts/lib/coverage-capture.js +195 -0
- package/.agents/scripts/lib/coverage-utils.js +239 -0
- package/.agents/scripts/lib/cpu-pool.js +223 -0
- package/.agents/scripts/lib/crap-engine.js +119 -0
- package/.agents/scripts/lib/crap-utils.js +479 -0
- package/.agents/scripts/lib/degraded-mode.js +69 -0
- package/.agents/scripts/lib/dependency-parser.js +129 -0
- package/.agents/scripts/lib/duplicate-search.js +189 -0
- package/.agents/scripts/lib/dynamic-workflow/architecture-report-contract.js +70 -0
- package/.agents/scripts/lib/dynamic-workflow/audit-orchestrator.js +197 -0
- package/.agents/scripts/lib/dynamic-workflow/capability.js +396 -0
- package/.agents/scripts/lib/dynamic-workflow/clean-code-report-contract.js +80 -0
- package/.agents/scripts/lib/dynamic-workflow/performance-report-contract.js +72 -0
- package/.agents/scripts/lib/dynamic-workflow/quality-report-contract.js +90 -0
- package/.agents/scripts/lib/dynamic-workflow/report-contract-core.js +43 -0
- package/.agents/scripts/lib/dynamic-workflow/security-report-contract.js +83 -0
- package/.agents/scripts/lib/env-loader.js +52 -0
- package/.agents/scripts/lib/epic-merge-lock.js +239 -0
- package/.agents/scripts/lib/epic-plan-clarity.js +142 -0
- package/.agents/scripts/lib/epic-plan-ideation.js +228 -0
- package/.agents/scripts/lib/error-redactor.js +125 -0
- package/.agents/scripts/lib/errors/index.js +67 -0
- package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +230 -0
- package/.agents/scripts/lib/feedback-loop/code-review-graduator.js +207 -0
- package/.agents/scripts/lib/feedback-loop/graduator-core.js +421 -0
- package/.agents/scripts/lib/feedback-loop/memory-freshness.js +480 -0
- package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +229 -0
- package/.agents/scripts/lib/findings/classify-finding.js +195 -0
- package/.agents/scripts/lib/findings/promote-finding.js +353 -0
- package/.agents/scripts/lib/findings/route-finding.js +283 -0
- package/.agents/scripts/lib/findings/semantic-issue-search.js +179 -0
- package/.agents/scripts/lib/findings/severity.js +102 -0
- package/.agents/scripts/lib/gates/baseline-store.js +106 -0
- package/.agents/scripts/lib/gates/friction.js +43 -0
- package/.agents/scripts/lib/gh-exec.js +553 -0
- package/.agents/scripts/lib/git/cached-fetch.js +0 -0
- package/.agents/scripts/lib/git/sync-from-base.js +162 -0
- package/.agents/scripts/lib/git-branch-cleanup.js +213 -0
- package/.agents/scripts/lib/git-branch-lifecycle.js +353 -0
- package/.agents/scripts/lib/git-merge-orchestrator.js +261 -0
- package/.agents/scripts/lib/git-utils.js +363 -0
- package/.agents/scripts/lib/github-url.js +29 -0
- package/.agents/scripts/lib/install-cmd-parser.js +51 -0
- package/.agents/scripts/lib/issue-link-parser.js +74 -0
- package/.agents/scripts/lib/json-utils.js +60 -0
- package/.agents/scripts/lib/label-constants.js +169 -0
- package/.agents/scripts/lib/label-taxonomy.js +200 -0
- package/.agents/scripts/lib/maintainability-engine.js +164 -0
- package/.agents/scripts/lib/maintainability-utils.js +343 -0
- package/.agents/scripts/lib/mandrel-catalog.js +170 -0
- package/.agents/scripts/lib/mutation/baseline-snapshot.js +238 -0
- package/.agents/scripts/lib/mutation/config-detector.js +119 -0
- package/.agents/scripts/lib/mutation/stryker-runner.js +306 -0
- package/.agents/scripts/lib/mutation/survivor-report.js +160 -0
- package/.agents/scripts/lib/notifications/notifier.js +75 -0
- package/.agents/scripts/lib/observability/active-story-env.js +182 -0
- package/.agents/scripts/lib/observability/baseline-refresh-rate.js +221 -0
- package/.agents/scripts/lib/observability/perf-aggregator.js +887 -0
- package/.agents/scripts/lib/observability/perf-report-readers.js +319 -0
- package/.agents/scripts/lib/observability/perf-report-render.js +182 -0
- package/.agents/scripts/lib/observability/signals-writer.js +296 -0
- package/.agents/scripts/lib/observability/source-classifier.js +103 -0
- package/.agents/scripts/lib/observability/tool-trace-hook.js +417 -0
- package/.agents/scripts/lib/onboard/detect-stack.js +300 -0
- package/.agents/scripts/lib/onboard/scaffold-docs.js +128 -0
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +173 -0
- package/.agents/scripts/lib/orchestration/cascade-grouping.js +275 -0
- package/.agents/scripts/lib/orchestration/check-baselines/phases/compare.js +131 -0
- package/.agents/scripts/lib/orchestration/check-baselines/phases/evaluate.js +80 -0
- package/.agents/scripts/lib/orchestration/check-baselines/phases/floors.js +132 -0
- package/.agents/scripts/lib/orchestration/check-baselines/phases/friction.js +142 -0
- package/.agents/scripts/lib/orchestration/check-baselines/phases/parse-args.js +149 -0
- package/.agents/scripts/lib/orchestration/check-baselines/phases/pipeline.js +158 -0
- package/.agents/scripts/lib/orchestration/check-baselines/phases/report.js +56 -0
- package/.agents/scripts/lib/orchestration/code-review.js +652 -0
- package/.agents/scripts/lib/orchestration/column-sync.js +286 -0
- package/.agents/scripts/lib/orchestration/context-envelope.js +280 -0
- package/.agents/scripts/lib/orchestration/context-hydration-engine.js +581 -0
- package/.agents/scripts/lib/orchestration/dependency-analyzer.js +88 -0
- package/.agents/scripts/lib/orchestration/detectors-phase.js +188 -0
- package/.agents/scripts/lib/orchestration/dispatch-engine.js +144 -0
- package/.agents/scripts/lib/orchestration/dispatch-pipeline.js +206 -0
- package/.agents/scripts/lib/orchestration/doc-reader.js +94 -0
- package/.agents/scripts/lib/orchestration/epic-cleanup.js +473 -0
- package/.agents/scripts/lib/orchestration/epic-deliver-lease-guard.js +310 -0
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/cli.js +167 -0
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/context.js +151 -0
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/creation.js +74 -0
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/dag.js +78 -0
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/diagnostics.js +72 -0
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist-helpers.js +155 -0
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist.js +321 -0
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/planning-artifacts.js +75 -0
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/reconcile-spawn.js +86 -0
- package/.agents/scripts/lib/orchestration/epic-plan-lease-guard.js +235 -0
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/authoring-context.js +197 -0
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/cli-args.js +48 -0
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/drain.js +94 -0
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/plan-epic.js +414 -0
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/prompts.js +55 -0
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/risk-verdict.js +105 -0
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +235 -0
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/spec-freshness.js +120 -0
- package/.agents/scripts/lib/orchestration/epic-plan-state-store.js +118 -0
- package/.agents/scripts/lib/orchestration/epic-run-state-store.js +295 -0
- package/.agents/scripts/lib/orchestration/epic-runner/concurrency-gate.js +186 -0
- package/.agents/scripts/lib/orchestration/epic-runner/deliver-phases.js +50 -0
- package/.agents/scripts/lib/orchestration/epic-runner/phases/build-wave-dag.js +146 -0
- package/.agents/scripts/lib/orchestration/epic-runner/phases/snapshot.js +110 -0
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +392 -0
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +217 -0
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/transport.js +235 -0
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter.js +69 -0
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/_bullet-format.js +32 -0
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/crap-drift.js +291 -0
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/maintainability-drift.js +175 -0
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/stalled-worktree.js +37 -0
- package/.agents/scripts/lib/orchestration/epic-runner/story-launcher.js +127 -0
- package/.agents/scripts/lib/orchestration/epic-runner/story-run-progress-writer.js +400 -0
- package/.agents/scripts/lib/orchestration/epic-runner/sub-agent-return.js +285 -0
- package/.agents/scripts/lib/orchestration/epic-runner/wave-scheduler.js +66 -0
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-apply.js +797 -0
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +619 -0
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-discriminator.js +335 -0
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-format.js +230 -0
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-ops.js +363 -0
- package/.agents/scripts/lib/orchestration/error-journal.js +139 -0
- package/.agents/scripts/lib/orchestration/file-assumption-enum.js +31 -0
- package/.agents/scripts/lib/orchestration/file-assumptions.js +506 -0
- package/.agents/scripts/lib/orchestration/finalize/close-planning-tickets.js +116 -0
- package/.agents/scripts/lib/orchestration/finalize/open-or-locate-pr.js +241 -0
- package/.agents/scripts/lib/orchestration/finalize/post-handoff-comment.js +489 -0
- package/.agents/scripts/lib/orchestration/finalize/sanitize-skip-ci.js +88 -0
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/branches-reap.js +219 -0
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/branches.js +309 -0
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/cli.js +99 -0
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/fast-forward.js +123 -0
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/filters.js +57 -0
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/git-probes-ff.js +114 -0
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/git-probes.js +426 -0
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/parse-args.js +84 -0
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/phase-drivers.js +365 -0
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/prompts.js +72 -0
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/prune.js +69 -0
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/render.js +214 -0
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/stashes.js +137 -0
- package/.agents/scripts/lib/orchestration/label-transitions.js +43 -0
- package/.agents/scripts/lib/orchestration/lifecycle/bus.js +309 -0
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-dispatch-end.js +147 -0
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +155 -0
- package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +226 -0
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +69 -0
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/acceptance-reconciler.js +378 -0
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-armer.js +248 -0
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +527 -0
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/branch-cleaner.js +259 -0
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/checkpoint-pointer-writer.js +278 -0
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/cleaner.js +355 -0
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +647 -0
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +331 -0
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/intervention-recorder.js +140 -0
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +421 -0
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/notify-dispatcher.js +168 -0
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +668 -0
- package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +322 -0
- package/.agents/scripts/lib/orchestration/lint-baseline-service.js +114 -0
- package/.agents/scripts/lib/orchestration/manifest-builder.js +216 -0
- package/.agents/scripts/lib/orchestration/model-attribution.js +390 -0
- package/.agents/scripts/lib/orchestration/parked-follow-ons.js +147 -0
- package/.agents/scripts/lib/orchestration/phase-runner.js +87 -0
- package/.agents/scripts/lib/orchestration/plan-review-routing.js +63 -0
- package/.agents/scripts/lib/orchestration/plan-runner/plan-router.js +86 -0
- package/.agents/scripts/lib/orchestration/plan-runner/worktree-sweep.js +212 -0
- package/.agents/scripts/lib/orchestration/planning-context-budget.js +213 -0
- package/.agents/scripts/lib/orchestration/planning-risk.js +155 -0
- package/.agents/scripts/lib/orchestration/planning-state-manager.js +318 -0
- package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +56 -0
- package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +33 -0
- package/.agents/scripts/lib/orchestration/post-merge/phases/notification.js +78 -0
- package/.agents/scripts/lib/orchestration/post-merge/phases/temp-cleanup.js +68 -0
- package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +118 -0
- package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +396 -0
- package/.agents/scripts/lib/orchestration/post-merge-pipeline.js +205 -0
- package/.agents/scripts/lib/orchestration/pr-base-guard.js +47 -0
- package/.agents/scripts/lib/orchestration/preflight-cache.js +164 -0
- package/.agents/scripts/lib/orchestration/reassert-status-column.js +202 -0
- package/.agents/scripts/lib/orchestration/reconciler.js +137 -0
- package/.agents/scripts/lib/orchestration/recurring-failure-detector.js +152 -0
- package/.agents/scripts/lib/orchestration/recut.js +56 -0
- package/.agents/scripts/lib/orchestration/resolves-token.js +127 -0
- package/.agents/scripts/lib/orchestration/retro/phases/checks.js +94 -0
- package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +448 -0
- package/.agents/scripts/lib/orchestration/retro/phases/gather-signals.js +335 -0
- package/.agents/scripts/lib/orchestration/retro/phases/post-and-mirror.js +133 -0
- package/.agents/scripts/lib/orchestration/retro-heuristics.js +57 -0
- package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +275 -0
- package/.agents/scripts/lib/orchestration/retro-proposals.js +395 -0
- package/.agents/scripts/lib/orchestration/retro-runner.js +171 -0
- package/.agents/scripts/lib/orchestration/review-depth.js +93 -0
- package/.agents/scripts/lib/orchestration/review-providers/codex.js +363 -0
- package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +205 -0
- package/.agents/scripts/lib/orchestration/review-providers/native.js +805 -0
- package/.agents/scripts/lib/orchestration/review-providers/review-depth.js +73 -0
- package/.agents/scripts/lib/orchestration/review-providers/review-provider-factory.js +396 -0
- package/.agents/scripts/lib/orchestration/review-providers/security-review.js +373 -0
- package/.agents/scripts/lib/orchestration/review-providers/types.js +89 -0
- package/.agents/scripts/lib/orchestration/review-providers/ultrareview.js +107 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +159 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +194 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +81 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +190 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +70 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +106 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/push.js +42 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +73 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +225 -0
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +315 -0
- package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +149 -0
- package/.agents/scripts/lib/orchestration/skill-capsule-loader.js +110 -0
- package/.agents/scripts/lib/orchestration/spec-freshness.js +320 -0
- package/.agents/scripts/lib/orchestration/spec-renderer.js +456 -0
- package/.agents/scripts/lib/orchestration/spec-section-validator.js +80 -0
- package/.agents/scripts/lib/orchestration/story-close/auto-refresh-runner.js +797 -0
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/gate-failure.js +163 -0
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/pre-merge-attribution.js +152 -0
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/refresh-commit.js +387 -0
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/regression-projection.js +266 -0
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/scope-discovery.js +48 -0
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution-wiring.js +67 -0
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution.js +161 -0
- package/.agents/scripts/lib/orchestration/story-close/baseline-friction-body.js +117 -0
- package/.agents/scripts/lib/orchestration/story-close/cd-out-guard.js +86 -0
- package/.agents/scripts/lib/orchestration/story-close/cleanup-reconciler.js +147 -0
- package/.agents/scripts/lib/orchestration/story-close/close-inputs.js +142 -0
- package/.agents/scripts/lib/orchestration/story-close/comment-bodies.js +62 -0
- package/.agents/scripts/lib/orchestration/story-close/format-autofix-scoped.js +221 -0
- package/.agents/scripts/lib/orchestration/story-close/format-autofix-shared.js +123 -0
- package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +216 -0
- package/.agents/scripts/lib/orchestration/story-close/merge-runner.js +636 -0
- package/.agents/scripts/lib/orchestration/story-close/merge-subject.js +198 -0
- package/.agents/scripts/lib/orchestration/story-close/phases/branch-restore.js +105 -0
- package/.agents/scripts/lib/orchestration/story-close/phases/close.js +222 -0
- package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +220 -0
- package/.agents/scripts/lib/orchestration/story-close/phases/gates.js +291 -0
- package/.agents/scripts/lib/orchestration/story-close/phases/locked-pipeline.js +234 -0
- package/.agents/scripts/lib/orchestration/story-close/phases/preflight.js +110 -0
- package/.agents/scripts/lib/orchestration/story-close/phases/refresh.js +86 -0
- package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked-emitter.js +112 -0
- package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked.js +157 -0
- package/.agents/scripts/lib/orchestration/story-close/post-merge-close.js +434 -0
- package/.agents/scripts/lib/orchestration/story-close/pre-merge-validation.js +290 -0
- package/.agents/scripts/lib/orchestration/story-close-recovery.js +643 -0
- package/.agents/scripts/lib/orchestration/structured-comment-parser.js +67 -0
- package/.agents/scripts/lib/orchestration/task-body-validator.js +391 -0
- package/.agents/scripts/lib/orchestration/ticket-lease.js +358 -0
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +783 -0
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +367 -0
- package/.agents/scripts/lib/orchestration/ticket-validator.js +691 -0
- package/.agents/scripts/lib/orchestration/ticketing/bulk.js +723 -0
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +474 -0
- package/.agents/scripts/lib/orchestration/ticketing/state.js +559 -0
- package/.agents/scripts/lib/orchestration/ticketing.js +55 -0
- package/.agents/scripts/lib/orchestration/wave-marker.js +28 -0
- package/.agents/scripts/lib/orchestration/wave-record-io.js +277 -0
- package/.agents/scripts/lib/orchestration/wave-record-notifications.js +189 -0
- package/.agents/scripts/lib/orchestration/wave-record-projection.js +423 -0
- package/.agents/scripts/lib/path-security.js +25 -0
- package/.agents/scripts/lib/plan-phase-cleanup.js +125 -0
- package/.agents/scripts/lib/preflight-runner.js +196 -0
- package/.agents/scripts/lib/presentation/dispatch-manifest-render.js +95 -0
- package/.agents/scripts/lib/presentation/manifest-builder.js +245 -0
- package/.agents/scripts/lib/presentation/manifest-formatter.js +243 -0
- package/.agents/scripts/lib/presentation/manifest-helpers.js +213 -0
- package/.agents/scripts/lib/presentation/manifest-persistence.js +262 -0
- package/.agents/scripts/lib/presentation/manifest-procedures.js +55 -0
- package/.agents/scripts/lib/presentation/manifest-render-waves.js +252 -0
- package/.agents/scripts/lib/presentation/manifest-renderer.js +188 -0
- package/.agents/scripts/lib/presentation/manifest-story-views.js +119 -0
- package/.agents/scripts/lib/provider-factory.js +80 -0
- package/.agents/scripts/lib/push-epic-retry.js +209 -0
- package/.agents/scripts/lib/qa/console-allowlist.js +151 -0
- package/.agents/scripts/lib/qa/coverage-report.js +181 -0
- package/.agents/scripts/lib/qa/coverage-verdict.js +296 -0
- package/.agents/scripts/lib/qa/propose-missing-test.js +95 -0
- package/.agents/scripts/lib/qa/qa-context-hydrator.js +296 -0
- package/.agents/scripts/lib/qa/qa-session.js +197 -0
- package/.agents/scripts/lib/qa/redact-evidence.js +245 -0
- package/.agents/scripts/lib/qa/resolve-qa-contract.js +190 -0
- package/.agents/scripts/lib/qa/resolve-selection.js +373 -0
- package/.agents/scripts/lib/runtime-deps/ensure-installed.js +100 -0
- package/.agents/scripts/lib/runtime-deps/manifest.js +96 -0
- package/.agents/scripts/lib/runtime-deps/preflight.js +78 -0
- package/.agents/scripts/lib/runtime-deps/scan-imports.js +202 -0
- package/.agents/scripts/lib/signals/detectors/common.js +36 -0
- package/.agents/scripts/lib/signals/detectors/hotspot.js +298 -0
- package/.agents/scripts/lib/signals/detectors/index.js +14 -0
- package/.agents/scripts/lib/signals/detectors/retry.js +289 -0
- package/.agents/scripts/lib/signals/detectors/rework.js +204 -0
- package/.agents/scripts/lib/signals/index.js +39 -0
- package/.agents/scripts/lib/signals/read.js +268 -0
- package/.agents/scripts/lib/signals/schema.js +225 -0
- package/.agents/scripts/lib/signals/span-tree.js +290 -0
- package/.agents/scripts/lib/signals/write.js +19 -0
- package/.agents/scripts/lib/single-story/confirm-merge.js +201 -0
- package/.agents/scripts/lib/single-story/story-merged-notify.js +126 -0
- package/.agents/scripts/lib/single-story-sweep/protection.js +274 -0
- package/.agents/scripts/lib/single-story-sweep/sweep-lock.js +169 -0
- package/.agents/scripts/lib/single-story-sweep.js +329 -0
- package/.agents/scripts/lib/skills/parse-skill.js +202 -0
- package/.agents/scripts/lib/skills/walk-skill-files.js +56 -0
- package/.agents/scripts/lib/spec/index.js +36 -0
- package/.agents/scripts/lib/spec/loader.js +425 -0
- package/.agents/scripts/lib/spec/state.js +217 -0
- package/.agents/scripts/lib/story-body/story-body.js +743 -0
- package/.agents/scripts/lib/story-init/blocker-validator.js +68 -0
- package/.agents/scripts/lib/story-init/branch-initializer.js +422 -0
- package/.agents/scripts/lib/story-init/context-resolver.js +92 -0
- package/.agents/scripts/lib/story-init/donor-precheck.js +207 -0
- package/.agents/scripts/lib/story-init/hierarchy-tracer.js +36 -0
- package/.agents/scripts/lib/story-init/state-transitioner.js +80 -0
- package/.agents/scripts/lib/story-init/task-graph-builder.js +114 -0
- package/.agents/scripts/lib/story-init/transition-summary.js +34 -0
- package/.agents/scripts/lib/story-lifecycle.js +186 -0
- package/.agents/scripts/lib/story-plan.js +246 -0
- package/.agents/scripts/lib/task-utils.js +26 -0
- package/.agents/scripts/lib/templates/decomposer-prompts.js +168 -0
- package/.agents/scripts/lib/test-env.js +30 -0
- package/.agents/scripts/lib/test-isolate/env-snapshot-loader.js +52 -0
- package/.agents/scripts/lib/test-isolate/list-files.js +90 -0
- package/.agents/scripts/lib/test-isolate/parse-tap.js +75 -0
- package/.agents/scripts/lib/test-isolate/runner.js +483 -0
- package/.agents/scripts/lib/test-profile/parse-tap.js +136 -0
- package/.agents/scripts/lib/test-profile/render-report.js +45 -0
- package/.agents/scripts/lib/test-reserved-epic-temp-ids.js +35 -0
- package/.agents/scripts/lib/test-tiers.js +94 -0
- package/.agents/scripts/lib/util/concurrent-map.js +59 -0
- package/.agents/scripts/lib/util/phase-timer-state.js +72 -0
- package/.agents/scripts/lib/util/phase-timer.js +163 -0
- package/.agents/scripts/lib/util/poll-loop.js +86 -0
- package/.agents/scripts/lib/util/with-timeout.js +32 -0
- package/.agents/scripts/lib/validation-evidence.js +323 -0
- package/.agents/scripts/lib/wave-runner/tick.js +665 -0
- package/.agents/scripts/lib/wave-runner/wave-checkpoint.js +91 -0
- package/.agents/scripts/lib/wave-runner/wave-runner-error.js +19 -0
- package/.agents/scripts/lib/workers/crap-worker.js +197 -0
- package/.agents/scripts/lib/workers/maintainability-report-worker.js +137 -0
- package/.agents/scripts/lib/workers/maintainability-worker.js +79 -0
- package/.agents/scripts/lib/workspace-provisioner.js +189 -0
- package/.agents/scripts/lib/worktree/bootstrapper.js +48 -0
- package/.agents/scripts/lib/worktree/inspector.js +140 -0
- package/.agents/scripts/lib/worktree/lifecycle/creation.js +118 -0
- package/.agents/scripts/lib/worktree/lifecycle/drift-detection.js +62 -0
- package/.agents/scripts/lib/worktree/lifecycle/force-drain.js +276 -0
- package/.agents/scripts/lib/worktree/lifecycle/gc.js +49 -0
- package/.agents/scripts/lib/worktree/lifecycle/merge-reachability.js +178 -0
- package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +264 -0
- package/.agents/scripts/lib/worktree/lifecycle/precheck.js +100 -0
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +588 -0
- package/.agents/scripts/lib/worktree/lifecycle/registry-sync.js +124 -0
- package/.agents/scripts/lib/worktree/lifecycle/shared.js +26 -0
- package/.agents/scripts/lib/worktree/lifecycle-manager.js +40 -0
- package/.agents/scripts/lib/worktree/node-modules-strategy.js +349 -0
- package/.agents/scripts/lib/worktree-manager.js +243 -0
- package/.agents/scripts/lifecycle-diff.js +206 -0
- package/.agents/scripts/lifecycle-emit-story-dispatch.js +194 -0
- package/.agents/scripts/lifecycle-emit.js +479 -0
- package/.agents/scripts/lint-baseline.js +507 -0
- package/.agents/scripts/lint-label-vocabulary.js +237 -0
- package/.agents/scripts/loc-delta.js +205 -0
- package/.agents/scripts/notify.js +307 -0
- package/.agents/scripts/package.json +3 -0
- package/.agents/scripts/post-structured-comment.js +127 -0
- package/.agents/scripts/pr-watch-with-update.js +152 -0
- package/.agents/scripts/providers/github/auth.js +65 -0
- package/.agents/scripts/providers/github/board-add.js +63 -0
- package/.agents/scripts/providers/github/branch-protection.js +186 -0
- package/.agents/scripts/providers/github/cache.js +72 -0
- package/.agents/scripts/providers/github/comments.js +131 -0
- package/.agents/scripts/providers/github/compose.js +111 -0
- package/.agents/scripts/providers/github/errors.js +242 -0
- package/.agents/scripts/providers/github/issues.js +242 -0
- package/.agents/scripts/providers/github/labels.js +179 -0
- package/.agents/scripts/providers/github/mappers.js +126 -0
- package/.agents/scripts/providers/github/merge-methods.js +82 -0
- package/.agents/scripts/providers/github/project-board.js +47 -0
- package/.agents/scripts/providers/github/projects-v2-graphql.js +472 -0
- package/.agents/scripts/providers/github/prs.js +103 -0
- package/.agents/scripts/providers/github/request-helpers.js +110 -0
- package/.agents/scripts/providers/github/sub-issues.js +369 -0
- package/.agents/scripts/providers/github/tickets.js +381 -0
- package/.agents/scripts/providers/github/transient-retry.js +62 -0
- package/.agents/scripts/providers/github.js +157 -0
- package/.agents/scripts/quality-preview.js +327 -0
- package/.agents/scripts/quality-watch.js +223 -0
- package/.agents/scripts/render-manifest.js +143 -0
- package/.agents/scripts/resync-status-column.js +176 -0
- package/.agents/scripts/retro-run.js +167 -0
- package/.agents/scripts/run-audit-suite.js +97 -0
- package/.agents/scripts/run-coverage.js +103 -0
- package/.agents/scripts/run-lint.js +94 -0
- package/.agents/scripts/run-test-profile.js +126 -0
- package/.agents/scripts/run-tests.js +185 -0
- package/.agents/scripts/run-verify.js +56 -0
- package/.agents/scripts/select-audits.js +155 -0
- package/.agents/scripts/signals-view.js +294 -0
- package/.agents/scripts/single-story-close.js +83 -0
- package/.agents/scripts/single-story-confirm-merge.js +183 -0
- package/.agents/scripts/single-story-init.js +692 -0
- package/.agents/scripts/stories-wave-tick.js +415 -0
- package/.agents/scripts/story-close.js +246 -0
- package/.agents/scripts/story-deliver-prepare.js +267 -0
- package/.agents/scripts/story-init.js +516 -0
- package/.agents/scripts/story-phase.js +327 -0
- package/.agents/scripts/story-plan.js +284 -0
- package/.agents/scripts/sync-agentrc.js +71 -0
- package/.agents/scripts/sync-branch-from-base.js +138 -0
- package/.agents/scripts/sync-claude-commands.js +151 -0
- package/.agents/scripts/test-isolate.js +222 -0
- package/.agents/scripts/test-wrapper.js +108 -0
- package/.agents/scripts/update-coverage-baseline.js +129 -0
- package/.agents/scripts/update-crap-baseline.js +177 -0
- package/.agents/scripts/update-duplication-baseline.js +134 -0
- package/.agents/scripts/update-maintainability-baseline.js +183 -0
- package/.agents/scripts/update-mutation-baseline.js +189 -0
- package/.agents/scripts/update-ticket-state.js +107 -0
- package/.agents/scripts/validate-docs-freshness.js +259 -0
- package/.agents/scripts/validate-skills.js +278 -0
- package/.agents/scripts/wave-tick.js +335 -0
- package/.agents/skills/core/analyze-execution/SKILL.md +98 -0
- package/.agents/skills/core/api-and-interface-design/SKILL.md +327 -0
- package/.agents/skills/core/baseline-refresh/SKILL.md +181 -0
- package/.agents/skills/core/browser-testing-with-devtools/SKILL.md +352 -0
- package/.agents/skills/core/ci-cd-and-automation/SKILL.md +274 -0
- package/.agents/skills/core/ci-cd-and-automation/examples.md +211 -0
- package/.agents/skills/core/code-review-and-quality/SKILL.md +421 -0
- package/.agents/skills/core/code-simplification/SKILL.md +389 -0
- package/.agents/skills/core/context-engineering/SKILL.md +309 -0
- package/.agents/skills/core/context-engineering/examples.md +58 -0
- package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +338 -0
- package/.agents/skills/core/deprecation-and-migration/SKILL.md +250 -0
- package/.agents/skills/core/diagnose-friction/SKILL.md +79 -0
- package/.agents/skills/core/documentation-and-adrs/SKILL.md +323 -0
- package/.agents/skills/core/epic-plan-consolidate/SKILL.md +145 -0
- package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +425 -0
- package/.agents/skills/core/epic-plan-spec-author/SKILL.md +393 -0
- package/.agents/skills/core/frontend-ui-engineering/SKILL.md +357 -0
- package/.agents/skills/core/git-workflow-and-versioning/SKILL.md +352 -0
- package/.agents/skills/core/hydrate-context/SKILL.md +118 -0
- package/.agents/skills/core/idea-refinement/SKILL.md +317 -0
- package/.agents/skills/core/idea-refinement/examples.md +437 -0
- package/.agents/skills/core/idea-refinement/frameworks.md +135 -0
- package/.agents/skills/core/idea-refinement/refinement-criteria.md +155 -0
- package/.agents/skills/core/idea-refinement/scripts/idea-refine.sh +15 -0
- package/.agents/skills/core/incremental-implementation/SKILL.md +271 -0
- package/.agents/skills/core/introducing-a-baseline-gate/SKILL.md +213 -0
- package/.agents/skills/core/knowledge-transfer/SKILL.md +175 -0
- package/.agents/skills/core/mutation-survivor-remediation/SKILL.md +117 -0
- package/.agents/skills/core/performance-optimization/SKILL.md +314 -0
- package/.agents/skills/core/planning-and-task-breakdown/SKILL.md +277 -0
- package/.agents/skills/core/property-based-testing/SKILL.md +148 -0
- package/.agents/skills/core/qa-coverage-mapping/SKILL.md +105 -0
- package/.agents/skills/core/refactoring-discipline/SKILL.md +111 -0
- package/.agents/skills/core/scope-triage/SKILL.md +127 -0
- package/.agents/skills/core/security-and-hardening/SKILL.md +400 -0
- package/.agents/skills/core/shipping-and-launch/SKILL.md +328 -0
- package/.agents/skills/core/spec-driven-development/SKILL.md +252 -0
- package/.agents/skills/core/test-driven-development/SKILL.md +475 -0
- package/.agents/skills/core/using-agent-skills/SKILL.md +232 -0
- package/.agents/skills/skills.index.json +596 -0
- package/.agents/skills/stack/architecture/monorepo-path-strategist/SKILL.md +31 -0
- package/.agents/skills/stack/architecture/structured-output-zod/SKILL.md +51 -0
- package/.agents/skills/stack/architecture/subagent-orchestration/SKILL.md +48 -0
- package/.agents/skills/stack/backend/cloudflare-hono-architect/SKILL.md +31 -0
- package/.agents/skills/stack/backend/cloudflare-hono-architect/examples/route-template.ts +33 -0
- package/.agents/skills/stack/backend/cloudflare-queue-manager/SKILL.md +31 -0
- package/.agents/skills/stack/backend/cloudflare-workers/SKILL.md +51 -0
- package/.agents/skills/stack/backend/highlevel-crm/SKILL.md +54 -0
- package/.agents/skills/stack/backend/sqlite-drizzle-expert/SKILL.md +29 -0
- package/.agents/skills/stack/backend/sqlite-drizzle-expert/examples/schema-template.ts +30 -0
- package/.agents/skills/stack/backend/stripe-integration/SKILL.md +57 -0
- package/.agents/skills/stack/backend/stripe-integration/scripts/listen-stripe.sh +9 -0
- package/.agents/skills/stack/backend/turso-sqlite/SKILL.md +48 -0
- package/.agents/skills/stack/frontend/astro/SKILL.md +62 -0
- package/.agents/skills/stack/frontend/astro-react-island-strategist/SKILL.md +30 -0
- package/.agents/skills/stack/frontend/expo-react-native-developer/SKILL.md +29 -0
- package/.agents/skills/stack/frontend/google-analytics-v4/SKILL.md +50 -0
- package/.agents/skills/stack/frontend/tailwind-v4/SKILL.md +58 -0
- package/.agents/skills/stack/frontend/ui-accessibility-engineer/SKILL.md +34 -0
- package/.agents/skills/stack/qa/audit-accessibility/SKILL.md +51 -0
- package/.agents/skills/stack/qa/gherkin-authoring/SKILL.md +257 -0
- package/.agents/skills/stack/qa/gherkin-authoring/examples/invoice-issue.feature +41 -0
- package/.agents/skills/stack/qa/lighthouse-baseline/SKILL.md +199 -0
- package/.agents/skills/stack/qa/playwright/SKILL.md +50 -0
- package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +188 -0
- package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +142 -0
- package/.agents/skills/stack/qa/qa-harness/SKILL.md +220 -0
- package/.agents/skills/stack/qa/vitest/SKILL.md +51 -0
- package/.agents/skills/stack/security/backend-security-patterns/SKILL.md +68 -0
- package/.agents/starter-agentrc.json +22 -0
- package/.agents/templates/agent-protocol.md +72 -0
- package/.agents/templates/docs/architecture.md +30 -0
- package/.agents/templates/docs/decisions.md +24 -0
- package/.agents/templates/epic-from-idea.md +21 -0
- package/.agents/templates/single-story-body.md +17 -0
- package/.agents/workflows/agents-update.md +415 -0
- package/.agents/workflows/audit-architecture.md +312 -0
- package/.agents/workflows/audit-clean-code.md +179 -0
- package/.agents/workflows/audit-dependencies.md +91 -0
- package/.agents/workflows/audit-devops.md +110 -0
- package/.agents/workflows/audit-lighthouse.md +260 -0
- package/.agents/workflows/audit-performance.md +161 -0
- package/.agents/workflows/audit-privacy.md +104 -0
- package/.agents/workflows/audit-quality.md +191 -0
- package/.agents/workflows/audit-security.md +156 -0
- package/.agents/workflows/audit-seo.md +118 -0
- package/.agents/workflows/audit-sre.md +139 -0
- package/.agents/workflows/audit-to-stories.md +257 -0
- package/.agents/workflows/audit-ux-ui.md +102 -0
- package/.agents/workflows/epic-deliver.md +864 -0
- package/.agents/workflows/epic-plan.md +998 -0
- package/.agents/workflows/explain.md +118 -0
- package/.agents/workflows/git-cleanup.md +250 -0
- package/.agents/workflows/git-commit-all.md +15 -0
- package/.agents/workflows/git-merge-pr.md +377 -0
- package/.agents/workflows/git-pr-all.md +278 -0
- package/.agents/workflows/git-push.md +60 -0
- package/.agents/workflows/helpers/_merge-conflict-template.md +54 -0
- package/.agents/workflows/helpers/acceptance-self-eval.md +74 -0
- package/.agents/workflows/helpers/agents-sync-config.md +129 -0
- package/.agents/workflows/helpers/code-quality-guardrails.md +101 -0
- package/.agents/workflows/helpers/code-review.md +370 -0
- package/.agents/workflows/helpers/diagnose.md +117 -0
- package/.agents/workflows/helpers/epic-audit.md +295 -0
- package/.agents/workflows/helpers/epic-deliver-story.md +370 -0
- package/.agents/workflows/helpers/epic-plan-decompose.md +199 -0
- package/.agents/workflows/helpers/epic-plan-spec.md +184 -0
- package/.agents/workflows/helpers/epic-testing.md +125 -0
- package/.agents/workflows/helpers/parallel-tooling.md +88 -0
- package/.agents/workflows/helpers/signals.md +112 -0
- package/.agents/workflows/helpers/single-story-deliver.md +636 -0
- package/.agents/workflows/helpers/worktree-lifecycle.md +317 -0
- package/.agents/workflows/onboard.md +207 -0
- package/.agents/workflows/qa-assist.md +293 -0
- package/.agents/workflows/qa-explore.md +350 -0
- package/.agents/workflows/qa-run-harness.md +288 -0
- package/.agents/workflows/story-deliver.md +327 -0
- package/.agents/workflows/story-plan.md +233 -0
- package/LICENSE +21 -0
- package/README.md +193 -0
- package/bin/mandrel.js +56 -0
- package/bin/postinstall.js +195 -0
- package/lib/cli/__tests__/migrate.test.js +268 -0
- package/lib/cli/__tests__/sync-local-zone.test.js +247 -0
- package/lib/cli/__tests__/sync.test.js +372 -0
- package/lib/cli/__tests__/update-major.test.js +217 -0
- package/lib/cli/__tests__/update.test.js +696 -0
- package/lib/cli/__tests__/version-check.test.js +398 -0
- package/lib/cli/doctor.js +124 -0
- package/lib/cli/explain.js +107 -0
- package/lib/cli/migrate.js +260 -0
- package/lib/cli/registry.js +830 -0
- package/lib/cli/sync-commands.js +50 -0
- package/lib/cli/sync.js +200 -0
- package/lib/cli/uninstall.js +795 -0
- package/lib/cli/update.js +854 -0
- package/lib/cli/version-check.js +206 -0
- package/lib/migrations/README.md +69 -0
- package/lib/migrations/__tests__/index.test.js +216 -0
- package/lib/migrations/index.js +164 -0
- package/package.json +105 -0
|
@@ -0,0 +1,1378 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* bootstrap.js — single-command consumer setup for Mandrel.
|
|
4
|
+
*
|
|
5
|
+
* The sole bootstrap orchestrator (Story #3690 collapsed the temporary
|
|
6
|
+
* bootstrap-new.js fork into this file). Key behaviours:
|
|
7
|
+
* - No config profiles — `.agentrc.json` always seeds from the bundled
|
|
8
|
+
* `.agents/starter-agentrc.json` starter reference.
|
|
9
|
+
* - Runs even when the directory is NOT a git repo yet (preflight detects
|
|
10
|
+
* git state instead of failing on it).
|
|
11
|
+
* - Adds a Projects V2 permission check to preflight (warns rather than
|
|
12
|
+
* failing when classic token scopes cannot be read, e.g. fine-grained
|
|
13
|
+
* PATs).
|
|
14
|
+
* - Uses a plain summary + confirm loop (interactive runs can go back and
|
|
15
|
+
* re-answer) instead of a phased-approval manifest.
|
|
16
|
+
* - Provisions the missing pieces of a cold start: initializes the local
|
|
17
|
+
* git repo (with a first commit) when absent, creates the GitHub repo
|
|
18
|
+
* (linking + pushing the local tree), and creates the Projects V2 board
|
|
19
|
+
* from a typed name — capturing its number for the rest of the run.
|
|
20
|
+
*
|
|
21
|
+
* Usage:
|
|
22
|
+
* node .agents/scripts/bootstrap.js [flags]
|
|
23
|
+
*
|
|
24
|
+
* Flags:
|
|
25
|
+
* --owner <name> GitHub owner (default: parsed from origin remote)
|
|
26
|
+
* --repo <name> GitHub repo (default: parsed from origin remote)
|
|
27
|
+
* --visibility <v> Visibility for a newly created repo:
|
|
28
|
+
* private | public | internal (default: private)
|
|
29
|
+
* --operator-handle <name> GitHub handle for github.operatorHandle
|
|
30
|
+
* --base-branch <name> Base branch (default: origin/HEAD or 'main')
|
|
31
|
+
* --project-number <n> Projects V2 number/name (optional)
|
|
32
|
+
* --assume-yes Accept every default + approve GitHub-admin
|
|
33
|
+
* mutations. A non-TTY run requires this (or
|
|
34
|
+
* --approve-github-admin) — there is no operator
|
|
35
|
+
* to confirm the summary.
|
|
36
|
+
* --approve-github-admin Consent to the irreversible GitHub-admin phase
|
|
37
|
+
* (labels, Projects V2, branch protection, merge
|
|
38
|
+
* methods) without accepting every other default.
|
|
39
|
+
* --skip-github Skip the GitHub-side bootstrap entirely
|
|
40
|
+
* --skip-quality Skip the quality-gates bootstrap
|
|
41
|
+
* --dry-run Collect info and print the plan; change nothing
|
|
42
|
+
* --reap-conflicting-workflows Delete Projects V2 built-in workflows that
|
|
43
|
+
* race against the orchestrator (destructive)
|
|
44
|
+
* --help Print this help
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
import { spawnSync } from 'node:child_process';
|
|
48
|
+
import fs from 'node:fs';
|
|
49
|
+
import path from 'node:path';
|
|
50
|
+
import readline from 'node:readline/promises';
|
|
51
|
+
import { fileURLToPath } from 'node:url';
|
|
52
|
+
|
|
53
|
+
// Reused bootstrap library helpers (unchanged).
|
|
54
|
+
import {
|
|
55
|
+
buildManualInstructions,
|
|
56
|
+
COMMIT_SUBJECT,
|
|
57
|
+
resolveStagePaths,
|
|
58
|
+
stageBootstrapFiles,
|
|
59
|
+
} from './lib/bootstrap/commit-push.js';
|
|
60
|
+
import { listProjects, listRepos } from './lib/bootstrap/gh-list.js';
|
|
61
|
+
import {
|
|
62
|
+
buildLedgerRecord,
|
|
63
|
+
writeInstallLedger,
|
|
64
|
+
} from './lib/bootstrap/install-ledger.js';
|
|
65
|
+
import {
|
|
66
|
+
buildMutationManifest,
|
|
67
|
+
PHASE_GROUPS,
|
|
68
|
+
} from './lib/bootstrap/manifest.js';
|
|
69
|
+
import { runPreflight } from './lib/bootstrap/preflight.js';
|
|
70
|
+
import { applyProjectBootstrap } from './lib/bootstrap/project-bootstrap.js';
|
|
71
|
+
import {
|
|
72
|
+
collectAnswers,
|
|
73
|
+
inferDefaults,
|
|
74
|
+
parseFlags,
|
|
75
|
+
} from './lib/bootstrap/prompt.js';
|
|
76
|
+
import { runAsCli } from './lib/cli-utils.js';
|
|
77
|
+
import { exec, GhNotFoundError } from './lib/gh-exec.js';
|
|
78
|
+
import { Logger } from './lib/Logger.js';
|
|
79
|
+
|
|
80
|
+
const HELP = `bootstrap.js — single-command consumer setup for Mandrel.
|
|
81
|
+
|
|
82
|
+
Usage: node .agents/scripts/bootstrap.js [flags]
|
|
83
|
+
|
|
84
|
+
Flags:
|
|
85
|
+
--owner <name> GitHub owner (default: parsed from origin remote)
|
|
86
|
+
--repo <name> GitHub repo (default: parsed from origin remote)
|
|
87
|
+
--visibility <v> Visibility for a newly created repo:
|
|
88
|
+
private | public | internal (default: private)
|
|
89
|
+
--operator-handle <name> GitHub handle for github.operatorHandle
|
|
90
|
+
--base-branch <name> Base branch (default: origin/HEAD or 'main')
|
|
91
|
+
--project-number <n> Projects V2 number/name (optional)
|
|
92
|
+
--assume-yes Accept every default + approve GitHub-admin
|
|
93
|
+
mutations. A non-TTY run requires this (or
|
|
94
|
+
--approve-github-admin) — there is no operator
|
|
95
|
+
to confirm the summary.
|
|
96
|
+
--approve-github-admin Consent to the irreversible GitHub-admin phase
|
|
97
|
+
(labels, Projects V2, branch protection, merge
|
|
98
|
+
methods) without accepting every other default.
|
|
99
|
+
--skip-github Skip the GitHub-side bootstrap entirely
|
|
100
|
+
--skip-quality Skip the quality-gates bootstrap
|
|
101
|
+
--dry-run Collect info and print the plan; change nothing
|
|
102
|
+
--reap-conflicting-workflows Delete Projects V2 built-in workflows that
|
|
103
|
+
race against the orchestrator (destructive)
|
|
104
|
+
--help Print this help
|
|
105
|
+
`;
|
|
106
|
+
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// Small helpers
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
/** Strip an `owner/` prefix off a repo slug, leaving the bare repo name. */
|
|
112
|
+
function bareRepoName(slug) {
|
|
113
|
+
const slash = slug.indexOf('/');
|
|
114
|
+
return slash === -1 ? slug : slug.slice(slash + 1);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Normalize an operator handle to the bare login the starter template expects.
|
|
119
|
+
*
|
|
120
|
+
* The starter `.agentrc.json` carries `"operatorHandle": "@[USERNAME]"` and the
|
|
121
|
+
* seed step substitutes `[USERNAME]` with this answer — so the answer MUST be
|
|
122
|
+
* the bare handle (no leading `@`), or the seeded value becomes `@@foo`. The
|
|
123
|
+
* interactive validator already rejects a leading `@`, but the
|
|
124
|
+
* `--operator-handle @x` flag and `GH_OPERATOR_HANDLE=@x` env paths skip that
|
|
125
|
+
* validator (Story #3700). Stripping a single leading `@` here closes that gap
|
|
126
|
+
* for every resolution path. Idempotent: a bare handle is returned unchanged,
|
|
127
|
+
* so a re-run never re-strips or re-accumulates.
|
|
128
|
+
*
|
|
129
|
+
* @param {string|undefined|null} handle
|
|
130
|
+
* @returns {string|undefined|null} the input with a single leading `@` removed
|
|
131
|
+
*/
|
|
132
|
+
export function normalizeHandleAnswer(handle) {
|
|
133
|
+
if (typeof handle !== 'string') return handle;
|
|
134
|
+
return handle.replace(/^@/, '');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Run a list-producing fn, returning [] on any throw. */
|
|
138
|
+
function safeList(fn) {
|
|
139
|
+
try {
|
|
140
|
+
return fn() ?? [];
|
|
141
|
+
} catch {
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Resolve the GitHub owner for the pickers: flag → env → inferred default. */
|
|
147
|
+
export function resolveOwnerForPicker(defaults, flags, env = process.env) {
|
|
148
|
+
if (typeof flags?.owner === 'string' && flags.owner.length > 0) {
|
|
149
|
+
return flags.owner;
|
|
150
|
+
}
|
|
151
|
+
if (typeof env?.GH_OWNER === 'string' && env.GH_OWNER.length > 0) {
|
|
152
|
+
return env.GH_OWNER;
|
|
153
|
+
}
|
|
154
|
+
if (typeof defaults?.owner === 'string' && defaults.owner.length > 0) {
|
|
155
|
+
return defaults.owner;
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Ask a yes/no question. Non-interactive runs auto-accept (return true). */
|
|
161
|
+
async function confirmYesNo(message, interactive) {
|
|
162
|
+
if (!interactive) return true;
|
|
163
|
+
const rl = readline.createInterface({
|
|
164
|
+
input: process.stdin,
|
|
165
|
+
output: process.stdout,
|
|
166
|
+
});
|
|
167
|
+
try {
|
|
168
|
+
const raw = (await rl.question(`${message} [Y/n]: `)).trim().toLowerCase();
|
|
169
|
+
return raw === '' || raw === 'y' || raw === 'yes';
|
|
170
|
+
} finally {
|
|
171
|
+
rl.close();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Run a git command in `cwd`. Returns the normalized
|
|
177
|
+
* `{ ok, status, stdout, stderr, error }` shape (mirroring the bootstrap
|
|
178
|
+
* preflight/gh-list runners) so callers branch on `ok` without juggling
|
|
179
|
+
* spawnSync internals.
|
|
180
|
+
*/
|
|
181
|
+
function runGit(args, cwd) {
|
|
182
|
+
const result = spawnSync('git', args, { cwd, encoding: 'utf8' });
|
|
183
|
+
return {
|
|
184
|
+
ok: !result.error && result.status === 0,
|
|
185
|
+
status: result.status,
|
|
186
|
+
stdout: typeof result.stdout === 'string' ? result.stdout.trim() : '',
|
|
187
|
+
stderr: typeof result.stderr === 'string' ? result.stderr.trim() : '',
|
|
188
|
+
error: result.error,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Surface a `gh`/exec failure with the same detail the GitHub bootstrap
|
|
194
|
+
* step prints — message plus the real gh stderr/stdout/args carried on a
|
|
195
|
+
* `GhExecError` — so a bare "gh exited with code 1" is actually diagnosable.
|
|
196
|
+
*/
|
|
197
|
+
function logGhError(label, err) {
|
|
198
|
+
Logger.error(`[bootstrap] ${label} failed: ${err.message}`);
|
|
199
|
+
if (err.stderr)
|
|
200
|
+
Logger.error(`[bootstrap] gh stderr: ${String(err.stderr).trim()}`);
|
|
201
|
+
if (err.stdout)
|
|
202
|
+
Logger.error(`[bootstrap] gh stdout: ${String(err.stdout).trim()}`);
|
|
203
|
+
if (Array.isArray(err.args)) {
|
|
204
|
+
Logger.error(`[bootstrap] gh args: ${err.args.join(' ')}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Per-command git identity args. The first commit fails when neither a repo-
|
|
210
|
+
* nor global-level `user.name`/`user.email` is configured, which is common on
|
|
211
|
+
* a freshly provisioned machine. When either is missing we supply a
|
|
212
|
+
* non-persistent identity via `-c` (derived from the operator handle) so the
|
|
213
|
+
* commit succeeds without mutating the operator's git config.
|
|
214
|
+
*/
|
|
215
|
+
function gitIdentityArgs(cwd, answers) {
|
|
216
|
+
const haveName = runGit(['config', 'user.name'], cwd).ok;
|
|
217
|
+
const haveEmail = runGit(['config', 'user.email'], cwd).ok;
|
|
218
|
+
if (haveName && haveEmail) return [];
|
|
219
|
+
const handle = answers.operatorHandle || answers.owner || 'mandrel';
|
|
220
|
+
return [
|
|
221
|
+
'-c',
|
|
222
|
+
`user.name=${handle}`,
|
|
223
|
+
'-c',
|
|
224
|
+
`user.email=${handle}@users.noreply.github.com`,
|
|
225
|
+
];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Initialize the local git repo when one is not already present, and ensure
|
|
230
|
+
* at least one commit exists so `gh repo create --source=. --push` has
|
|
231
|
+
* something to push. Idempotent: a repo that already resolves `HEAD` is left
|
|
232
|
+
* untouched. Returns `{ ok, initialized, committed }` (or `{ ok:false, error }`
|
|
233
|
+
* on failure).
|
|
234
|
+
*/
|
|
235
|
+
function ensureGitInitialized(state) {
|
|
236
|
+
const cwd = state.projectRoot;
|
|
237
|
+
const branch = state.answers.baseBranch || 'main';
|
|
238
|
+
let initialized = false;
|
|
239
|
+
if (!state.gitInitialized) {
|
|
240
|
+
// `git init -b <branch>` (git ≥ 2.28) sets the initial branch directly;
|
|
241
|
+
// fall back to a plain init + symbolic-ref for older git.
|
|
242
|
+
let init = runGit(['init', '-b', branch], cwd);
|
|
243
|
+
if (!init.ok) {
|
|
244
|
+
init = runGit(['init'], cwd);
|
|
245
|
+
if (!init.ok)
|
|
246
|
+
return { ok: false, error: init.stderr || 'git init failed' };
|
|
247
|
+
runGit(['symbolic-ref', 'HEAD', `refs/heads/${branch}`], cwd);
|
|
248
|
+
}
|
|
249
|
+
initialized = true;
|
|
250
|
+
state.gitInitialized = true;
|
|
251
|
+
Logger.info(
|
|
252
|
+
`[bootstrap] Initialized git repo (branch ${branch}) at ${cwd}.`,
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// A push needs a commit; create one only when HEAD does not resolve yet.
|
|
257
|
+
//
|
|
258
|
+
// SECURITY (Story #3894): do NOT `git add -A` here. `.gitignore` seeding
|
|
259
|
+
// (`ensureGitignore`) runs two phases later in the pipeline, so at this
|
|
260
|
+
// point a cold-start folder may still contain secret-bearing files
|
|
261
|
+
// (`.env`, `.mcp.json`). Staging the whole tree before any gitignore
|
|
262
|
+
// exists — followed immediately by `gh repo create --push` — would push
|
|
263
|
+
// those secrets to a brand-new (often public) remote with no per-file
|
|
264
|
+
// consent, violating `security-baseline.md` § Secrets Management. The
|
|
265
|
+
// push only needs a commit to *exist*, so we create an empty one; the
|
|
266
|
+
// operator's own first content commit lands after the gitignore phase has
|
|
267
|
+
// already excluded the secret-bearing paths.
|
|
268
|
+
let committed = false;
|
|
269
|
+
if (!runGit(['rev-parse', '--verify', 'HEAD'], cwd).ok) {
|
|
270
|
+
const commit = runGit(
|
|
271
|
+
[
|
|
272
|
+
...gitIdentityArgs(cwd, state.answers),
|
|
273
|
+
'commit',
|
|
274
|
+
'--allow-empty',
|
|
275
|
+
'-m',
|
|
276
|
+
'Initial commit',
|
|
277
|
+
],
|
|
278
|
+
cwd,
|
|
279
|
+
);
|
|
280
|
+
if (!commit.ok) {
|
|
281
|
+
return { ok: false, error: commit.stderr || 'git commit failed' };
|
|
282
|
+
}
|
|
283
|
+
committed = true;
|
|
284
|
+
Logger.info('[bootstrap] Created initial commit.');
|
|
285
|
+
}
|
|
286
|
+
return { ok: true, initialized, committed };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Wire the local `origin` remote to owner/repo when it is missing, so the
|
|
291
|
+
* GitHub bootstrap — which infers the target repo from the local remote —
|
|
292
|
+
* can run. This is the companion to `createGithubRepo`: that path wires
|
|
293
|
+
* `origin` itself via `--remote origin`, but a repo that already exists (or
|
|
294
|
+
* a re-run after a partial failure) leaves the local folder unlinked. Only
|
|
295
|
+
* acts when the repo actually exists on GitHub; pushes the base branch to
|
|
296
|
+
* set upstream, downgrading a rejected push to a warning since the bootstrap
|
|
297
|
+
* only needs the remote to resolve (content sync is the operator's to settle).
|
|
298
|
+
*/
|
|
299
|
+
async function ensureGitRemote(state, execImpl = exec) {
|
|
300
|
+
const cwd = state.projectRoot;
|
|
301
|
+
const { owner, repo } = state.answers;
|
|
302
|
+
const branch = state.answers.baseBranch || 'main';
|
|
303
|
+
if (runGit(['remote', 'get-url', 'origin'], cwd).ok) return;
|
|
304
|
+
if (!(await repoExists(owner, repo, execImpl))) {
|
|
305
|
+
Logger.warn(
|
|
306
|
+
`[bootstrap] No 'origin' remote and ${owner}/${repo} does not exist on GitHub — skipping remote wiring.`,
|
|
307
|
+
);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
const url = `https://github.com/${owner}/${repo}.git`;
|
|
311
|
+
const add = runGit(['remote', 'add', 'origin', url], cwd);
|
|
312
|
+
if (!add.ok) {
|
|
313
|
+
Logger.warn(`[bootstrap] Could not add 'origin' remote: ${add.stderr}`);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
Logger.info(`[bootstrap] Wired 'origin' → ${url}.`);
|
|
317
|
+
const push = runGit(['push', '-u', 'origin', branch], cwd);
|
|
318
|
+
if (!push.ok) {
|
|
319
|
+
Logger.warn(
|
|
320
|
+
`[bootstrap] 'origin' is set but push of '${branch}' failed (resolve manually, e.g. \`git pull --rebase origin ${branch}\`): ${push.stderr}`,
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Authoritatively check whether `owner/repo` exists, via `gh repo view`.
|
|
327
|
+
* Returns false on a not-found (so the repo can be created), true when it
|
|
328
|
+
* resolves, and true on any other error (auth/network/etc.) so a transient
|
|
329
|
+
* failure never triggers a spurious create attempt. Used instead of an
|
|
330
|
+
* `is it in the repo-list?` heuristic, which mis-fires for a brand-new
|
|
331
|
+
* account whose `gh repo list` is empty.
|
|
332
|
+
*/
|
|
333
|
+
async function repoExists(owner, repo, execImpl = exec) {
|
|
334
|
+
try {
|
|
335
|
+
await execImpl({
|
|
336
|
+
args: ['repo', 'view', `${owner}/${repo}`, '--json', 'name'],
|
|
337
|
+
});
|
|
338
|
+
return true;
|
|
339
|
+
} catch (err) {
|
|
340
|
+
if (err instanceof GhNotFoundError) return false;
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Link the repo to the Projects V2 board (`gh project link`) so issues and
|
|
347
|
+
* PRs from the repo surface on the project. Runs whenever both a numeric
|
|
348
|
+
* project number and a repo are resolved — a freshly created project or an
|
|
349
|
+
* existing one picked from the list. Non-fatal and re-run-safe: an
|
|
350
|
+
* already-linked repo or a transient hiccup is downgraded to a warning so it
|
|
351
|
+
* never fails the bootstrap.
|
|
352
|
+
*/
|
|
353
|
+
async function ensureProjectLinked(state, execImpl = exec) {
|
|
354
|
+
const { owner, repo } = state.answers;
|
|
355
|
+
const pn = String(state.answers.projectNumber ?? '');
|
|
356
|
+
if (!/^\d+$/.test(pn) || !repo) return;
|
|
357
|
+
try {
|
|
358
|
+
await execImpl({
|
|
359
|
+
args: ['project', 'link', pn, '--owner', owner, '--repo', repo],
|
|
360
|
+
});
|
|
361
|
+
Logger.info(
|
|
362
|
+
`[bootstrap] Linked repo ${owner}/${repo} to Project V2 #${pn}.`,
|
|
363
|
+
);
|
|
364
|
+
} catch (err) {
|
|
365
|
+
Logger.warn(
|
|
366
|
+
`[bootstrap] Could not link repo ${owner}/${repo} to Project V2 #${pn} (continuing): ${err.message}`,
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/** Visibilities `gh repo create` accepts; each maps to a `--<v>` flag. */
|
|
372
|
+
export const REPO_VISIBILITIES = Object.freeze([
|
|
373
|
+
'private',
|
|
374
|
+
'public',
|
|
375
|
+
'internal',
|
|
376
|
+
]);
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Resolve the new-repo visibility from `--visibility` (default `private`).
|
|
380
|
+
* Case-insensitive. Returns `null` for an unrecognized value so the caller
|
|
381
|
+
* can reject it with a clear message instead of silently defaulting.
|
|
382
|
+
*/
|
|
383
|
+
export function resolveRepoVisibility(flags = {}) {
|
|
384
|
+
const raw = flags.visibility;
|
|
385
|
+
if (typeof raw !== 'string' || raw.length === 0) return 'private';
|
|
386
|
+
const value = raw.trim().toLowerCase();
|
|
387
|
+
return REPO_VISIBILITIES.includes(value) ? value : null;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Create the GitHub repo from the resolved owner/repo. `--source` links the
|
|
392
|
+
* existing local repo, `--remote origin` wires the remote, and `--push`
|
|
393
|
+
* uploads the current branch — so the local tree and the new remote stay in
|
|
394
|
+
* lockstep and Step 1's auto-detection works on a re-run. Visibility comes
|
|
395
|
+
* from `--visibility` (default private). Throws GhExecError on failure
|
|
396
|
+
* (surfaced by the caller).
|
|
397
|
+
*/
|
|
398
|
+
async function createGithubRepo(state, execImpl = exec) {
|
|
399
|
+
const { owner, repo } = state.answers;
|
|
400
|
+
const slug = `${owner}/${repo}`;
|
|
401
|
+
const visibility = resolveRepoVisibility(state.flags);
|
|
402
|
+
await execImpl({
|
|
403
|
+
args: [
|
|
404
|
+
'repo',
|
|
405
|
+
'create',
|
|
406
|
+
slug,
|
|
407
|
+
`--${visibility}`,
|
|
408
|
+
'--source',
|
|
409
|
+
state.projectRoot,
|
|
410
|
+
'--remote',
|
|
411
|
+
'origin',
|
|
412
|
+
'--push',
|
|
413
|
+
],
|
|
414
|
+
});
|
|
415
|
+
Logger.info(
|
|
416
|
+
`[bootstrap] Created GitHub repo ${slug} (${visibility}) and pushed.`,
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Find an existing Projects V2 board owned by `owner` whose title matches
|
|
422
|
+
* `title` exactly (case-insensitive, trimmed), returning its numeric id or
|
|
423
|
+
* `null` when none matches. Lists through the injected `execImpl` seam (the
|
|
424
|
+
* same `gh project list --owner X --format json` shape `gh-list` parses) so
|
|
425
|
+
* the dedupe is unit-testable without spawning a real `gh`. Any non-zero
|
|
426
|
+
* exit, spawn error, or unparseable payload degrades to `null` (no match) so
|
|
427
|
+
* a transient list failure never blocks creation.
|
|
428
|
+
*
|
|
429
|
+
* @param {string} owner
|
|
430
|
+
* @param {string} title
|
|
431
|
+
* @param {typeof exec} execImpl
|
|
432
|
+
* @returns {Promise<number|null>}
|
|
433
|
+
*/
|
|
434
|
+
async function findExistingProjectNumber(owner, title, execImpl) {
|
|
435
|
+
const wanted = title.trim().toLowerCase();
|
|
436
|
+
if (wanted.length === 0) return null;
|
|
437
|
+
let res;
|
|
438
|
+
try {
|
|
439
|
+
res = await execImpl({
|
|
440
|
+
args: ['project', 'list', '--owner', owner, '--format', 'json'],
|
|
441
|
+
});
|
|
442
|
+
} catch {
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
445
|
+
let parsed;
|
|
446
|
+
try {
|
|
447
|
+
parsed = JSON.parse(res?.stdout ?? '');
|
|
448
|
+
} catch {
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
const projects = Array.isArray(parsed)
|
|
452
|
+
? parsed
|
|
453
|
+
: Array.isArray(parsed?.projects)
|
|
454
|
+
? parsed.projects
|
|
455
|
+
: [];
|
|
456
|
+
for (const item of projects) {
|
|
457
|
+
if (!item || typeof item !== 'object') continue;
|
|
458
|
+
if (!Number.isInteger(item.number)) continue;
|
|
459
|
+
const itemTitle =
|
|
460
|
+
typeof item.title === 'string' ? item.title.trim().toLowerCase() : '';
|
|
461
|
+
if (itemTitle === wanted) return item.number;
|
|
462
|
+
}
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Resolve the typed Projects V2 name into a numeric id and rewrite
|
|
468
|
+
* `state.answers.projectNumber` so the downstream persist + GitHub bootstrap
|
|
469
|
+
* steps treat it as an existing project (and never create a duplicate). Before
|
|
470
|
+
* creating, it **dedupes against the owner's existing project titles**: if a
|
|
471
|
+
* board with the same title already exists, that board is adopted instead of
|
|
472
|
+
* running `gh project create` — so a re-run that re-types the same name never
|
|
473
|
+
* spawns a second same-titled board (Story #3896 / review Finding B.3). Throws
|
|
474
|
+
* on create failure or when gh returns no number.
|
|
475
|
+
*/
|
|
476
|
+
async function createGithubProject(state, execImpl = exec) {
|
|
477
|
+
const { owner } = state.answers;
|
|
478
|
+
const title = String(state.answers.projectNumber);
|
|
479
|
+
const existing = await findExistingProjectNumber(owner, title, execImpl);
|
|
480
|
+
if (Number.isInteger(existing)) {
|
|
481
|
+
state.answers.projectNumber = String(existing);
|
|
482
|
+
Logger.info(
|
|
483
|
+
`[bootstrap] Reusing existing GitHub Project V2 "${title}" (#${existing}) — no duplicate created.`,
|
|
484
|
+
);
|
|
485
|
+
return existing;
|
|
486
|
+
}
|
|
487
|
+
// `gh project create` uses `--format json` (not `--json`), so exec returns
|
|
488
|
+
// the raw `{ stdout }` envelope — parse the number ourselves.
|
|
489
|
+
const res = await execImpl({
|
|
490
|
+
args: [
|
|
491
|
+
'project',
|
|
492
|
+
'create',
|
|
493
|
+
'--owner',
|
|
494
|
+
owner,
|
|
495
|
+
'--title',
|
|
496
|
+
title,
|
|
497
|
+
'--format',
|
|
498
|
+
'json',
|
|
499
|
+
],
|
|
500
|
+
});
|
|
501
|
+
let number = null;
|
|
502
|
+
try {
|
|
503
|
+
number = JSON.parse(res.stdout)?.number ?? null;
|
|
504
|
+
} catch {
|
|
505
|
+
/* fall through to the guard below */
|
|
506
|
+
}
|
|
507
|
+
if (!Number.isInteger(number)) {
|
|
508
|
+
throw new Error(
|
|
509
|
+
`gh project create returned no numeric project number (stdout: ${res.stdout?.trim() ?? ''})`,
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
state.answers.projectNumber = String(number);
|
|
513
|
+
Logger.info(`[bootstrap] Created GitHub Project V2 "${title}" (#${number}).`);
|
|
514
|
+
return number;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// ---------------------------------------------------------------------------
|
|
518
|
+
// Question list (Step 3).
|
|
519
|
+
// ---------------------------------------------------------------------------
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Build the Step 3 question list. `silentAccept` keys (owner/repo/baseBranch/
|
|
523
|
+
* operatorHandle) are git-inferred and accepted without prompting unless an
|
|
524
|
+
* override is supplied. The `operatorHandle` and `projectNumber` defaults
|
|
525
|
+
* track the repo owner / repo name respectively (see post-processing in
|
|
526
|
+
* `collectAndConfirm`).
|
|
527
|
+
*/
|
|
528
|
+
export function buildQuestions(defaults, flags, env = process.env, lists = {}) {
|
|
529
|
+
const owner = resolveOwnerForPicker(defaults, flags, env);
|
|
530
|
+
// Pre-fetched lists (shared with the summary display) are a fast path used
|
|
531
|
+
// only when populated. They're empty when the owner is unknown up front
|
|
532
|
+
// (a folder with no git remote), so the pickers fall back to a live fetch
|
|
533
|
+
// keyed off the owner the operator just typed (`answers.owner`).
|
|
534
|
+
const reposList = lists.reposList;
|
|
535
|
+
const projectsList = lists.projectsList;
|
|
536
|
+
const pickerOwner = (answers) => answers?.owner || owner;
|
|
537
|
+
return [
|
|
538
|
+
{
|
|
539
|
+
key: 'owner',
|
|
540
|
+
flag: 'owner',
|
|
541
|
+
env: 'GH_OWNER',
|
|
542
|
+
message: 'Github repo owner',
|
|
543
|
+
default: defaults.owner,
|
|
544
|
+
required: true,
|
|
545
|
+
validate: (v) =>
|
|
546
|
+
/^[A-Za-z0-9][A-Za-z0-9-]*$/.test(v) ? null : 'Invalid GitHub owner',
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
key: 'operatorHandle',
|
|
550
|
+
flag: 'operator-handle',
|
|
551
|
+
env: 'GH_OPERATOR_HANDLE',
|
|
552
|
+
message: 'Github username/handle (without the @)',
|
|
553
|
+
// Default tracks the repo owner; resolved post-collect if left blank.
|
|
554
|
+
default: defaults.owner,
|
|
555
|
+
required: false,
|
|
556
|
+
validate: (v) =>
|
|
557
|
+
v.length === 0 || /^[A-Za-z0-9-]+$/.test(v)
|
|
558
|
+
? null
|
|
559
|
+
: 'Invalid GitHub handle',
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
key: 'repo',
|
|
563
|
+
flag: 'repo',
|
|
564
|
+
env: 'GH_REPO',
|
|
565
|
+
message:
|
|
566
|
+
'Github repo name - Select from the list or enter a new name to create one',
|
|
567
|
+
default: defaults.repo,
|
|
568
|
+
required: true,
|
|
569
|
+
picker: {
|
|
570
|
+
list: (answers) => {
|
|
571
|
+
if (Array.isArray(reposList) && reposList.length > 0)
|
|
572
|
+
return reposList;
|
|
573
|
+
const o = pickerOwner(answers);
|
|
574
|
+
return o ? listRepos({ owner: o }).map(bareRepoName) : [];
|
|
575
|
+
},
|
|
576
|
+
},
|
|
577
|
+
validate: (v) =>
|
|
578
|
+
/^[A-Za-z0-9._-]+$/.test(v) ? null : 'Invalid GitHub repo name',
|
|
579
|
+
},
|
|
580
|
+
{
|
|
581
|
+
key: 'baseBranch',
|
|
582
|
+
flag: 'base-branch',
|
|
583
|
+
env: 'GH_BASE_BRANCH',
|
|
584
|
+
message: 'Base branch',
|
|
585
|
+
default: defaults.baseBranch || 'main',
|
|
586
|
+
required: true,
|
|
587
|
+
validate: (v) => (v.length > 0 ? null : 'Base branch is required'),
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
key: 'projectNumber',
|
|
591
|
+
flag: 'project-number',
|
|
592
|
+
env: 'GH_PROJECT_NUMBER',
|
|
593
|
+
message:
|
|
594
|
+
'Github Project V2 name - Select from the list or enter a new name to create one',
|
|
595
|
+
// Prefer the already-stored numeric project number (an
|
|
596
|
+
// already-provisioned project on a re-run) so `--assume-yes` resolves a
|
|
597
|
+
// numeric answer that `detectCreation` treats as existing — never a
|
|
598
|
+
// duplicate board (Story #3896). Falls back to the repo name only on a
|
|
599
|
+
// genuine first run where nothing is stored yet.
|
|
600
|
+
default: defaults.projectNumber || defaults.repo,
|
|
601
|
+
required: false,
|
|
602
|
+
picker: {
|
|
603
|
+
list: (answers) => {
|
|
604
|
+
if (Array.isArray(projectsList) && projectsList.length > 0) {
|
|
605
|
+
return projectsList;
|
|
606
|
+
}
|
|
607
|
+
const o = pickerOwner(answers);
|
|
608
|
+
return o ? listProjects({ owner: o }) : [];
|
|
609
|
+
},
|
|
610
|
+
},
|
|
611
|
+
// Accept blank (skip), an existing project number, or a new project
|
|
612
|
+
// name (letters/digits/space/._-).
|
|
613
|
+
validate: (v) =>
|
|
614
|
+
v.length === 0 || /^\d+$/.test(v) || /^[A-Za-z0-9 ._-]+$/.test(v)
|
|
615
|
+
? null
|
|
616
|
+
: 'Invalid project name',
|
|
617
|
+
},
|
|
618
|
+
];
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
const INFERRED_KEYS = Object.freeze([
|
|
622
|
+
'owner',
|
|
623
|
+
'repo',
|
|
624
|
+
'baseBranch',
|
|
625
|
+
'operatorHandle',
|
|
626
|
+
]);
|
|
627
|
+
const FLAG_BY_KEY = Object.freeze({
|
|
628
|
+
owner: 'owner',
|
|
629
|
+
repo: 'repo',
|
|
630
|
+
baseBranch: 'base-branch',
|
|
631
|
+
operatorHandle: 'operator-handle',
|
|
632
|
+
});
|
|
633
|
+
const ENV_BY_KEY = Object.freeze({
|
|
634
|
+
owner: 'GH_OWNER',
|
|
635
|
+
repo: 'GH_REPO',
|
|
636
|
+
baseBranch: 'GH_BASE_BRANCH',
|
|
637
|
+
operatorHandle: 'GH_OPERATOR_HANDLE',
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
/** Keys whose git-inferred default is accepted without prompting. */
|
|
641
|
+
export function resolveSilentAccept(defaults, flags, env = process.env) {
|
|
642
|
+
const out = [];
|
|
643
|
+
for (const key of INFERRED_KEYS) {
|
|
644
|
+
const value = defaults?.[key];
|
|
645
|
+
if (typeof value !== 'string' || value.length === 0) continue;
|
|
646
|
+
if (typeof flags?.[FLAG_BY_KEY[key]] === 'string') continue;
|
|
647
|
+
if (typeof env?.[ENV_BY_KEY[key]] === 'string') continue;
|
|
648
|
+
out.push(key);
|
|
649
|
+
}
|
|
650
|
+
return out;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// ---------------------------------------------------------------------------
|
|
654
|
+
// GitHub-side bootstrap (Step 6) — same wiring as bootstrap.js.
|
|
655
|
+
// ---------------------------------------------------------------------------
|
|
656
|
+
|
|
657
|
+
async function runGithubBootstrap(answers, opts) {
|
|
658
|
+
const { runBootstrap, preflightGh, preflightRuntimeDeps } = await import(
|
|
659
|
+
'./agents-bootstrap-github.js'
|
|
660
|
+
);
|
|
661
|
+
await preflightGh();
|
|
662
|
+
await preflightRuntimeDeps();
|
|
663
|
+
const { resolveConfig, validateOrchestrationConfig } = await import(
|
|
664
|
+
'./lib/config-resolver.js'
|
|
665
|
+
);
|
|
666
|
+
// `resolveConfig` reads github.projectNumber from .agentrc.json, which the
|
|
667
|
+
// persistProjectNumber step writes BEFORE this runs — so the provider reuses
|
|
668
|
+
// the existing project instead of creating a new one (Bug: gave #8, created
|
|
669
|
+
// #12 because the number never reached the provider config).
|
|
670
|
+
const config = resolveConfig();
|
|
671
|
+
validateOrchestrationConfig(config);
|
|
672
|
+
return runBootstrap(config, {
|
|
673
|
+
project: config.project,
|
|
674
|
+
github: config.github,
|
|
675
|
+
assumeYes: opts.assumeYes,
|
|
676
|
+
baseBranch: answers.baseBranch,
|
|
677
|
+
// Real consent signal threaded from `parseAndValidate` (Story #3897):
|
|
678
|
+
// interactive operator confirmation, `--assume-yes`, or
|
|
679
|
+
// `--approve-github-admin`. Default-deny at the boundary gate when absent.
|
|
680
|
+
githubAdminApproved: opts.githubAdminApproved === true,
|
|
681
|
+
// Opt-in: delete the Projects V2 built-in workflows that race against the
|
|
682
|
+
// orchestrator's ColumnSync (e.g. "Pull request merged"). Off by default.
|
|
683
|
+
reapConflictingWorkflows: Boolean(opts.reapConflictingWorkflows),
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/** True only when every github-admin sub-mutation that ran succeeded. */
|
|
688
|
+
function githubSubMutationsSucceeded(gh) {
|
|
689
|
+
if (gh.branchProtection?.status === 'failed') return false;
|
|
690
|
+
if (gh.mergeMethods?.status === 'failed') return false;
|
|
691
|
+
return true;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/** Phase groups whose mutations actually landed, for the install ledger. */
|
|
695
|
+
function resolveAppliedGroups(approvedGroups, report) {
|
|
696
|
+
const applied = new Set();
|
|
697
|
+
for (const group of approvedGroups ?? []) {
|
|
698
|
+
if (group === PHASE_GROUPS.GITHUB_ADMIN) {
|
|
699
|
+
const gh = report?.github;
|
|
700
|
+
if (gh && !gh.error && !gh.skipped && githubSubMutationsSucceeded(gh)) {
|
|
701
|
+
applied.add(group);
|
|
702
|
+
}
|
|
703
|
+
continue;
|
|
704
|
+
}
|
|
705
|
+
applied.add(group);
|
|
706
|
+
}
|
|
707
|
+
return applied;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// ---------------------------------------------------------------------------
|
|
711
|
+
// Pipeline phases
|
|
712
|
+
// ---------------------------------------------------------------------------
|
|
713
|
+
|
|
714
|
+
/**
|
|
715
|
+
* Step 1 — Parse argv, handle `--help`, and enforce the non-TTY contract.
|
|
716
|
+
*
|
|
717
|
+
* Consent contract (Story #3897). A non-TTY run has no operator to confirm
|
|
718
|
+
* the summary loop in `collectAndConfirm`, so the irreversible GitHub-admin
|
|
719
|
+
* mutations cannot ride on a real confirmation — they need an explicit
|
|
720
|
+
* up-front signal. The gate therefore requires **either** `--assume-yes`
|
|
721
|
+
* **or** `--approve-github-admin` on any non-TTY run (matching the
|
|
722
|
+
* `--help` text), and computes `githubAdminApproved` once for the whole run:
|
|
723
|
+
*
|
|
724
|
+
* - **interactive (TTY)** → consent is the operator's `Is this correct?`
|
|
725
|
+
* confirmation in `collectAndConfirm`, so the run is approved.
|
|
726
|
+
* - **non-TTY** → consent is `--assume-yes` or `--approve-github-admin`;
|
|
727
|
+
* without one of those the run halts before any mutation.
|
|
728
|
+
*
|
|
729
|
+
* `githubAdminApproved` flows down to `runGithubBootstrap`, which forwards it
|
|
730
|
+
* to the boundary gate in `agents-bootstrap-github.js#runBootstrap`. That
|
|
731
|
+
* gate is default-deny, so a non-approved value makes the GitHub-admin phase
|
|
732
|
+
* a verified no-op instead of a silent mutation.
|
|
733
|
+
*/
|
|
734
|
+
export function parseAndValidate(argv, opts = {}) {
|
|
735
|
+
const stdout = opts.stdout ?? process.stdout;
|
|
736
|
+
const stdin = opts.stdin ?? process.stdin;
|
|
737
|
+
const flags = parseFlags(argv);
|
|
738
|
+
if (flags.help) {
|
|
739
|
+
stdout.write(HELP);
|
|
740
|
+
return { ok: false, exit: 0 };
|
|
741
|
+
}
|
|
742
|
+
const interactive = Boolean(stdin.isTTY) && !flags['assume-yes'];
|
|
743
|
+
const assumeYes = Boolean(flags['assume-yes']);
|
|
744
|
+
const approveGithubAdmin = Boolean(flags['approve-github-admin']);
|
|
745
|
+
// A non-TTY run cannot collect operator consent interactively, so it MUST
|
|
746
|
+
// carry an explicit consent signal. This restores parity with the --help
|
|
747
|
+
// text, which has always claimed --assume-yes is required for non-TTY runs.
|
|
748
|
+
// (owner/repo are resolved from flags/env/git-remote downstream — a consent
|
|
749
|
+
// signal alone is sufficient to advance, exactly as the pre-Story #3897
|
|
750
|
+
// `--assume-yes` path did.)
|
|
751
|
+
if (!interactive && !assumeYes && !approveGithubAdmin) {
|
|
752
|
+
Logger.error(
|
|
753
|
+
'[bootstrap] non-TTY run requires --assume-yes or --approve-github-admin ' +
|
|
754
|
+
'(no operator is present to confirm the GitHub-admin mutations).',
|
|
755
|
+
);
|
|
756
|
+
return { ok: false, exit: 1 };
|
|
757
|
+
}
|
|
758
|
+
// Real GitHub-admin consent: an interactive run confirms it in
|
|
759
|
+
// `collectAndConfirm`; a non-TTY run signals it via flag (above).
|
|
760
|
+
const githubAdminApproved = interactive || assumeYes || approveGithubAdmin;
|
|
761
|
+
if (resolveRepoVisibility(flags) === null) {
|
|
762
|
+
Logger.error(
|
|
763
|
+
`[bootstrap] invalid --visibility "${flags.visibility}". ` +
|
|
764
|
+
`Expected one of: ${REPO_VISIBILITIES.join(', ')}.`,
|
|
765
|
+
);
|
|
766
|
+
return { ok: false, exit: 1 };
|
|
767
|
+
}
|
|
768
|
+
return {
|
|
769
|
+
ok: true,
|
|
770
|
+
payload: { flags, interactive, assumeYes, githubAdminApproved },
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
/**
|
|
775
|
+
* Step 1b — Resolve paths, infer defaults from git, and echo the detected
|
|
776
|
+
* values back to the operator (the "share found values" requirement).
|
|
777
|
+
*/
|
|
778
|
+
export function prepareContext(state, opts = {}) {
|
|
779
|
+
const scriptUrl = opts.scriptUrl ?? import.meta.url;
|
|
780
|
+
const here = path.dirname(fileURLToPath(scriptUrl));
|
|
781
|
+
const projectRoot = opts.projectRoot ?? process.cwd();
|
|
782
|
+
const agentRoot = path.resolve(here, '..');
|
|
783
|
+
const defaults = inferDefaults(projectRoot);
|
|
784
|
+
const silentAccept = resolveSilentAccept(defaults, state.flags);
|
|
785
|
+
|
|
786
|
+
Logger.info('[bootstrap] Detected from local git:');
|
|
787
|
+
Logger.info(` owner ${defaults.owner ?? '(none)'}`);
|
|
788
|
+
Logger.info(` repo ${defaults.repo ?? '(none)'}`);
|
|
789
|
+
Logger.info(` base branch ${defaults.baseBranch ?? '(none)'}`);
|
|
790
|
+
Logger.info(` username ${defaults.operatorHandle ?? '(none)'}`);
|
|
791
|
+
|
|
792
|
+
return {
|
|
793
|
+
ok: true,
|
|
794
|
+
payload: { projectRoot, agentRoot, defaults, silentAccept },
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* Step 2 — Preflight. Work-tree check is informational (does not fail the
|
|
800
|
+
* gate); adds the Projects V2 permission check. Prints a pass/fail line for
|
|
801
|
+
* every check.
|
|
802
|
+
*/
|
|
803
|
+
export async function runPreflightPhase(state, opts = {}) {
|
|
804
|
+
const run = opts.run ?? runPreflight;
|
|
805
|
+
const skipGithub = Boolean(state.flags['skip-github']);
|
|
806
|
+
const result = await run({
|
|
807
|
+
skipGithub,
|
|
808
|
+
requireWorkTree: false,
|
|
809
|
+
checkProjectScope: !skipGithub,
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
for (const check of result.checks) {
|
|
813
|
+
if (check.ok) {
|
|
814
|
+
Logger.info(
|
|
815
|
+
`[bootstrap] ✓ ${check.name}${check.detail ? ` — ${check.detail}` : ''}`,
|
|
816
|
+
);
|
|
817
|
+
} else {
|
|
818
|
+
Logger.error(`[bootstrap] ✗ ${check.name}: ${check.remedy}`);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
if (!result.ok) {
|
|
823
|
+
Logger.error(
|
|
824
|
+
'[bootstrap] Preflight failed. Resolve the issues above and re-run.',
|
|
825
|
+
);
|
|
826
|
+
return { ok: false, exit: 1 };
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
Logger.info(
|
|
830
|
+
`[bootstrap] git initialized: ${result.gitInitialized ? 'yes' : 'no'}`,
|
|
831
|
+
);
|
|
832
|
+
return {
|
|
833
|
+
ok: true,
|
|
834
|
+
payload: { preflight: result, gitInitialized: result.gitInitialized },
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
/** Render the resolved answers as a human-readable summary block. */
|
|
839
|
+
function renderAnswerSummary(
|
|
840
|
+
answers,
|
|
841
|
+
creation,
|
|
842
|
+
project,
|
|
843
|
+
gitInitialized,
|
|
844
|
+
visibility,
|
|
845
|
+
) {
|
|
846
|
+
const newRepoNote = creation.newRepo
|
|
847
|
+
? ` (NEW — will be created, ${visibility})`
|
|
848
|
+
: '';
|
|
849
|
+
const lines = [
|
|
850
|
+
'\n=== Review your answers ===',
|
|
851
|
+
` Repo owner ${answers.owner}`,
|
|
852
|
+
` Username/handle ${answers.operatorHandle || '(none)'}`,
|
|
853
|
+
` Repo name ${answers.repo}${newRepoNote}`,
|
|
854
|
+
` Base branch ${answers.baseBranch}`,
|
|
855
|
+
` Project V2 name ${project.name}${creation.newProject ? ' (NEW — will be created)' : ''}`,
|
|
856
|
+
` Project V2 # ${project.number}`,
|
|
857
|
+
` Local git ${gitInitialized ? 'initialized' : 'will be initialized'}`,
|
|
858
|
+
];
|
|
859
|
+
return lines.join('\n');
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
/**
|
|
863
|
+
* Determine whether the answers ask for resources that do not exist yet.
|
|
864
|
+
* The repo is "new" when `gh repo view owner/repo` reports it does not
|
|
865
|
+
* exist — an authoritative per-repo probe rather than an "is it in the
|
|
866
|
+
* repo-list?" check, which mis-fired for a brand-new account whose
|
|
867
|
+
* `gh repo list` is empty (it then assumed the repo already existed and
|
|
868
|
+
* skipped creation). A non-numeric project answer (a typed name, not a
|
|
869
|
+
* picked number) is "new". When GitHub is skipped there is nothing to
|
|
870
|
+
* create, so detection is bypassed.
|
|
871
|
+
*/
|
|
872
|
+
async function detectCreation(answers, skipGithub) {
|
|
873
|
+
const creation = { newRepo: false, newProject: false };
|
|
874
|
+
if (skipGithub) return creation;
|
|
875
|
+
if (answers.repo && answers.owner) {
|
|
876
|
+
creation.newRepo = !(await repoExists(answers.owner, answers.repo));
|
|
877
|
+
}
|
|
878
|
+
const pn = answers.projectNumber;
|
|
879
|
+
if (typeof pn === 'string' && pn.length > 0 && !/^\d+$/.test(pn)) {
|
|
880
|
+
creation.newProject = true;
|
|
881
|
+
}
|
|
882
|
+
return creation;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
/**
|
|
886
|
+
* Resolve the Projects V2 answer into a `{ name, number }` pair for the
|
|
887
|
+
* summary. The picker stores only the numeric value, so for an existing
|
|
888
|
+
* project (numeric answer) we look the name up in the owner's project list.
|
|
889
|
+
* A typed answer (non-numeric) is a new project name with no number yet.
|
|
890
|
+
*/
|
|
891
|
+
function resolveProjectDisplay(answers, skipGithub, projectsList) {
|
|
892
|
+
const pn = answers.projectNumber;
|
|
893
|
+
if (!pn) return { name: '(skip)', number: '(skip)' };
|
|
894
|
+
if (/^\d+$/.test(pn)) {
|
|
895
|
+
let name = '(unknown)';
|
|
896
|
+
if (!skipGithub) {
|
|
897
|
+
const projects =
|
|
898
|
+
projectsList ?? safeList(() => listProjects({ owner: answers.owner }));
|
|
899
|
+
const match = projects.find((p) => p.value === pn);
|
|
900
|
+
if (match) {
|
|
901
|
+
const m = /^(.*)\s+\(#\d+\)$/.exec(match.label);
|
|
902
|
+
name = m ? m[1] : match.label;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
return { name, number: pn };
|
|
906
|
+
}
|
|
907
|
+
// Typed name → new project; number is assigned at creation time.
|
|
908
|
+
return { name: pn, number: '(new)' };
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
/**
|
|
912
|
+
* Steps 3 + 4 — Collect answers, show a summary, and confirm. Interactive
|
|
913
|
+
* runs that answer "no" loop back and re-ask. Non-interactive runs
|
|
914
|
+
* auto-accept. Then collect creation approval when a new repo/project was
|
|
915
|
+
* requested.
|
|
916
|
+
*/
|
|
917
|
+
export async function collectAndConfirm(state) {
|
|
918
|
+
const skipGithub = Boolean(state.flags['skip-github']);
|
|
919
|
+
const owner = resolveOwnerForPicker(state.defaults, state.flags);
|
|
920
|
+
// Fetch the owner's repos + projects ONCE and reuse for the pickers and
|
|
921
|
+
// the summary display — so the resolved project name never depends on a
|
|
922
|
+
// second (flaky) `gh` call. (Repo existence for the creation check is a
|
|
923
|
+
// separate, authoritative `gh repo view` probe in `detectCreation`.)
|
|
924
|
+
const reposList =
|
|
925
|
+
!skipGithub && owner
|
|
926
|
+
? safeList(() => listRepos({ owner }).map(bareRepoName))
|
|
927
|
+
: [];
|
|
928
|
+
const projectsList =
|
|
929
|
+
!skipGithub && owner ? safeList(() => listProjects({ owner })) : [];
|
|
930
|
+
|
|
931
|
+
let silentAccept = state.silentAccept;
|
|
932
|
+
// Loop until the operator confirms the summary (or we auto-accept).
|
|
933
|
+
for (;;) {
|
|
934
|
+
const { answers, missing } = await collectAnswers({
|
|
935
|
+
questions: buildQuestions(state.defaults, state.flags, process.env, {
|
|
936
|
+
reposList,
|
|
937
|
+
projectsList,
|
|
938
|
+
}),
|
|
939
|
+
flags: state.flags,
|
|
940
|
+
interactive: state.interactive,
|
|
941
|
+
assumeYes: state.assumeYes,
|
|
942
|
+
silentAccept,
|
|
943
|
+
});
|
|
944
|
+
if (missing.length > 0) {
|
|
945
|
+
Logger.error(
|
|
946
|
+
`[bootstrap] missing required answers: ${missing.join(', ')}`,
|
|
947
|
+
);
|
|
948
|
+
return { ok: false, exit: 1 };
|
|
949
|
+
}
|
|
950
|
+
// Defaults that track another answer: handle ⇐ owner, project ⇐ repo.
|
|
951
|
+
if (!answers.operatorHandle) answers.operatorHandle = answers.owner;
|
|
952
|
+
// Strip a single leading `@` so the starter template's `@[USERNAME]`
|
|
953
|
+
// substitution yields `@foo`, not `@@foo` (Story #3700). The flag/env
|
|
954
|
+
// paths bypass the interactive validator that already rejects a leading
|
|
955
|
+
// `@`, so normalize uniformly here.
|
|
956
|
+
answers.operatorHandle = normalizeHandleAnswer(answers.operatorHandle);
|
|
957
|
+
|
|
958
|
+
const creation = await detectCreation(answers, skipGithub);
|
|
959
|
+
const project = resolveProjectDisplay(answers, skipGithub, projectsList);
|
|
960
|
+
Logger.info(
|
|
961
|
+
renderAnswerSummary(
|
|
962
|
+
answers,
|
|
963
|
+
creation,
|
|
964
|
+
project,
|
|
965
|
+
state.gitInitialized,
|
|
966
|
+
resolveRepoVisibility(state.flags),
|
|
967
|
+
),
|
|
968
|
+
);
|
|
969
|
+
const correct = await confirmYesNo('Is this correct?', state.interactive);
|
|
970
|
+
if (!correct) {
|
|
971
|
+
Logger.info('[bootstrap] Okay — let’s try again.');
|
|
972
|
+
// Re-prompt everything on the next pass (drop silent-accept).
|
|
973
|
+
silentAccept = [];
|
|
974
|
+
continue;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
// In --dry-run we only collect/confirm info, so never ask to create.
|
|
978
|
+
if (!state.flags['dry-run'] && (creation.newRepo || creation.newProject)) {
|
|
979
|
+
const approved = await confirmYesNo(
|
|
980
|
+
'Create the new GitHub repo/project listed above?',
|
|
981
|
+
state.interactive,
|
|
982
|
+
);
|
|
983
|
+
if (!approved) {
|
|
984
|
+
Logger.error(
|
|
985
|
+
'[bootstrap] Creation declined — cannot continue without the repo/project. Exiting.',
|
|
986
|
+
);
|
|
987
|
+
return { ok: false, exit: 1 };
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
return { ok: true, payload: { answers, creation } };
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
/**
|
|
995
|
+
* --dry-run gate — print the resolved answers and the full mutation plan,
|
|
996
|
+
* then halt BEFORE any file write, GitHub change, or label creation. Runs
|
|
997
|
+
* after collect/confirm so the operator sees exactly what would happen.
|
|
998
|
+
*/
|
|
999
|
+
/** Render the dry-run plan as a per-section layout (no mutations happen). */
|
|
1000
|
+
function renderDryRunPlan(state) {
|
|
1001
|
+
const a = state.answers ?? {};
|
|
1002
|
+
const c = state.creation ?? {};
|
|
1003
|
+
const flagList = Object.entries(state.flags ?? {}).map(([k, v]) =>
|
|
1004
|
+
v === true ? k : `${k}=${v}`,
|
|
1005
|
+
);
|
|
1006
|
+
return [
|
|
1007
|
+
'\n=== Dry run — nothing will be changed ===',
|
|
1008
|
+
'Values',
|
|
1009
|
+
` owner ${a.owner ?? '(none)'}`,
|
|
1010
|
+
` operator handle ${a.operatorHandle ?? '(none)'}`,
|
|
1011
|
+
` repo ${a.repo ?? '(none)'}`,
|
|
1012
|
+
` base branch ${a.baseBranch ?? '(none)'}`,
|
|
1013
|
+
` project number ${a.projectNumber || '(skip)'}`,
|
|
1014
|
+
'',
|
|
1015
|
+
'Creation',
|
|
1016
|
+
` git init ${state.gitInitialized ? 'no' : 'yes'}`,
|
|
1017
|
+
` new repo ${c.newRepo ? `yes (${resolveRepoVisibility(state.flags)})` : 'no'}`,
|
|
1018
|
+
` new project ${c.newProject ? 'yes' : 'no'}`,
|
|
1019
|
+
'',
|
|
1020
|
+
'Flags',
|
|
1021
|
+
` ${flagList.length ? flagList.join(', ') : '(none)'}`,
|
|
1022
|
+
].join('\n');
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
export function dryRunPlan(state) {
|
|
1026
|
+
if (!state.flags['dry-run']) return { ok: true, payload: {} };
|
|
1027
|
+
Logger.info(
|
|
1028
|
+
'[bootstrap] --dry-run: no files, GitHub settings, or labels will be changed.',
|
|
1029
|
+
);
|
|
1030
|
+
Logger.info(renderDryRunPlan(state));
|
|
1031
|
+
return { ok: false, exit: 0 };
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
/**
|
|
1035
|
+
* Step 5 — Provision the missing pieces of a cold start, in dependency order:
|
|
1036
|
+
*
|
|
1037
|
+
* 1. Local git — `git init` + an initial commit when the folder is not a
|
|
1038
|
+
* repo yet (so the repo create below has something to push).
|
|
1039
|
+
* 2. GitHub repo — `gh repo create --source --remote --push` when the repo
|
|
1040
|
+
* does not exist for the owner; otherwise wire the `origin` remote to the
|
|
1041
|
+
* existing repo when the local folder is not yet linked. Either way the
|
|
1042
|
+
* GitHub bootstrap can resolve the target from the local remote.
|
|
1043
|
+
* 3. GitHub Project V2 — `gh project create` from the typed name when the
|
|
1044
|
+
* project answer is a name rather than an existing number; the assigned
|
|
1045
|
+
* number is written back onto `state.answers.projectNumber`.
|
|
1046
|
+
* 4. Link — `gh project link` ties the repo to the project board so its
|
|
1047
|
+
* issues/PRs surface there (non-fatal; safe to re-run).
|
|
1048
|
+
*
|
|
1049
|
+
* Every action is idempotent and guarded by the detection done in
|
|
1050
|
+
* `collectAndConfirm`, so a re-run on an already-provisioned project is a
|
|
1051
|
+
* no-op. `--skip-github` suppresses the GitHub mutations but still runs the
|
|
1052
|
+
* local git init. `--dry-run` never reaches this step (it halts earlier).
|
|
1053
|
+
*
|
|
1054
|
+
* `deps.exec` injects the `gh-exec` seam so the GitHub-touching branches
|
|
1055
|
+
* (`gh repo create`, `gh project create`, `gh project link`) are unit-testable
|
|
1056
|
+
* without spawning a real `gh`; it defaults to the module's `exec`.
|
|
1057
|
+
*/
|
|
1058
|
+
export async function provisionResources(state, deps = {}) {
|
|
1059
|
+
const execImpl = deps.exec ?? exec;
|
|
1060
|
+
const skipGithub = Boolean(state.flags['skip-github']);
|
|
1061
|
+
|
|
1062
|
+
// 1. Local git — initialize + first commit when missing (idempotent).
|
|
1063
|
+
const git = ensureGitInitialized(state);
|
|
1064
|
+
if (!git.ok) {
|
|
1065
|
+
Logger.error(`[bootstrap] git initialization failed: ${git.error}`);
|
|
1066
|
+
return { ok: false, exit: 1 };
|
|
1067
|
+
}
|
|
1068
|
+
if (!git.initialized && !git.committed) {
|
|
1069
|
+
Logger.info('[bootstrap] git already initialized — leaving as-is.');
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
const { newRepo, newProject } = state.creation;
|
|
1073
|
+
if (skipGithub) {
|
|
1074
|
+
if (newRepo || newProject) {
|
|
1075
|
+
Logger.info(
|
|
1076
|
+
'[bootstrap] --skip-github set; not creating the GitHub repo/project.',
|
|
1077
|
+
);
|
|
1078
|
+
}
|
|
1079
|
+
return { ok: true, payload: {} };
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
// 2. GitHub repo — create + link + push when it does not exist yet;
|
|
1083
|
+
// otherwise ensure the local `origin` remote points at the existing repo
|
|
1084
|
+
// so the GitHub bootstrap can resolve the target (idempotent re-runs and
|
|
1085
|
+
// pre-created repos would otherwise leave the folder unlinked).
|
|
1086
|
+
if (newRepo) {
|
|
1087
|
+
try {
|
|
1088
|
+
await createGithubRepo(state, execImpl);
|
|
1089
|
+
} catch (err) {
|
|
1090
|
+
logGhError('repo create', err);
|
|
1091
|
+
return { ok: false, exit: 1 };
|
|
1092
|
+
}
|
|
1093
|
+
} else {
|
|
1094
|
+
await ensureGitRemote(state, execImpl);
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
// 3. GitHub Project V2 — create from the typed name; capture its number so
|
|
1098
|
+
// the persist + GitHub bootstrap steps reuse it instead of duplicating.
|
|
1099
|
+
if (newProject) {
|
|
1100
|
+
try {
|
|
1101
|
+
await createGithubProject(state, execImpl);
|
|
1102
|
+
// It now exists with a real number; downstream treats it as existing.
|
|
1103
|
+
state.creation.newProject = false;
|
|
1104
|
+
} catch (err) {
|
|
1105
|
+
logGhError('project create', err);
|
|
1106
|
+
return { ok: false, exit: 1 };
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
if (!newRepo && !newProject) {
|
|
1111
|
+
Logger.info('[bootstrap] No new GitHub resources needed.');
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
// 4. Link the repo to the project board so issues/PRs surface on it
|
|
1115
|
+
// (idempotent + non-fatal; runs for both freshly created and existing
|
|
1116
|
+
// repo/project pairs).
|
|
1117
|
+
await ensureProjectLinked(state, execImpl);
|
|
1118
|
+
|
|
1119
|
+
return { ok: true, payload: {} };
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
/**
|
|
1123
|
+
* Step 6a — Project-side bootstrap. With phased approval removed, all
|
|
1124
|
+
* project-side phase groups are treated as approved.
|
|
1125
|
+
*/
|
|
1126
|
+
export async function executeBootstrap(state) {
|
|
1127
|
+
Logger.info(
|
|
1128
|
+
`[bootstrap] Starting project bootstrap at ${state.projectRoot} (owner=${state.answers.owner} repo=${state.answers.repo} base=${state.answers.baseBranch})`,
|
|
1129
|
+
);
|
|
1130
|
+
const approvedGroups = new Set(Object.values(PHASE_GROUPS));
|
|
1131
|
+
const report = await applyProjectBootstrap({
|
|
1132
|
+
projectRoot: state.projectRoot,
|
|
1133
|
+
agentRoot: state.agentRoot,
|
|
1134
|
+
answers: state.answers,
|
|
1135
|
+
approvedGroups,
|
|
1136
|
+
skipQuality: Boolean(state.flags['skip-quality']),
|
|
1137
|
+
});
|
|
1138
|
+
return { ok: true, payload: { report, approvedGroups } };
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
/**
|
|
1142
|
+
* Step 6 (between project + GitHub) — Persist the chosen Projects V2 number
|
|
1143
|
+
* into .agentrc.json's github block so it is the stored source of truth that
|
|
1144
|
+
* resolveConfig (and the orchestrator) read back. Runs AFTER the project-side
|
|
1145
|
+
* bootstrap has ensured .agentrc.json exists and BEFORE the GitHub bootstrap,
|
|
1146
|
+
* so the provider reuses the existing project instead of creating a new one.
|
|
1147
|
+
* Merges into an existing file (ensureAgentrc never overwrites one). Stored as
|
|
1148
|
+
* an integer per the schema; a blank/new-project answer stores nothing.
|
|
1149
|
+
*/
|
|
1150
|
+
export function persistProjectNumber(state) {
|
|
1151
|
+
const pn = String(state.answers.projectNumber ?? '');
|
|
1152
|
+
if (!/^\d+$/.test(pn)) {
|
|
1153
|
+
return { ok: true, payload: {} };
|
|
1154
|
+
}
|
|
1155
|
+
const target = path.join(state.projectRoot, '.agentrc.json');
|
|
1156
|
+
let config;
|
|
1157
|
+
try {
|
|
1158
|
+
config = JSON.parse(fs.readFileSync(target, 'utf8'));
|
|
1159
|
+
} catch (err) {
|
|
1160
|
+
Logger.error(
|
|
1161
|
+
`[bootstrap] Could not read ${target} to store projectNumber: ${err.message}`,
|
|
1162
|
+
);
|
|
1163
|
+
return { ok: true, payload: {} };
|
|
1164
|
+
}
|
|
1165
|
+
config.github = config.github ?? {};
|
|
1166
|
+
// Minimal-write contract (Story #3700): only re-serialize `.agentrc.json`
|
|
1167
|
+
// when the stored number actually changes. When the value is already present
|
|
1168
|
+
// and equal, leave the file byte-for-byte untouched — a re-run must not churn
|
|
1169
|
+
// the consumer's hand-formatting or whitespace.
|
|
1170
|
+
if (config.github.projectNumber === Number(pn)) {
|
|
1171
|
+
return { ok: true, payload: {} };
|
|
1172
|
+
}
|
|
1173
|
+
config.github.projectNumber = Number(pn);
|
|
1174
|
+
fs.writeFileSync(target, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
|
|
1175
|
+
Logger.info(`[bootstrap] Stored github.projectNumber=${pn} in .agentrc.json`);
|
|
1176
|
+
return { ok: true, payload: {} };
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
/** Step 6b — GitHub-side bootstrap. Honours `--skip-github`. */
|
|
1180
|
+
export async function executeGithubBootstrap(state) {
|
|
1181
|
+
if (state.flags['skip-github']) {
|
|
1182
|
+
Logger.info('[bootstrap] --skip-github set; skipping GitHub bootstrap.');
|
|
1183
|
+
return { ok: true, payload: {} };
|
|
1184
|
+
}
|
|
1185
|
+
try {
|
|
1186
|
+
state.report.github = await runGithubBootstrap(state.answers, {
|
|
1187
|
+
assumeYes: state.assumeYes,
|
|
1188
|
+
githubAdminApproved: state.githubAdminApproved === true,
|
|
1189
|
+
reapConflictingWorkflows: Boolean(
|
|
1190
|
+
state.flags['reap-conflicting-workflows'],
|
|
1191
|
+
),
|
|
1192
|
+
});
|
|
1193
|
+
} catch (err) {
|
|
1194
|
+
// GhExecError carries the real gh stderr/stdout/exit code — surface it so
|
|
1195
|
+
// a generic "gh exited with code 1" is actually diagnosable.
|
|
1196
|
+
logGhError('GitHub bootstrap', err);
|
|
1197
|
+
state.report.github = { error: err.message };
|
|
1198
|
+
}
|
|
1199
|
+
return { ok: true, payload: {} };
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
/** Step 6c — Record the install ledger for a future uninstall. */
|
|
1203
|
+
export function recordLedger(state) {
|
|
1204
|
+
const appliedGroups = resolveAppliedGroups(
|
|
1205
|
+
state.approvedGroups,
|
|
1206
|
+
state.report,
|
|
1207
|
+
);
|
|
1208
|
+
const manifestCtx = {
|
|
1209
|
+
answers: state.answers,
|
|
1210
|
+
skipGithub: Boolean(state.flags['skip-github']),
|
|
1211
|
+
skipQuality: Boolean(state.flags['skip-quality']),
|
|
1212
|
+
};
|
|
1213
|
+
const entries = buildMutationManifest(manifestCtx).filter((e) =>
|
|
1214
|
+
appliedGroups.has(e.phaseGroup),
|
|
1215
|
+
);
|
|
1216
|
+
if (entries.length === 0) {
|
|
1217
|
+
state.report.ledger = { written: false, reason: 'no-mutations-applied' };
|
|
1218
|
+
return { ok: true, payload: {} };
|
|
1219
|
+
}
|
|
1220
|
+
const record = buildLedgerRecord({
|
|
1221
|
+
entries,
|
|
1222
|
+
approvedGroups: appliedGroups,
|
|
1223
|
+
answers: state.answers,
|
|
1224
|
+
// The live execution report lets the ledger record `already-present` vs
|
|
1225
|
+
// `seeded` per entry, so uninstall never deletes a pre-existing
|
|
1226
|
+
// `.agentrc.json` the install merely left in place (Story #3895).
|
|
1227
|
+
report: state.report,
|
|
1228
|
+
});
|
|
1229
|
+
const result = writeInstallLedger(state.projectRoot, record);
|
|
1230
|
+
state.report.ledger = { ...result, approvedGroups: [...appliedGroups] };
|
|
1231
|
+
return { ok: true, payload: {} };
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
/**
|
|
1235
|
+
* Step 7 — Offer to commit + push the bootstrap wiring (Story #3899).
|
|
1236
|
+
*
|
|
1237
|
+
* Story delivery runs in git worktrees that check out **tracked files only**,
|
|
1238
|
+
* so an uncommitted `.agents/` tree means every Story sub-agent breaks. This
|
|
1239
|
+
* step closes that "worked in my checkout, broke in delivery" trap by offering
|
|
1240
|
+
* the commit + push at the end of the run.
|
|
1241
|
+
*
|
|
1242
|
+
* Ordering: this phase runs LAST in the pipeline, after `executeBootstrap`
|
|
1243
|
+
* (which seeds the secret-safe `.gitignore`) and `recordLedger`. The stage
|
|
1244
|
+
* step also uses an explicit allowlist and refuses to stage `.env` /
|
|
1245
|
+
* `.mcp.json` / `.agentrc.local.json` regardless of `.gitignore` state, so the
|
|
1246
|
+
* commit never carries a secret even before the gitignore-ordering Story
|
|
1247
|
+
* (#3894) lands.
|
|
1248
|
+
*
|
|
1249
|
+
* Behaviour:
|
|
1250
|
+
* - `--dry-run` → no-op (the dry-run gate already halted earlier; this is a
|
|
1251
|
+
* belt-and-braces guard for direct calls).
|
|
1252
|
+
* - Interactive + accept → stage the allowlist, commit with a conventional
|
|
1253
|
+
* subject, push the base branch.
|
|
1254
|
+
* - Interactive + decline → print the exact manual commands; no git mutation.
|
|
1255
|
+
* - Non-interactive (`--assume-yes` / no TTY) → the defined safe path is to
|
|
1256
|
+
* print the manual commands and make NO git mutation, so a CI run never
|
|
1257
|
+
* surprises the operator with a push it did not ask for.
|
|
1258
|
+
*
|
|
1259
|
+
* `deps.runGit` injects the git seam and `deps.confirm` the yes/no prompt seam
|
|
1260
|
+
* for unit testing; both default to the module's implementations.
|
|
1261
|
+
*/
|
|
1262
|
+
export async function offerCommitPush(state, deps = {}) {
|
|
1263
|
+
if (state.flags['dry-run']) return { ok: true, payload: {} };
|
|
1264
|
+
const runGitImpl = deps.runGit ?? runGit;
|
|
1265
|
+
const confirmImpl = deps.confirm ?? confirmYesNo;
|
|
1266
|
+
const cwd = state.projectRoot;
|
|
1267
|
+
const branch = state.answers.baseBranch || 'main';
|
|
1268
|
+
const stagePaths = resolveStagePaths(cwd);
|
|
1269
|
+
const instructions = buildManualInstructions({
|
|
1270
|
+
stagePaths,
|
|
1271
|
+
baseBranch: branch,
|
|
1272
|
+
});
|
|
1273
|
+
|
|
1274
|
+
// Non-interactive (--assume-yes / no TTY): never push unprompted. Print the
|
|
1275
|
+
// exact commands and leave the working tree untouched.
|
|
1276
|
+
if (!state.interactive) {
|
|
1277
|
+
Logger.info(`\n[bootstrap] ${instructions}`);
|
|
1278
|
+
return { ok: true, payload: { commitPush: { action: 'instructed' } } };
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
const accepted = await confirmImpl(
|
|
1282
|
+
'Commit and push the Mandrel setup?',
|
|
1283
|
+
state.interactive,
|
|
1284
|
+
);
|
|
1285
|
+
if (!accepted) {
|
|
1286
|
+
Logger.info(`\n[bootstrap] ${instructions}`);
|
|
1287
|
+
return { ok: true, payload: { commitPush: { action: 'declined' } } };
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
const staged = stageBootstrapFiles({ projectRoot: cwd, runGit: runGitImpl });
|
|
1291
|
+
if (!staged.ok) {
|
|
1292
|
+
Logger.warn(`[bootstrap] Could not stage the wiring: ${staged.error}`);
|
|
1293
|
+
Logger.info(`\n[bootstrap] ${instructions}`);
|
|
1294
|
+
return { ok: true, payload: { commitPush: { action: 'stage-failed' } } };
|
|
1295
|
+
}
|
|
1296
|
+
const commit = runGitImpl(
|
|
1297
|
+
[...gitIdentityArgs(cwd, state.answers), 'commit', '-m', COMMIT_SUBJECT],
|
|
1298
|
+
cwd,
|
|
1299
|
+
);
|
|
1300
|
+
if (!commit.ok) {
|
|
1301
|
+
// A "nothing to commit" exit is benign — the wiring is already committed.
|
|
1302
|
+
Logger.warn(
|
|
1303
|
+
`[bootstrap] git commit did not create a commit (already committed?): ${commit.stderr || commit.stdout}`,
|
|
1304
|
+
);
|
|
1305
|
+
Logger.info(`\n[bootstrap] ${instructions}`);
|
|
1306
|
+
return { ok: true, payload: { commitPush: { action: 'commit-skipped' } } };
|
|
1307
|
+
}
|
|
1308
|
+
Logger.info('[bootstrap] Committed the Mandrel wiring.');
|
|
1309
|
+
const push = runGitImpl(['push', '-u', 'origin', branch], cwd);
|
|
1310
|
+
if (!push.ok) {
|
|
1311
|
+
Logger.warn(
|
|
1312
|
+
`[bootstrap] Commit landed but push of '${branch}' failed (push it manually with \`git push -u origin ${branch}\`): ${push.stderr}`,
|
|
1313
|
+
);
|
|
1314
|
+
return { ok: true, payload: { commitPush: { action: 'push-failed' } } };
|
|
1315
|
+
}
|
|
1316
|
+
Logger.info(`[bootstrap] Pushed '${branch}' to origin.`);
|
|
1317
|
+
return { ok: true, payload: { commitPush: { action: 'committed-pushed' } } };
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
/** Pipeline driver — threads accumulated state through each phase. */
|
|
1321
|
+
export async function runPipeline(phases) {
|
|
1322
|
+
let state = {};
|
|
1323
|
+
for (const phase of phases) {
|
|
1324
|
+
const result = await phase(state);
|
|
1325
|
+
if (!result.ok) return { ok: false, exit: result.exit, state };
|
|
1326
|
+
state = { ...state, ...(result.payload ?? {}) };
|
|
1327
|
+
}
|
|
1328
|
+
return { ok: true, state };
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
export async function main(argv = process.argv.slice(2), deps = {}) {
|
|
1332
|
+
// `deps.phases` lets tests inject a substitute pipeline so the
|
|
1333
|
+
// post-pipeline GitHub-failure detection can be exercised
|
|
1334
|
+
// deterministically without spawning `gh` (Story #3898).
|
|
1335
|
+
const phases = deps.phases ?? [
|
|
1336
|
+
() => parseAndValidate(argv),
|
|
1337
|
+
(s) => prepareContext(s),
|
|
1338
|
+
(s) => runPreflightPhase(s),
|
|
1339
|
+
(s) => collectAndConfirm(s),
|
|
1340
|
+
(s) => dryRunPlan(s),
|
|
1341
|
+
(s) => provisionResources(s),
|
|
1342
|
+
(s) => executeBootstrap(s),
|
|
1343
|
+
(s) => persistProjectNumber(s),
|
|
1344
|
+
(s) => executeGithubBootstrap(s),
|
|
1345
|
+
(s) => recordLedger(s),
|
|
1346
|
+
(s) => offerCommitPush(s),
|
|
1347
|
+
];
|
|
1348
|
+
const result = await runPipeline(phases);
|
|
1349
|
+
if (!result.ok) return result.exit;
|
|
1350
|
+
|
|
1351
|
+
// GitHub-side bootstrap failures are non-fatal to the pipeline (so the
|
|
1352
|
+
// ledger still records the project-side mutations that already landed —
|
|
1353
|
+
// the failure is surfaced, not silently rolled back), but they MUST NOT
|
|
1354
|
+
// exit 0. `executeGithubBootstrap` records `report.github.error` instead
|
|
1355
|
+
// of throwing; detect it here and exit non-zero with a distinct final
|
|
1356
|
+
// status line so `create-mandrel` and CI see the failure (Story #3898).
|
|
1357
|
+
const githubError = result.state?.report?.github?.error;
|
|
1358
|
+
if (githubError) {
|
|
1359
|
+
Logger.error(
|
|
1360
|
+
`\n[bootstrap] GitHub bootstrap failed: ${githubError}. ` +
|
|
1361
|
+
'Project-side setup (labels are GitHub-side; the local .agentrc.json / ' +
|
|
1362
|
+
'quality-gate / workflow files that were applied are recorded in the ' +
|
|
1363
|
+
'install ledger) completed, but the GitHub label/board/views/protection ' +
|
|
1364
|
+
'setup did not. Resolve the cause above (commonly `gh auth login` or a ' +
|
|
1365
|
+
'missing repo/project scope) and re-run `mandrel bootstrap` — the run is ' +
|
|
1366
|
+
'idempotent and will skip what already succeeded.',
|
|
1367
|
+
);
|
|
1368
|
+
return 1;
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
Logger.info('\n[bootstrap] Done.');
|
|
1372
|
+
return 0;
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
runAsCli(import.meta.url, main, {
|
|
1376
|
+
source: 'Bootstrap',
|
|
1377
|
+
propagateExitCode: true,
|
|
1378
|
+
});
|