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
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* acceptance-eval.js — bounded per-Story acceptance self-eval gate (Story #3819).
|
|
5
5
|
*
|
|
6
6
|
* The Story-implementation phase runs an independent (fresh-context)
|
|
7
|
-
* critic pass that scores the
|
|
8
|
-
* `acceptance[]` item, emitting one verdict file per round
|
|
7
|
+
* critic pass that scores the caller-injected change set against each
|
|
8
|
+
* inline `acceptance[]` item, emitting one verdict file per round
|
|
9
9
|
* (`.agents/schemas/acceptance-eval-verdict.schema.json`). This CLI is the
|
|
10
10
|
* deterministic substrate that turns that verdict into the loop's next
|
|
11
11
|
* action:
|
|
@@ -32,26 +32,20 @@
|
|
|
32
32
|
* — this CLI is the decision + signal boundary, mirroring how the existing
|
|
33
33
|
* gates separate decision from ticket mutation.
|
|
34
34
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* with NO `--story`. The verdict scores one AC cluster of the Epic's
|
|
40
|
-
* `## Acceptance Table` against the cumulative `main..epic/<id>` diff;
|
|
41
|
-
* round scoping is per cluster off the Epic's `signals.ndjson`. This is
|
|
42
|
-
* the acceptance-dilution guard — `ceil(totalACs / clusterCeiling)`
|
|
43
|
-
* independent maker-blind critic passes, one per cluster.
|
|
35
|
+
* One invocation shape: per-Story. The diff is one Story's; round scoping
|
|
36
|
+
* is per Story off the Story's `signals.ndjson`. (v2.0.0 removed the Epic
|
|
37
|
+
* tier along with the per-AC-cluster `--epic <id> --cluster <id>` mode that
|
|
38
|
+
* scored an Epic `## Acceptance Table` against a `main..epic/<id>` diff.)
|
|
44
39
|
*
|
|
45
40
|
* CLI:
|
|
46
|
-
* --story <id> Story ID (required
|
|
47
|
-
* --epic <id> Parent Epic ID (required with --cluster).
|
|
48
|
-
* --cluster <id> AC-cluster id for the single-delivery critic.
|
|
41
|
+
* --story <id> Story ID (required).
|
|
49
42
|
* --verdict <path> Path to the round's verdict JSON (required).
|
|
50
43
|
* --no-signal Suppress the signal emit (tests).
|
|
51
44
|
*
|
|
52
45
|
* Stdout: a single JSON envelope
|
|
53
46
|
* { storyId, epicId, decision, round, cap, capReached, totalCriteria,
|
|
54
47
|
* metCount, unmetCriteria[], signalEmitted }
|
|
48
|
+
* (`epicId` is retained as a always-null field for envelope stability.)
|
|
55
49
|
*
|
|
56
50
|
* @see .agents/scripts/lib/orchestration/acceptance-eval-decision.js
|
|
57
51
|
* @see .agents/schemas/acceptance-eval-verdict.schema.json
|
|
@@ -68,10 +62,7 @@ import addFormats from 'ajv-formats';
|
|
|
68
62
|
import { runAsCli } from './lib/cli-utils.js';
|
|
69
63
|
import { getAcceptanceEval, resolveConfig } from './lib/config-resolver.js';
|
|
70
64
|
import { Logger } from './lib/Logger.js';
|
|
71
|
-
import {
|
|
72
|
-
appendEpicSignal,
|
|
73
|
-
appendSignal,
|
|
74
|
-
} from './lib/observability/signals-writer.js';
|
|
65
|
+
import { appendSignal } from './lib/observability/signals-writer.js';
|
|
75
66
|
import {
|
|
76
67
|
buildAcceptanceEvalSignal,
|
|
77
68
|
decideAcceptanceEval,
|
|
@@ -139,22 +130,14 @@ function parseCliArgs(argv) {
|
|
|
139
130
|
args: argv,
|
|
140
131
|
options: {
|
|
141
132
|
story: { type: 'string' },
|
|
142
|
-
epic: { type: 'string' },
|
|
143
|
-
cluster: { type: 'string' },
|
|
144
133
|
verdict: { type: 'string' },
|
|
145
134
|
'no-signal': { type: 'boolean', default: false },
|
|
146
135
|
},
|
|
147
136
|
strict: false,
|
|
148
137
|
});
|
|
149
138
|
const storyId = Number.parseInt(values.story ?? '', 10);
|
|
150
|
-
const epicRaw = Number.parseInt(values.epic ?? '', 10);
|
|
151
139
|
return {
|
|
152
140
|
storyId: Number.isInteger(storyId) && storyId > 0 ? storyId : null,
|
|
153
|
-
epicId: Number.isInteger(epicRaw) && epicRaw > 0 ? epicRaw : null,
|
|
154
|
-
clusterId:
|
|
155
|
-
typeof values.cluster === 'string' && values.cluster.length > 0
|
|
156
|
-
? values.cluster
|
|
157
|
-
: null,
|
|
158
141
|
verdictPath: values.verdict ?? null,
|
|
159
142
|
emitSignal: values['no-signal'] !== true,
|
|
160
143
|
};
|
|
@@ -168,7 +151,6 @@ function parseCliArgs(argv) {
|
|
|
168
151
|
*
|
|
169
152
|
* @param {object} args
|
|
170
153
|
* @param {number} args.storyId
|
|
171
|
-
* @param {number | null} args.epicId
|
|
172
154
|
* @param {object} args.verdict — validated verdict object.
|
|
173
155
|
* @param {object} args.config — resolved `.agentrc.json`.
|
|
174
156
|
* @param {boolean} args.emitSignal
|
|
@@ -182,23 +164,18 @@ function parseCliArgs(argv) {
|
|
|
182
164
|
* @returns {Promise<{ envelope: object, exitCode: number }>}
|
|
183
165
|
*/
|
|
184
166
|
export async function runAcceptanceEval(
|
|
185
|
-
{ storyId,
|
|
167
|
+
{ storyId, verdict, config, emitSignal, round },
|
|
186
168
|
deps = {},
|
|
187
169
|
) {
|
|
188
170
|
const {
|
|
189
171
|
appendSignalFn = appendSignal,
|
|
190
|
-
appendEpicSignalFn = appendEpicSignal,
|
|
191
172
|
deriveRoundFn = deriveAcceptanceEvalRound,
|
|
192
173
|
} = deps;
|
|
193
|
-
const clusterMode =
|
|
194
|
-
typeof clusterId === 'string' &&
|
|
195
|
-
clusterId.length > 0 &&
|
|
196
|
-
Number.isInteger(epicId);
|
|
197
174
|
const { maxRounds } = getAcceptanceEval(config);
|
|
198
175
|
const resolvedRound =
|
|
199
176
|
Number.isInteger(round) && round >= 1
|
|
200
177
|
? round
|
|
201
|
-
: deriveRoundFn({ epicId:
|
|
178
|
+
: deriveRoundFn({ epicId: null, storyId, config });
|
|
202
179
|
const outcome = decideAcceptanceEval({
|
|
203
180
|
verdict,
|
|
204
181
|
maxRounds,
|
|
@@ -208,21 +185,16 @@ export async function runAcceptanceEval(
|
|
|
208
185
|
let signalEmitted = false;
|
|
209
186
|
if (emitSignal) {
|
|
210
187
|
const signal = {
|
|
211
|
-
...buildAcceptanceEvalSignal({ storyId, epicId, outcome
|
|
188
|
+
...buildAcceptanceEvalSignal({ storyId, epicId: null, outcome }),
|
|
212
189
|
ts: new Date().toISOString(),
|
|
213
190
|
};
|
|
214
191
|
try {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
epicId,
|
|
222
|
-
storyId,
|
|
223
|
-
signal,
|
|
224
|
-
config,
|
|
225
|
-
});
|
|
192
|
+
signalEmitted = await appendSignalFn({
|
|
193
|
+
epicId: null,
|
|
194
|
+
storyId,
|
|
195
|
+
signal,
|
|
196
|
+
config,
|
|
197
|
+
});
|
|
226
198
|
} catch (err) {
|
|
227
199
|
// Observability is best-effort — a failed signal write must never
|
|
228
200
|
// take down the gate. The decision still stands.
|
|
@@ -236,8 +208,7 @@ export async function runAcceptanceEval(
|
|
|
236
208
|
|
|
237
209
|
const envelope = {
|
|
238
210
|
storyId: storyId ?? null,
|
|
239
|
-
epicId:
|
|
240
|
-
...(clusterMode ? { clusterId } : {}),
|
|
211
|
+
epicId: null,
|
|
241
212
|
decision: outcome.decision,
|
|
242
213
|
round: outcome.round,
|
|
243
214
|
cap: outcome.cap,
|
|
@@ -261,19 +232,11 @@ export async function runAcceptanceEval(
|
|
|
261
232
|
}
|
|
262
233
|
|
|
263
234
|
export async function main(argv = process.argv.slice(2)) {
|
|
264
|
-
const { storyId,
|
|
265
|
-
parseCliArgs(argv);
|
|
235
|
+
const { storyId, verdictPath, emitSignal } = parseCliArgs(argv);
|
|
266
236
|
|
|
267
|
-
|
|
268
|
-
// - Per-Story (fan-out): --story <id> [--epic <id>].
|
|
269
|
-
// - Per-AC-cluster (single delivery): --epic <id> --cluster <id> (no
|
|
270
|
-
// --story); the verdict scores one AC cluster of the Epic's Acceptance
|
|
271
|
-
// Table against the cumulative main..epic/<id> diff.
|
|
272
|
-
const clusterMode = Boolean(clusterId) && Number.isInteger(epicId);
|
|
273
|
-
if (!storyId && !clusterMode) {
|
|
237
|
+
if (!storyId) {
|
|
274
238
|
throw new Error(
|
|
275
|
-
'Usage: node acceptance-eval.js --story <id>
|
|
276
|
-
' or: node acceptance-eval.js --epic <id> --cluster <clusterId> --verdict <path> [--no-signal]',
|
|
239
|
+
'Usage: node acceptance-eval.js --story <id> --verdict <path> [--no-signal]',
|
|
277
240
|
);
|
|
278
241
|
}
|
|
279
242
|
if (!verdictPath) {
|
|
@@ -305,13 +268,8 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
305
268
|
const verdict = validateVerdict(parsed);
|
|
306
269
|
|
|
307
270
|
// A verdict whose embedded storyId disagrees with the CLI flag is a
|
|
308
|
-
// wiring error worth failing on, not a silent mismatch.
|
|
309
|
-
|
|
310
|
-
if (
|
|
311
|
-
!clusterMode &&
|
|
312
|
-
Number.isInteger(verdict.storyId) &&
|
|
313
|
-
verdict.storyId !== storyId
|
|
314
|
-
) {
|
|
271
|
+
// wiring error worth failing on, not a silent mismatch.
|
|
272
|
+
if (Number.isInteger(verdict.storyId) && verdict.storyId !== storyId) {
|
|
315
273
|
throw new Error(
|
|
316
274
|
`acceptance-eval: verdict storyId (${verdict.storyId}) does not match --story ${storyId}.`,
|
|
317
275
|
);
|
|
@@ -320,8 +278,6 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
320
278
|
const config = resolveConfig();
|
|
321
279
|
const { envelope, exitCode } = await runAcceptanceEval({
|
|
322
280
|
storyId,
|
|
323
|
-
epicId,
|
|
324
|
-
clusterId,
|
|
325
281
|
verdict,
|
|
326
282
|
config,
|
|
327
283
|
emitSignal,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* agents-bootstrap-github — Idempotent Label & Field Setup
|
|
4
4
|
*
|
|
5
5
|
* Creates the required label taxonomy and project board custom fields
|
|
6
|
-
* for the v5
|
|
6
|
+
* for the v5 Story-centric flow on a target GitHub repo. Idempotent —
|
|
7
7
|
* skips resources that already exist.
|
|
8
8
|
*
|
|
9
9
|
* Usage:
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
* and custom fields. Off by default — the project
|
|
46
46
|
* board object is still created when a project
|
|
47
47
|
* name is supplied, but decoration is skipped.
|
|
48
|
-
* --with-issue-forms Opt-in: generate .github/ISSUE_TEMPLATE/story.yml
|
|
49
|
-
*
|
|
48
|
+
* --with-issue-forms Opt-in: generate .github/ISSUE_TEMPLATE/story.yml.
|
|
49
|
+
* Off by default.
|
|
50
50
|
* --reap-conflicting-workflows Delete Projects V2 built-in workflows that
|
|
51
51
|
* race against the orchestrator (destructive)
|
|
52
52
|
* --help Print this help
|
|
@@ -112,7 +112,7 @@ Flags:
|
|
|
112
112
|
--with-project-board Opt-in: provision the Projects V2 Status field
|
|
113
113
|
and custom fields (default: off — prompted y/N).
|
|
114
114
|
--with-issue-forms Opt-in: generate .github/ISSUE_TEMPLATE/story.yml
|
|
115
|
-
|
|
115
|
+
(default: off — prompted y/N).
|
|
116
116
|
--reap-conflicting-workflows Delete Projects V2 built-in workflows that
|
|
117
117
|
race against the orchestrator (destructive)
|
|
118
118
|
--help Print this help
|
|
@@ -9,12 +9,18 @@
|
|
|
9
9
|
* misconfigured knip installation cannot block CI when we have no current
|
|
10
10
|
* snapshot to compare against.
|
|
11
11
|
*
|
|
12
|
+
* Story #4575 adds the **production pass** (`--production`), the test-only-
|
|
13
|
+
* importer discount: default mode counts a test as an importer, so
|
|
14
|
+
* production-dead code hides behind its own tests. See
|
|
15
|
+
* `lib/dead-exports-mode.js` for why the two passes carry separate baselines.
|
|
16
|
+
*
|
|
12
17
|
* Contract:
|
|
13
|
-
* - Reads the committed baseline at `baselines/dead-exports.json`
|
|
14
|
-
*
|
|
18
|
+
* - Reads the committed baseline at `baselines/dead-exports.json` — or
|
|
19
|
+
* `baselines/dead-exports-production.json` under `--production`
|
|
20
|
+
* (override either with `--baseline <path>`). Envelope shape:
|
|
15
21
|
* { $schema, kernelVersion, generatedAt, rows: [{ file, symbol }] }
|
|
16
|
-
* - Spawns `npx knip --reporter json --no-progress
|
|
17
|
-
* extracts `{ file, symbol }` rows from `issues[].exports[]`.
|
|
22
|
+
* - Spawns `npx knip --reporter json --no-progress` (plus `--production`),
|
|
23
|
+
* parses stdout, extracts `{ file, symbol }` rows from `issues[].exports[]`.
|
|
18
24
|
* - Diffs current vs. baseline by `(file, symbol)` identity.
|
|
19
25
|
* - Prints `+ <file>: <symbol>` for each added dead export and
|
|
20
26
|
* `- <file>: <symbol>` for each removed one, then a summary line.
|
|
@@ -25,24 +31,30 @@
|
|
|
25
31
|
* Knip spawn/parse failure exits 0 (advisory) with a stderr warning.
|
|
26
32
|
*/
|
|
27
33
|
|
|
28
|
-
import { spawnSync } from 'node:child_process';
|
|
29
34
|
import fs from 'node:fs';
|
|
30
35
|
import path from 'node:path';
|
|
31
36
|
import process from 'node:process';
|
|
32
37
|
import { runAsCli } from './lib/cli-utils.js';
|
|
38
|
+
import {
|
|
39
|
+
extractRowsFromKnip,
|
|
40
|
+
readKnipOutput,
|
|
41
|
+
runKnip,
|
|
42
|
+
} from './lib/dead-exports-knip.js';
|
|
43
|
+
import { resolveDeadExportsMode } from './lib/dead-exports-mode.js';
|
|
33
44
|
|
|
34
45
|
/**
|
|
35
|
-
* Parse argv for `--baseline <path>`, `--json`,
|
|
36
|
-
* `--knip-output` is a test seam: pass a pre-captured knip JSON
|
|
37
|
-
* of spawning knip. Exported so unit tests can pin the parser.
|
|
46
|
+
* Parse argv for `--baseline <path>`, `--json`, `--knip-output <path>`, and
|
|
47
|
+
* `--production`. `--knip-output` is a test seam: pass a pre-captured knip JSON
|
|
48
|
+
* file instead of spawning knip. Exported so unit tests can pin the parser.
|
|
38
49
|
*
|
|
39
50
|
* @param {string[]} argv
|
|
40
|
-
* @returns {{ baselinePath: string | null, json: boolean, knipOutputPath: string | null }}
|
|
51
|
+
* @returns {{ baselinePath: string | null, json: boolean, knipOutputPath: string | null, production: boolean }}
|
|
41
52
|
*/
|
|
42
53
|
export function parseArgv(argv = []) {
|
|
43
54
|
let baselinePath = null;
|
|
44
55
|
let json = false;
|
|
45
56
|
let knipOutputPath = null;
|
|
57
|
+
let production = false;
|
|
46
58
|
for (let i = 0; i < argv.length; i += 1) {
|
|
47
59
|
const a = argv[i];
|
|
48
60
|
if (a === '--baseline') {
|
|
@@ -53,6 +65,8 @@ export function parseArgv(argv = []) {
|
|
|
53
65
|
}
|
|
54
66
|
} else if (a === '--json') {
|
|
55
67
|
json = true;
|
|
68
|
+
} else if (a === '--production') {
|
|
69
|
+
production = true;
|
|
56
70
|
} else if (a === '--knip-output') {
|
|
57
71
|
const next = argv[i + 1];
|
|
58
72
|
if (next && !next.startsWith('--')) {
|
|
@@ -61,7 +75,7 @@ export function parseArgv(argv = []) {
|
|
|
61
75
|
}
|
|
62
76
|
}
|
|
63
77
|
}
|
|
64
|
-
return { baselinePath, json, knipOutputPath };
|
|
78
|
+
return { baselinePath, json, knipOutputPath, production };
|
|
65
79
|
}
|
|
66
80
|
|
|
67
81
|
/**
|
|
@@ -84,36 +98,6 @@ export function loadBaseline(baselinePath) {
|
|
|
84
98
|
}
|
|
85
99
|
}
|
|
86
100
|
|
|
87
|
-
/**
|
|
88
|
-
* Pure helper: normalize knip's `--reporter json` output into a flat array of
|
|
89
|
-
* `{ file, symbol }` rows. Knip emits `{ issues: [{ file, exports: [{ name, ... }], ... }, ...] }`.
|
|
90
|
-
* Only `exports` rows are mapped — the dead-export ratchet ignores file-level,
|
|
91
|
-
* dependency-level, and duplicate-level issues (knip surfaces those via
|
|
92
|
-
* separate `rules` keys).
|
|
93
|
-
*
|
|
94
|
-
* @param {unknown} knipEnvelope The parsed knip JSON report.
|
|
95
|
-
* @returns {Array<{ file: string, symbol: string }>}
|
|
96
|
-
*/
|
|
97
|
-
export function extractRowsFromKnip(knipEnvelope) {
|
|
98
|
-
const rows = [];
|
|
99
|
-
if (!knipEnvelope || typeof knipEnvelope !== 'object') return rows;
|
|
100
|
-
const issues = Array.isArray(knipEnvelope.issues) ? knipEnvelope.issues : [];
|
|
101
|
-
for (const issue of issues) {
|
|
102
|
-
const file = issue?.file;
|
|
103
|
-
if (typeof file !== 'string' || file.length === 0) continue;
|
|
104
|
-
const exports_ = Array.isArray(issue.exports) ? issue.exports : [];
|
|
105
|
-
for (const e of exports_) {
|
|
106
|
-
const symbol =
|
|
107
|
-
(e && typeof e.name === 'string' && e.name) ||
|
|
108
|
-
(e && typeof e.symbol === 'string' && e.symbol) ||
|
|
109
|
-
null;
|
|
110
|
-
if (!symbol) continue;
|
|
111
|
-
rows.push({ file, symbol });
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
return rows;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
101
|
/**
|
|
118
102
|
* Pure helper: diff two `{ file, symbol }` row sets. Returns `added` (in
|
|
119
103
|
* current but not baseline) and `removed` (in baseline but not current).
|
|
@@ -151,75 +135,24 @@ export function diffRows(baselineRows, currentRows) {
|
|
|
151
135
|
* "no drift" signal. When added rows are present the summary includes a
|
|
152
136
|
* "(gate fail)" marker so the ratchet violation is visible in CI output.
|
|
153
137
|
*
|
|
138
|
+
* `label` distinguishes the two passes in CI logs, which run back to back and
|
|
139
|
+
* would otherwise emit two identical-looking summaries.
|
|
140
|
+
*
|
|
154
141
|
* @param {{ added: Array, removed: Array }} diff
|
|
142
|
+
* @param {string} [label='dead-exports']
|
|
155
143
|
* @returns {string}
|
|
156
144
|
*/
|
|
157
|
-
export function renderDiff(diff) {
|
|
145
|
+
export function renderDiff(diff, label = 'dead-exports') {
|
|
158
146
|
const lines = [];
|
|
159
147
|
for (const r of diff.added) lines.push(`+ ${r.file}: ${r.symbol}`);
|
|
160
148
|
for (const r of diff.removed) lines.push(`- ${r.file}: ${r.symbol}`);
|
|
161
149
|
const tag = diff.added.length > 0 ? '(gate fail)' : '(ok)';
|
|
162
150
|
lines.push(
|
|
163
|
-
`[
|
|
151
|
+
`[${label}] added=${diff.added.length} removed=${diff.removed.length} ${tag}`,
|
|
164
152
|
);
|
|
165
153
|
return lines.join('\n');
|
|
166
154
|
}
|
|
167
155
|
|
|
168
|
-
/**
|
|
169
|
-
* Spawn `npx knip --reporter json --no-progress` and return the parsed
|
|
170
|
-
* envelope. Returns `null` on spawn / parse failure — the caller logs the
|
|
171
|
-
* underlying error and falls back to treating current rows as empty (which
|
|
172
|
-
* surfaces every baseline row as "removed", a loud-but-safe signal).
|
|
173
|
-
*
|
|
174
|
-
* Exported as a hook so tests can stub the spawn without setting up a
|
|
175
|
-
* functioning knip workspace.
|
|
176
|
-
*
|
|
177
|
-
* @param {{ cwd?: string, spawn?: typeof spawnSync }} [opts]
|
|
178
|
-
* @returns {{ ok: true, envelope: unknown } | { ok: false, error: string }}
|
|
179
|
-
*/
|
|
180
|
-
export function runKnip({ cwd = process.cwd(), spawn = spawnSync } = {}) {
|
|
181
|
-
const result = spawn(
|
|
182
|
-
process.platform === 'win32' ? 'npx.cmd' : 'npx',
|
|
183
|
-
['knip', '--reporter', 'json', '--no-progress'],
|
|
184
|
-
{
|
|
185
|
-
cwd,
|
|
186
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
187
|
-
encoding: 'utf-8',
|
|
188
|
-
shell: process.platform === 'win32',
|
|
189
|
-
},
|
|
190
|
-
);
|
|
191
|
-
if (result.error) {
|
|
192
|
-
return { ok: false, error: `spawn failed: ${result.error.message}` };
|
|
193
|
-
}
|
|
194
|
-
const stdout = typeof result.stdout === 'string' ? result.stdout : '';
|
|
195
|
-
if (stdout.trim().length === 0) {
|
|
196
|
-
return { ok: false, error: 'knip produced empty stdout' };
|
|
197
|
-
}
|
|
198
|
-
try {
|
|
199
|
-
return { ok: true, envelope: JSON.parse(stdout) };
|
|
200
|
-
} catch (err) {
|
|
201
|
-
return {
|
|
202
|
-
ok: false,
|
|
203
|
-
error: `knip JSON parse failed: ${err?.message ?? err}`,
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Read a pre-captured knip JSON envelope from disk (for the `--knip-output`
|
|
210
|
-
* test seam). Returns the parsed envelope or `null` on failure.
|
|
211
|
-
*
|
|
212
|
-
* @param {string} filePath
|
|
213
|
-
* @returns {unknown}
|
|
214
|
-
*/
|
|
215
|
-
function readKnipOutput(filePath) {
|
|
216
|
-
try {
|
|
217
|
-
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
218
|
-
} catch {
|
|
219
|
-
return null;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
156
|
/**
|
|
224
157
|
* Top-level CLI entry. Exported so tests can drive the full pipeline through
|
|
225
158
|
* injected hooks without spawning knip.
|
|
@@ -242,10 +175,15 @@ export async function runCli({
|
|
|
242
175
|
runKnipImpl = runKnip,
|
|
243
176
|
loadBaselineImpl = loadBaseline,
|
|
244
177
|
} = {}) {
|
|
245
|
-
const { baselinePath, json, knipOutputPath } = parseArgv(argv);
|
|
178
|
+
const { baselinePath, json, knipOutputPath, production } = parseArgv(argv);
|
|
179
|
+
const {
|
|
180
|
+
mode,
|
|
181
|
+
label,
|
|
182
|
+
baseline: defaultBaseline,
|
|
183
|
+
} = resolveDeadExportsMode(production);
|
|
246
184
|
const resolvedBaselinePath = path.resolve(
|
|
247
185
|
cwd,
|
|
248
|
-
baselinePath ??
|
|
186
|
+
baselinePath ?? defaultBaseline,
|
|
249
187
|
);
|
|
250
188
|
const baseline = loadBaselineImpl(resolvedBaselinePath);
|
|
251
189
|
const baselineRows = Array.isArray(baseline?.rows) ? baseline.rows : [];
|
|
@@ -256,7 +194,7 @@ export async function runCli({
|
|
|
256
194
|
knipEnvelope = readKnipOutput(path.resolve(cwd, knipOutputPath));
|
|
257
195
|
if (!knipEnvelope) knipError = `failed to read ${knipOutputPath}`;
|
|
258
196
|
} else {
|
|
259
|
-
const result = runKnipImpl({ cwd });
|
|
197
|
+
const result = runKnipImpl({ cwd, production });
|
|
260
198
|
if (result.ok) {
|
|
261
199
|
knipEnvelope = result.envelope;
|
|
262
200
|
} else {
|
|
@@ -275,6 +213,7 @@ export async function runCli({
|
|
|
275
213
|
if (json) {
|
|
276
214
|
const envelope = {
|
|
277
215
|
kind: 'dead-exports-report',
|
|
216
|
+
mode,
|
|
278
217
|
baselinePath: resolvedBaselinePath,
|
|
279
218
|
baselineRows,
|
|
280
219
|
currentRows,
|
|
@@ -287,14 +226,14 @@ export async function runCli({
|
|
|
287
226
|
} else {
|
|
288
227
|
if (!baseline) {
|
|
289
228
|
stderr.write(
|
|
290
|
-
`[
|
|
229
|
+
`[${label}] ⚠ baseline not found at ${resolvedBaselinePath} — treating as empty\n`,
|
|
291
230
|
);
|
|
292
231
|
}
|
|
293
232
|
if (knipError) {
|
|
294
|
-
stderr.write(`[
|
|
233
|
+
stderr.write(`[${label}] ⚠ knip run failed: ${knipError}\n`);
|
|
295
234
|
}
|
|
296
|
-
stdout.write(`\n---
|
|
297
|
-
stdout.write(`${renderDiff(diff)}\n`);
|
|
235
|
+
stdout.write(`\n--- ${label} preview ---\n`);
|
|
236
|
+
stdout.write(`${renderDiff(diff, label)}\n`);
|
|
298
237
|
}
|
|
299
238
|
|
|
300
239
|
return exitCode;
|
|
@@ -257,8 +257,8 @@ function stripAnchorAndQuery(target) {
|
|
|
257
257
|
// A slash token is `/<name>` where:
|
|
258
258
|
// - the preceding char is NOT a word char, `/`, `:`, `.`, `>`, `]`, `)`,
|
|
259
259
|
// so we don't match URL path segments or fragments embedded in paths
|
|
260
|
-
// like `temp/
|
|
261
|
-
// `temp/
|
|
260
|
+
// like `temp/run-<id>/lifecycle.ndjson` (preceded by `>`) or
|
|
261
|
+
// `temp/run-[ID]/tickets.json` (preceded by `]`).
|
|
262
262
|
// - the following char is NOT a word char, `-`, or `.`, so file
|
|
263
263
|
// extensions like `/tickets.json` and identifier suffixes don't match.
|
|
264
264
|
// The optional `(?::[a-z][a-z0-9-]*)?` tail captures the namespaced
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* Any module under `.agents/scripts/lib/orchestration/lifecycle/listeners/**`
|
|
18
18
|
* that calls `bus.on('*', …)` MUST NOT import a side-effecting module.
|
|
19
19
|
* The static blocklist is small (the modules that mutate GitHub state,
|
|
20
|
-
* the worktree, or write outside `temp/
|
|
20
|
+
* the worktree, or write outside `temp/run-<id>/`); we match by module
|
|
21
21
|
* specifier suffix to keep the rule simple and stable.
|
|
22
22
|
*
|
|
23
23
|
* Rule 3 — "Auto-merge lockout" (Story #2253 / Task #2255, Epic #2172
|
|
@@ -61,6 +61,19 @@ const BACKTICK_CALL_RE = /`[A-Za-z_$][\w$]*\s*\(/;
|
|
|
61
61
|
const EXPORTED_FROM_RE = /\bexported from\b/;
|
|
62
62
|
const LIB_IMPORT_RE = /\b(?:import|require)\(\s*['"`][^'"`]*scripts\/lib\//;
|
|
63
63
|
|
|
64
|
+
/** A markdown table row (leading pipe, optionally indented). */
|
|
65
|
+
const TABLE_ROW_RE = /^\s*\|/;
|
|
66
|
+
/** A markdown heading of any level — the lookback boundary for Rule 4. */
|
|
67
|
+
const HEADING_RE = /^\s*#{1,6}\s/;
|
|
68
|
+
/** A backticked `--flag` occupying a table row's first cell. */
|
|
69
|
+
const FLAG_CELL_RE = /^\s*\|\s*`?--[\w-]+/;
|
|
70
|
+
/** A `.js` script filename — bare or path-qualified. */
|
|
71
|
+
const SCRIPT_NAME_RE = /([\w./-]*[\w-]+\.js)\b/;
|
|
72
|
+
/** A markdown table's delimiter row (`| --- | --- |`). */
|
|
73
|
+
const TABLE_DELIMITER_RE = /^\s*\|[\s|:-]+\|?\s*$/;
|
|
74
|
+
/** Minimum `--flag` rows before a table counts as a flag *table*. */
|
|
75
|
+
export const MIN_FLAG_ROWS = 2;
|
|
76
|
+
|
|
64
77
|
/**
|
|
65
78
|
* Strip fenced code blocks (``` / ~~~), replacing their lines with empty
|
|
66
79
|
* strings so line numbers stay stable. Complete runnable commands live in
|
|
@@ -106,6 +119,82 @@ export function toParagraphs(lines) {
|
|
|
106
119
|
return paragraphs;
|
|
107
120
|
}
|
|
108
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Rule 4 — CLI flag table. Find markdown tables that enumerate the `--flags`
|
|
124
|
+
* of a script the repo already owns, and flag them as duplicated surface.
|
|
125
|
+
*
|
|
126
|
+
* The script's own argument parser is the source of truth for its flags, and
|
|
127
|
+
* it prints them itself; a copy in prose is drift waiting to happen (the very
|
|
128
|
+
* drift Story #4546 removed). Prose must point at the command instead.
|
|
129
|
+
*
|
|
130
|
+
* The signal is deliberately narrow, to stay at zero false positives:
|
|
131
|
+
*
|
|
132
|
+
* 1. The table has at least `MIN_FLAG_ROWS` data rows whose **first cell**
|
|
133
|
+
* is a `--flag`. One flag row among prose rows is a contract/behaviour
|
|
134
|
+
* table (e.g. "Default | …", "`--dry-run` | …"), not a flag enumeration.
|
|
135
|
+
* 2. A `.js` script is named between the table and the nearest preceding
|
|
136
|
+
* heading (inclusive) — i.e. the section is *about* that script, so the
|
|
137
|
+
* table is restating its surface.
|
|
138
|
+
*
|
|
139
|
+
* A slash command's own argument table (`/plan`, `/deliver`) has no script
|
|
140
|
+
* behind it — nothing owns those flags but the workflow prose itself — so
|
|
141
|
+
* condition 2 leaves it alone by design.
|
|
142
|
+
*
|
|
143
|
+
* @param {string[]} rawLines unstripped source lines (fenced commands are the
|
|
144
|
+
* usual place a script is named, so this rule reads the original text).
|
|
145
|
+
* @returns {Array<{ rule: string, line: number, hint: string }>}
|
|
146
|
+
*/
|
|
147
|
+
export function lintFlagTables(rawLines) {
|
|
148
|
+
const violations = [];
|
|
149
|
+
let inFence = false;
|
|
150
|
+
|
|
151
|
+
for (let i = 0; i < rawLines.length; i++) {
|
|
152
|
+
if (/^\s*(```|~~~)/.test(rawLines[i])) {
|
|
153
|
+
inFence = !inFence;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (inFence || !TABLE_ROW_RE.test(rawLines[i])) continue;
|
|
157
|
+
|
|
158
|
+
// Collect the contiguous table block starting here.
|
|
159
|
+
const start = i;
|
|
160
|
+
let end = i;
|
|
161
|
+
while (end + 1 < rawLines.length && TABLE_ROW_RE.test(rawLines[end + 1])) {
|
|
162
|
+
end += 1;
|
|
163
|
+
}
|
|
164
|
+
i = end; // continue scanning after this table
|
|
165
|
+
|
|
166
|
+
const flagRows = rawLines
|
|
167
|
+
.slice(start, end + 1)
|
|
168
|
+
.filter((l) => !TABLE_DELIMITER_RE.test(l) && FLAG_CELL_RE.test(l));
|
|
169
|
+
if (flagRows.length < MIN_FLAG_ROWS) continue;
|
|
170
|
+
|
|
171
|
+
// Look back to the nearest heading (inclusive) for a named script.
|
|
172
|
+
let script = null;
|
|
173
|
+
for (let j = start - 1; j >= 0; j--) {
|
|
174
|
+
const m = SCRIPT_NAME_RE.exec(rawLines[j]);
|
|
175
|
+
if (m) {
|
|
176
|
+
script = m[1];
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
if (HEADING_RE.test(rawLines[j])) break;
|
|
180
|
+
}
|
|
181
|
+
if (!script) continue;
|
|
182
|
+
|
|
183
|
+
const base = script.split('/').pop();
|
|
184
|
+
violations.push({
|
|
185
|
+
rule: 'no-cli-flag-table',
|
|
186
|
+
line: start + 1,
|
|
187
|
+
hint:
|
|
188
|
+
`Workflow prose restates the flag surface of \`${base}\` as a table (${flagRows.length} flag rows). ` +
|
|
189
|
+
"The script's argument parser owns those flags, so a prose copy is drift waiting to happen. " +
|
|
190
|
+
`Delete the table and point at the command (\`node .agents/scripts/${base} …\`), keeping only the ` +
|
|
191
|
+
'judgement a reader cannot get from the command itself. If the script has no help output to point ' +
|
|
192
|
+
'at, add one — do not re-inline the table.',
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
return violations;
|
|
196
|
+
}
|
|
197
|
+
|
|
109
198
|
/**
|
|
110
199
|
* Lint one markdown source. Returns violations
|
|
111
200
|
* `{ rule, line, hint }[]` (empty when clean).
|
|
@@ -115,7 +204,9 @@ export function toParagraphs(lines) {
|
|
|
115
204
|
*/
|
|
116
205
|
export function lintWorkflowSource(source) {
|
|
117
206
|
const violations = [];
|
|
207
|
+
const rawLines = source.split('\n');
|
|
118
208
|
const lines = stripFences(source);
|
|
209
|
+
violations.push(...lintFlagTables(rawLines));
|
|
119
210
|
for (const para of toParagraphs(lines)) {
|
|
120
211
|
if (IMPERATIVE_CALL_RE.test(para.text)) {
|
|
121
212
|
violations.push({
|