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
|
@@ -28,259 +28,111 @@
|
|
|
28
28
|
* critical-halt path so the Epic-scoped lifecycle ledger still sees
|
|
29
29
|
* the Story drop out.
|
|
30
30
|
*
|
|
31
|
-
*
|
|
32
|
-
* `
|
|
33
|
-
*
|
|
34
|
-
*
|
|
31
|
+
* The shared spine both close paths call `runCodeReview` through
|
|
32
|
+
* (`runStoryReviewCore`, Story #3653) and the shift-left local-lens pass
|
|
33
|
+
* (Epic #4405) live in `review-core.js` and `local-lens-review.js`
|
|
34
|
+
* respectively (extracted by Story #4603). This module is the Epic-attached
|
|
35
|
+
* phase entry point: it owns the advisory error posture and the
|
|
36
|
+
* critical-halt → blocked-envelope translation, and nothing else.
|
|
35
37
|
*/
|
|
36
38
|
|
|
37
|
-
import {
|
|
38
|
-
runAuditSuite,
|
|
39
|
-
selectLocalLenses,
|
|
40
|
-
} from '../../../audit-suite/index.js';
|
|
41
|
-
import { gitSpawn } from '../../../git-utils.js';
|
|
42
39
|
import { Logger } from '../../../Logger.js';
|
|
43
40
|
import { runCodeReview } from '../../code-review.js';
|
|
44
41
|
import { emitBlockedCloseResult } from '../emit-blocked.js';
|
|
42
|
+
import { runLocalLensReview } from './local-lens-review.js';
|
|
43
|
+
import { runStoryReviewCore } from './review-core.js';
|
|
45
44
|
|
|
46
45
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* the maker-blind Story-scope review runs its matched local lenses at `light`
|
|
50
|
-
* depth here rather than paying a deeper pass at Epic close. Fixed for this
|
|
51
|
-
* tier — it is not risk-scaled like the code-review pillar depth.
|
|
52
|
-
*/
|
|
53
|
-
export const STORY_SCOPE_LENS_DEPTH = 'light';
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Enumerate the files changed in the `baseRef...headRef` diff via
|
|
57
|
-
* `git diff --name-only`. Best-effort: returns `[]` when the diff cannot be
|
|
58
|
-
* enumerated (git failure, missing ref) and never throws, mirroring the
|
|
59
|
-
* advisory posture of the surrounding review phase. Synchronous `gitSpawn`
|
|
60
|
-
* (returns `{ status, stdout }`) is the same seam `code-review.js#countChangedFiles`
|
|
61
|
-
* uses.
|
|
46
|
+
* Read a review envelope's severity counts, tolerating the partial envelope a
|
|
47
|
+
* misbehaving provider adapter can return. Pure.
|
|
62
48
|
*
|
|
63
|
-
* @param {
|
|
64
|
-
* @returns {
|
|
49
|
+
* @param {object|null|undefined} reviewResult
|
|
50
|
+
* @returns {{ critical: number, high: number, medium: number, suggestion: number }}
|
|
65
51
|
*/
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
process.cwd(),
|
|
74
|
-
'diff',
|
|
75
|
-
'--name-only',
|
|
76
|
-
`${baseRef}...${headRef}`,
|
|
77
|
-
);
|
|
78
|
-
if (!result || result.status !== 0 || typeof result.stdout !== 'string') {
|
|
79
|
-
return [];
|
|
52
|
+
function resolveSeverity(reviewResult) {
|
|
53
|
+
return (
|
|
54
|
+
reviewResult?.severity ?? {
|
|
55
|
+
critical: 0,
|
|
56
|
+
high: 0,
|
|
57
|
+
medium: 0,
|
|
58
|
+
suggestion: 0,
|
|
80
59
|
}
|
|
81
|
-
|
|
82
|
-
.split('\n')
|
|
83
|
-
.map((f) => f.trim())
|
|
84
|
-
.filter(Boolean);
|
|
85
|
-
} catch {
|
|
86
|
-
return [];
|
|
87
|
-
}
|
|
60
|
+
);
|
|
88
61
|
}
|
|
89
62
|
|
|
90
63
|
/**
|
|
91
|
-
*
|
|
92
|
-
* `filePatterns` match the actual Story diff (`baseRef...headRef`) and
|
|
93
|
-
* materialize their lens-prompt bodies at `light` depth. This is the
|
|
94
|
-
* shift-left tier from Epic #4405 — it runs **inside** the story-close
|
|
95
|
-
* subprocess spine (called only from {@link runStoryReviewCore}), never in the
|
|
96
|
-
* delivering child's (maker's) context, so a maker never grades its own work.
|
|
97
|
-
*
|
|
98
|
-
* A diff that matches no local lens adds **no** lens work: the roster is empty
|
|
99
|
-
* and `runAuditSuite` is never invoked. Best-effort and total — a git or
|
|
100
|
-
* materialization failure degrades to `{ skipped: true, lenses: [] }` and is
|
|
101
|
-
* logged via `progress`, matching the advisory posture the review phase already
|
|
102
|
-
* takes for provider/transport failures.
|
|
64
|
+
* Collect the extra fields for the code-review-critical blocked envelope. Pure.
|
|
103
65
|
*
|
|
104
|
-
* @param {{
|
|
105
|
-
*
|
|
106
|
-
* headRef: string,
|
|
107
|
-
* progress: (tag: string, msg: string) => void,
|
|
108
|
-
* progressTag?: string,
|
|
109
|
-
* gitSpawnFn?: typeof gitSpawn,
|
|
110
|
-
* selectLocalLensesFn?: typeof selectLocalLenses,
|
|
111
|
-
* runAuditSuiteFn?: typeof runAuditSuite,
|
|
112
|
-
* }} args
|
|
113
|
-
* @returns {Promise<{
|
|
114
|
-
* depth: 'light',
|
|
115
|
-
* lenses: string[],
|
|
116
|
-
* skipped: boolean,
|
|
117
|
-
* materialized: object|null,
|
|
118
|
-
* }>}
|
|
119
|
-
*/
|
|
120
|
-
export async function runLocalLensReview({
|
|
121
|
-
baseRef,
|
|
122
|
-
headRef,
|
|
123
|
-
progress,
|
|
124
|
-
progressTag = 'CODE-REVIEW',
|
|
125
|
-
gitSpawnFn = gitSpawn,
|
|
126
|
-
selectLocalLensesFn = selectLocalLenses,
|
|
127
|
-
runAuditSuiteFn = runAuditSuite,
|
|
128
|
-
}) {
|
|
129
|
-
const empty = {
|
|
130
|
-
depth: STORY_SCOPE_LENS_DEPTH,
|
|
131
|
-
lenses: [],
|
|
132
|
-
skipped: true,
|
|
133
|
-
materialized: null,
|
|
134
|
-
};
|
|
135
|
-
let lenses;
|
|
136
|
-
try {
|
|
137
|
-
const changedFiles = enumerateChangedFiles({
|
|
138
|
-
baseRef,
|
|
139
|
-
headRef,
|
|
140
|
-
gitSpawnFn,
|
|
141
|
-
});
|
|
142
|
-
lenses = selectLocalLensesFn({ changedFiles });
|
|
143
|
-
if (lenses.length === 0) {
|
|
144
|
-
progress(
|
|
145
|
-
progressTag,
|
|
146
|
-
'No local lens matched the Story diff — skipping the lens pass.',
|
|
147
|
-
);
|
|
148
|
-
return empty;
|
|
149
|
-
}
|
|
150
|
-
const materialized = await runAuditSuiteFn({ auditWorkflows: lenses });
|
|
151
|
-
progress(
|
|
152
|
-
progressTag,
|
|
153
|
-
`Ran ${lenses.length} local lens(es) at ${STORY_SCOPE_LENS_DEPTH} depth: ${lenses.join(', ')}.`,
|
|
154
|
-
);
|
|
155
|
-
return {
|
|
156
|
-
depth: STORY_SCOPE_LENS_DEPTH,
|
|
157
|
-
lenses,
|
|
158
|
-
skipped: false,
|
|
159
|
-
materialized,
|
|
160
|
-
};
|
|
161
|
-
} catch (err) {
|
|
162
|
-
// The lens pass is advisory: a git or materialization failure must not
|
|
163
|
-
// fail the close. Log and degrade to a skipped envelope.
|
|
164
|
-
progress(
|
|
165
|
-
progressTag,
|
|
166
|
-
`⚠️ local lens pass failed (continuing without it): ${err?.message ?? err}`,
|
|
167
|
-
);
|
|
168
|
-
return empty;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Collect the extra fields for the code-review-critical blocked envelope.
|
|
174
|
-
* Pure; used by `runStoryCodeReview` to populate the `extra` argument of
|
|
175
|
-
* `emitBlockedCloseResult`.
|
|
66
|
+
* @param {{ storyId: number, reviewResult: object }} args
|
|
67
|
+
* @returns {object}
|
|
176
68
|
*/
|
|
177
69
|
function buildCodeReviewBlockedExtra({ storyId, reviewResult }) {
|
|
178
|
-
const severity = reviewResult?.severity ?? {
|
|
179
|
-
critical: 0,
|
|
180
|
-
high: 0,
|
|
181
|
-
medium: 0,
|
|
182
|
-
suggestion: 0,
|
|
183
|
-
};
|
|
184
70
|
return {
|
|
185
|
-
storyId
|
|
71
|
+
storyId,
|
|
186
72
|
blockerReason: reviewResult?.blockerReason ?? null,
|
|
187
|
-
severity,
|
|
73
|
+
severity: resolveSeverity(reviewResult),
|
|
188
74
|
posted: reviewResult?.posted ?? false,
|
|
189
75
|
exitCode: 1,
|
|
190
76
|
};
|
|
191
77
|
}
|
|
192
78
|
|
|
193
79
|
/**
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
* (`runStoryCodeReview`) and the standalone close path
|
|
197
|
-
* (`single-story-close/phases/code-review.js#runStoryScopeReview`) so the
|
|
198
|
-
* invocation pattern lives in one place (Story #3653).
|
|
199
|
-
*
|
|
200
|
-
* The caller is responsible for error handling and result interpretation —
|
|
201
|
-
* this function propagates throws rather than swallowing them, because the
|
|
202
|
-
* two callers have different advisory postures:
|
|
80
|
+
* Render the operator-facing one-line summary of a completed (non-halting)
|
|
81
|
+
* review. Pure.
|
|
203
82
|
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
|
|
83
|
+
* @param {object} reviewResult
|
|
84
|
+
* @returns {string}
|
|
85
|
+
*/
|
|
86
|
+
function formatReviewSummary(reviewResult) {
|
|
87
|
+
const { high, medium, suggestion } = resolveSeverity(reviewResult);
|
|
88
|
+
const posted = reviewResult?.posted ?? false;
|
|
89
|
+
return `Review complete — high=${high} medium=${medium} suggestion=${suggestion} (posted=${posted}).`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Run the shared review spine, absorbing an adapter / wiring failure into this
|
|
94
|
+
* phase's advisory posture: the review is best-effort when the provider cannot
|
|
95
|
+
* complete, and the gates already vouched for the diff at this point.
|
|
207
96
|
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
97
|
+
* @param {object} args Spine arguments (see {@link runStoryReviewCore}).
|
|
98
|
+
* @returns {Promise<object|null>} The review envelope, or `null` when the
|
|
99
|
+
* review threw and the close should proceed unblocked.
|
|
100
|
+
*/
|
|
101
|
+
async function invokeReviewCore(args) {
|
|
102
|
+
try {
|
|
103
|
+
return await runStoryReviewCore(args);
|
|
104
|
+
} catch (err) {
|
|
105
|
+
Logger.warn?.(
|
|
106
|
+
`[story-close] ⚠️ code-review phase failed (continuing without blocker): ${err?.message ?? err}`,
|
|
107
|
+
);
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Translate a halting (critical-findings) review into the blocked envelope the
|
|
114
|
+
* caller returns verbatim, emitting `story.blocked` onto the bus on the way.
|
|
217
115
|
*
|
|
218
116
|
* @param {{
|
|
219
|
-
* storyId: number
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
* commentTargetId?: number|null,
|
|
223
|
-
* provider: object,
|
|
117
|
+
* storyId: number,
|
|
118
|
+
* reviewResult: object,
|
|
119
|
+
* bus: { emit: Function }|null,
|
|
224
120
|
* progress: (tag: string, msg: string) => void,
|
|
225
|
-
* progressTag?: string,
|
|
226
|
-
* planningRisk?: { overallLevel?: ('low'|'medium'|'high'), axes?: Array<{ axis?: string, level?: string }> }|null,
|
|
227
|
-
* runCodeReviewFn?: typeof runCodeReview,
|
|
228
|
-
* runLocalLensReviewFn?: typeof runLocalLensReview,
|
|
229
121
|
* }} args
|
|
230
|
-
* @returns {Promise<object>}
|
|
231
|
-
* with a `localLensReview` field carrying the Story-scope local-lens pass
|
|
232
|
-
* outcome (Epic #4405, Story #4409). Both close entry points reach the lens
|
|
233
|
-
* pass through this single spine, so it runs on the Epic-attached and
|
|
234
|
-
* standalone paths alike and always inside the close subprocess.
|
|
122
|
+
* @returns {Promise<object>} The blocked envelope.
|
|
235
123
|
*/
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
planningRisk = null,
|
|
245
|
-
runCodeReviewFn = runCodeReview,
|
|
246
|
-
runLocalLensReviewFn = runLocalLensReview,
|
|
247
|
-
}) {
|
|
248
|
-
const storyIdNum = Number(storyId);
|
|
249
|
-
const opts = {
|
|
250
|
-
scope: 'story',
|
|
251
|
-
ticketId: storyIdNum,
|
|
252
|
-
baseRef,
|
|
253
|
-
headRef,
|
|
254
|
-
provider,
|
|
255
|
-
logger: {
|
|
256
|
-
info: (m) => progress(progressTag, m),
|
|
257
|
-
warn: (m) => progress(progressTag, `⚠️ ${m}`),
|
|
258
|
-
},
|
|
259
|
-
};
|
|
260
|
-
if (commentTargetId != null) {
|
|
261
|
-
opts.commentTargetId = commentTargetId;
|
|
262
|
-
}
|
|
263
|
-
// Forward the parent Epic's judged risk only on the Epic-attached path; the
|
|
264
|
-
// standalone caller leaves this null so `runCodeReview` resolves depth from
|
|
265
|
-
// diff width alone (no plan checkpoint exists for a standalone Story).
|
|
266
|
-
if (planningRisk != null) {
|
|
267
|
-
opts.planningRisk = planningRisk;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Shift-left local-lens pass (Epic #4405). Runs matched local lenses at
|
|
271
|
-
// `light` depth against the actual Story diff, inside this close-subprocess
|
|
272
|
-
// spine so the maker never grades its own work. Advisory — it never blocks
|
|
273
|
-
// the close and its outcome rides on the returned envelope for downstream
|
|
274
|
-
// consumers.
|
|
275
|
-
const localLensReview = await runLocalLensReviewFn({
|
|
276
|
-
baseRef,
|
|
277
|
-
headRef,
|
|
124
|
+
async function emitCriticalBlock({ storyId, reviewResult, bus, progress }) {
|
|
125
|
+
const { critical } = resolveSeverity(reviewResult);
|
|
126
|
+
return emitBlockedCloseResult({
|
|
127
|
+
storyId,
|
|
128
|
+
phase: 'closing',
|
|
129
|
+
reason: 'code-review-critical',
|
|
130
|
+
extra: buildCodeReviewBlockedExtra({ storyId, reviewResult }),
|
|
131
|
+
bus,
|
|
278
132
|
progress,
|
|
279
|
-
|
|
133
|
+
blockedMessage: `Story #${storyId} blocked: code-review reported ${critical} critical blocker(s).`,
|
|
134
|
+
logger: Logger,
|
|
280
135
|
});
|
|
281
|
-
|
|
282
|
-
const result = await runCodeReviewFn(opts);
|
|
283
|
-
return { ...result, localLensReview };
|
|
284
136
|
}
|
|
285
137
|
|
|
286
138
|
/**
|
|
@@ -291,23 +143,19 @@ export async function runStoryReviewCore({
|
|
|
291
143
|
* `blocked` is either `null` (caller proceeds to open/merge the PR) or the
|
|
292
144
|
* blocked-envelope (caller returns it verbatim and the CLI exits 1).
|
|
293
145
|
*
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
*
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
* high-risk Epic still earns `deep`, a small Story under a low-risk Epic gets
|
|
300
|
-
* `light`, and an absent envelope resolves `standard` (today's behaviour).
|
|
301
|
-
* Depth is input-only: it never changes `{ blocked }` or the posted comment.
|
|
146
|
+
* Review depth is derived inside `runCodeReview` from this Story's own base…head
|
|
147
|
+
* diff — a narrow change touching a registered sensitive path earns `deep`, a
|
|
148
|
+
* small change touching none gets `light`, a wide diff earns `deep` on size, and
|
|
149
|
+
* an unenumerable diff resolves `standard` (Story #4542). Depth is input-only:
|
|
150
|
+
* it never changes `{ blocked }` or the posted comment.
|
|
302
151
|
*
|
|
303
152
|
* @param {{
|
|
304
153
|
* storyId: number|string,
|
|
305
|
-
*
|
|
154
|
+
* baseBranch: string,
|
|
306
155
|
* storyBranch: string,
|
|
307
156
|
* provider: object,
|
|
308
157
|
* bus: { emit: Function }|null,
|
|
309
158
|
* progress: (tag: string, msg: string) => void,
|
|
310
|
-
* planningRisk?: { overallLevel?: ('low'|'medium'|'high'), axes?: Array<{ axis?: string, level?: string }> }|null,
|
|
311
159
|
* runCodeReviewFn?: typeof runCodeReview,
|
|
312
160
|
* runLocalLensReviewFn?: typeof runLocalLensReview,
|
|
313
161
|
* }} args
|
|
@@ -316,67 +164,44 @@ export async function runStoryReviewCore({
|
|
|
316
164
|
* (Epic #4405, Story #4409) when the review completed; it is absent only when
|
|
317
165
|
* the whole review phase threw (advisory failure).
|
|
318
166
|
*/
|
|
319
|
-
export async function runStoryCodeReview(
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
runLocalLensReviewFn = runLocalLensReview,
|
|
330
|
-
} = args;
|
|
331
|
-
|
|
167
|
+
export async function runStoryCodeReview({
|
|
168
|
+
storyId,
|
|
169
|
+
baseBranch,
|
|
170
|
+
storyBranch,
|
|
171
|
+
provider,
|
|
172
|
+
bus,
|
|
173
|
+
progress,
|
|
174
|
+
runCodeReviewFn = runCodeReview,
|
|
175
|
+
runLocalLensReviewFn = runLocalLensReview,
|
|
176
|
+
}) {
|
|
332
177
|
const storyIdNum = Number(storyId);
|
|
333
178
|
progress(
|
|
334
179
|
'CODE-REVIEW',
|
|
335
|
-
`Running Story-scope review (${
|
|
180
|
+
`Running Story-scope review (${baseBranch}…${storyBranch})...`,
|
|
336
181
|
);
|
|
337
182
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
runLocalLensReviewFn,
|
|
349
|
-
});
|
|
350
|
-
} catch (err) {
|
|
351
|
-
// Adapter / wiring failure — log and proceed. The review is advisory
|
|
352
|
-
// when the provider cannot complete; the gates already vouched for
|
|
353
|
-
// the diff at this point.
|
|
354
|
-
Logger.warn?.(
|
|
355
|
-
`[story-close] ⚠️ code-review phase failed (continuing without blocker): ${err?.message ?? err}`,
|
|
356
|
-
);
|
|
357
|
-
return { blocked: null };
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
const localLensReview = reviewResult?.localLensReview;
|
|
183
|
+
const reviewResult = await invokeReviewCore({
|
|
184
|
+
storyId: storyIdNum,
|
|
185
|
+
baseRef: baseBranch,
|
|
186
|
+
headRef: storyBranch,
|
|
187
|
+
provider,
|
|
188
|
+
progress,
|
|
189
|
+
runCodeReviewFn,
|
|
190
|
+
runLocalLensReviewFn,
|
|
191
|
+
});
|
|
192
|
+
if (reviewResult === null) return { blocked: null };
|
|
361
193
|
|
|
362
|
-
|
|
363
|
-
|
|
194
|
+
const localLensReview = reviewResult.localLensReview;
|
|
195
|
+
if (reviewResult.halted) {
|
|
196
|
+
const blocked = await emitCriticalBlock({
|
|
364
197
|
storyId: storyIdNum,
|
|
365
|
-
|
|
366
|
-
reason: 'code-review-critical',
|
|
367
|
-
extra: buildCodeReviewBlockedExtra({ storyId: storyIdNum, reviewResult }),
|
|
198
|
+
reviewResult,
|
|
368
199
|
bus,
|
|
369
200
|
progress,
|
|
370
|
-
blockedMessage: `Story #${storyIdNum} blocked: code-review reported ${reviewResult.severity.critical} critical blocker(s).`,
|
|
371
|
-
logger: Logger,
|
|
372
201
|
});
|
|
373
202
|
return { blocked, localLensReview };
|
|
374
203
|
}
|
|
375
204
|
|
|
376
|
-
|
|
377
|
-
progress(
|
|
378
|
-
'CODE-REVIEW',
|
|
379
|
-
`Review complete — high=${counts.high ?? 0} medium=${counts.medium ?? 0} suggestion=${counts.suggestion ?? 0} (posted=${reviewResult?.posted ?? false}).`,
|
|
380
|
-
);
|
|
205
|
+
progress('CODE-REVIEW', formatReviewSummary(reviewResult));
|
|
381
206
|
return { blocked: null, localLensReview };
|
|
382
207
|
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* phases/local-lens-review.js — the Story-scope local-lens pass.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from `phases/code-review.js` (Story #4603) so the review spine and
|
|
5
|
+
* the lens pass each carry one reason to change. The spine
|
|
6
|
+
* (`runStoryReviewCore`) owns the single per-close-run diff enumeration and the
|
|
7
|
+
* `runCodeReview` invocation; this module owns lens selection + materialization.
|
|
8
|
+
*
|
|
9
|
+
* Shift-left tier (Epic #4405): local concerns are cheap to decide on a single
|
|
10
|
+
* Story's diff, so the maker-blind Story-scope review runs its matched local
|
|
11
|
+
* lenses here, inside the story-close subprocess, rather than paying a deeper
|
|
12
|
+
* pass at Epic close.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
runAuditSuite,
|
|
17
|
+
selectLocalLenses,
|
|
18
|
+
} from '../../../audit-suite/index.js';
|
|
19
|
+
import { gitSpawn } from '../../../git-utils.js';
|
|
20
|
+
import { computeChangeSet } from '../../change-set.js';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The review depth the Story-scope local-lens pass runs at. Fixed for this
|
|
24
|
+
* tier — it is not risk-scaled like the code-review pillar depth.
|
|
25
|
+
*
|
|
26
|
+
* Module-local: it is an implementation detail of {@link runLocalLensReview}
|
|
27
|
+
* (it rides out on the returned envelope's `depth` field), not a public seam.
|
|
28
|
+
* Tests assert the observable `'light'` on that envelope rather than importing
|
|
29
|
+
* the constant, so it stays off the public surface (Story #4603).
|
|
30
|
+
*/
|
|
31
|
+
const STORY_SCOPE_LENS_DEPTH = 'light';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Enumerate the files changed in the `baseRef...headRef` diff. Thin adapter over
|
|
35
|
+
* the shared {@link computeChangeSet} enumerator (Story #4593) that flattens its
|
|
36
|
+
* `files: string[]|null` envelope to this phase's historical `[]`-on-failure
|
|
37
|
+
* contract: the lens roster treats "nothing changed" and "diff unknown"
|
|
38
|
+
* identically, because an unknown diff matches no `filePatterns` and therefore
|
|
39
|
+
* adds no lens work either way.
|
|
40
|
+
*
|
|
41
|
+
* Best-effort and total — never throws, mirroring the advisory posture of the
|
|
42
|
+
* surrounding review phase. Retained as the self-enumeration fallback for
|
|
43
|
+
* {@link runLocalLensReview} when no change set is injected.
|
|
44
|
+
*
|
|
45
|
+
* @param {{
|
|
46
|
+
* baseRef: string,
|
|
47
|
+
* headRef: string,
|
|
48
|
+
* gitSpawnFn?: import('../../change-set.js').GitSpawnFn,
|
|
49
|
+
* }} args
|
|
50
|
+
* Module-local (Story #4603): a private fallback of {@link runLocalLensReview},
|
|
51
|
+
* exercised through that public entry point rather than imported directly, so it
|
|
52
|
+
* adds no public export a production path fails to reach.
|
|
53
|
+
*
|
|
54
|
+
* @returns {string[]} Changed file paths, or `[]` on any failure.
|
|
55
|
+
*/
|
|
56
|
+
function enumerateChangedFiles({ baseRef, headRef, gitSpawnFn = gitSpawn }) {
|
|
57
|
+
return computeChangeSet({ baseRef, headRef, gitSpawnFn }).files ?? [];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Resolve the change set the lens roster reads, honouring all THREE injection
|
|
62
|
+
* states (Story #4603 — the fix for #4593's single-enumeration leak).
|
|
63
|
+
*
|
|
64
|
+
* The distinction between `null` and `undefined` is load-bearing and mirrors
|
|
65
|
+
* the sibling contract in `orchestration/code-review.js#resolveInjectedChangedFiles`:
|
|
66
|
+
*
|
|
67
|
+
* - **array** — the caller's change set; use it verbatim.
|
|
68
|
+
* - **`null`** — the caller (`runStoryReviewCore`) already tried and the
|
|
69
|
+
* diff is unenumerable. Re-running git here would only fail
|
|
70
|
+
* again, so degrade straight to the fail-safe empty roster.
|
|
71
|
+
* - **`undefined`** — nobody enumerated (standalone callers), so the shared
|
|
72
|
+
* enumerator runs as the fallback.
|
|
73
|
+
*
|
|
74
|
+
* The prior `Array.isArray()` discriminator collapsed `null` and `undefined`
|
|
75
|
+
* into one branch and re-spawned git on the unenumerable path, contradicting the
|
|
76
|
+
* spine's documented "the one enumeration per close run" invariant.
|
|
77
|
+
*
|
|
78
|
+
* Module-local (Story #4603): a private detail of {@link runLocalLensReview}.
|
|
79
|
+
* The three-state contract is asserted through that public entry point (does an
|
|
80
|
+
* injected `null` re-spawn git? does `undefined` self-enumerate?), so it needs
|
|
81
|
+
* no public export — keeping the fix from re-introducing the very kind of
|
|
82
|
+
* production-dead public symbol this Story's ratchet root-cause is about.
|
|
83
|
+
*
|
|
84
|
+
* @param {{
|
|
85
|
+
* changedFiles: string[]|null|undefined,
|
|
86
|
+
* baseRef: string,
|
|
87
|
+
* headRef: string,
|
|
88
|
+
* gitSpawnFn?: import('../../change-set.js').GitSpawnFn,
|
|
89
|
+
* }} args
|
|
90
|
+
* @returns {string[]}
|
|
91
|
+
*/
|
|
92
|
+
function resolveLensChangeSet({
|
|
93
|
+
changedFiles,
|
|
94
|
+
baseRef,
|
|
95
|
+
headRef,
|
|
96
|
+
gitSpawnFn = gitSpawn,
|
|
97
|
+
}) {
|
|
98
|
+
if (changedFiles === undefined) {
|
|
99
|
+
return enumerateChangedFiles({ baseRef, headRef, gitSpawnFn });
|
|
100
|
+
}
|
|
101
|
+
return changedFiles ?? [];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Run the Story-scope local-lens pass: select the LOCAL-tier lenses whose
|
|
106
|
+
* `filePatterns` match the actual Story diff (`baseRef...headRef`) and
|
|
107
|
+
* materialize their lens-prompt bodies at `light` depth. Called only from
|
|
108
|
+
* `runStoryReviewCore`, never in the delivering child's (maker's) context, so a
|
|
109
|
+
* maker never grades its own work.
|
|
110
|
+
*
|
|
111
|
+
* A diff that matches no local lens adds **no** lens work: the roster is empty
|
|
112
|
+
* and `runAuditSuite` is never invoked. Best-effort and total — a git or
|
|
113
|
+
* materialization failure degrades to `{ skipped: true, lenses: [] }` and is
|
|
114
|
+
* logged via `progress`, matching the advisory posture the review phase already
|
|
115
|
+
* takes for provider/transport failures.
|
|
116
|
+
*
|
|
117
|
+
* Story #4593 — `changedFiles` is injected by `runStoryReviewCore`, which
|
|
118
|
+
* computes the change set once per close run and hands the same list to this
|
|
119
|
+
* pass and to `runCodeReview`. Self-enumeration is the **fallback only**, kept
|
|
120
|
+
* for standalone callers that supply no list; see {@link resolveLensChangeSet}
|
|
121
|
+
* for the three-state contract.
|
|
122
|
+
*
|
|
123
|
+
* @param {{
|
|
124
|
+
* baseRef: string,
|
|
125
|
+
* headRef: string,
|
|
126
|
+
* changedFiles?: string[]|null,
|
|
127
|
+
* progress: (tag: string, msg: string) => void,
|
|
128
|
+
* progressTag?: string,
|
|
129
|
+
* gitSpawnFn?: import('../../change-set.js').GitSpawnFn,
|
|
130
|
+
* selectLocalLensesFn?: typeof selectLocalLenses,
|
|
131
|
+
* runAuditSuiteFn?: typeof runAuditSuite,
|
|
132
|
+
* }} args
|
|
133
|
+
* @returns {Promise<{
|
|
134
|
+
* depth: 'light',
|
|
135
|
+
* lenses: string[],
|
|
136
|
+
* skipped: boolean,
|
|
137
|
+
* materialized: object|null,
|
|
138
|
+
* }>}
|
|
139
|
+
*/
|
|
140
|
+
export async function runLocalLensReview({
|
|
141
|
+
baseRef,
|
|
142
|
+
headRef,
|
|
143
|
+
changedFiles: injectedChangedFiles,
|
|
144
|
+
progress,
|
|
145
|
+
progressTag = 'CODE-REVIEW',
|
|
146
|
+
gitSpawnFn = gitSpawn,
|
|
147
|
+
selectLocalLensesFn = selectLocalLenses,
|
|
148
|
+
runAuditSuiteFn = runAuditSuite,
|
|
149
|
+
}) {
|
|
150
|
+
const empty = {
|
|
151
|
+
depth: STORY_SCOPE_LENS_DEPTH,
|
|
152
|
+
lenses: [],
|
|
153
|
+
skipped: true,
|
|
154
|
+
materialized: null,
|
|
155
|
+
};
|
|
156
|
+
try {
|
|
157
|
+
const changedFiles = resolveLensChangeSet({
|
|
158
|
+
changedFiles: injectedChangedFiles,
|
|
159
|
+
baseRef,
|
|
160
|
+
headRef,
|
|
161
|
+
gitSpawnFn,
|
|
162
|
+
});
|
|
163
|
+
const lenses = selectLocalLensesFn({ changedFiles });
|
|
164
|
+
if (lenses.length === 0) {
|
|
165
|
+
progress(
|
|
166
|
+
progressTag,
|
|
167
|
+
'No local lens matched the Story diff — skipping the lens pass.',
|
|
168
|
+
);
|
|
169
|
+
return empty;
|
|
170
|
+
}
|
|
171
|
+
const materialized = await runAuditSuiteFn({ auditWorkflows: lenses });
|
|
172
|
+
progress(
|
|
173
|
+
progressTag,
|
|
174
|
+
`Ran ${lenses.length} local lens(es) at ${STORY_SCOPE_LENS_DEPTH} depth: ${lenses.join(', ')}.`,
|
|
175
|
+
);
|
|
176
|
+
return {
|
|
177
|
+
depth: STORY_SCOPE_LENS_DEPTH,
|
|
178
|
+
lenses,
|
|
179
|
+
skipped: false,
|
|
180
|
+
materialized,
|
|
181
|
+
};
|
|
182
|
+
} catch (err) {
|
|
183
|
+
// The lens pass is advisory: a git or materialization failure must not
|
|
184
|
+
// fail the close. Log and degrade to a skipped envelope.
|
|
185
|
+
progress(
|
|
186
|
+
progressTag,
|
|
187
|
+
`⚠️ local lens pass failed (continuing without it): ${err?.message ?? err}`,
|
|
188
|
+
);
|
|
189
|
+
return empty;
|
|
190
|
+
}
|
|
191
|
+
}
|