mandrel 1.93.0 → 2.0.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 +59 -73
- package/.agents/agents/acceptance-critic.md +129 -0
- package/.agents/agents/story-worker.md +161 -0
- package/.agents/docs/SDLC.md +489 -1285
- package/.agents/docs/agentrc-reference.json +177 -67
- package/.agents/docs/configuration.md +108 -136
- package/.agents/docs/execution-reference.md +44 -22
- package/.agents/docs/quality-gates.md +13 -19
- package/.agents/docs/workflows.md +3 -3
- package/.agents/instructions.md +107 -108
- package/.agents/rules/ci-remediation.md +8 -12
- package/.agents/rules/git-conventions-reference.md +224 -0
- package/.agents/rules/git-conventions.md +42 -223
- package/.agents/rules/security-baseline.md +5 -0
- package/.agents/rules/testing-standards.md +106 -13
- package/.agents/schemas/acceptance-eval-verdict.schema.json +1 -1
- package/.agents/schemas/agentrc.schema.json +71 -201
- package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
- package/.agents/schemas/lifecycle/retro.end.schema.json +1 -1
- package/.agents/schemas/risk-verdict.schema.json +0 -13
- package/.agents/scripts/acceptance-eval.js +62 -18
- package/.agents/scripts/agents-bootstrap-github.js +1 -1
- package/.agents/scripts/analyze-execution.js +1 -1
- package/.agents/scripts/audit-to-stories.js +7 -7
- package/.agents/scripts/boot-sweep.js +1 -1
- package/.agents/scripts/check-context-budget.js +62 -5
- package/.agents/scripts/check-lifecycle-lint.js +6 -9
- package/.agents/scripts/check-prepush-recovery.js +1 -1
- package/.agents/scripts/cleanup-repo-test-temp.js +6 -1
- package/.agents/scripts/diagnose-friction.js +0 -6
- package/.agents/scripts/lib/Logger.js +6 -10
- package/.agents/scripts/lib/audit-suite/runner.js +2 -2
- package/.agents/scripts/lib/audit-suite/selector.js +5 -5
- package/.agents/scripts/lib/audit-to-stories/{seed-epic-from-findings.js → seed-from-findings.js} +9 -9
- package/.agents/scripts/lib/baselines/kernel.js +206 -18
- package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -4
- package/.agents/scripts/lib/baselines/reader.js +1 -6
- package/.agents/scripts/lib/bdd-runner-detect.js +5 -9
- package/.agents/scripts/lib/bootstrap/issue-forms-template.js +32 -33
- package/.agents/scripts/lib/bootstrap/project-bootstrap.js +56 -18
- package/.agents/scripts/lib/checks/core-bare-clean.js +2 -2
- package/.agents/scripts/lib/checks/index.js +2 -1
- package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +23 -21
- package/.agents/scripts/lib/cli/standard-args.js +13 -22
- package/.agents/scripts/lib/cli-args.js +16 -7
- package/.agents/scripts/lib/close-validation/gates.js +160 -22
- package/.agents/scripts/lib/config/acceptance-eval.js +52 -5
- package/.agents/scripts/lib/config/ci.js +6 -31
- package/.agents/scripts/lib/config/delivery-routing.js +103 -0
- package/.agents/scripts/lib/config/explain.js +57 -36
- package/.agents/scripts/lib/config/limits.js +17 -58
- package/.agents/scripts/lib/config/paths.js +0 -2
- package/.agents/scripts/lib/config/quality.js +1 -1
- package/.agents/scripts/lib/config/runners.js +17 -50
- package/.agents/scripts/lib/config/temp-paths.js +19 -14
- package/.agents/scripts/lib/config/worktree-isolation.js +0 -5
- package/.agents/scripts/lib/config-resolver.js +3 -8
- package/.agents/scripts/lib/config-settings-schema-delivery.js +46 -136
- package/.agents/scripts/lib/config-settings-schema-quality.js +17 -14
- package/.agents/scripts/lib/config-settings-schema.js +52 -38
- package/.agents/scripts/lib/dependency-parser.js +3 -2
- package/.agents/scripts/lib/doc-tiers.js +39 -4
- package/.agents/scripts/lib/duplicate-search.js +211 -41
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +1 -1
- package/.agents/scripts/lib/findings/promote-finding.js +5 -5
- package/.agents/scripts/lib/framework-version.js +2 -3
- package/.agents/scripts/lib/git-branch-cleanup.js +1 -10
- package/.agents/scripts/lib/git-branch-lifecycle.js +17 -22
- package/.agents/scripts/lib/git-utils.js +32 -6
- package/.agents/scripts/lib/github/framework-repo.js +6 -0
- package/.agents/scripts/lib/label-constants.js +10 -23
- package/.agents/scripts/lib/label-taxonomy.js +9 -43
- package/.agents/scripts/lib/observability/active-story-env.js +112 -3
- package/.agents/scripts/lib/observability/hook-heartbeat.js +187 -0
- package/.agents/scripts/lib/observability/source-classifier.js +3 -3
- package/.agents/scripts/lib/observability/tool-trace-hook.js +15 -4
- package/.agents/scripts/lib/onboard/init-tail.js +1 -3
- package/.agents/scripts/lib/orchestration/acceptance-clusters.js +111 -0
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +32 -4
- package/.agents/scripts/lib/orchestration/audit-lens-routing.js +128 -0
- package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +273 -0
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +204 -0
- package/.agents/scripts/lib/orchestration/code-review.js +20 -268
- package/.agents/scripts/lib/orchestration/column-sync.js +1 -1
- package/.agents/scripts/lib/orchestration/consolidation-precondition.js +1 -1
- package/.agents/scripts/lib/orchestration/context-envelope.js +2 -5
- package/.agents/scripts/lib/orchestration/docs-digest.js +8 -8
- package/.agents/scripts/lib/orchestration/file-assumptions.js +7 -13
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/cli.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +8 -8
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +2 -2
- package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +6 -3
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +17 -43
- package/.agents/scripts/lib/orchestration/lint-baseline-service.js +4 -4
- package/.agents/scripts/lib/orchestration/merge-block-class.js +1 -1
- package/.agents/scripts/lib/orchestration/phase-runner.js +3 -2
- package/.agents/scripts/lib/orchestration/plan-context.js +248 -266
- package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +3 -2
- package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +1 -1
- package/.agents/scripts/lib/orchestration/plan-navigation.js +92 -0
- package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +61 -0
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +97 -0
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +223 -854
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +361 -0
- package/.agents/scripts/lib/orchestration/plan-persist/summary.js +35 -108
- package/.agents/scripts/lib/orchestration/plan-reachability.js +9 -14
- package/.agents/scripts/lib/orchestration/plan-runner/worktree-sweep.js +1 -1
- package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/authoring-context.js +14 -14
- package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +27 -0
- package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/risk-verdict.js +3 -4
- package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +2 -2
- package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +8 -20
- package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +3 -2
- package/.agents/scripts/lib/orchestration/pr-base-guard.js +18 -28
- package/.agents/scripts/lib/orchestration/preflight-cache.js +5 -5
- package/.agents/scripts/lib/orchestration/remote-verifier.js +1 -1
- package/.agents/scripts/lib/orchestration/resolve-plan-run.js +155 -0
- package/.agents/scripts/lib/orchestration/resolves-token.js +1 -1
- package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +8 -8
- package/.agents/scripts/lib/orchestration/retro-proposals.js +140 -79
- package/.agents/scripts/lib/orchestration/review-depth.js +26 -12
- package/.agents/scripts/lib/orchestration/review-providers/codex.js +2 -2
- package/.agents/scripts/lib/orchestration/review-providers/review-provider-factory.js +21 -56
- package/.agents/scripts/lib/orchestration/run-epilogue.js +426 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +1 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +95 -41
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +16 -13
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +40 -0
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +11 -3
- package/.agents/scripts/lib/orchestration/spec-freshness.js +14 -205
- package/.agents/scripts/lib/orchestration/spec-section-validator.js +4 -5
- package/.agents/scripts/lib/orchestration/spec-spill.js +60 -0
- package/.agents/scripts/lib/orchestration/split-policy-validator.js +188 -0
- package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +49 -0
- package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +15 -12
- package/.agents/scripts/lib/orchestration/story-follow-ups.js +237 -0
- package/.agents/scripts/lib/orchestration/story-init-remote.js +47 -0
- package/.agents/scripts/lib/orchestration/story-plan-state.js +48 -0
- package/.agents/scripts/lib/orchestration/{epic-runner → story-progress}/story-run-progress-writer.js +3 -3
- package/.agents/scripts/lib/orchestration/structured-comment-parser.js +1 -1
- package/.agents/scripts/lib/orchestration/task-body-validator.js +8 -18
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +11 -61
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +189 -373
- package/.agents/scripts/lib/orchestration/ticket-validator.js +3 -8
- package/.agents/scripts/lib/orchestration/ticketing/bulk.js +0 -25
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +29 -26
- package/.agents/scripts/lib/orchestration/ticketing/transition.js +5 -5
- package/.agents/scripts/lib/planning-corpus.js +16 -11
- package/.agents/scripts/lib/preflight-runner.js +2 -2
- package/.agents/scripts/lib/provider-factory.js +1 -1
- package/.agents/scripts/lib/qa/coverage-verdict.js +5 -5
- package/.agents/scripts/lib/single-story/confirm-merge-follow-ups.js +36 -0
- package/.agents/scripts/lib/single-story-sweep/protection-ctx.js +1 -1
- package/.agents/scripts/lib/story-adjacency.js +11 -14
- package/.agents/scripts/lib/story-body/story-body.js +124 -70
- package/.agents/scripts/lib/story-plan.js +2 -4
- package/.agents/scripts/lib/templates/decomposer-prompts.js +46 -45
- package/.agents/scripts/lib/templates/spec-author-prompts.js +47 -45
- package/.agents/scripts/lib/{epic-body-sections.js → ticket-body-sections.js} +26 -26
- package/.agents/scripts/lib/validation-evidence.js +1 -1
- package/.agents/scripts/lib/wave-runner/ready-set.js +6 -6
- package/.agents/scripts/lib/workspace-provisioner.js +1 -1
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +5 -7
- package/.agents/scripts/lint-issue-body.js +71 -40
- package/.agents/scripts/mandrel-update-preflight.js +1 -1
- package/.agents/scripts/notify.js +4 -3
- package/.agents/scripts/plan-context.js +64 -74
- package/.agents/scripts/plan-persist.js +121 -280
- package/.agents/scripts/plan-run-epilogue.js +97 -0
- package/.agents/scripts/post-structured-comment.js +38 -0
- package/.agents/scripts/providers/github/issues.js +17 -33
- package/.agents/scripts/providers/github/mappers.js +0 -12
- package/.agents/scripts/providers/github/tickets.js +2 -5
- package/.agents/scripts/resolve-plan-run.js +117 -0
- package/.agents/scripts/signals-view.js +24 -19
- package/.agents/scripts/single-story-close.js +11 -14
- package/.agents/scripts/single-story-confirm-merge.js +39 -23
- package/.agents/scripts/single-story-init.js +29 -20
- package/.agents/scripts/stories-wave-tick.js +6 -6
- package/.agents/scripts/story-plan.js +26 -47
- package/.agents/scripts/sync-claude-agents.js +165 -0
- package/.agents/scripts/update-ticket-state.js +37 -15
- package/.agents/skills/core/analyze-execution/SKILL.md +21 -18
- package/.agents/skills/core/api-and-interface-design/SKILL.md +5 -3
- package/.agents/skills/core/code-review-and-quality/SKILL.md +63 -7
- package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +1 -1
- package/.agents/skills/core/gates-and-baselines/SKILL.md +149 -0
- package/.agents/skills/core/idea-refinement/SKILL.md +8 -14
- package/.agents/skills/core/qa-coverage-mapping/SKILL.md +7 -7
- package/.agents/skills/core/scope-triage/SKILL.md +28 -172
- package/.agents/skills/skills.index.json +8 -418
- package/.agents/starter-agentrc.json +0 -5
- package/.agents/templates/agent-protocol.md +9 -10
- package/.agents/workflows/audit-architecture.md +3 -3
- package/.agents/workflows/audit-clean-code.md +3 -3
- package/.agents/workflows/audit-dependencies.md +3 -3
- package/.agents/workflows/audit-devops.md +3 -3
- package/.agents/workflows/audit-documentation.md +5 -5
- package/.agents/workflows/audit-lighthouse.md +3 -3
- package/.agents/workflows/audit-navigability.md +3 -2
- package/.agents/workflows/audit-performance.md +3 -3
- package/.agents/workflows/audit-privacy.md +3 -3
- package/.agents/workflows/audit-quality.md +3 -3
- package/.agents/workflows/audit-security.md +3 -3
- package/.agents/workflows/audit-seo.md +3 -3
- package/.agents/workflows/audit-sre.md +3 -3
- package/.agents/workflows/audit-to-stories.md +20 -20
- package/.agents/workflows/audit-ux-ui.md +3 -3
- package/.agents/workflows/deliver.md +122 -131
- package/.agents/workflows/git-cleanup.md +3 -4
- package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
- package/.agents/workflows/helpers/acceptance-self-eval.md +52 -40
- package/.agents/workflows/helpers/code-review.md +70 -193
- package/.agents/workflows/helpers/{single-story-deliver-reference.md → deliver-story-reference.md} +12 -14
- package/.agents/workflows/helpers/{single-story-deliver.md → deliver-story.md} +113 -139
- package/.agents/workflows/helpers/diagnose.md +10 -10
- package/.agents/workflows/helpers/mandrel-sync-config.md +1 -1
- package/.agents/workflows/helpers/parallel-tooling.md +1 -1
- package/.agents/workflows/helpers/signals.md +16 -16
- package/.agents/workflows/helpers/worktree-lifecycle.md +48 -64
- package/.agents/workflows/mandrel-update.md +3 -2
- package/.agents/workflows/plan.md +112 -145
- package/.agents/workflows/qa-assist.md +24 -30
- package/.agents/workflows/qa-explore.md +29 -38
- package/.agents/workflows/qa-run.md +2 -2
- package/README.md +9 -8
- package/docs/CHANGELOG.md +46 -0
- package/lib/cli/registry.js +95 -0
- package/lib/migrations/index.js +6 -5
- package/package.json +5 -3
- package/.agents/personas/architect.md +0 -113
- package/.agents/personas/devops-engineer.md +0 -38
- package/.agents/personas/engineer-mobile.md +0 -120
- package/.agents/personas/engineer-web.md +0 -111
- package/.agents/personas/engineer.md +0 -119
- package/.agents/personas/product.md +0 -94
- package/.agents/personas/project-manager.md +0 -114
- package/.agents/personas/qa-engineer.md +0 -95
- package/.agents/personas/refactorer.md +0 -113
- package/.agents/personas/security-engineer.md +0 -112
- package/.agents/personas/sre.md +0 -86
- package/.agents/personas/technical-writer.md +0 -101
- package/.agents/personas/ux-designer.md +0 -95
- package/.agents/schemas/dispatch-manifest.json +0 -232
- package/.agents/schemas/epic-spec.schema.json +0 -153
- package/.agents/scripts/acceptance-spec-reconciler.js +0 -642
- package/.agents/scripts/dispatcher.js +0 -295
- package/.agents/scripts/epic-audit-prepare.js +0 -497
- package/.agents/scripts/epic-audit-recheck.js +0 -274
- package/.agents/scripts/epic-deliver-note-intervention.js +0 -192
- package/.agents/scripts/epic-deliver-preflight.js +0 -462
- package/.agents/scripts/epic-deliver-prepare.js +0 -590
- package/.agents/scripts/epic-execute-record-wave.js +0 -449
- package/.agents/scripts/epic-plan-clarity.js +0 -211
- package/.agents/scripts/epic-plan-decompose.js +0 -54
- package/.agents/scripts/epic-plan-healthcheck.js +0 -581
- package/.agents/scripts/epic-plan-spec.js +0 -64
- package/.agents/scripts/epic-reconcile.js +0 -625
- package/.agents/scripts/lib/baseline-snapshot.js +0 -979
- package/.agents/scripts/lib/checks/epic-merge-lock-stale.js +0 -54
- package/.agents/scripts/lib/checks/stale-origin-epic.js +0 -49
- package/.agents/scripts/lib/config/lifecycle.js +0 -40
- package/.agents/scripts/lib/config/preflight.js +0 -58
- package/.agents/scripts/lib/config/retro.js +0 -77
- package/.agents/scripts/lib/epic-merge-lock.js +0 -322
- package/.agents/scripts/lib/epic-plan-clarity.js +0 -181
- package/.agents/scripts/lib/epic-plan-ideation.js +0 -261
- package/.agents/scripts/lib/orchestration/context-hydration-engine.js +0 -660
- package/.agents/scripts/lib/orchestration/dispatch-engine.js +0 -134
- package/.agents/scripts/lib/orchestration/dispatch-pipeline.js +0 -183
- package/.agents/scripts/lib/orchestration/epic-cleanup.js +0 -801
- package/.agents/scripts/lib/orchestration/epic-deliver-lease-guard.js +0 -310
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/context.js +0 -163
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/creation.js +0 -140
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/dag.js +0 -64
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/diagnostics.js +0 -72
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist-helpers.js +0 -156
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist.js +0 -345
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/planning-artifacts.js +0 -41
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/reconcile-spawn.js +0 -86
- package/.agents/scripts/lib/orchestration/epic-plan-lease-guard.js +0 -391
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/drain.js +0 -94
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/plan-epic.js +0 -236
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +0 -307
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/spec-freshness.js +0 -117
- package/.agents/scripts/lib/orchestration/epic-plan-state-store.js +0 -117
- package/.agents/scripts/lib/orchestration/epic-run-state-store.js +0 -388
- package/.agents/scripts/lib/orchestration/epic-runner/concurrency-gate.js +0 -186
- package/.agents/scripts/lib/orchestration/epic-runner/deliver-phases.js +0 -50
- package/.agents/scripts/lib/orchestration/epic-runner/phases/build-wave-dag.js +0 -129
- package/.agents/scripts/lib/orchestration/epic-runner/phases/snapshot.js +0 -103
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +0 -267
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +0 -210
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/transport.js +0 -238
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/_bullet-format.js +0 -32
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/component-drift.js +0 -203
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/crap-drift.js +0 -227
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/maintainability-drift.js +0 -117
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/stalled-worktree.js +0 -37
- package/.agents/scripts/lib/orchestration/epic-runner/story-launcher.js +0 -127
- package/.agents/scripts/lib/orchestration/epic-runner/sub-agent-return.js +0 -276
- package/.agents/scripts/lib/orchestration/epic-runner/wave-scheduler.js +0 -66
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-apply.js +0 -789
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +0 -676
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-discriminator.js +0 -389
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-format.js +0 -230
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-ops.js +0 -361
- package/.agents/scripts/lib/orchestration/finalize/open-or-locate-pr.js +0 -306
- package/.agents/scripts/lib/orchestration/finalize/post-handoff-comment.js +0 -489
- package/.agents/scripts/lib/orchestration/finalize/sanitize-skip-ci.js +0 -88
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-dispatch-end.js +0 -147
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/acceptance-reconciler.js +0 -384
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-armer.js +0 -501
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +0 -984
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/branch-cleaner.js +0 -264
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/checkpoint-pointer-writer.js +0 -278
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/cleaner.js +0 -355
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +0 -673
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +0 -378
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/intervention-recorder.js +0 -140
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/label-transitioner.js +0 -144
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/notify-dispatcher.js +0 -174
- package/.agents/scripts/lib/orchestration/manifest-builder.js +0 -222
- package/.agents/scripts/lib/orchestration/plan-persist/amend.js +0 -359
- package/.agents/scripts/lib/orchestration/plan-persist/delivery-mode.js +0 -127
- package/.agents/scripts/lib/orchestration/post-merge-pipeline.js +0 -205
- package/.agents/scripts/lib/orchestration/recurring-failure-detector.js +0 -152
- package/.agents/scripts/lib/orchestration/retro/phases/checks.js +0 -94
- package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +0 -571
- package/.agents/scripts/lib/orchestration/retro/phases/gather-signals.js +0 -450
- package/.agents/scripts/lib/orchestration/retro/phases/post-and-mirror.js +0 -191
- package/.agents/scripts/lib/orchestration/retro-heuristics.js +0 -57
- package/.agents/scripts/lib/orchestration/retro-runner.js +0 -197
- package/.agents/scripts/lib/orchestration/skill-capsule-loader.js +0 -109
- package/.agents/scripts/lib/orchestration/spec-renderer.js +0 -447
- package/.agents/scripts/lib/orchestration/story-close/auto-refresh-runner.js +0 -747
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/gate-failure.js +0 -211
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/pre-merge-attribution.js +0 -158
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/refresh-commit.js +0 -446
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/regression-projection.js +0 -297
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/scope-discovery.js +0 -48
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution-wiring.js +0 -67
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution.js +0 -161
- package/.agents/scripts/lib/orchestration/story-close/baseline-friction-body.js +0 -117
- package/.agents/scripts/lib/orchestration/story-close/cd-out-guard.js +0 -86
- package/.agents/scripts/lib/orchestration/story-close/cleanup-reconciler.js +0 -147
- package/.agents/scripts/lib/orchestration/story-close/close-inputs.js +0 -142
- package/.agents/scripts/lib/orchestration/story-close/comment-bodies.js +0 -62
- package/.agents/scripts/lib/orchestration/story-close/merge-runner.js +0 -658
- package/.agents/scripts/lib/orchestration/story-close/merge-subject.js +0 -198
- package/.agents/scripts/lib/orchestration/story-close/phases/branch-restore.js +0 -105
- package/.agents/scripts/lib/orchestration/story-close/phases/close.js +0 -222
- package/.agents/scripts/lib/orchestration/story-close/phases/gates.js +0 -292
- package/.agents/scripts/lib/orchestration/story-close/phases/locked-pipeline.js +0 -270
- package/.agents/scripts/lib/orchestration/story-close/phases/preflight.js +0 -110
- package/.agents/scripts/lib/orchestration/story-close/phases/refresh.js +0 -86
- package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked-emitter.js +0 -112
- package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked.js +0 -157
- package/.agents/scripts/lib/orchestration/story-close/post-merge-close.js +0 -421
- package/.agents/scripts/lib/orchestration/story-close/pre-merge-validation.js +0 -301
- package/.agents/scripts/lib/orchestration/story-close/shared-checkout-guard.js +0 -163
- package/.agents/scripts/lib/orchestration/story-close-recovery.js +0 -690
- package/.agents/scripts/lib/orchestration/wave-marker.js +0 -28
- package/.agents/scripts/lib/orchestration/wave-record-io.js +0 -218
- package/.agents/scripts/lib/orchestration/wave-record-notifications.js +0 -145
- package/.agents/scripts/lib/orchestration/wave-record-projection.js +0 -212
- package/.agents/scripts/lib/presentation/dispatch-manifest-render.js +0 -111
- package/.agents/scripts/lib/presentation/manifest-builder.js +0 -239
- package/.agents/scripts/lib/presentation/manifest-formatter.js +0 -242
- package/.agents/scripts/lib/presentation/manifest-helpers.js +0 -213
- package/.agents/scripts/lib/presentation/manifest-persistence.js +0 -261
- package/.agents/scripts/lib/presentation/manifest-procedures.js +0 -55
- package/.agents/scripts/lib/presentation/manifest-render-waves.js +0 -306
- package/.agents/scripts/lib/presentation/manifest-renderer.js +0 -188
- package/.agents/scripts/lib/presentation/manifest-story-views.js +0 -110
- package/.agents/scripts/lib/push-epic-retry.js +0 -209
- package/.agents/scripts/lib/spec/index.js +0 -36
- package/.agents/scripts/lib/spec/loader.js +0 -425
- package/.agents/scripts/lib/spec/state.js +0 -208
- package/.agents/scripts/lib/story-init/blocker-validator.js +0 -68
- package/.agents/scripts/lib/story-init/branch-initializer.js +0 -408
- package/.agents/scripts/lib/story-init/context-resolver.js +0 -92
- package/.agents/scripts/lib/story-init/donor-precheck.js +0 -207
- package/.agents/scripts/lib/story-init/state-transitioner.js +0 -80
- package/.agents/scripts/lib/story-init/task-graph-builder.js +0 -124
- package/.agents/scripts/lib/story-init/transition-summary.js +0 -34
- package/.agents/scripts/lib/test-reserved-epic-temp-ids.js +0 -35
- package/.agents/scripts/lib/wave-runner/tick.js +0 -754
- package/.agents/scripts/lib/wave-runner/wave-runner-error.js +0 -20
- package/.agents/scripts/lifecycle-emit-story-dispatch.js +0 -194
- package/.agents/scripts/lifecycle-emit.js +0 -510
- package/.agents/scripts/plan-critics.js +0 -199
- package/.agents/scripts/retro-run.js +0 -218
- package/.agents/scripts/standalone-feedback-rollup.js +0 -188
- package/.agents/scripts/story-close.js +0 -294
- package/.agents/scripts/story-init.js +0 -599
- package/.agents/scripts/story-phase.js +0 -369
- package/.agents/scripts/wave-tick.js +0 -335
- package/.agents/skills/core/baseline-refresh/SKILL.md +0 -181
- package/.agents/skills/core/ci-cd-and-automation/SKILL.md +0 -274
- package/.agents/skills/core/ci-cd-and-automation/examples.md +0 -211
- package/.agents/skills/core/code-simplification/SKILL.md +0 -389
- package/.agents/skills/core/context-engineering/SKILL.md +0 -309
- package/.agents/skills/core/context-engineering/examples.md +0 -58
- package/.agents/skills/core/deprecation-and-migration/SKILL.md +0 -250
- package/.agents/skills/core/epic-plan-consolidate/SKILL.md +0 -172
- package/.agents/skills/core/epic-plan-consolidate/examples.md +0 -51
- package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +0 -441
- package/.agents/skills/core/epic-plan-decompose-author/examples.md +0 -47
- package/.agents/skills/core/epic-plan-premortem/SKILL.md +0 -146
- package/.agents/skills/core/epic-plan-premortem/examples.md +0 -53
- package/.agents/skills/core/epic-plan-spec-author/SKILL.md +0 -413
- package/.agents/skills/core/epic-plan-spec-author/examples.md +0 -91
- package/.agents/skills/core/frontend-ui-engineering/SKILL.md +0 -357
- package/.agents/skills/core/hydrate-context/SKILL.md +0 -123
- package/.agents/skills/core/idea-refinement/examples.md +0 -437
- package/.agents/skills/core/idea-refinement/frameworks.md +0 -135
- package/.agents/skills/core/incremental-implementation/SKILL.md +0 -271
- package/.agents/skills/core/introducing-a-baseline-gate/SKILL.md +0 -213
- package/.agents/skills/core/knowledge-transfer/SKILL.md +0 -180
- package/.agents/skills/core/mutation-survivor-remediation/SKILL.md +0 -117
- package/.agents/skills/core/performance-optimization/SKILL.md +0 -314
- package/.agents/skills/core/planning-and-task-breakdown/SKILL.md +0 -277
- package/.agents/skills/core/property-based-testing/SKILL.md +0 -148
- package/.agents/skills/core/refactoring-discipline/SKILL.md +0 -111
- package/.agents/skills/core/shipping-and-launch/SKILL.md +0 -328
- package/.agents/skills/core/spec-driven-development/SKILL.md +0 -252
- package/.agents/skills/core/test-driven-development/SKILL.md +0 -475
- package/.agents/skills/core/using-agent-skills/SKILL.md +0 -232
- package/.agents/skills/stack/architecture/monorepo-path-strategist/SKILL.md +0 -31
- package/.agents/skills/stack/architecture/structured-output-zod/SKILL.md +0 -51
- package/.agents/skills/stack/architecture/subagent-orchestration/SKILL.md +0 -76
- package/.agents/skills/stack/backend/cloudflare-hono-architect/SKILL.md +0 -31
- package/.agents/skills/stack/backend/cloudflare-hono-architect/examples/route-template.ts +0 -33
- package/.agents/skills/stack/backend/cloudflare-queue-manager/SKILL.md +0 -31
- package/.agents/skills/stack/backend/cloudflare-workers/SKILL.md +0 -51
- package/.agents/skills/stack/backend/highlevel-crm/SKILL.md +0 -54
- package/.agents/skills/stack/backend/sqlite-drizzle-expert/SKILL.md +0 -29
- package/.agents/skills/stack/backend/sqlite-drizzle-expert/examples/schema-template.ts +0 -30
- package/.agents/skills/stack/backend/stripe-integration/SKILL.md +0 -57
- package/.agents/skills/stack/backend/stripe-integration/scripts/listen-stripe.sh +0 -9
- package/.agents/skills/stack/backend/turso-sqlite/SKILL.md +0 -48
- package/.agents/skills/stack/frontend/astro/SKILL.md +0 -62
- package/.agents/skills/stack/frontend/astro-react-island-strategist/SKILL.md +0 -30
- package/.agents/skills/stack/frontend/expo-react-native-developer/SKILL.md +0 -29
- package/.agents/skills/stack/frontend/google-analytics-v4/SKILL.md +0 -50
- package/.agents/skills/stack/frontend/tailwind-v4/SKILL.md +0 -58
- package/.agents/skills/stack/frontend/ui-accessibility-engineer/SKILL.md +0 -34
- package/.agents/skills/stack/qa/audit-accessibility/SKILL.md +0 -51
- package/.agents/skills/stack/qa/lighthouse-baseline/SKILL.md +0 -199
- package/.agents/skills/stack/security/backend-security-patterns/SKILL.md +0 -68
- package/.agents/workflows/helpers/deliver-epic-reference.md +0 -534
- package/.agents/workflows/helpers/deliver-epic.md +0 -955
- package/.agents/workflows/helpers/deliver-stories.md +0 -440
- package/.agents/workflows/helpers/epic-audit.md +0 -189
- package/.agents/workflows/helpers/epic-deliver-story.md +0 -427
- package/.agents/workflows/helpers/epic-testing.md +0 -125
- package/.agents/workflows/helpers/plan-epic-reference.md +0 -160
- package/.agents/workflows/helpers/plan-epic.md +0 -351
- package/.agents/workflows/helpers/plan-story.md +0 -251
- package/.agents/workflows/helpers/scope-triage-gate.md +0 -108
- /package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/spec-authoring-grounding.js +0 -0
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* push-epic-retry.js — Bounded retry for concurrent epic-branch pushes.
|
|
3
|
-
*
|
|
4
|
-
* story-close holds a per-Epic filesystem lock for the rebase / merge /
|
|
5
|
-
* push sequence, but that lock is local to one machine. N sprint sessions on
|
|
6
|
-
* different machines (or different Claude Code web workers) will not see each
|
|
7
|
-
* other's lock and can race on `origin/epic/<id>`. Whoever pushes second will
|
|
8
|
-
* be rejected with a non-fast-forward error.
|
|
9
|
-
*
|
|
10
|
-
* This module provides `pushEpicWithRetry`: first push attempt is a direct
|
|
11
|
-
* `git push` — single-session behaviour is byte-identical to pre-change code.
|
|
12
|
-
* Only on a non-fast-forward rejection do we enter the retry loop: fetch the
|
|
13
|
-
* advanced remote, reset local epic onto `origin/<epic>`, reapply the story
|
|
14
|
-
* merge, and retry the push. A real content conflict during re-apply is
|
|
15
|
-
* non-recoverable: we abort the merge (restoring a clean tree) and throw a
|
|
16
|
-
* `PushRetryConflictError` naming the conflicting files.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import { DEFAULT_STORY_MERGE_RETRY } from './config/runners.js';
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Stderr patterns git emits when `git push` is rejected because the remote
|
|
23
|
-
* tip has advanced beyond our local branch head. Only these patterns trigger
|
|
24
|
-
* a retry; every other push error surfaces immediately so we do not mask
|
|
25
|
-
* permission denials, protected-branch rejections, or network errors behind
|
|
26
|
-
* a retry loop.
|
|
27
|
-
*/
|
|
28
|
-
const NON_FAST_FORWARD_PATTERNS = [
|
|
29
|
-
/non-fast-forward/i,
|
|
30
|
-
/fetch first/i,
|
|
31
|
-
/failed to push some refs/i,
|
|
32
|
-
/\[rejected\]/,
|
|
33
|
-
/Updates were rejected/i,
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
export function isNonFastForwardPush(stderr) {
|
|
37
|
-
if (!stderr) return false;
|
|
38
|
-
return NON_FAST_FORWARD_PATTERNS.some((p) => p.test(stderr));
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Thrown when the retry loop's re-apply step (`git merge --no-ff story-<id>`
|
|
43
|
-
* onto the freshly-fetched epic tip) hits a real content conflict. The
|
|
44
|
-
* caller should surface this to the operator — we do not attempt automated
|
|
45
|
-
* resolution. The merge is aborted before throwing so the working tree is
|
|
46
|
-
* clean and recoverable.
|
|
47
|
-
*/
|
|
48
|
-
export class PushRetryConflictError extends Error {
|
|
49
|
-
constructor(conflictFiles, gitStderr) {
|
|
50
|
-
const fileList = conflictFiles.length
|
|
51
|
-
? conflictFiles.join(', ')
|
|
52
|
-
: '(unknown)';
|
|
53
|
-
super(
|
|
54
|
-
`Content conflict while reapplying story merge onto origin/epic. ` +
|
|
55
|
-
`Conflicting file(s): ${fileList}. ` +
|
|
56
|
-
`The merge has been aborted and the working tree is clean — no ` +
|
|
57
|
-
`half-merged files remain. Resolve manually by rebasing the story ` +
|
|
58
|
-
`branch onto the updated epic, committing, and re-running ` +
|
|
59
|
-
`story-close.\n\n` +
|
|
60
|
-
`git stderr:\n${gitStderr}`,
|
|
61
|
-
);
|
|
62
|
-
this.name = 'PushRetryConflictError';
|
|
63
|
-
this.conflictFiles = conflictFiles;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** Re-apply the story merge during a non-ff retry, optionally pinning the
|
|
68
|
-
* conventional-commit subject so the post-merge `(resolves #N)` grep keeps
|
|
69
|
-
* finding the merge commit (without it, git falls back to its default
|
|
70
|
-
* `Merge branch …` subject and the grep misses). */
|
|
71
|
-
function runRetryMerge({ cwd, git, storyBranch, mergeMessage }) {
|
|
72
|
-
return mergeMessage
|
|
73
|
-
? git.gitSpawn(cwd, 'merge', '--no-ff', '-m', mergeMessage, storyBranch)
|
|
74
|
-
: git.gitSpawn(cwd, 'merge', '--no-ff', '--no-edit', storyBranch);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** Collect the list of files with unmerged paths after a failed merge. */
|
|
78
|
-
function collectConflictFiles(cwd, git) {
|
|
79
|
-
const result = git.gitSpawn(cwd, 'diff', '--name-only', '--diff-filter=U');
|
|
80
|
-
if (result.status !== 0 || !result.stdout) return [];
|
|
81
|
-
return result.stdout
|
|
82
|
-
.split(/\r?\n/)
|
|
83
|
-
.map((s) => s.trim())
|
|
84
|
-
.filter(Boolean);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Push `epicBranch` with a bounded retry on non-fast-forward rejections.
|
|
89
|
-
*
|
|
90
|
-
* The first attempt is a direct push (no fetch, no reapply) so single-session
|
|
91
|
-
* runs behave identically to the pre-change path. Retries fetch the advanced
|
|
92
|
-
* remote, reset local `<epicBranch>` to `origin/<epicBranch>`, and reapply
|
|
93
|
-
* `<storyBranch>` via `merge --no-ff --no-edit`.
|
|
94
|
-
*
|
|
95
|
-
* @param {object} opts
|
|
96
|
-
* @param {string} opts.cwd
|
|
97
|
-
* @param {string} opts.epicBranch
|
|
98
|
-
* @param {string} opts.storyBranch
|
|
99
|
-
* @param {{ maxAttempts?: number, backoffMs?: number[] }} [opts.storyMergeRetry]
|
|
100
|
-
* @param {{ gitSpawn: (cwd: string, ...args: string[]) => { status: number, stdout: string, stderr: string } }} opts.git
|
|
101
|
-
* @param {(ms: number) => Promise<void>} [opts.sleep]
|
|
102
|
-
* @param {(msg: string) => void} [opts.log]
|
|
103
|
-
* @returns {Promise<{ ok: boolean, attempts: number, reason?: string, result: object }>}
|
|
104
|
-
* @throws {PushRetryConflictError} On content conflict during re-apply.
|
|
105
|
-
*/
|
|
106
|
-
export async function pushEpicWithRetry({
|
|
107
|
-
cwd,
|
|
108
|
-
epicBranch,
|
|
109
|
-
storyBranch,
|
|
110
|
-
storyMergeRetry,
|
|
111
|
-
git,
|
|
112
|
-
sleep = (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
113
|
-
log = () => {},
|
|
114
|
-
mergeMessage = null,
|
|
115
|
-
}) {
|
|
116
|
-
if (!cwd) throw new Error('pushEpicWithRetry: cwd is required');
|
|
117
|
-
if (!epicBranch) throw new Error('pushEpicWithRetry: epicBranch is required');
|
|
118
|
-
if (!storyBranch)
|
|
119
|
-
throw new Error('pushEpicWithRetry: storyBranch is required');
|
|
120
|
-
if (!git || typeof git.gitSpawn !== 'function') {
|
|
121
|
-
throw new Error('pushEpicWithRetry: git.gitSpawn injection is required');
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const maxAttempts =
|
|
125
|
-
storyMergeRetry?.maxAttempts ?? DEFAULT_STORY_MERGE_RETRY.maxAttempts;
|
|
126
|
-
const backoffMs =
|
|
127
|
-
storyMergeRetry?.backoffMs ?? DEFAULT_STORY_MERGE_RETRY.backoffMs;
|
|
128
|
-
|
|
129
|
-
let lastResult;
|
|
130
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
131
|
-
lastResult = git.gitSpawn(cwd, 'push', '--no-verify', 'origin', epicBranch);
|
|
132
|
-
if (lastResult.status === 0) {
|
|
133
|
-
return { ok: true, attempts: attempt, result: lastResult };
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (!isNonFastForwardPush(lastResult.stderr)) {
|
|
137
|
-
return {
|
|
138
|
-
ok: false,
|
|
139
|
-
attempts: attempt,
|
|
140
|
-
result: lastResult,
|
|
141
|
-
reason: 'non-retryable-push-error',
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (attempt === maxAttempts) {
|
|
146
|
-
return {
|
|
147
|
-
ok: false,
|
|
148
|
-
attempts: attempt,
|
|
149
|
-
result: lastResult,
|
|
150
|
-
reason: 'retry-exhausted',
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const backoff = backoffMs[Math.min(attempt - 1, backoffMs.length - 1)] ?? 0;
|
|
155
|
-
log(
|
|
156
|
-
`[push-epic-retry] Push rejected (non-fast-forward) on attempt ` +
|
|
157
|
-
`${attempt}/${maxAttempts}; fetching remote and reapplying after ` +
|
|
158
|
-
`${backoff}ms backoff.`,
|
|
159
|
-
);
|
|
160
|
-
await sleep(backoff);
|
|
161
|
-
|
|
162
|
-
const fetchResult = git.gitSpawn(cwd, 'fetch', 'origin', epicBranch);
|
|
163
|
-
if (fetchResult.status !== 0) {
|
|
164
|
-
return {
|
|
165
|
-
ok: false,
|
|
166
|
-
attempts: attempt,
|
|
167
|
-
result: fetchResult,
|
|
168
|
-
reason: 'fetch-failed',
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const resetResult = git.gitSpawn(
|
|
173
|
-
cwd,
|
|
174
|
-
'reset',
|
|
175
|
-
'--hard',
|
|
176
|
-
`origin/${epicBranch}`,
|
|
177
|
-
);
|
|
178
|
-
if (resetResult.status !== 0) {
|
|
179
|
-
return {
|
|
180
|
-
ok: false,
|
|
181
|
-
attempts: attempt,
|
|
182
|
-
result: resetResult,
|
|
183
|
-
reason: 'reset-failed',
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const mergeResult = runRetryMerge({
|
|
188
|
-
cwd,
|
|
189
|
-
git,
|
|
190
|
-
storyBranch,
|
|
191
|
-
mergeMessage,
|
|
192
|
-
});
|
|
193
|
-
if (mergeResult.status !== 0) {
|
|
194
|
-
const conflicts = collectConflictFiles(cwd, git);
|
|
195
|
-
git.gitSpawn(cwd, 'merge', '--abort');
|
|
196
|
-
throw new PushRetryConflictError(
|
|
197
|
-
conflicts,
|
|
198
|
-
mergeResult.stderr || mergeResult.stdout || '',
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
return {
|
|
204
|
-
ok: false,
|
|
205
|
-
attempts: maxAttempts,
|
|
206
|
-
result: lastResult,
|
|
207
|
-
reason: 'retry-exhausted',
|
|
208
|
-
};
|
|
209
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* lib/spec/index.js — public surface for the spec I/O module.
|
|
3
|
-
*
|
|
4
|
-
* Re-exports the loader (and, in Wave 1, future spec utilities) so
|
|
5
|
-
* downstream consumers — the reconciler, the rewritten /plan, the
|
|
6
|
-
* wave-runner — can `import * from '../lib/spec/index.js'` without
|
|
7
|
-
* reaching into individual submodules.
|
|
8
|
-
*
|
|
9
|
-
* The module intentionally re-exports only the public surface defined
|
|
10
|
-
* by Story #1491; the loader's internal helpers (`_resetValidatorCacheForTests`,
|
|
11
|
-
* `sortKeysDeep`, `renderStateJson`) remain importable from
|
|
12
|
-
* `./loader.js` directly for tests but are not part of the consumer
|
|
13
|
-
* contract.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
export {
|
|
17
|
-
loadSpec,
|
|
18
|
-
loadState,
|
|
19
|
-
SpecNotFoundError,
|
|
20
|
-
SpecParseError,
|
|
21
|
-
SpecValidationError,
|
|
22
|
-
specPath,
|
|
23
|
-
statePath,
|
|
24
|
-
writeSpec,
|
|
25
|
-
writeState,
|
|
26
|
-
} from './loader.js';
|
|
27
|
-
|
|
28
|
-
export {
|
|
29
|
-
buildState,
|
|
30
|
-
canonicalise,
|
|
31
|
-
canonicalStringify,
|
|
32
|
-
hashSpecEntry,
|
|
33
|
-
iterSpecEntries,
|
|
34
|
-
projectMapping,
|
|
35
|
-
sha256Hex,
|
|
36
|
-
} from './state.js';
|
|
@@ -1,425 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* lib/spec/loader.js — spec + state file I/O for the epic-spec reconciler.
|
|
3
|
-
*
|
|
4
|
-
* Owns the two files that bracket the structural SSOT migration (Epic
|
|
5
|
-
* #1182 / Tech Spec #1483):
|
|
6
|
-
*
|
|
7
|
-
* - `temp/epic-<epic-id>/<epic-id>.yaml` — declarative spec (regenerated)
|
|
8
|
-
* - `temp/epic-<epic-id>/<epic-id>.state.json` — slug→issue mapping (observed)
|
|
9
|
-
*
|
|
10
|
-
* Both files live under the per-Epic ephemeral tree `temp/epic-<id>/`
|
|
11
|
-
* (already gitignored) so /plan reruns don't churn a tracked path
|
|
12
|
-
* and so concurrent Epics never collide on a single shared directory.
|
|
13
|
-
* Tests inject `opts.epicsDir` to point at a sandbox; the default for
|
|
14
|
-
* production callers is derived from `lib/config/temp-paths.js#epicTempDir`.
|
|
15
|
-
*
|
|
16
|
-
* The module is intentionally a thin, dependency-light I/O layer:
|
|
17
|
-
*
|
|
18
|
-
* • `loadSpec(epicId)` → parses YAML, validates against
|
|
19
|
-
* `.agents/schemas/epic-spec.schema.json` (Ajv2020). Throws a
|
|
20
|
-
* `SpecValidationError` carrying the offending JSON Pointer paths
|
|
21
|
-
* when the spec is structurally invalid. Missing file throws
|
|
22
|
-
* `SpecNotFoundError`.
|
|
23
|
-
* • `loadState(epicId)` → returns `{ epicId, mapping: {} }` (with
|
|
24
|
-
* `lastReconciledAt` omitted) when the state file is missing, so
|
|
25
|
-
* callers can start reconciling against a fresh Epic without a
|
|
26
|
-
* pre-existing state file.
|
|
27
|
-
* • `writeState(epicId, state)` → writes pretty-printed JSON with
|
|
28
|
-
* deterministically sorted keys (recursive). Repeated writes of an
|
|
29
|
-
* equivalent state produce a byte-identical file. Trailing newline
|
|
30
|
-
* included so the file behaves well under git + POSIX `cat`.
|
|
31
|
-
*
|
|
32
|
-
* The loader does **not** make any GitHub calls; it is pure file I/O
|
|
33
|
-
* over the two on-disk artefacts. The reconciler (Wave 1) layers diff
|
|
34
|
-
* + apply on top of this surface.
|
|
35
|
-
*
|
|
36
|
-
* All public functions accept an optional `{ epicsDir, schemaPath, fs }`
|
|
37
|
-
* options bag so tests can point the loader at a sandbox directory
|
|
38
|
-
* without monkey-patching `process.cwd()` or the project schema path.
|
|
39
|
-
*
|
|
40
|
-
* Cross-references:
|
|
41
|
-
* - Schema: `.agents/schemas/epic-spec.schema.json` (Story #1490)
|
|
42
|
-
* - Fixtures: `tests/fixtures/epic-specs/*.json` (Story #1490)
|
|
43
|
-
* - Tech Spec §"`.agents/epics/<epic-id>.yaml` (spec)" + §"state.json"
|
|
44
|
-
*/
|
|
45
|
-
|
|
46
|
-
import {
|
|
47
|
-
existsSync as defaultExistsSync,
|
|
48
|
-
mkdirSync as defaultMkdirSync,
|
|
49
|
-
readFileSync as defaultReadFileSync,
|
|
50
|
-
writeFileSync as defaultWriteFileSync,
|
|
51
|
-
} from 'node:fs';
|
|
52
|
-
import path from 'node:path';
|
|
53
|
-
import { fileURLToPath } from 'node:url';
|
|
54
|
-
import Ajv2020 from 'ajv/dist/2020.js';
|
|
55
|
-
import addFormats from 'ajv-formats';
|
|
56
|
-
import yaml from 'js-yaml';
|
|
57
|
-
|
|
58
|
-
import { epicTempDir } from '../config/temp-paths.js';
|
|
59
|
-
|
|
60
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
61
|
-
|
|
62
|
-
// scripts/lib/spec/ → scripts/lib/ → scripts/ → .agents/
|
|
63
|
-
const PROJECT_AGENTS_DIR = path.resolve(__dirname, '..', '..', '..');
|
|
64
|
-
const DEFAULT_SCHEMA_PATH = path.join(
|
|
65
|
-
PROJECT_AGENTS_DIR,
|
|
66
|
-
'schemas',
|
|
67
|
-
'epic-spec.schema.json',
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
// Resolve the default per-Epic spec directory under `temp/epic-<id>/`.
|
|
71
|
-
// Caller-injected `opts.epicsDir` still wins for tests and any external
|
|
72
|
-
// tooling that wants to point at a sandbox. Production callers omit the
|
|
73
|
-
// option and route through this helper.
|
|
74
|
-
function defaultEpicsDir(epicId) {
|
|
75
|
-
return epicTempDir(epicId);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const defaultFsAdapter = Object.freeze({
|
|
79
|
-
existsSync: defaultExistsSync,
|
|
80
|
-
mkdirSync: defaultMkdirSync,
|
|
81
|
-
readFileSync: defaultReadFileSync,
|
|
82
|
-
writeFileSync: defaultWriteFileSync,
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
let cachedValidator = null;
|
|
86
|
-
let cachedValidatorKey = null;
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Compile (and cache) the Ajv2020 validator for the epic-spec schema.
|
|
90
|
-
* Cached by absolute schema path so tests can swap to a sandbox schema.
|
|
91
|
-
*
|
|
92
|
-
* @param {string} schemaPath
|
|
93
|
-
* @param {{ readFileSync: typeof defaultReadFileSync }} fs
|
|
94
|
-
* @returns {(data: unknown) => boolean}
|
|
95
|
-
*/
|
|
96
|
-
function getValidator(schemaPath, fs) {
|
|
97
|
-
if (cachedValidator && cachedValidatorKey === schemaPath) {
|
|
98
|
-
return cachedValidator;
|
|
99
|
-
}
|
|
100
|
-
const ajv = new Ajv2020({ allErrors: true, strict: false });
|
|
101
|
-
addFormats(ajv);
|
|
102
|
-
const schema = JSON.parse(fs.readFileSync(schemaPath, 'utf8'));
|
|
103
|
-
cachedValidator = ajv.compile(schema);
|
|
104
|
-
cachedValidatorKey = schemaPath;
|
|
105
|
-
return cachedValidator;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Test-only hook: drop the cached validator so a subsequent call
|
|
110
|
-
* recompiles. Safe to leave exported — production code never invokes it.
|
|
111
|
-
*/
|
|
112
|
-
export function _resetValidatorCacheForTests() {
|
|
113
|
-
cachedValidator = null;
|
|
114
|
-
cachedValidatorKey = null;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Structured error raised by `loadSpec` when the YAML parses but fails
|
|
119
|
-
* schema validation. The Ajv error list is normalised to an array of
|
|
120
|
-
* `{ path, message }` so callers (reconciler CLI, tests) can render the
|
|
121
|
-
* offending JSON Pointer without re-parsing the Ajv envelope.
|
|
122
|
-
*/
|
|
123
|
-
export class SpecValidationError extends Error {
|
|
124
|
-
/**
|
|
125
|
-
* @param {string} epicId
|
|
126
|
-
* @param {Array<{path: string, message: string, params?: object}>} issues
|
|
127
|
-
*/
|
|
128
|
-
constructor(epicId, issues) {
|
|
129
|
-
const head = issues[0] ?? { path: '/', message: 'unknown' };
|
|
130
|
-
super(
|
|
131
|
-
`Spec for epic ${epicId} failed schema validation at ${head.path}: ${head.message}`,
|
|
132
|
-
);
|
|
133
|
-
this.name = 'SpecValidationError';
|
|
134
|
-
this.epicId = epicId;
|
|
135
|
-
this.issues = issues;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Raised by `loadSpec` when the on-disk YAML file does not exist.
|
|
141
|
-
*/
|
|
142
|
-
export class SpecNotFoundError extends Error {
|
|
143
|
-
/**
|
|
144
|
-
* @param {string} epicId
|
|
145
|
-
* @param {string} filePath
|
|
146
|
-
*/
|
|
147
|
-
constructor(epicId, filePath) {
|
|
148
|
-
super(`Spec file missing for epic ${epicId}: ${filePath}`);
|
|
149
|
-
this.name = 'SpecNotFoundError';
|
|
150
|
-
this.epicId = epicId;
|
|
151
|
-
this.filePath = filePath;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Raised by `loadSpec` when the file exists but is not parseable YAML.
|
|
157
|
-
*/
|
|
158
|
-
export class SpecParseError extends Error {
|
|
159
|
-
/**
|
|
160
|
-
* @param {string} epicId
|
|
161
|
-
* @param {string} filePath
|
|
162
|
-
* @param {Error} cause
|
|
163
|
-
*/
|
|
164
|
-
constructor(epicId, filePath, cause) {
|
|
165
|
-
super(
|
|
166
|
-
`Spec file for epic ${epicId} is not valid YAML (${filePath}): ${cause.message}`,
|
|
167
|
-
);
|
|
168
|
-
this.name = 'SpecParseError';
|
|
169
|
-
this.epicId = epicId;
|
|
170
|
-
this.filePath = filePath;
|
|
171
|
-
this.cause = cause;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function resolveOpts(epicId, opts = {}) {
|
|
176
|
-
return {
|
|
177
|
-
epicsDir: opts.epicsDir ?? defaultEpicsDir(epicId),
|
|
178
|
-
schemaPath: opts.schemaPath ?? DEFAULT_SCHEMA_PATH,
|
|
179
|
-
fs: opts.fs ?? defaultFsAdapter,
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Resolve the on-disk spec path for `epicId` under the configured
|
|
185
|
-
* epics dir. Exported for tests and for the reconciler CLI's error
|
|
186
|
-
* messages.
|
|
187
|
-
*
|
|
188
|
-
* @param {number|string} epicId
|
|
189
|
-
* @param {{epicsDir?: string}} [opts]
|
|
190
|
-
* @returns {string}
|
|
191
|
-
*/
|
|
192
|
-
export function specPath(epicId, opts = {}) {
|
|
193
|
-
const { epicsDir } = resolveOpts(epicId, opts);
|
|
194
|
-
return path.join(epicsDir, `${String(epicId)}.yaml`);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Resolve the on-disk state path for `epicId` under the configured
|
|
199
|
-
* epics dir.
|
|
200
|
-
*
|
|
201
|
-
* @param {number|string} epicId
|
|
202
|
-
* @param {{epicsDir?: string}} [opts]
|
|
203
|
-
* @returns {string}
|
|
204
|
-
*/
|
|
205
|
-
export function statePath(epicId, opts = {}) {
|
|
206
|
-
const { epicsDir } = resolveOpts(epicId, opts);
|
|
207
|
-
return path.join(epicsDir, `${String(epicId)}.state.json`);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Convert Ajv's error array into the loader's `{ path, message }`
|
|
212
|
-
* shape. Ajv2020 uses `instancePath` for the JSON Pointer into the
|
|
213
|
-
* data; for `required` errors it leaves the missing property in
|
|
214
|
-
* `params.missingProperty` rather than the path, so we append it so
|
|
215
|
-
* the caller sees `/epic` instead of `` (root) for the canonical
|
|
216
|
-
* `epic required` failure.
|
|
217
|
-
*
|
|
218
|
-
* @param {Array<{instancePath:string,message:string,keyword:string,params?:Record<string,unknown>}>} ajvErrors
|
|
219
|
-
* @returns {Array<{path:string,message:string,params?:object}>}
|
|
220
|
-
*/
|
|
221
|
-
function normaliseAjvErrors(ajvErrors) {
|
|
222
|
-
return ajvErrors.map((err) => {
|
|
223
|
-
let p = err.instancePath || '/';
|
|
224
|
-
if (
|
|
225
|
-
err.keyword === 'required' &&
|
|
226
|
-
typeof err.params?.missingProperty === 'string'
|
|
227
|
-
) {
|
|
228
|
-
const sep = p === '/' ? '' : '/';
|
|
229
|
-
p = `${p}${sep}${err.params.missingProperty}`;
|
|
230
|
-
}
|
|
231
|
-
return {
|
|
232
|
-
path: p,
|
|
233
|
-
message: err.message ?? 'validation failed',
|
|
234
|
-
params: err.params,
|
|
235
|
-
};
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Load and validate the spec YAML for `epicId`. Returns the parsed
|
|
241
|
-
* JavaScript object on success. Throws `SpecNotFoundError`,
|
|
242
|
-
* `SpecParseError`, or `SpecValidationError` otherwise.
|
|
243
|
-
*
|
|
244
|
-
* @param {number|string} epicId
|
|
245
|
-
* @param {{epicsDir?: string, schemaPath?: string, fs?: typeof defaultFsAdapter}} [opts]
|
|
246
|
-
* @returns {object}
|
|
247
|
-
*/
|
|
248
|
-
export function loadSpec(epicId, opts = {}) {
|
|
249
|
-
const { schemaPath, fs } = resolveOpts(epicId, opts);
|
|
250
|
-
const filePath = specPath(epicId, opts);
|
|
251
|
-
|
|
252
|
-
if (!fs.existsSync(filePath)) {
|
|
253
|
-
throw new SpecNotFoundError(String(epicId), filePath);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const raw = fs.readFileSync(filePath, 'utf8');
|
|
257
|
-
let parsed;
|
|
258
|
-
try {
|
|
259
|
-
parsed = yaml.load(raw, { filename: filePath });
|
|
260
|
-
} catch (err) {
|
|
261
|
-
throw new SpecParseError(String(epicId), filePath, err);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
if (parsed == null || typeof parsed !== 'object') {
|
|
265
|
-
throw new SpecValidationError(String(epicId), [
|
|
266
|
-
{
|
|
267
|
-
path: '/',
|
|
268
|
-
message: 'spec root must be an object',
|
|
269
|
-
},
|
|
270
|
-
]);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
const validate = getValidator(schemaPath, fs);
|
|
274
|
-
const ok = validate(parsed);
|
|
275
|
-
if (!ok) {
|
|
276
|
-
throw new SpecValidationError(
|
|
277
|
-
String(epicId),
|
|
278
|
-
normaliseAjvErrors(validate.errors ?? []),
|
|
279
|
-
);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
return parsed;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* Empty-state default. `loadState` returns this shape when the state
|
|
287
|
-
* file does not exist; callers can rely on `mapping` being a plain
|
|
288
|
-
* object (never undefined).
|
|
289
|
-
*
|
|
290
|
-
* @param {number|string} epicId
|
|
291
|
-
* @returns {{epicId: number, mapping: Record<string, never>}}
|
|
292
|
-
*/
|
|
293
|
-
function emptyState(epicId) {
|
|
294
|
-
return { epicId: Number(epicId), mapping: {} };
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* Load the state file for `epicId`. Returns an empty mapping when the
|
|
299
|
-
* file is missing (the canonical "fresh Epic" case the reconciler
|
|
300
|
-
* faces on first apply). Throws if the file exists but is not valid
|
|
301
|
-
* JSON.
|
|
302
|
-
*
|
|
303
|
-
* @param {number|string} epicId
|
|
304
|
-
* @param {{epicsDir?: string, fs?: typeof defaultFsAdapter}} [opts]
|
|
305
|
-
* @returns {{epicId: number, mapping: object, lastReconciledAt?: string}}
|
|
306
|
-
*/
|
|
307
|
-
export function loadState(epicId, opts = {}) {
|
|
308
|
-
const { fs } = resolveOpts(epicId, opts);
|
|
309
|
-
const filePath = statePath(epicId, opts);
|
|
310
|
-
|
|
311
|
-
if (!fs.existsSync(filePath)) {
|
|
312
|
-
return emptyState(epicId);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
const raw = fs.readFileSync(filePath, 'utf8');
|
|
316
|
-
return JSON.parse(raw);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/**
|
|
320
|
-
* Recursively sort object keys (arrays preserve order). Returns a new
|
|
321
|
-
* value with the same shape — leaves are returned unchanged.
|
|
322
|
-
*
|
|
323
|
-
* Exported for `state-writer.js` so the hashing path can share the
|
|
324
|
-
* exact same canonicalisation as the file writer.
|
|
325
|
-
*
|
|
326
|
-
* @param {unknown} value
|
|
327
|
-
* @returns {unknown}
|
|
328
|
-
*/
|
|
329
|
-
export function sortKeysDeep(value) {
|
|
330
|
-
if (Array.isArray(value)) {
|
|
331
|
-
return value.map(sortKeysDeep);
|
|
332
|
-
}
|
|
333
|
-
if (value && typeof value === 'object') {
|
|
334
|
-
const out = {};
|
|
335
|
-
for (const key of Object.keys(value).sort()) {
|
|
336
|
-
out[key] = sortKeysDeep(value[key]);
|
|
337
|
-
}
|
|
338
|
-
return out;
|
|
339
|
-
}
|
|
340
|
-
return value;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/**
|
|
344
|
-
* Render `state` to deterministic JSON. Public so the test suite can
|
|
345
|
-
* assert the byte-identical-roundtrip property without re-implementing
|
|
346
|
-
* the formatter.
|
|
347
|
-
*
|
|
348
|
-
* @param {object} state
|
|
349
|
-
* @returns {string} pretty-printed JSON, terminated by a single newline.
|
|
350
|
-
*/
|
|
351
|
-
export function renderStateJson(state) {
|
|
352
|
-
return `${JSON.stringify(sortKeysDeep(state), null, 2)}\n`;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* Write the state file for `epicId`. Creates the parent directory
|
|
357
|
-
* lazily. Object keys are recursively sorted so re-writing the same
|
|
358
|
-
* logical state produces a byte-identical file (AC: "diffs stay
|
|
359
|
-
* stable", "byte-identical when written twice").
|
|
360
|
-
*
|
|
361
|
-
* Returns the absolute path written so callers can log it.
|
|
362
|
-
*
|
|
363
|
-
* @param {number|string} epicId
|
|
364
|
-
* @param {object} state
|
|
365
|
-
* @param {{epicsDir?: string, fs?: typeof defaultFsAdapter}} [opts]
|
|
366
|
-
* @returns {string}
|
|
367
|
-
*/
|
|
368
|
-
export function writeState(epicId, state, opts = {}) {
|
|
369
|
-
const { epicsDir, fs } = resolveOpts(epicId, opts);
|
|
370
|
-
if (!fs.existsSync(epicsDir)) {
|
|
371
|
-
fs.mkdirSync(epicsDir, { recursive: true });
|
|
372
|
-
}
|
|
373
|
-
const filePath = statePath(epicId, opts);
|
|
374
|
-
fs.writeFileSync(filePath, renderStateJson(state), 'utf8');
|
|
375
|
-
return filePath;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
/**
|
|
379
|
-
* Write the spec YAML file for `epicId`. Creates the parent directory
|
|
380
|
-
* lazily and emits a top-level `$schema` reference so editors with
|
|
381
|
-
* YAML-schema autocomplete (e.g. the Red Hat YAML extension) resolve
|
|
382
|
-
* the schema from the file itself.
|
|
383
|
-
*
|
|
384
|
-
* Story #1498 / Task #1525 introduced this writer so the rewritten
|
|
385
|
-
* `/plan` halves can persist the spec from the decomposer's
|
|
386
|
-
* ticket-array projection (`renderSpec`) without reaching into raw
|
|
387
|
-
* `js-yaml` calls scattered across the planning scripts.
|
|
388
|
-
*
|
|
389
|
-
* The function validates the spec via the same Ajv2020 compiler the
|
|
390
|
-
* loader caches — a malformed spec is rejected synchronously instead of
|
|
391
|
-
* being persisted and tripping `loadSpec` on the next reconciler run.
|
|
392
|
-
*
|
|
393
|
-
* @param {number|string} epicId
|
|
394
|
-
* @param {object} spec spec object matching `epic-spec.schema.json`.
|
|
395
|
-
* @param {{epicsDir?: string, schemaPath?: string, fs?: typeof defaultFsAdapter}} [opts]
|
|
396
|
-
* @returns {string} the absolute path written.
|
|
397
|
-
*/
|
|
398
|
-
export function writeSpec(epicId, spec, opts = {}) {
|
|
399
|
-
const { epicsDir, schemaPath, fs } = resolveOpts(epicId, opts);
|
|
400
|
-
if (!spec || typeof spec !== 'object') {
|
|
401
|
-
throw new TypeError('[writeSpec] spec must be an object');
|
|
402
|
-
}
|
|
403
|
-
const validate = getValidator(schemaPath, fs);
|
|
404
|
-
const ok = validate(spec);
|
|
405
|
-
if (!ok) {
|
|
406
|
-
throw new SpecValidationError(
|
|
407
|
-
String(epicId),
|
|
408
|
-
normaliseAjvErrors(validate.errors ?? []),
|
|
409
|
-
);
|
|
410
|
-
}
|
|
411
|
-
if (!fs.existsSync(epicsDir)) {
|
|
412
|
-
fs.mkdirSync(epicsDir, { recursive: true });
|
|
413
|
-
}
|
|
414
|
-
const filePath = specPath(epicId, opts);
|
|
415
|
-
// Lazy require: `js-yaml` is already a runtime dep of the loader, but
|
|
416
|
-
// keeping the import top-level would force every consumer of `loader.js`
|
|
417
|
-
// to pay the parse cost even when they only need state helpers.
|
|
418
|
-
const yamlDump = yaml.dump(spec, {
|
|
419
|
-
noRefs: true,
|
|
420
|
-
sortKeys: false,
|
|
421
|
-
lineWidth: 120,
|
|
422
|
-
});
|
|
423
|
-
fs.writeFileSync(filePath, yamlDump, 'utf8');
|
|
424
|
-
return filePath;
|
|
425
|
-
}
|