mandrel 2.0.0 → 2.1.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 +9 -7
- package/.agents/agents/story-worker.md +41 -46
- package/.agents/audit-checklists/performance.md +1 -1
- package/.agents/docs/SDLC.md +51 -44
- package/.agents/docs/agentrc-reference.json +8 -13
- package/.agents/docs/configuration.md +32 -56
- package/.agents/docs/execution-reference.md +39 -10
- package/.agents/docs/quality-gates.md +14 -16
- package/.agents/docs/workflows.md +6 -6
- package/.agents/instructions.md +64 -79
- package/.agents/rules/ci-remediation.md +3 -3
- package/.agents/rules/git-conventions-reference.md +42 -51
- package/.agents/schemas/agentrc.schema.json +34 -45
- 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 +22 -66
- 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/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 +31 -13
- 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/code-review.js +58 -168
- 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 +114 -24
- package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +11 -22
- package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +3 -7
- 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 -75
- 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/planning/authoring-context.js +41 -40
- 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 +3 -12
- 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 +15 -32
- 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 +6 -5
- package/.agents/scripts/lib/templates/decomposer-prompts.js +7 -3
- 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/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 -6
- 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/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 +79 -4
- 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 +98 -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 +21 -13
- package/.agents/workflows/helpers/code-quality-guardrails.md +7 -7
- package/.agents/workflows/helpers/code-review.md +12 -10
- package/.agents/workflows/helpers/deliver-story-reference.md +73 -32
- package/.agents/workflows/helpers/deliver-story.md +193 -118
- package/.agents/workflows/helpers/parallel-tooling.md +2 -2
- package/.agents/workflows/helpers/worktree-lifecycle.md +28 -32
- package/.agents/workflows/plan.md +184 -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 +40 -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 +12 -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/package.json +1 -1
- 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
|
@@ -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({
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* deliver-recover.js — probe a stranded Story's live state and print the ONE
|
|
5
|
+
* command that resumes it (Story #4543).
|
|
6
|
+
*
|
|
7
|
+
* Read-only by construction. This CLI mutates nothing: it reads the ticket's
|
|
8
|
+
* labels and lease, the Story branch locally and via its tracking ref, the
|
|
9
|
+
* worktree, and the PR (state + checks), then walks a fixed decision table
|
|
10
|
+
* and prints one command with the evidence it was derived from. It never
|
|
11
|
+
* prints a menu — a menu of options is what the operator already has, and it
|
|
12
|
+
* is the thing they cannot act on.
|
|
13
|
+
*
|
|
14
|
+
* The strand this exists for above all others is the **merged-but-label-stale**
|
|
15
|
+
* Story: a `/deliver` re-run refuses it outright, because
|
|
16
|
+
* `single-story-init.js` hard-errors on an already-closed Story. Before this
|
|
17
|
+
* surface, that Story had no automated way back.
|
|
18
|
+
*
|
|
19
|
+
* Usage:
|
|
20
|
+
* node .agents/scripts/deliver-recover.js --story <STORY_ID> [--cwd <main-repo>]
|
|
21
|
+
* [--json]
|
|
22
|
+
*
|
|
23
|
+
* Exit codes:
|
|
24
|
+
* 0 — a recovery shape was resolved and its next command printed (including
|
|
25
|
+
* the "nothing to recover" shapes). Reading state is not a failure.
|
|
26
|
+
* 1 — the probe itself could not run (unreadable ticket, bad input).
|
|
27
|
+
*
|
|
28
|
+
* @see .agents/scripts/lib/orchestration/deliver-recover.js
|
|
29
|
+
* @see .agents/schemas/story-deliver-terminal.schema.json
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { parseArgs } from 'node:util';
|
|
33
|
+
import { runAsCli } from './lib/cli-utils.js';
|
|
34
|
+
import { resolveConfig } from './lib/config-resolver.js';
|
|
35
|
+
import { Logger } from './lib/Logger.js';
|
|
36
|
+
import {
|
|
37
|
+
recoverStory,
|
|
38
|
+
renderRecovery,
|
|
39
|
+
} from './lib/orchestration/deliver-recover.js';
|
|
40
|
+
import { PROJECT_ROOT } from './lib/project-root.js';
|
|
41
|
+
import { createProvider } from './lib/provider-factory.js';
|
|
42
|
+
|
|
43
|
+
const HELP = `Usage: node .agents/scripts/deliver-recover.js --story <id> [--cwd <main-repo>] [--json]
|
|
44
|
+
|
|
45
|
+
Probes a Story's live delivery state — labels, lease, branch, worktree, PR
|
|
46
|
+
state and checks — and prints the single next command that resumes it, with
|
|
47
|
+
the evidence it was derived from. Read-only: mutates nothing.
|
|
48
|
+
|
|
49
|
+
Flags:
|
|
50
|
+
--story GitHub issue number of the Story (required).
|
|
51
|
+
--cwd Main-repo checkout to probe (default: project root).
|
|
52
|
+
--json Emit the full recovery envelope as JSON instead of prose.
|
|
53
|
+
--help Show this message.
|
|
54
|
+
`;
|
|
55
|
+
|
|
56
|
+
export function parseArgv(argv) {
|
|
57
|
+
const { values } = parseArgs({
|
|
58
|
+
args: argv,
|
|
59
|
+
options: {
|
|
60
|
+
story: { type: 'string' },
|
|
61
|
+
cwd: { type: 'string' },
|
|
62
|
+
json: { type: 'boolean', default: false },
|
|
63
|
+
help: { type: 'boolean', default: false },
|
|
64
|
+
},
|
|
65
|
+
strict: false,
|
|
66
|
+
});
|
|
67
|
+
return {
|
|
68
|
+
storyId: Number.parseInt(String(values.story ?? ''), 10),
|
|
69
|
+
cwd: values.cwd ?? null,
|
|
70
|
+
json: Boolean(values.json),
|
|
71
|
+
help: Boolean(values.help),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Probe and report. Exported for testing.
|
|
77
|
+
*/
|
|
78
|
+
export async function runDeliverRecover({
|
|
79
|
+
storyId: storyIdParam,
|
|
80
|
+
cwd: cwdParam,
|
|
81
|
+
json: jsonParam,
|
|
82
|
+
argv,
|
|
83
|
+
injectedProvider,
|
|
84
|
+
injectedConfig,
|
|
85
|
+
injectedGh,
|
|
86
|
+
injectedGitSpawn,
|
|
87
|
+
} = {}) {
|
|
88
|
+
const parsed =
|
|
89
|
+
storyIdParam !== undefined
|
|
90
|
+
? { storyId: storyIdParam, cwd: cwdParam ?? null, json: !!jsonParam }
|
|
91
|
+
: parseArgv(argv ?? process.argv.slice(2));
|
|
92
|
+
|
|
93
|
+
if (parsed.help) {
|
|
94
|
+
Logger.info(HELP);
|
|
95
|
+
return { success: true, result: null };
|
|
96
|
+
}
|
|
97
|
+
if (!Number.isInteger(parsed.storyId) || parsed.storyId <= 0) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
'Usage: node deliver-recover.js --story <STORY_ID> [--cwd <main-repo>] [--json]',
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const cwd = parsed.cwd ?? PROJECT_ROOT;
|
|
104
|
+
const config = injectedConfig || resolveConfig({ cwd });
|
|
105
|
+
const provider = injectedProvider || createProvider(config);
|
|
106
|
+
|
|
107
|
+
const recovery = await recoverStory({
|
|
108
|
+
storyId: parsed.storyId,
|
|
109
|
+
cwd,
|
|
110
|
+
provider,
|
|
111
|
+
config,
|
|
112
|
+
gh: injectedGh,
|
|
113
|
+
gitSpawnFn: injectedGitSpawn,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
Logger.info(
|
|
117
|
+
parsed.json ? JSON.stringify(recovery, null, 2) : renderRecovery(recovery),
|
|
118
|
+
);
|
|
119
|
+
return { success: true, result: recovery };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
runAsCli(import.meta.url, runDeliverRecover, { source: 'deliver-recover' });
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* any still-stuck entries by enumerating the processes holding handles
|
|
9
9
|
* inside the worktree path and terminating them.
|
|
10
10
|
*
|
|
11
|
-
* Invoked by `/deliver
|
|
11
|
+
* Invoked by `/deliver` and `/plan`
|
|
12
12
|
* (via `drainPendingCleanupAtBoot` → `worktree-sweep.js`), and
|
|
13
13
|
* `story-close` so the pending-cleanup ledger drains automatically
|
|
14
14
|
* across the sprint lifecycle. Operators can also run it standalone:
|
|
@@ -13,42 +13,22 @@
|
|
|
13
13
|
*
|
|
14
14
|
* Usage:
|
|
15
15
|
* node .agents/scripts/evidence-gate.js \
|
|
16
|
-
*
|
|
16
|
+
* --standalone --scope-id <storyId> --gate <name> \
|
|
17
17
|
* [--worktree <path>] [--no-evidence] -- <cmd> [args...]
|
|
18
18
|
*
|
|
19
19
|
* Examples:
|
|
20
|
-
* node .agents/scripts/evidence-gate.js --epic-id 802 --scope-id 817 --gate lint -- npm run lint
|
|
21
|
-
* node .agents/scripts/evidence-gate.js --epic-id 1030 --scope-id 1030 --gate test -- npm test
|
|
22
|
-
* node .agents/scripts/evidence-gate.js --epic-id 1114 --scope-id 1120 --gate test \
|
|
23
|
-
* --worktree .worktrees/story-1120 -- npm test
|
|
24
20
|
* node .agents/scripts/evidence-gate.js --standalone --scope-id 4250 --gate lint \
|
|
25
21
|
* --worktree .worktrees/story-4250 -- npm run lint
|
|
22
|
+
* node .agents/scripts/evidence-gate.js --standalone --scope-id 4250 --gate test \
|
|
23
|
+
* --worktree .worktrees/story-4250 -- npm test
|
|
26
24
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* (`<tempRoot>/epic-<eid>/validation-evidence.json`); when `scope-id !==
|
|
30
|
-
* epic-id` it is Story-scoped under
|
|
31
|
-
* `<tempRoot>/epic-<eid>/story-<sid>/validation-evidence.json`. With
|
|
32
|
-
* `--standalone` (Story #4250) the file is anchored on the Story id alone at
|
|
25
|
+
* `--standalone` (Story #4250) is required: the evidence file is anchored on
|
|
26
|
+
* the Story id at
|
|
33
27
|
* `<tempRoot>/standalone/stories/story-<sid>/validation-evidence.json` — the
|
|
34
28
|
* same keyspace the standalone close consults, so the acceptance-self-eval
|
|
35
29
|
* critic's verify[] runs (lint / typecheck) are shared with the close.
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* is the *evidence cwd* — it locates the per-Epic temp tree under the main
|
|
39
|
-
* checkout's `.git/`. `--worktree` is the *spawn cwd* — when supplied, the
|
|
40
|
-
* gate runner is launched with `cwd: <worktree>` and the HEAD-SHA used as
|
|
41
|
-
* the evidence key is read from the worktree (so caching keys to the Story
|
|
42
|
-
* branch's HEAD, not the main branch's). When `--worktree` is omitted,
|
|
43
|
-
* spawn cwd falls back to `--cwd` (legacy single-tree behaviour).
|
|
44
|
-
*
|
|
45
|
-
* Exit codes:
|
|
46
|
-
* 0 — gate passed (or skipped via evidence)
|
|
47
|
-
* N — gate failed (passes through the runner's exit code)
|
|
48
|
-
*
|
|
49
|
-
* `--no-evidence` forces the runner regardless of recorded state. The
|
|
50
|
-
* evidence file is gitignored — evidence is a perf optimization, not a
|
|
51
|
-
* trust boundary; pre-push hooks and CI continue to verify independently.
|
|
30
|
+
* v2.0.0 removed the Epic tier along with the `--epic-id` Epic-keyed
|
|
31
|
+
* keyspace.
|
|
52
32
|
*/
|
|
53
33
|
|
|
54
34
|
import { spawnSync } from 'node:child_process';
|
|
@@ -88,7 +68,6 @@ export function parseWrapperArgs(argv) {
|
|
|
88
68
|
args: argv,
|
|
89
69
|
options: {
|
|
90
70
|
'scope-id': { type: 'string' },
|
|
91
|
-
'epic-id': { type: 'string' },
|
|
92
71
|
gate: { type: 'string' },
|
|
93
72
|
'no-evidence': { type: 'boolean', default: false },
|
|
94
73
|
standalone: { type: 'boolean', default: false },
|
|
@@ -98,10 +77,8 @@ export function parseWrapperArgs(argv) {
|
|
|
98
77
|
strict: false,
|
|
99
78
|
});
|
|
100
79
|
const scopeId = Number.parseInt(values['scope-id'] ?? '', 10);
|
|
101
|
-
const epicId = Number.parseInt(values['epic-id'] ?? '', 10);
|
|
102
80
|
return {
|
|
103
81
|
scopeId: Number.isNaN(scopeId) || scopeId <= 0 ? null : scopeId,
|
|
104
|
-
epicId: Number.isNaN(epicId) || epicId <= 0 ? null : epicId,
|
|
105
82
|
standalone: values.standalone === true,
|
|
106
83
|
gate: values.gate ?? null,
|
|
107
84
|
useEvidence: values['no-evidence'] !== true,
|
|
@@ -126,15 +103,13 @@ function resolveHeadShaDefault(cwd, gitSpawnFn) {
|
|
|
126
103
|
* Exported for tests + the CLI `main()`.
|
|
127
104
|
*
|
|
128
105
|
* @param {object} params
|
|
129
|
-
* @param {number} params.scopeId — Story
|
|
130
|
-
* @param {
|
|
131
|
-
*
|
|
132
|
-
* storyId-anchored standalone keyspace instead of the Epic-keyed path
|
|
133
|
-
* (Story #4250). Substitutes for `epicId`.
|
|
106
|
+
* @param {number} params.scopeId — Story ID (positive integer).
|
|
107
|
+
* @param {boolean} [params.standalone] — Required. Routes to the
|
|
108
|
+
* storyId-anchored standalone keyspace (Story #4250).
|
|
134
109
|
* @param {string} params.gate — Logical gate name (`lint`, `typecheck`, …).
|
|
135
110
|
* @param {boolean} params.useEvidence — When false, force the runner.
|
|
136
|
-
* @param {string} params.cwd — Evidence cwd (locates the
|
|
137
|
-
*
|
|
111
|
+
* @param {string} params.cwd — Evidence cwd (locates the temp
|
|
112
|
+
* tree). The runner is spawned in `worktreePath` when set, else `cwd`.
|
|
138
113
|
* @param {string|null} [params.worktreePath] — Spawn cwd override (Story #1120).
|
|
139
114
|
* When set, the runner runs in the Story worktree and the HEAD-SHA used as
|
|
140
115
|
* the evidence cache key is read from the worktree, not from `cwd`.
|
|
@@ -159,7 +134,6 @@ export async function runEvidenceGate(params, deps = {}) {
|
|
|
159
134
|
} = deps;
|
|
160
135
|
const {
|
|
161
136
|
scopeId,
|
|
162
|
-
epicId,
|
|
163
137
|
standalone = false,
|
|
164
138
|
gate,
|
|
165
139
|
useEvidence,
|
|
@@ -168,31 +142,27 @@ export async function runEvidenceGate(params, deps = {}) {
|
|
|
168
142
|
runnerArgs,
|
|
169
143
|
} = params ?? {};
|
|
170
144
|
|
|
171
|
-
// `--
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
// can record verify[] evidence into the same keyspace the standalone close
|
|
175
|
-
// consults.
|
|
145
|
+
// `--standalone` (Story #4250) routes the evidence file to the
|
|
146
|
+
// storyId-anchored keyspace so the acceptance-self-eval critic can record
|
|
147
|
+
// verify[] evidence into the same keyspace the standalone close consults.
|
|
176
148
|
if (
|
|
177
149
|
!scopeId ||
|
|
178
|
-
|
|
150
|
+
!standalone ||
|
|
179
151
|
!gate ||
|
|
180
152
|
!runnerArgs ||
|
|
181
153
|
runnerArgs.length === 0
|
|
182
154
|
) {
|
|
183
155
|
logger.fatal(
|
|
184
|
-
'Usage: node evidence-gate.js
|
|
156
|
+
'Usage: node evidence-gate.js --standalone --scope-id <id> --gate <name> [--worktree <path>] [--no-evidence] -- <cmd> [args...]',
|
|
185
157
|
);
|
|
186
158
|
return { status: 1, skipped: false };
|
|
187
159
|
}
|
|
188
|
-
// Evidence-store opts shared by shouldSkip + recordPass below.
|
|
189
|
-
|
|
190
|
-
// resolves under `epicId`.
|
|
191
|
-
const evidenceStoreOpts = { cwd, epicId, standalone };
|
|
160
|
+
// Evidence-store opts shared by shouldSkip + recordPass below.
|
|
161
|
+
const evidenceStoreOpts = { cwd, standalone };
|
|
192
162
|
|
|
193
163
|
// Spawn cwd is the worktree when supplied — every gate command sees the
|
|
194
164
|
// Story branch's tree, not the main checkout. Evidence cwd stays anchored
|
|
195
|
-
// to the main checkout so the
|
|
165
|
+
// to the main checkout so the temp tree resolves under the main
|
|
196
166
|
// `.git/`. The HEAD-SHA used as the cache key is read from the spawn cwd
|
|
197
167
|
// (the worktree), so cache entries key against the Story branch's HEAD.
|
|
198
168
|
const spawnCwd = worktreePath ?? cwd;
|