mandrel 2.0.0 → 2.2.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 -28
- package/.agents/agents/acceptance-critic.md +20 -9
- package/.agents/agents/story-worker.md +45 -48
- package/.agents/audit-checklists/performance.md +1 -1
- package/.agents/docs/SDLC.md +60 -46
- package/.agents/docs/agentrc-reference.json +8 -13
- package/.agents/docs/configuration.md +33 -57
- package/.agents/docs/execution-reference.md +39 -10
- package/.agents/docs/quality-gates.md +17 -19
- package/.agents/docs/workflows.md +6 -6
- package/.agents/instructions.md +64 -79
- package/.agents/rules/ci-remediation.md +3 -3
- package/.agents/rules/gherkin-standards.md +10 -0
- package/.agents/rules/git-conventions-reference.md +42 -51
- package/.agents/schemas/acceptance-eval-verdict.schema.json +2 -2
- package/.agents/schemas/agentrc.schema.json +35 -46
- package/.agents/schemas/audit-rules.json +59 -1
- package/.agents/schemas/audit-rules.schema.json +33 -1
- package/.agents/schemas/lifecycle/README.md +1 -2
- package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
- package/.agents/schemas/lifecycle/merge.flip-failed.schema.json +33 -0
- package/.agents/schemas/lifecycle/merge.unlanded.schema.json +1 -0
- package/.agents/schemas/lifecycle/story.merged.schema.json +1 -1
- package/.agents/schemas/signal-event.schema.json +3 -3
- package/.agents/schemas/story-deliver-terminal.schema.json +152 -0
- package/.agents/schemas/validation-evidence.schema.json +1 -1
- package/.agents/scripts/acceptance-eval.js +24 -68
- package/.agents/scripts/agents-bootstrap-github.js +1 -1
- package/.agents/scripts/bootstrap.js +3 -3
- package/.agents/scripts/check-dead-exports.js +43 -104
- package/.agents/scripts/check-doc-links.js +2 -2
- package/.agents/scripts/check-lifecycle-lint.js +1 -1
- package/.agents/scripts/check-workflow-cli-lint.js +91 -0
- package/.agents/scripts/deliver-recover.js +122 -0
- package/.agents/scripts/drain-pending-cleanup.js +1 -1
- package/.agents/scripts/evidence-gate.js +20 -50
- package/.agents/scripts/generate-skills-index.js +17 -1
- package/.agents/scripts/generate-workflows-doc.js +4 -4
- package/.agents/scripts/lib/ITicketingProvider.js +1 -19
- package/.agents/scripts/lib/audit-suite/selector.js +323 -23
- package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -11
- package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +28 -33
- package/.agents/scripts/lib/bootstrap/manifest.js +8 -11
- package/.agents/scripts/lib/bootstrap/project-bootstrap.js +30 -53
- package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -2
- package/.agents/scripts/lib/checks/core-bare-clean.js +4 -1
- package/.agents/scripts/lib/checks/index.js +1 -1
- package/.agents/scripts/lib/checks/loop-health.js +12 -11
- package/.agents/scripts/lib/checks/state.js +17 -248
- package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +3 -3
- package/.agents/scripts/lib/checks/subagent-agent-tool-required.js +3 -4
- package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +2 -2
- package/.agents/scripts/lib/checks/worktree-residue-biome.js +3 -3
- package/.agents/scripts/lib/cli-args.js +23 -2
- package/.agents/scripts/lib/close-validation/gates.js +13 -13
- package/.agents/scripts/lib/close-validation/projections/inputs.js +7 -7
- package/.agents/scripts/lib/close-validation/projections/maintainability.js +12 -12
- package/.agents/scripts/lib/close-validation/runner.js +13 -21
- package/.agents/scripts/lib/close-validation/telemetry.js +17 -8
- package/.agents/scripts/lib/config/acceptance-eval.js +2 -2
- package/.agents/scripts/lib/config/delivery-routing.js +7 -6
- package/.agents/scripts/lib/config/explain.js +10 -16
- package/.agents/scripts/lib/config/github.js +7 -5
- package/.agents/scripts/lib/config/limits.js +15 -25
- package/.agents/scripts/lib/config/quality.js +11 -14
- package/.agents/scripts/lib/config/runners.js +8 -21
- package/.agents/scripts/lib/config/temp-paths.js +18 -56
- package/.agents/scripts/lib/config-settings-schema-delivery.js +34 -16
- package/.agents/scripts/lib/config-settings-schema-quality.js +9 -2
- package/.agents/scripts/lib/config-settings-schema.js +48 -22
- package/.agents/scripts/lib/dead-exports-knip.js +105 -0
- package/.agents/scripts/lib/dead-exports-mode.js +51 -0
- package/.agents/scripts/lib/duplicate-search.js +38 -7
- package/.agents/scripts/lib/findings/promote-finding.js +23 -14
- package/.agents/scripts/lib/format-generated-json.js +97 -0
- package/.agents/scripts/lib/framework-version.js +19 -189
- package/.agents/scripts/lib/gh-exec.js +8 -0
- package/.agents/scripts/lib/git-branch-lifecycle.js +0 -158
- package/.agents/scripts/lib/git-utils.js +0 -14
- package/.agents/scripts/lib/json-utils.js +1 -2
- package/.agents/scripts/lib/label-constants.js +0 -15
- package/.agents/scripts/lib/label-taxonomy.js +1 -12
- package/.agents/scripts/lib/observability/active-story-env.js +42 -163
- package/.agents/scripts/lib/observability/runtime-friction.js +243 -0
- package/.agents/scripts/lib/observability/signal-validator.js +4 -4
- package/.agents/scripts/lib/observability/signals-writer.js +6 -82
- package/.agents/scripts/lib/observability/source-classifier.js +2 -2
- package/.agents/scripts/lib/observability/tool-trace-hook.js +2 -12
- package/.agents/scripts/lib/orchestration/acceptance-clusters.js +1 -1
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +2 -2
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +43 -45
- package/.agents/scripts/lib/orchestration/change-set.js +103 -0
- package/.agents/scripts/lib/orchestration/code-review.js +70 -191
- package/.agents/scripts/lib/orchestration/consolidation-precondition.js +3 -3
- package/.agents/scripts/lib/orchestration/deliver-recover.js +328 -0
- package/.agents/scripts/lib/orchestration/detectors-phase.js +12 -6
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/fast-forward.js +34 -0
- package/.agents/scripts/lib/orchestration/lease-guard-shared.js +3 -2
- package/.agents/scripts/lib/orchestration/lifecycle/emit-ledger-event.js +142 -0
- package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +9 -11
- package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-flip-failed.js +86 -0
- package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +37 -103
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +7 -3
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +50 -85
- package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +3 -14
- package/.agents/scripts/lib/orchestration/merge-block-class.js +76 -20
- package/.agents/scripts/lib/orchestration/merge-poll.js +104 -0
- package/.agents/scripts/lib/orchestration/plan-context.js +116 -33
- package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +26 -36
- package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +31 -22
- package/.agents/scripts/lib/orchestration/plan-metrics.js +38 -6
- package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +16 -6
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +173 -25
- package/.agents/scripts/lib/orchestration/plan-persist/plan-context-source.js +116 -0
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +280 -100
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +472 -55
- package/.agents/scripts/lib/orchestration/plan-persist/summary.js +21 -16
- package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +509 -0
- package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +230 -0
- package/.agents/scripts/lib/orchestration/planning/authoring-context.js +41 -40
- package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +1 -2
- package/.agents/scripts/lib/orchestration/planning/spec-authoring-grounding.js +1 -1
- package/.agents/scripts/lib/orchestration/resolve-stories.js +344 -0
- package/.agents/scripts/lib/orchestration/retro-proposals.js +7 -7
- package/.agents/scripts/lib/orchestration/review-depth.js +105 -40
- package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +3 -13
- package/.agents/scripts/lib/orchestration/review-providers/native.js +1 -154
- package/.agents/scripts/lib/orchestration/review-providers/review-depth.js +3 -2
- package/.agents/scripts/lib/orchestration/review-providers/security-review.js +1 -1
- package/.agents/scripts/lib/orchestration/review-providers/types.js +5 -4
- package/.agents/scripts/lib/orchestration/review-providers/ultrareview.js +1 -1
- package/.agents/scripts/lib/orchestration/run-epilogue.js +374 -16
- package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +24 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +11 -9
- package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +4 -4
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +4 -13
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +608 -152
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +72 -30
- package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +305 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +12 -8
- package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +37 -4
- package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +2 -2
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +264 -43
- package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +1 -1
- package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +10 -10
- package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +104 -279
- package/.agents/scripts/lib/orchestration/story-close/phases/local-lens-review.js +191 -0
- package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +120 -0
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +360 -0
- package/.agents/scripts/lib/orchestration/story-follow-ups.js +75 -14
- package/.agents/scripts/lib/orchestration/story-init-remote.js +12 -8
- package/.agents/scripts/lib/orchestration/story-plan-state.js +14 -29
- package/.agents/scripts/lib/orchestration/task-body-validator.js +52 -7
- package/.agents/scripts/lib/orchestration/ticket-lease.js +27 -74
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +119 -14
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +3 -4
- package/.agents/scripts/lib/orchestration/ticket-validator.js +121 -18
- package/.agents/scripts/lib/orchestration/ticketing/bulk.js +14 -47
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +19 -32
- package/.agents/scripts/lib/orchestration/ticketing/transition.js +61 -1
- package/.agents/scripts/lib/orchestration/ticketing.js +0 -1
- package/.agents/scripts/lib/plan-phase-cleanup.js +12 -14
- package/.agents/scripts/lib/planning-corpus.js +12 -286
- package/.agents/scripts/lib/preflight-runner.js +2 -2
- package/.agents/scripts/lib/qa/qa-context-hydrator.js +5 -5
- package/.agents/scripts/lib/signals/index.js +4 -17
- package/.agents/scripts/lib/signals/read.js +35 -35
- package/.agents/scripts/lib/signals/schema.js +8 -11
- package/.agents/scripts/lib/signals/span-tree.js +7 -7
- package/.agents/scripts/lib/signals/write.js +0 -1
- package/.agents/scripts/lib/single-story/story-merged-notify.js +13 -2
- package/.agents/scripts/lib/skills/parse-skill.js +16 -3
- package/.agents/scripts/lib/story-adjacency.js +8 -7
- package/.agents/scripts/lib/story-body/story-body.js +81 -13
- package/.agents/scripts/lib/templates/decomposer-prompts.js +15 -16
- package/.agents/scripts/lib/test-env.js +14 -1
- package/.agents/scripts/lib/test-tiers.js +0 -3
- package/.agents/scripts/lib/ticket-body-sections.js +0 -14
- package/.agents/scripts/lib/validation-evidence.js +31 -59
- package/.agents/scripts/lib/wave-runner/live-probe.js +315 -0
- package/.agents/scripts/lib/wave-runner/ready-set.js +32 -6
- package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +1 -1
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +68 -19
- package/.agents/scripts/lib/worktree/lifecycle-manager.js +1 -2
- package/.agents/scripts/plan-context.js +38 -7
- package/.agents/scripts/plan-critics.js +203 -0
- package/.agents/scripts/plan-persist.js +145 -35
- package/.agents/scripts/plan-run-epilogue.js +83 -38
- package/.agents/scripts/post-structured-comment.js +0 -38
- package/.agents/scripts/pr-watch-with-update.js +43 -22
- package/.agents/scripts/providers/github/compose.js +0 -1
- package/.agents/scripts/providers/github/errors.js +0 -19
- package/.agents/scripts/providers/github/issues.js +1 -11
- package/.agents/scripts/providers/github/mappers.js +5 -0
- package/.agents/scripts/providers/github/sub-issues.js +0 -47
- package/.agents/scripts/providers/github/tickets.js +33 -153
- package/.agents/scripts/providers/github.js +17 -6
- package/.agents/scripts/quality-preview.js +13 -6
- package/.agents/scripts/resolve-stories.js +236 -0
- package/.agents/scripts/run-coverage.js +4 -1
- package/.agents/scripts/run-lint.js +2 -2
- package/.agents/scripts/run-verify.js +31 -2
- package/.agents/scripts/signals-view.js +9 -10
- package/.agents/scripts/single-story-close.js +173 -18
- package/.agents/scripts/single-story-confirm-merge.js +288 -15
- package/.agents/scripts/single-story-init.js +6 -10
- package/.agents/scripts/stories-wave-tick.js +380 -53
- package/.agents/scripts/story-plan.js +3 -3
- package/.agents/scripts/update-ticket-state.js +8 -50
- package/.agents/skills/core/code-review-and-quality/SKILL.md +28 -450
- package/.agents/skills/core/code-review-and-quality/reference.md +458 -0
- package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +22 -315
- package/.agents/skills/core/debugging-and-error-recovery/reference.md +323 -0
- package/.agents/skills/core/diagnose-friction/SKILL.md +14 -18
- package/.agents/skills/core/documentation-and-adrs/SKILL.md +25 -397
- package/.agents/skills/core/documentation-and-adrs/reference.md +403 -0
- package/.agents/skills/core/gates-and-baselines/SKILL.md +12 -12
- package/.agents/skills/core/idea-refinement/SKILL.md +3 -3
- package/.agents/skills/core/scope-triage/SKILL.md +3 -0
- package/.agents/skills/core/security-and-hardening/SKILL.md +22 -367
- package/.agents/skills/core/security-and-hardening/reference.md +375 -0
- package/.agents/skills/skills.index.json +2 -12
- package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +2 -4
- package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +1 -1
- package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -3
- package/.agents/workflows/audit-architecture.md +3 -4
- package/.agents/workflows/audit-clean-code.md +4 -4
- package/.agents/workflows/audit-documentation.md +4 -5
- package/.agents/workflows/audit-lighthouse.md +8 -0
- package/.agents/workflows/audit-navigability.md +10 -0
- package/.agents/workflows/audit-performance.md +2 -3
- package/.agents/workflows/audit-quality.md +8 -9
- package/.agents/workflows/audit-security.md +1 -2
- package/.agents/workflows/audit-seo.md +10 -0
- package/.agents/workflows/audit-ux-ui.md +7 -0
- package/.agents/workflows/deliver.md +133 -45
- package/.agents/workflows/git-cleanup.md +2 -2
- package/.agents/workflows/git-deliver.md +1 -1
- package/.agents/workflows/helpers/acceptance-self-eval.md +34 -17
- package/.agents/workflows/helpers/code-quality-guardrails.md +15 -12
- package/.agents/workflows/helpers/code-review.md +14 -12
- package/.agents/workflows/helpers/deliver-story-reference.md +73 -32
- package/.agents/workflows/helpers/deliver-story.md +209 -118
- package/.agents/workflows/helpers/parallel-tooling.md +2 -2
- package/.agents/workflows/helpers/worktree-lifecycle.md +28 -32
- package/.agents/workflows/plan.md +239 -19
- package/.agents/workflows/qa-assist.md +6 -6
- package/.agents/workflows/qa-explore.md +3 -3
- package/.agents/workflows/qa-run.md +1 -5
- package/bin/mandrel.js +12 -1
- package/docs/CHANGELOG.md +62 -0
- package/lib/cli/registry.js +262 -19
- package/lib/cli/sync-agents.js +157 -0
- package/lib/cli/sync-commands.js +115 -6
- package/lib/cli/sync.js +168 -6
- package/lib/cli/update.js +105 -8
- package/lib/cli/version-helpers.js +131 -0
- package/lib/migrations/README.md +7 -5
- package/lib/migrations/index.js +17 -9
- package/lib/migrations/steps/2.1.0-retire-mi-drop-knobs.js +100 -0
- package/lib/migrations/steps/2.1.0-retire-verify-concurrency-cap.js +101 -0
- package/lib/migrations/steps/2.2.0-retire-epic-ac-tags.js +154 -0
- package/package.json +2 -2
- package/.agents/schemas/epic-perf-report.schema.json +0 -89
- package/.agents/schemas/lifecycle/acceptance.reconcile.failed.schema.json +0 -13
- package/.agents/schemas/lifecycle/acceptance.reconcile.ok.schema.json +0 -13
- package/.agents/schemas/lifecycle/acceptance.reconcile.skipped.schema.json +0 -13
- package/.agents/schemas/lifecycle/acceptance.reconcile.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/acceptance.reconcile.waived.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.automerge.end.schema.json +0 -15
- package/.agents/schemas/lifecycle/epic.automerge.start.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.blocked.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.cleanup.end.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.cleanup.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.close.end.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.complete.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.finalize.end.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.finalize.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.merge.armed.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.merge.blocked.schema.json +0 -14
- package/.agents/schemas/lifecycle/epic.merge.confirmed.schema.json +0 -17
- package/.agents/schemas/lifecycle/epic.merge.ready.schema.json +0 -15
- package/.agents/schemas/lifecycle/epic.plan.end.schema.json +0 -18
- package/.agents/schemas/lifecycle/epic.plan.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.snapshot.end.schema.json +0 -16
- package/.agents/schemas/lifecycle/epic.snapshot.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.watch.end.schema.json +0 -29
- package/.agents/schemas/lifecycle/epic.watch.start.schema.json +0 -16
- package/.agents/schemas/lifecycle/story.heartbeat.schema.json +0 -20
- package/.agents/schemas/risk-verdict.schema.json +0 -53
- package/.agents/schemas/story-perf-summary.schema.json +0 -73
- package/.agents/scripts/analyze-execution.js +0 -444
- package/.agents/scripts/check-prepush-recovery.js +0 -90
- package/.agents/scripts/lib/git-merge-orchestrator.js +0 -261
- package/.agents/scripts/lib/observability/baseline-refresh-rate.js +0 -221
- package/.agents/scripts/lib/observability/hook-heartbeat.js +0 -187
- package/.agents/scripts/lib/observability/perf-aggregator.js +0 -813
- package/.agents/scripts/lib/observability/perf-report-readers.js +0 -328
- package/.agents/scripts/lib/observability/perf-report-render.js +0 -182
- package/.agents/scripts/lib/orchestration/audit-lens-routing.js +0 -128
- package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +0 -273
- package/.agents/scripts/lib/orchestration/error-journal.js +0 -139
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +0 -155
- package/.agents/scripts/lib/orchestration/lifecycle/ledger-diff.js +0 -140
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +0 -665
- package/.agents/scripts/lib/orchestration/plan-review-routing.js +0 -63
- package/.agents/scripts/lib/orchestration/planning/risk-verdict.js +0 -104
- package/.agents/scripts/lib/orchestration/planning-context-budget.js +0 -213
- package/.agents/scripts/lib/orchestration/planning-risk.js +0 -194
- package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +0 -56
- package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +0 -21
- package/.agents/scripts/lib/orchestration/post-merge/phases/notification.js +0 -78
- package/.agents/scripts/lib/orchestration/post-merge/phases/temp-cleanup.js +0 -68
- package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +0 -118
- package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +0 -397
- package/.agents/scripts/lib/orchestration/preflight-cache.js +0 -187
- package/.agents/scripts/lib/orchestration/resolve-plan-run.js +0 -155
- package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +0 -275
- package/.agents/scripts/lib/orchestration/story-progress/story-run-progress-writer.js +0 -400
- package/.agents/scripts/lib/single-story/confirm-merge-follow-ups.js +0 -36
- package/.agents/scripts/resolve-plan-run.js +0 -117
- package/.agents/skills/core/analyze-execution/SKILL.md +0 -101
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* plan-critics-evaluate.js — shared critic-dispatch evaluation for the
|
|
3
|
-
* collapsed /plan flow (#4496 fix 6
|
|
4
|
-
* CLI so the persist surface folds the same evaluation in as a pre-write
|
|
5
|
-
* phase).
|
|
3
|
+
* collapsed /plan flow (#4496 fix 6).
|
|
6
4
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
5
|
+
* One consumer: the `plan-critics.js` CLI, which `/plan` runs between its
|
|
6
|
+
* Author and Persist steps. The CLI loads the draft artifacts, calls this
|
|
7
|
+
* module, prints the verdict as JSON, and records every skip on the
|
|
8
|
+
* plan-metrics ledger; the workflow dispatches a fresh-context critic
|
|
9
|
+
* sub-agent on a `dispatch: true` verdict and folds the findings into a
|
|
10
|
+
* re-author round before persist.
|
|
11
|
+
*
|
|
12
|
+
* Story #4592 moved that evaluation here from `run-plan-persist.js`, which
|
|
13
|
+
* ran it after authoring was finished and immediately before
|
|
14
|
+
* `createStoryIssues` — the one point where a `dispatch: true` verdict has
|
|
15
|
+
* no re-author loop to route to. Persist no longer evaluates critics; this
|
|
16
|
+
* module has exactly one evaluation point.
|
|
15
17
|
*
|
|
16
18
|
* Pure evaluation: no file I/O, no GitHub calls, no ledger writes — the
|
|
17
|
-
*
|
|
19
|
+
* caller owns artifact loading and skip recording.
|
|
18
20
|
*
|
|
19
21
|
* @module lib/orchestration/plan-critics-evaluate
|
|
20
22
|
*/
|
|
@@ -24,6 +26,7 @@ import {
|
|
|
24
26
|
evaluateConsolidationDispatch,
|
|
25
27
|
evaluatePremortemDispatch,
|
|
26
28
|
} from './plan-critic-conditions.js';
|
|
29
|
+
import { evaluateTextHygiene } from './plan-text-hygiene.js';
|
|
27
30
|
|
|
28
31
|
/**
|
|
29
32
|
* Resolve the planning risk heuristics list from the canonical config
|
|
@@ -41,30 +44,33 @@ function resolveRiskHeuristics(config = {}) {
|
|
|
41
44
|
|
|
42
45
|
/**
|
|
43
46
|
* Evaluate the consolidation + pre-mortem critic dispatch conditions over
|
|
44
|
-
* the authored planning artifacts (
|
|
45
|
-
* unchanged):
|
|
47
|
+
* the authored planning artifacts (#4474 PR6 conditions, unchanged):
|
|
46
48
|
*
|
|
47
49
|
* - Consolidation: skipped outright when `tickets` is null/absent (the
|
|
48
50
|
* single-delivery shape authors no draft tickets); otherwise the
|
|
49
51
|
* deterministic precondition + size/divergence conditions.
|
|
50
|
-
* - Pre-mortem:
|
|
51
|
-
*
|
|
52
|
-
* the
|
|
52
|
+
* - Pre-mortem: ticket count at least half `maxTickets`, OR any
|
|
53
|
+
* `planning.riskHeuristics` phrase matching the plan text. Story #4542
|
|
54
|
+
* retired its authored-risk-level condition with the verdict itself.
|
|
55
|
+
* - Text hygiene (Story #4599, advisory-only): deterministic body lints
|
|
56
|
+
* (dangling-citation / open-question / slicing-mass) over the draft
|
|
57
|
+
* stories. It has no `dispatch` semantics and spawns nothing — its
|
|
58
|
+
* `findings[]` are re-author-round input, and the consolidation /
|
|
59
|
+
* premortem dispatch verdicts are untouched by it.
|
|
53
60
|
*
|
|
54
61
|
* @param {{
|
|
55
62
|
* techSpecContent: string,
|
|
56
|
-
* riskVerdict: { summary?: string },
|
|
57
63
|
* tickets?: Array<object>|null,
|
|
58
64
|
* config?: object,
|
|
59
65
|
* }} args
|
|
60
66
|
* @returns {{
|
|
61
67
|
* consolidation: { critic: string, dispatch: boolean, reasons: string[] },
|
|
62
68
|
* premortem: { critic: string, dispatch: boolean, reasons: string[] },
|
|
69
|
+
* textHygiene: { critic: string, findings: Array<object> },
|
|
63
70
|
* }}
|
|
64
71
|
*/
|
|
65
72
|
export function evaluatePlanCritics({
|
|
66
73
|
techSpecContent,
|
|
67
|
-
riskVerdict,
|
|
68
74
|
tickets = null,
|
|
69
75
|
config = {},
|
|
70
76
|
}) {
|
|
@@ -84,16 +90,19 @@ export function evaluatePlanCritics({
|
|
|
84
90
|
});
|
|
85
91
|
|
|
86
92
|
const premortem = evaluatePremortemDispatch({
|
|
87
|
-
riskVerdict,
|
|
88
93
|
ticketCount: ticketList?.length ?? 0,
|
|
89
94
|
maxTickets: getLimits(config).maxTickets,
|
|
90
95
|
riskHeuristics: resolveRiskHeuristics(config),
|
|
91
96
|
planText: [
|
|
92
97
|
techSpecContent ?? '',
|
|
93
98
|
ticketList ? JSON.stringify(ticketList) : '',
|
|
94
|
-
riskVerdict?.summary ?? '',
|
|
95
99
|
].join('\n'),
|
|
96
100
|
});
|
|
97
101
|
|
|
98
|
-
|
|
102
|
+
const textHygiene = {
|
|
103
|
+
critic: 'text-hygiene',
|
|
104
|
+
findings: evaluateTextHygiene({ draftStories: ticketList }).findings,
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
return { consolidation, premortem, textHygiene };
|
|
99
108
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* phase is removed, every plan CLI invocation stamps an entry/exit record so
|
|
6
6
|
* the current 12-phase baseline is captured on disk. Each record is one
|
|
7
7
|
* newline-terminated JSON line appended to
|
|
8
|
-
* `temp/
|
|
8
|
+
* `temp/run-<id>/plan-metrics.json` (per-Epic plan CLIs) or
|
|
9
9
|
* `temp/standalone/plan-metrics.json` (the standalone `story-plan.js` path
|
|
10
10
|
* and Epic-less healthcheck runs — same standalone routing the friction
|
|
11
11
|
* ledger uses).
|
|
@@ -61,7 +61,7 @@ import path from 'node:path';
|
|
|
61
61
|
|
|
62
62
|
import {
|
|
63
63
|
anchorTempRoot,
|
|
64
|
-
|
|
64
|
+
runArtifactPath,
|
|
65
65
|
tempRootFrom,
|
|
66
66
|
} from '../config/temp-paths.js';
|
|
67
67
|
import { Logger } from '../Logger.js';
|
|
@@ -95,7 +95,7 @@ export function planMetricsPath(epicId, config) {
|
|
|
95
95
|
PLAN_METRICS_BASENAME,
|
|
96
96
|
);
|
|
97
97
|
}
|
|
98
|
-
return
|
|
98
|
+
return runArtifactPath(epicId, PLAN_METRICS_BASENAME, config);
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
/**
|
|
@@ -314,7 +314,8 @@ export async function readPlanMetrics(epicId, config) {
|
|
|
314
314
|
|
|
315
315
|
/**
|
|
316
316
|
* Roll a read ledger up into the compact summary surfaced by the persist
|
|
317
|
-
* summary
|
|
317
|
+
* summary. (Story #4545 deleted its second consumer, `analyze-execution.js`,
|
|
318
|
+
* with the execution-analysis surface.) Returns `null` when there is nothing
|
|
318
319
|
* to summarize (missing ledger or zero parseable entries).
|
|
319
320
|
*
|
|
320
321
|
* Critic-skip records (kind: 'critic-skip') are counted separately from
|
|
@@ -322,7 +323,15 @@ export async function readPlanMetrics(epicId, config) {
|
|
|
322
323
|
* them down, so the skip-audit trail is visible in the persist summary
|
|
323
324
|
* without inflating the turns-per-plan proxy.
|
|
324
325
|
*
|
|
326
|
+
* Pass `opts.since` (an ISO-8601 instant) to scope the roll-up to one plan
|
|
327
|
+
* run (Story #4541). The Epic-less ledger at `temp/standalone/` is shared by
|
|
328
|
+
* every plan the repo has ever run, so an unfiltered summary reported
|
|
329
|
+
* lifetime totals under a line the reader takes to describe the invocation
|
|
330
|
+
* in front of them. Records are timestamped `startedAt` (invocations) or
|
|
331
|
+
* `at` (critic skips); either at-or-after `since` is in scope.
|
|
332
|
+
*
|
|
325
333
|
* @param {{ entries: object[], malformedLines?: number }} ledger
|
|
334
|
+
* @param {{ since?: string|null }} [opts]
|
|
326
335
|
* @returns {{
|
|
327
336
|
* invocations: number,
|
|
328
337
|
* failures: number,
|
|
@@ -337,8 +346,31 @@ export async function readPlanMetrics(epicId, config) {
|
|
|
337
346
|
* malformedLines: number,
|
|
338
347
|
* }|null}
|
|
339
348
|
*/
|
|
340
|
-
|
|
341
|
-
|
|
349
|
+
/**
|
|
350
|
+
* Timestamp a ledger record is ordered by: `startedAt` for invocation
|
|
351
|
+
* records, `at` for critic-skip records.
|
|
352
|
+
*
|
|
353
|
+
* @param {object} entry
|
|
354
|
+
* @returns {string|null}
|
|
355
|
+
*/
|
|
356
|
+
function recordTimestamp(entry) {
|
|
357
|
+
const stamp =
|
|
358
|
+
entry?.kind === PLAN_METRICS_KIND_CRITIC_SKIP ? entry.at : entry.startedAt;
|
|
359
|
+
return typeof stamp === 'string' ? stamp : null;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export function summarizePlanMetrics(ledger, opts = {}) {
|
|
363
|
+
const all = ledger?.entries ?? [];
|
|
364
|
+
const since = typeof opts.since === 'string' ? opts.since : null;
|
|
365
|
+
// ISO-8601 UTC strings sort lexicographically in time order, so a string
|
|
366
|
+
// compare is a correct (and allocation-free) instant compare here.
|
|
367
|
+
const entries =
|
|
368
|
+
since === null
|
|
369
|
+
? all
|
|
370
|
+
: all.filter((e) => {
|
|
371
|
+
const stamp = recordTimestamp(e);
|
|
372
|
+
return stamp !== null && stamp >= since;
|
|
373
|
+
});
|
|
342
374
|
if (entries.length === 0) return null;
|
|
343
375
|
const byCli = {};
|
|
344
376
|
const byMode = {};
|
|
@@ -6,7 +6,11 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { Logger } from '../../Logger.js';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
renderFanOutEvidence,
|
|
11
|
+
renderFanOutRemedy,
|
|
12
|
+
renderHardConflictError,
|
|
13
|
+
} from '../ticket-validator-conflicts.js';
|
|
10
14
|
|
|
11
15
|
/**
|
|
12
16
|
* @param {object[]} findings
|
|
@@ -24,8 +28,9 @@ export function enforceFanOutGate(
|
|
|
24
28
|
for (const f of fanOut) {
|
|
25
29
|
Logger.warn(
|
|
26
30
|
`[${tag}] Persisting a large-fan-out deletion: ` +
|
|
27
|
-
`
|
|
28
|
-
`
|
|
31
|
+
`Story "${f.storySlug}" deletes "${f.path}" with ${f.callSiteCount} ` +
|
|
32
|
+
`importer(s) (threshold ${f.threshold}). Operator override --allow-large-fan-out.` +
|
|
33
|
+
renderFanOutEvidence(f),
|
|
29
34
|
);
|
|
30
35
|
}
|
|
31
36
|
return;
|
|
@@ -33,13 +38,18 @@ export function enforceFanOutGate(
|
|
|
33
38
|
const lines = fanOut
|
|
34
39
|
.map(
|
|
35
40
|
(f) =>
|
|
36
|
-
` -
|
|
41
|
+
` - Story "${f.storySlug}" deletes "${f.path}" — ` +
|
|
42
|
+
`${f.callSiteCount} importer(s) (threshold ${f.threshold})` +
|
|
43
|
+
renderFanOutEvidence(f),
|
|
37
44
|
)
|
|
38
45
|
.join('\n');
|
|
46
|
+
// Each finding carries its own remedy — a rename-shaped deletion has no
|
|
47
|
+
// subsystems to split across, so a blanket "split it up" footer would be
|
|
48
|
+
// wrong advice for it (Story #4547).
|
|
49
|
+
const remedies = [...new Set(fanOut.map((f) => renderFanOutRemedy(f)))];
|
|
39
50
|
throw new Error(
|
|
40
51
|
`[${tag}] ${fanOut.length} Task(s) declare large-fan-out deletions:\n${lines}\n\n` +
|
|
41
|
-
|
|
42
|
-
`or rerun --allow-large-fan-out after confirming the deletion is intentional.`,
|
|
52
|
+
remedies.join('\n\n'),
|
|
43
53
|
);
|
|
44
54
|
}
|
|
45
55
|
|
|
@@ -2,16 +2,19 @@
|
|
|
2
2
|
* persist-helpers.js — pure helper surface for the flat Story `/plan` persist.
|
|
3
3
|
*
|
|
4
4
|
* Exports:
|
|
5
|
+
* - `resolveBaseBranchRef(config)` — the one place the persist gates learn
|
|
6
|
+
* which ref to probe.
|
|
5
7
|
* - `validateTickets(tickets, config)` — runs the cross-link, model-capacity,
|
|
6
8
|
* freshness, and task-body validators in one pass. Capacity settings are
|
|
7
9
|
* explicit inputs so the validator and decomposer share one live delivery
|
|
8
10
|
* envelope instead of silently falling back to framework defaults.
|
|
9
|
-
* - `makeDefaultFanOutCounter({ baseBranchRef, cwd })` — production
|
|
11
|
+
* - `makeDefaultFanOutCounter({ baseBranchRef, cwd, git })` — production
|
|
10
12
|
* fan-out probe used by the conflict policy.
|
|
11
13
|
*
|
|
12
14
|
* @module lib/orchestration/plan-persist/persist-helpers
|
|
13
15
|
*/
|
|
14
16
|
|
|
17
|
+
import posix from 'node:path/posix';
|
|
15
18
|
import { resolveListValue } from '../../config/shared.js';
|
|
16
19
|
import { gitSpawn } from '../../git-utils.js';
|
|
17
20
|
import { validateTaskBodies } from '../task-body-validator.js';
|
|
@@ -19,32 +22,156 @@ import { validateAndNormalizeTickets } from '../ticket-validator.js';
|
|
|
19
22
|
import { DEFAULT_REGISTRY_PATTERNS } from '../ticket-validator-conflicts.js';
|
|
20
23
|
|
|
21
24
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
* Extensions an import specifier may elide. Probed longest-path-first when
|
|
26
|
+
* resolving an extensionless specifier back onto a concrete repo path.
|
|
27
|
+
*/
|
|
28
|
+
const RESOLVABLE_EXTENSIONS = ['', '.js', '.mjs', '.cjs', '.jsx', '.json'];
|
|
29
|
+
|
|
30
|
+
/** Every quoted string on a candidate line — the specifier lives in one. */
|
|
31
|
+
const QUOTED_RE = /['"]([^'"\n]+)['"]/g;
|
|
32
|
+
|
|
33
|
+
function escapeRegExp(value) {
|
|
34
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The specifier tails an importer of `path` could plausibly write as the
|
|
39
|
+
* final segment of its quoted specifier: the basename, the basename minus
|
|
40
|
+
* its extension, and — for a directory-index module — the directory name
|
|
41
|
+
* (`./foo` resolving to `foo/index.js`).
|
|
42
|
+
*
|
|
43
|
+
* This is a *candidate* net only. It is deliberately generous because the
|
|
44
|
+
* resolution pass below re-checks every hit against the real path; a tail
|
|
45
|
+
* that over-matches costs one extra resolve, a tail that under-matches
|
|
46
|
+
* loses a genuine importer.
|
|
47
|
+
*/
|
|
48
|
+
function specifierTails(path) {
|
|
49
|
+
const base = posix.basename(path);
|
|
50
|
+
const ext = posix.extname(base);
|
|
51
|
+
const stem = ext ? base.slice(0, -ext.length) : base;
|
|
52
|
+
const tails = new Set([base, stem]);
|
|
53
|
+
if (stem === 'index') {
|
|
54
|
+
const dir = posix.basename(posix.dirname(path));
|
|
55
|
+
if (dir && dir !== '.') tails.add(dir);
|
|
56
|
+
}
|
|
57
|
+
return [...tails].filter((t) => t.length > 0);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* ERE matching a line that carries an `import` / `export … from` /
|
|
62
|
+
* `require(` / dynamic `import(` whose quoted specifier *ends* at one of
|
|
63
|
+
* `tails`. The `(^|/)`-equivalent guard (`([^'"]*\/)?`) is what keeps
|
|
64
|
+
* `notification.js` from matching `push-notification.js`.
|
|
65
|
+
*/
|
|
66
|
+
function buildProbePattern(tails) {
|
|
67
|
+
const alt = tails.map(escapeRegExp).join('|');
|
|
68
|
+
return `(from|require|import)[[:space:]]*\\(?[[:space:]]*['"]([^'"]*/)?(${alt})['"]`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Resolve a relative specifier written in `importerPath` back onto a repo
|
|
73
|
+
* path and report whether it names `deletedPath`.
|
|
74
|
+
*
|
|
75
|
+
* **Known boundary:** only relative (`./`, `../`) specifiers resolve. A
|
|
76
|
+
* consumer repo that imports its own modules through bare specifiers or a
|
|
77
|
+
* path alias (`#lib/x`, `@app/x`, a `tsconfig` `paths` entry) would
|
|
78
|
+
* under-count, because resolving those needs the resolver config this probe
|
|
79
|
+
* deliberately does not read. Mandrel's own internal imports are all
|
|
80
|
+
* relative. Under-counting is the *quiet* failure direction — it argues for
|
|
81
|
+
* a deletion rather than against one — so if alias-importing consumers
|
|
82
|
+
* appear, this is the place to teach the probe their resolver.
|
|
83
|
+
*/
|
|
84
|
+
function specifierResolvesTo(importerPath, specifier, deletedPath) {
|
|
85
|
+
if (!specifier.startsWith('./') && !specifier.startsWith('../')) return false;
|
|
86
|
+
const resolved = posix.normalize(
|
|
87
|
+
posix.join(posix.dirname(importerPath), specifier),
|
|
88
|
+
);
|
|
89
|
+
for (const ext of RESOLVABLE_EXTENSIONS) {
|
|
90
|
+
if (`${resolved}${ext}` === deletedPath) return true;
|
|
91
|
+
if (ext && `${resolved}/index${ext}` === deletedPath) return true;
|
|
92
|
+
}
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Quote one argv entry so the reported probe is **runnable as emitted**.
|
|
26
98
|
*
|
|
27
|
-
*
|
|
99
|
+
* The probe is the operator's route to checking the number, so it has to
|
|
100
|
+
* survive a paste into a shell. Unquoted, the ERE's `(`, `|`, `[[:space:]]`
|
|
101
|
+
* and `?` are glob/grouping metacharacters: zsh fails the paste with
|
|
102
|
+
* `no matches found` *and exits 0*, which reads as "zero importers" — the
|
|
103
|
+
* gate's own audit trail would then argue for the deletion it is meant to
|
|
104
|
+
* question (Story #4547).
|
|
28
105
|
*/
|
|
29
|
-
|
|
106
|
+
function shellQuote(value) {
|
|
107
|
+
if (/^[A-Za-z0-9_./-]+$/.test(value)) return value;
|
|
108
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Parse one `git grep -n` output line of the form `<ref>:<path>:<lineno>:<text>`.
|
|
113
|
+
*/
|
|
114
|
+
function parseGrepLine(line, baseBranchRef) {
|
|
115
|
+
const prefix = `${baseBranchRef}:`;
|
|
116
|
+
if (!line.startsWith(prefix)) return null;
|
|
117
|
+
const rest = line.slice(prefix.length);
|
|
118
|
+
const pathEnd = rest.indexOf(':');
|
|
119
|
+
if (pathEnd === -1) return null;
|
|
120
|
+
const path = rest.slice(0, pathEnd);
|
|
121
|
+
const afterPath = rest.slice(pathEnd + 1);
|
|
122
|
+
const lineEnd = afterPath.indexOf(':');
|
|
123
|
+
if (lineEnd === -1) return null;
|
|
124
|
+
return { path, text: afterPath.slice(lineEnd + 1) };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Default fan-out probe — resolves the *importers* of the deleted module at
|
|
129
|
+
* `baseBranchRef`, and reports the referencing files alongside the exact
|
|
130
|
+
* probe that produced them.
|
|
131
|
+
*
|
|
132
|
+
* Two-stage, because accuracy and cost pull in opposite directions:
|
|
133
|
+
*
|
|
134
|
+
* 1. `git grep -n -E` narrows the tree to lines whose quoted import /
|
|
135
|
+
* require specifier could name the module (final-segment match).
|
|
136
|
+
* 2. Each candidate specifier is resolved against its own importer's
|
|
137
|
+
* directory and compared to the deleted path. Only a real resolution
|
|
138
|
+
* counts.
|
|
139
|
+
*
|
|
140
|
+
* The predecessor (Story #2962) grepped the basename stem as a bare word
|
|
141
|
+
* across the whole tree, so a module named `notification` or `options`
|
|
142
|
+
* reported dozens of call sites drawn from prose, schemas, and unrelated
|
|
143
|
+
* modules — and the gate that fired on that number told the operator to
|
|
144
|
+
* split a migration that did not exist. It also returned 0 without probing
|
|
145
|
+
* for any stem under three characters, under-reporting in silence. Both are
|
|
146
|
+
* gone: coupling is measured by resolution, not vocabulary (Story #4547).
|
|
147
|
+
*
|
|
148
|
+
* @returns {(arg: { path: string }) => { count: number, files: string[], probe: string }}
|
|
149
|
+
*/
|
|
150
|
+
export function makeDefaultFanOutCounter({ baseBranchRef, cwd, git } = {}) {
|
|
151
|
+
const spawn = git?.gitSpawn ?? gitSpawn;
|
|
30
152
|
return ({ path }) => {
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
153
|
+
const tails = specifierTails(path);
|
|
154
|
+
const pattern = buildProbePattern(tails);
|
|
155
|
+
const args = ['grep', '-n', '-E', '--full-name', pattern, baseBranchRef];
|
|
156
|
+
const probe = `git ${args.map(shellQuote).join(' ')}`;
|
|
157
|
+
const result = spawn(cwd ?? process.cwd(), ...args);
|
|
158
|
+
// git grep exits 1 on "no matches" — an empty result, not a failure.
|
|
159
|
+
if (result.status !== 0) return { count: 0, files: [], probe };
|
|
160
|
+
const files = new Set();
|
|
161
|
+
for (const line of result.stdout.split('\n')) {
|
|
162
|
+
if (line.trim().length === 0) continue;
|
|
163
|
+
const hit = parseGrepLine(line, baseBranchRef);
|
|
164
|
+
// The deleted module's own self-references are not call sites.
|
|
165
|
+
if (!hit || hit.path === path) continue;
|
|
166
|
+
for (const match of hit.text.matchAll(QUOTED_RE)) {
|
|
167
|
+
if (specifierResolvesTo(hit.path, match[1], path)) {
|
|
168
|
+
files.add(hit.path);
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const sorted = [...files].sort();
|
|
174
|
+
return { count: sorted.length, files: sorted, probe };
|
|
48
175
|
};
|
|
49
176
|
}
|
|
50
177
|
|
|
@@ -72,8 +199,29 @@ function resolveConflictPolicy(cfg) {
|
|
|
72
199
|
return policy;
|
|
73
200
|
}
|
|
74
201
|
|
|
202
|
+
/**
|
|
203
|
+
* Resolve the ref the persist gates probe against.
|
|
204
|
+
*
|
|
205
|
+
* The canonical resolved config carries the base branch at
|
|
206
|
+
* `project.baseBranch` (`lib/config-resolver.js` defaults it to `main`).
|
|
207
|
+
* This helper used to read `config.baseBranch` — a key the resolver never
|
|
208
|
+
* produces — so every freshness / file-assumption / fan-out probe silently
|
|
209
|
+
* targeted the literal `main` regardless of configuration. Benign in a repo
|
|
210
|
+
* whose base branch *is* `main`; wrong for any consumer that configured
|
|
211
|
+
* something else (Story #4541).
|
|
212
|
+
*
|
|
213
|
+
* The flat `config.baseBranch` fallback is retained for the legacy
|
|
214
|
+
* `settings`-bag callers that pass `{ baseBranch, paths, planning }`.
|
|
215
|
+
*
|
|
216
|
+
* @param {object} [config] Resolved config, or a legacy settings bag.
|
|
217
|
+
* @returns {string}
|
|
218
|
+
*/
|
|
219
|
+
export function resolveBaseBranchRef(config) {
|
|
220
|
+
return config?.project?.baseBranch ?? config?.baseBranch ?? 'main';
|
|
221
|
+
}
|
|
222
|
+
|
|
75
223
|
export function validateTickets(tickets, config, opts = {}) {
|
|
76
|
-
const baseBranchRef = config
|
|
224
|
+
const baseBranchRef = resolveBaseBranchRef(config);
|
|
77
225
|
const conflictPolicy = resolveConflictPolicy(config);
|
|
78
226
|
if (typeof opts.fanOutCounter === 'function') {
|
|
79
227
|
conflictPolicy.fanOutCounter = opts.fanOutCounter;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* plan-context-source.js — locate and read the `plan-context.js` envelope so
|
|
3
|
+
* persist can derive the `/plan --tickets` source ids from the run that
|
|
4
|
+
* actually fetched them (Story #4554).
|
|
5
|
+
*
|
|
6
|
+
* This lives beside the persist ops rather than inside `plan-persist.js` so
|
|
7
|
+
* the discovery + failure policy is directly testable: the CLI is a thin
|
|
8
|
+
* `parseArgs` shell, and the interesting behaviour here is exactly the part
|
|
9
|
+
* that decides whether a `--tickets` run can quietly lose its source set.
|
|
10
|
+
*
|
|
11
|
+
* @module lib/orchestration/plan-persist/plan-context-source
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { readFile } from 'node:fs/promises';
|
|
15
|
+
import path from 'node:path';
|
|
16
|
+
|
|
17
|
+
import { Logger } from '../../Logger.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Filename the `/plan` interrogate step writes its envelope to inside
|
|
21
|
+
* `--plan-dir` (`plan-context.js --out <plan-dir>/plan-context.json`).
|
|
22
|
+
*/
|
|
23
|
+
export const PLAN_CONTEXT_FILENAME = 'plan-context.json';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Decide where to look for the envelope.
|
|
27
|
+
*
|
|
28
|
+
* An explicit `--plan-context` path wins; otherwise the conventional file
|
|
29
|
+
* inside `--plan-dir`. With neither, there is nothing to read — the caller
|
|
30
|
+
* gets `null` and must warn (see `loadPlanContextEnvelope`).
|
|
31
|
+
*
|
|
32
|
+
* @param {string|null|undefined} explicitPath `--plan-context`.
|
|
33
|
+
* @param {string|null|undefined} planDir `--plan-dir`.
|
|
34
|
+
* @returns {{ path: string, explicit: boolean }|null}
|
|
35
|
+
*/
|
|
36
|
+
export function resolvePlanContextPath(explicitPath, planDir) {
|
|
37
|
+
if (explicitPath) {
|
|
38
|
+
return { path: path.resolve(explicitPath), explicit: true };
|
|
39
|
+
}
|
|
40
|
+
if (planDir) {
|
|
41
|
+
return {
|
|
42
|
+
path: path.join(path.resolve(planDir), PLAN_CONTEXT_FILENAME),
|
|
43
|
+
explicit: false,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The advice printed whenever persist has no envelope to derive ids from.
|
|
51
|
+
* Single-homed so the two no-envelope paths cannot drift apart.
|
|
52
|
+
*/
|
|
53
|
+
const CAPTURE_HINT =
|
|
54
|
+
'Re-run step 1 with `node .agents/scripts/plan-context.js … --out ' +
|
|
55
|
+
'<plan-dir>/plan-context.json` and pass --plan-dir, or pass ' +
|
|
56
|
+
'--source-tickets explicitly.';
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Read the `plan-context.js` envelope.
|
|
60
|
+
*
|
|
61
|
+
* Failure policy — the point is that a `--tickets` run can never *quietly*
|
|
62
|
+
* lose its source set, so every no-envelope path is audible:
|
|
63
|
+
*
|
|
64
|
+
* - **No path at all** (neither `--plan-dir` nor `--plan-context`): warn.
|
|
65
|
+
* Persist cannot tell a legitimate `--seed` run from a `--tickets` run
|
|
66
|
+
* whose envelope was never captured, so it says so rather than returning a
|
|
67
|
+
* silent `null`.
|
|
68
|
+
* - **Explicit `--plan-context` missing**: throw. The operator named a file
|
|
69
|
+
* and meant it.
|
|
70
|
+
* - **Auto-discovered file simply absent**: warn and degrade to
|
|
71
|
+
* `--source-tickets`. A `--seed` run legitimately has no envelope, so
|
|
72
|
+
* absence alone is not fatal.
|
|
73
|
+
* - **Present but unparseable**: throw either way. A corrupt envelope is not
|
|
74
|
+
* the same as no envelope, and reading it as "no source tickets" is exactly
|
|
75
|
+
* the vacuous pass this module exists to prevent.
|
|
76
|
+
*
|
|
77
|
+
* @param {{ path: string, explicit: boolean }|null} planContext
|
|
78
|
+
* @returns {Promise<object|null>} Parsed envelope, or null when absent.
|
|
79
|
+
*/
|
|
80
|
+
export async function loadPlanContextEnvelope(planContext) {
|
|
81
|
+
if (!planContext) {
|
|
82
|
+
Logger.warn(
|
|
83
|
+
'[plan-persist] no --plan-dir or --plan-context given, so no ' +
|
|
84
|
+
'plan-context envelope was read. If this was a `/plan --tickets` ' +
|
|
85
|
+
'run, its source tickets can only come from --source-tickets and ' +
|
|
86
|
+
`will NOT be closed otherwise. ${CAPTURE_HINT}`,
|
|
87
|
+
);
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let raw;
|
|
92
|
+
try {
|
|
93
|
+
raw = await readFile(planContext.path, 'utf8');
|
|
94
|
+
} catch (err) {
|
|
95
|
+
if (err?.code === 'ENOENT' && !planContext.explicit) {
|
|
96
|
+
Logger.warn(
|
|
97
|
+
`[plan-persist] no plan-context envelope at ${planContext.path} — ` +
|
|
98
|
+
'source tickets can only come from --source-tickets. ' +
|
|
99
|
+
CAPTURE_HINT,
|
|
100
|
+
);
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
throw new Error(
|
|
104
|
+
`Cannot read plan-context envelope ${planContext.path}: ${err.message}`,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
return JSON.parse(raw);
|
|
110
|
+
} catch (err) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
`Failed to parse plan-context envelope "${planContext.path}" as JSON: ` +
|
|
113
|
+
`${err.message}. ${CAPTURE_HINT}`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}
|