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
package/lib/cli/sync.js
CHANGED
|
@@ -26,6 +26,16 @@
|
|
|
26
26
|
* managed `.agents/` zone (everything except `.agents/local/`) that has
|
|
27
27
|
* no counterpart in the package payload is deleted. Consumer additions
|
|
28
28
|
* under `.agents/local/` are never touched.
|
|
29
|
+
* - Generated, never-pruned registry (Story #4534). Framework-generated
|
|
30
|
+
* `.agents/` files have no payload counterpart by design, so the prune
|
|
31
|
+
* pass would delete each one unless it is declared in
|
|
32
|
+
* {@link GENERATED_FILES}. That registry — not a per-file special case in
|
|
33
|
+
* {@link listDestFiles} — is the single place a generated artifact is
|
|
34
|
+
* exempted. Registered today: the version marker (Story #4530) and the
|
|
35
|
+
* install ledger the bootstrap writes and `mandrel uninstall` reads.
|
|
36
|
+
* - Version marker (Story #4530). After the prune pass, `.agents/.mandrel-
|
|
37
|
+
* version` is (re)written with the executing package's own version. See
|
|
38
|
+
* {@link readVersionMarker} for how callers consume it.
|
|
29
39
|
*
|
|
30
40
|
* Security (Tech Spec #3459 "Postinstall safety"):
|
|
31
41
|
* - Does nothing beyond a local file copy: no network, no shell, no writes
|
|
@@ -45,8 +55,19 @@ import nodeFs from 'node:fs';
|
|
|
45
55
|
import { createRequire } from 'node:module';
|
|
46
56
|
import path from 'node:path';
|
|
47
57
|
|
|
58
|
+
import { LEDGER_RELATIVE_PATH } from '../../.agents/scripts/lib/bootstrap/install-ledger.js';
|
|
59
|
+
|
|
48
60
|
export const PACKAGE_NAME = 'mandrel';
|
|
49
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Name of the managed destination directory, relative to the project root.
|
|
64
|
+
* Generating modules export their artifact paths relative to the *project
|
|
65
|
+
* root* (e.g. `install-ledger.js`'s `LEDGER_RELATIVE_PATH`), whereas the
|
|
66
|
+
* prune pass compares paths relative to this directory — {@link
|
|
67
|
+
* toManagedZoneRelative} is the one conversion between the two.
|
|
68
|
+
*/
|
|
69
|
+
const AGENTS_DIR = '.agents';
|
|
70
|
+
|
|
50
71
|
/**
|
|
51
72
|
* Top-level directory name (relative to `.agents/`) reserved as the
|
|
52
73
|
* sync-exempt local-additions zone (Story #3498, f-drift-local-zone).
|
|
@@ -70,6 +91,119 @@ export const LOCAL_ZONE_DIR = 'local';
|
|
|
70
91
|
*/
|
|
71
92
|
export const LOCAL_OVERRIDE_RE = /\.local\.[^.]+$/;
|
|
72
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Relative path (from `.agents/`) of the version marker `runSync` writes
|
|
96
|
+
* after every sync, carrying the executing package's own version.
|
|
97
|
+
*/
|
|
98
|
+
export const VERSION_MARKER_RELATIVE_PATH = '.mandrel-version';
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Convert a project-root-relative POSIX path — the shape a generating module
|
|
102
|
+
* exports as its own source of truth (e.g. `install-ledger.js`'s
|
|
103
|
+
* `LEDGER_RELATIVE_PATH`, `'.agents/.install-manifest.json'`) — into the
|
|
104
|
+
* `.agents/`-relative, OS-separator form {@link listDestFiles} enumerates and
|
|
105
|
+
* {@link GENERATED_FILES} is compared against.
|
|
106
|
+
*
|
|
107
|
+
* Throws on a path outside `.agents/`: a generated file the sync prune pass
|
|
108
|
+
* never walks has no business in the registry, and registering one would be a
|
|
109
|
+
* silent no-op that reads as protection. Failing at module load surfaces the
|
|
110
|
+
* mistake at the first import rather than as a mystery deletion in a
|
|
111
|
+
* consumer's tree.
|
|
112
|
+
*
|
|
113
|
+
* @param {string} projectRelativePosixPath - e.g. `.agents/.install-manifest.json`.
|
|
114
|
+
* @returns {string} e.g. `.install-manifest.json` (OS separators).
|
|
115
|
+
*/
|
|
116
|
+
function toManagedZoneRelative(projectRelativePosixPath) {
|
|
117
|
+
const segments = projectRelativePosixPath.split('/');
|
|
118
|
+
if (segments[0] !== AGENTS_DIR || segments.length < 2) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
`generated-files registry: '${projectRelativePosixPath}' is not inside ${AGENTS_DIR}/ — ` +
|
|
121
|
+
'only files the sync prune pass walks can be registered as never-pruned.',
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
return path.join(...segments.slice(1));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* The **generated, never-pruned registry**: every framework-generated
|
|
129
|
+
* `.agents/` file that has no payload counterpart and must therefore be
|
|
130
|
+
* exempted from the prune pass. Paths are `.agents/`-relative with OS
|
|
131
|
+
* separators, matching what {@link listDestFiles} enumerates.
|
|
132
|
+
*
|
|
133
|
+
* The prune pass (Story #4046 A3) deletes any managed-zone file absent from
|
|
134
|
+
* the payload. That is the correct rule for consumer drift, but it is also
|
|
135
|
+
* exactly the shape of every file the framework itself generates into
|
|
136
|
+
* `.agents/` outside the payload — so each such artifact is deleted by the very
|
|
137
|
+
* sync that wrote it unless registered here. Two have hit this already: the
|
|
138
|
+
* install ledger (`.agents/.install-manifest.json`), whose loss made
|
|
139
|
+
* `mandrel uninstall` silently reverse nothing and exit 0 (Story #4534), and
|
|
140
|
+
* the version marker (Story #4530).
|
|
141
|
+
*
|
|
142
|
+
* This is deliberately a **registry, not a list of special cases**. Register a
|
|
143
|
+
* new generated file by adding one entry here — keyed off the generating
|
|
144
|
+
* module's own exported constant wherever one exists, never a duplicated
|
|
145
|
+
* string literal — rather than by adding another branch to
|
|
146
|
+
* {@link listDestFiles}. That keeps the never-prune contract declared in one
|
|
147
|
+
* place and keeps the registry honest: if the generator renames its artifact,
|
|
148
|
+
* the exemption moves with it.
|
|
149
|
+
*
|
|
150
|
+
* This registry does **not** subsume the `.agents/local/` zone or the
|
|
151
|
+
* `*.local.*` overrides ({@link LOCAL_ZONE_DIR}, {@link LOCAL_OVERRIDE_RE}).
|
|
152
|
+
* Those are a different concept — consumer-authored overrides, not
|
|
153
|
+
* framework-generated artifacts — and stay separate on purpose.
|
|
154
|
+
*/
|
|
155
|
+
export const GENERATED_FILES = Object.freeze([
|
|
156
|
+
// Version marker (Story #4530) — written by runSync itself, below.
|
|
157
|
+
VERSION_MARKER_RELATIVE_PATH,
|
|
158
|
+
// Install ledger (Story #4534) — written by the bootstrap
|
|
159
|
+
// (`install-ledger.js`), read by `mandrel uninstall` as its single source of
|
|
160
|
+
// truth. Keyed off the generator's exported constant.
|
|
161
|
+
toManagedZoneRelative(LEDGER_RELATIVE_PATH),
|
|
162
|
+
]);
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Resolve the version of the package whose `.agents/` payload was just
|
|
166
|
+
* copied — i.e. the version the marker must carry. Reads `package.json` at
|
|
167
|
+
* the already-resolved `packageRoot`, never the two-directories-up
|
|
168
|
+
* self-referential pattern `lib/cli/update.js`'s pre-Story-#4530
|
|
169
|
+
* `defaultCurrentVersion` used (that pattern conflates "the CLI that is
|
|
170
|
+
* running" with "the payload that was materialized" — the exact confusion
|
|
171
|
+
* #4525 fixes on the consumer-pin side of this Story).
|
|
172
|
+
*
|
|
173
|
+
* @param {string} packageRoot - Absolute path to the resolved package root.
|
|
174
|
+
* @param {typeof nodeFs} fsImpl
|
|
175
|
+
* @returns {string}
|
|
176
|
+
*/
|
|
177
|
+
function resolvePackageVersion(packageRoot, fsImpl) {
|
|
178
|
+
const pkgJsonPath = path.join(packageRoot, 'package.json');
|
|
179
|
+
const parsed = JSON.parse(fsImpl.readFileSync(pkgJsonPath, 'utf8'));
|
|
180
|
+
return String(parsed.version);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Read the consumer's `.agents/.mandrel-version` marker. Returns `null` when
|
|
185
|
+
* absent — a pre-marker install, or a tree that has never been synced —
|
|
186
|
+
* so callers (the sync-commands mismatch refusal, the doctor pin check) can
|
|
187
|
+
* degrade to their existing content-hash drift check rather than failing.
|
|
188
|
+
*
|
|
189
|
+
* @param {string} consumerRoot - Consumer project root (not `.agents/`).
|
|
190
|
+
* @param {typeof nodeFs} [fsImpl]
|
|
191
|
+
* @returns {string | null}
|
|
192
|
+
*/
|
|
193
|
+
export function readVersionMarker(consumerRoot, fsImpl = nodeFs) {
|
|
194
|
+
const markerPath = path.join(
|
|
195
|
+
consumerRoot,
|
|
196
|
+
'.agents',
|
|
197
|
+
VERSION_MARKER_RELATIVE_PATH,
|
|
198
|
+
);
|
|
199
|
+
try {
|
|
200
|
+
const raw = fsImpl.readFileSync(markerPath, 'utf8').trim();
|
|
201
|
+
return raw || null;
|
|
202
|
+
} catch {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
73
207
|
/**
|
|
74
208
|
* Default resolver: locate the installed `mandrel` package root by
|
|
75
209
|
* resolving its `package.json` and returning the directory that contains it.
|
|
@@ -126,12 +260,22 @@ export function listFiles(dir, fsImpl, prefix = '') {
|
|
|
126
260
|
|
|
127
261
|
/**
|
|
128
262
|
* Recursively enumerate every regular file under `dir`, returning paths
|
|
129
|
-
* relative to `dir` using OS separators.
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
263
|
+
* relative to `dir` using OS separators. Three disjoint classes are skipped,
|
|
264
|
+
* so a caller diffing this against the payload never treats them as stale:
|
|
265
|
+
*
|
|
266
|
+
* 1. The top-level `local/` subtree — consumer additions inside
|
|
267
|
+
* `.agents/local/` are never pruned (Story #3498).
|
|
268
|
+
* 2. Files matching the `*.local.*` convention (e.g.
|
|
269
|
+
* `.agents/instructions.local.md`) — the documented consumer override
|
|
270
|
+
* mechanism (instructions.md § 1.E); they never ship in the payload and
|
|
271
|
+
* must survive every sync.
|
|
272
|
+
* 3. Everything declared in the {@link GENERATED_FILES} registry —
|
|
273
|
+
* framework-generated artifacts with no payload counterpart by design
|
|
274
|
+
* (Story #4534). New generated files are registered there, not added as
|
|
275
|
+
* a fourth branch here.
|
|
276
|
+
*
|
|
277
|
+
* (1) and (2) are consumer-authored; (3) is framework-generated. They are
|
|
278
|
+
* kept separate deliberately — conflating them would muddy both contracts.
|
|
135
279
|
*
|
|
136
280
|
* Mirrors `listFiles` but operates on the destination tree so we can
|
|
137
281
|
* identify stale files that have no payload counterpart (Story #4046 A3).
|
|
@@ -162,6 +306,11 @@ function listDestFiles(dir, fsImpl, prefix = '') {
|
|
|
162
306
|
continue;
|
|
163
307
|
}
|
|
164
308
|
const rel = prefix ? path.join(prefix, ent.name) : ent.name;
|
|
309
|
+
// Framework-generated artifacts — declared once in the never-pruned
|
|
310
|
+
// registry (Story #4534); see GENERATED_FILES.
|
|
311
|
+
if (!ent.isDirectory() && GENERATED_FILES.includes(rel)) {
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
165
314
|
const abs = path.join(dir, ent.name);
|
|
166
315
|
if (ent.isDirectory()) {
|
|
167
316
|
out.push(...listDestFiles(abs, fsImpl, rel));
|
|
@@ -273,6 +422,19 @@ export function runSync({
|
|
|
273
422
|
fs.unlinkSync(dest);
|
|
274
423
|
}
|
|
275
424
|
|
|
425
|
+
// Version marker (Story #4530): written AFTER the prune pass, never
|
|
426
|
+
// before — the GENERATED_FILES registry already keeps listDestFiles from
|
|
427
|
+
// ever enumerating it as stale, but ordering the write after prune is a
|
|
428
|
+
// second, independent safeguard against the same class of bug (see the
|
|
429
|
+
// GENERATED_FILES doc comment) surviving a future prune-pass reorder. The
|
|
430
|
+
// install ledger gets no such belt-and-braces: it is written by the
|
|
431
|
+
// bootstrap, not here, so the registry is its only protection.
|
|
432
|
+
const packageVersion = resolvePackageVersion(packageRoot, fs);
|
|
433
|
+
fs.writeFileSync(
|
|
434
|
+
path.join(destRoot, VERSION_MARKER_RELATIVE_PATH),
|
|
435
|
+
`${packageVersion}\n`,
|
|
436
|
+
);
|
|
437
|
+
|
|
276
438
|
if (staleFiles.length > 0) {
|
|
277
439
|
write(
|
|
278
440
|
`✅ Installed ${payloadFiles.length} file(s) into ./.agents/ (pruned ${staleFiles.length} stale file(s))\n`,
|
package/lib/cli/update.js
CHANGED
|
@@ -136,8 +136,12 @@ import { fileURLToPath } from 'node:url';
|
|
|
136
136
|
import { detectPackageManagerWithWorkspace } from '../../.agents/scripts/lib/detect-package-manager.js';
|
|
137
137
|
import { runInstallCommand } from '../../.agents/scripts/lib/install-cmd-parser.js';
|
|
138
138
|
import { runAgentsDrift } from './registry.js';
|
|
139
|
+
import { defaultResolvePackageRoot } from './sync.js';
|
|
139
140
|
import { isStale } from './version-check.js';
|
|
140
|
-
import {
|
|
141
|
+
import {
|
|
142
|
+
compareVersions,
|
|
143
|
+
resolveConsumerPinVersion,
|
|
144
|
+
} from './version-helpers.js';
|
|
141
145
|
|
|
142
146
|
/** The published package whose newest version `mandrel update` advances to. */
|
|
143
147
|
const PACKAGE_NAME = 'mandrel';
|
|
@@ -163,6 +167,15 @@ const DEFAULT_CACHE_FILENAME = 'version-check.json';
|
|
|
163
167
|
* `package.json`. The module lives at `<root>/lib/cli/update.js`, so the
|
|
164
168
|
* manifest is two directories up.
|
|
165
169
|
*
|
|
170
|
+
* Pre-Story-#4525 this was the update decision's `current` — the exact
|
|
171
|
+
* self-referential confusion #4525 filed: "the version of the mandrel that
|
|
172
|
+
* is executing" is tautologically `>= target` whenever the installed
|
|
173
|
+
* package is newest, which made the `npm-update` step unreachable whenever
|
|
174
|
+
* a consumer's declared pin had fallen behind what happened to be resolved
|
|
175
|
+
* in `node_modules`. It survives here only as the last-resort fallback
|
|
176
|
+
* inside {@link resolveCurrentVersionForUpdate}, for the case where even
|
|
177
|
+
* `node_modules` resolution from the consumer root fails.
|
|
178
|
+
*
|
|
166
179
|
* @param {typeof nodeFs} [fs]
|
|
167
180
|
* @returns {string}
|
|
168
181
|
*/
|
|
@@ -173,6 +186,48 @@ function defaultCurrentVersion(fs = nodeFs) {
|
|
|
173
186
|
return String(parsed.version);
|
|
174
187
|
}
|
|
175
188
|
|
|
189
|
+
/**
|
|
190
|
+
* Resolve the "current" version for the `mandrel update` decision
|
|
191
|
+
* (Story #4525 / #4530): the consumer's declared `mandrel` dependency pin
|
|
192
|
+
* when it resolves to a plain semver, falling back — in order — to the
|
|
193
|
+
* version actually resolvable in the consumer's `node_modules` (anchored at
|
|
194
|
+
* `consumerRoot` via the same resolution `mandrel sync` uses, unlike the
|
|
195
|
+
* pre-#4525 self-referential `defaultCurrentVersion`), and finally to
|
|
196
|
+
* `defaultCurrentVersion` itself when neither resolves (a corrupted or
|
|
197
|
+
* highly unusual install — keeps `mandrel update` from throwing outright
|
|
198
|
+
* rather than silently misreporting "already current").
|
|
199
|
+
*
|
|
200
|
+
* The declared pin is preferred because it is exactly what the `npm-update`
|
|
201
|
+
* step moves: a consumer whose `package.json` pin lags an inflated
|
|
202
|
+
* `node_modules` resolution (e.g. an out-of-band symlink or manual
|
|
203
|
+
* `npm install mandrel@latest --no-save`) must still see `planUpdate`
|
|
204
|
+
* choose `updated`, not `resynced` — the bug #4525 reported.
|
|
205
|
+
*
|
|
206
|
+
* @param {string} consumerRoot
|
|
207
|
+
* @param {typeof nodeFs} [fs]
|
|
208
|
+
* @param {{ resolvePackageRoot?: (fromDir: string) => string }} [opts] - test
|
|
209
|
+
* seam for the `node_modules` resolution tier; defaults to the real
|
|
210
|
+
* `defaultResolvePackageRoot` from `sync.js`.
|
|
211
|
+
* @returns {string}
|
|
212
|
+
*/
|
|
213
|
+
export function resolveCurrentVersionForUpdate(
|
|
214
|
+
consumerRoot,
|
|
215
|
+
fs = nodeFs,
|
|
216
|
+
{ resolvePackageRoot = defaultResolvePackageRoot } = {},
|
|
217
|
+
) {
|
|
218
|
+
const pinned = resolveConsumerPinVersion(consumerRoot, fs);
|
|
219
|
+
if (pinned) return pinned;
|
|
220
|
+
try {
|
|
221
|
+
const packageRoot = resolvePackageRoot(consumerRoot);
|
|
222
|
+
const parsed = JSON.parse(
|
|
223
|
+
fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'),
|
|
224
|
+
);
|
|
225
|
+
return String(parsed.version);
|
|
226
|
+
} catch {
|
|
227
|
+
return defaultCurrentVersion(fs);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
176
231
|
/**
|
|
177
232
|
* Resolve the project root — the directory two levels up from this module
|
|
178
233
|
* (`<root>/lib/cli/update.js`). Mirrors `lib/cli/registry.js#resolveProjectRoot`.
|
|
@@ -685,18 +740,20 @@ export function defaultSpawnPhase(
|
|
|
685
740
|
* The ordered step names the orchestrator drives on an update. Shared
|
|
686
741
|
* by the live path and the `--dry-run` plan printout so the two never drift.
|
|
687
742
|
*
|
|
688
|
-
* Step ordering (Story #4046 A1c):
|
|
743
|
+
* Step ordering (Story #4046 A1c; sync-agents added by Story #4528/#4530):
|
|
689
744
|
* 1. npm-update — install the new version
|
|
690
745
|
* 2. runSync — re-materialize .agents/ from the new payload
|
|
691
746
|
* 3. sync-commands — regenerate .claude/commands/ from the new payload
|
|
692
|
-
* 4.
|
|
693
|
-
* 5.
|
|
694
|
-
* 6.
|
|
747
|
+
* 4. sync-agents — regenerate .claude/agents/ from the new payload
|
|
748
|
+
* 5. runMigrations — apply version-keyed migrations
|
|
749
|
+
* 6. doctor — validate the post-upgrade state
|
|
750
|
+
* 7. surface changelog — print the changelog (always last, best-effort)
|
|
695
751
|
*/
|
|
696
752
|
const STEP_PLAN = [
|
|
697
753
|
'npm-update',
|
|
698
754
|
'runSync',
|
|
699
755
|
'sync-commands',
|
|
756
|
+
'sync-agents',
|
|
700
757
|
'runMigrations',
|
|
701
758
|
'doctor',
|
|
702
759
|
'surface changelog',
|
|
@@ -744,6 +801,20 @@ function fullUpgradeSteps(current, target) {
|
|
|
744
801
|
'the .claude/commands/ tree may be out of sync. ' +
|
|
745
802
|
'Run `npm run sync:commands` manually to restore.',
|
|
746
803
|
},
|
|
804
|
+
{
|
|
805
|
+
// Story #4528/#4530: the CLI update path previously never projected the
|
|
806
|
+
// role-agent tree at all — only the bootstrap path did. Added alongside
|
|
807
|
+
// sync-commands so `.claude/agents/` materializes here too, which is
|
|
808
|
+
// what makes the tightened `agents-in-sync` doctor check satisfiable.
|
|
809
|
+
kind: 'spawn',
|
|
810
|
+
phase: 'sync-agents',
|
|
811
|
+
args: [],
|
|
812
|
+
label: 'sync-agents',
|
|
813
|
+
failMessage:
|
|
814
|
+
'mandrel update: `mandrel sync-agents` from new binary exited non-zero — ' +
|
|
815
|
+
'the .claude/agents/ tree may be out of sync. ' +
|
|
816
|
+
'Run `mandrel sync-agents` manually to restore.',
|
|
817
|
+
},
|
|
747
818
|
{
|
|
748
819
|
kind: 'spawn',
|
|
749
820
|
phase: 'migrate',
|
|
@@ -760,8 +831,9 @@ function fullUpgradeSteps(current, target) {
|
|
|
760
831
|
|
|
761
832
|
/**
|
|
762
833
|
* The ordered phase descriptors for a drift-heal (version already current, but
|
|
763
|
-
* `.agents/` is stale). No npm-update, no migrations, no doctor — only the
|
|
764
|
-
* sync phases re-materialize the payload from the already-installed
|
|
834
|
+
* `.agents/` is stale). No npm-update, no migrations, no doctor — only the
|
|
835
|
+
* three sync phases re-materialize the payload from the already-installed
|
|
836
|
+
* binary.
|
|
765
837
|
*
|
|
766
838
|
* @returns {Array<{ kind: 'spawn', phase: string, args: string[], label: string, failMessage: string }>}
|
|
767
839
|
*/
|
|
@@ -787,6 +859,17 @@ function driftHealSteps() {
|
|
|
787
859
|
'the .claude/commands/ tree may be out of sync. ' +
|
|
788
860
|
'Run `npm run sync:commands` manually to restore.',
|
|
789
861
|
},
|
|
862
|
+
{
|
|
863
|
+
// Story #4528/#4530: see the matching entry in fullUpgradeSteps().
|
|
864
|
+
kind: 'spawn',
|
|
865
|
+
phase: 'sync-agents',
|
|
866
|
+
args: [],
|
|
867
|
+
label: 'sync-agents',
|
|
868
|
+
failMessage:
|
|
869
|
+
'mandrel update: `mandrel sync-agents` from installed binary exited non-zero — ' +
|
|
870
|
+
'the .claude/agents/ tree may be out of sync. ' +
|
|
871
|
+
'Run `mandrel sync-agents` manually to restore.',
|
|
872
|
+
},
|
|
790
873
|
];
|
|
791
874
|
}
|
|
792
875
|
|
|
@@ -1070,6 +1153,9 @@ export async function runUpdate({
|
|
|
1070
1153
|
write(
|
|
1071
1154
|
' 2. sync-commands — regenerate .claude/commands/ from .agents/workflows/\n',
|
|
1072
1155
|
);
|
|
1156
|
+
write(
|
|
1157
|
+
' 3. sync-agents — regenerate .claude/agents/ from .agents/agents/\n',
|
|
1158
|
+
);
|
|
1073
1159
|
write('Dry run: no files written.\n');
|
|
1074
1160
|
} else {
|
|
1075
1161
|
write(`mandrel update — planned upgrade v${current} → v${target}\n`);
|
|
@@ -1202,6 +1288,8 @@ export async function runUpdate({
|
|
|
1202
1288
|
* changelogPath?: string,
|
|
1203
1289
|
* fetchChangelog?: (version: string) => Promise<string>,
|
|
1204
1290
|
* runUpdate?: typeof runUpdate,
|
|
1291
|
+
* cwd?: () => string,
|
|
1292
|
+
* checkDrift?: () => (boolean | Promise<boolean>),
|
|
1205
1293
|
* write?: (s: string) => void,
|
|
1206
1294
|
* writeErr?: (s: string) => void,
|
|
1207
1295
|
* exit?: (code: number) => void,
|
|
@@ -1224,9 +1312,16 @@ export default async function run(argv = [], deps = {}) {
|
|
|
1224
1312
|
writeErr = (s) => process.stderr.write(s),
|
|
1225
1313
|
exit = (code) => process.exit(code),
|
|
1226
1314
|
log,
|
|
1315
|
+
cwd,
|
|
1316
|
+
checkDrift,
|
|
1227
1317
|
} = deps;
|
|
1228
1318
|
|
|
1229
|
-
const
|
|
1319
|
+
const cwdFn = typeof cwd === 'function' ? cwd : () => process.cwd();
|
|
1320
|
+
|
|
1321
|
+
// Story #4525/#4530: prefer the consumer's declared dependency pin over
|
|
1322
|
+
// the pre-#4525 self-referential read — see resolveCurrentVersionForUpdate.
|
|
1323
|
+
const current =
|
|
1324
|
+
deps.currentVersion ?? resolveCurrentVersionForUpdate(cwdFn(), fs);
|
|
1230
1325
|
|
|
1231
1326
|
// The production spawnPhase: spawn each post-install phase from
|
|
1232
1327
|
// node_modules/.bin/mandrel (the newly-installed binary). This is the sole
|
|
@@ -1260,6 +1355,7 @@ export default async function run(argv = [], deps = {}) {
|
|
|
1260
1355
|
runInstall: runInstall ?? runInstallCommand,
|
|
1261
1356
|
fs,
|
|
1262
1357
|
}),
|
|
1358
|
+
...(checkDrift ? { checkDrift } : {}),
|
|
1263
1359
|
spawnPhase: productionSpawnPhase,
|
|
1264
1360
|
surfaceChangelog: (target) =>
|
|
1265
1361
|
defaultSurfaceChangelog(target, {
|
|
@@ -1273,5 +1369,6 @@ export default async function run(argv = [], deps = {}) {
|
|
|
1273
1369
|
write,
|
|
1274
1370
|
writeErr,
|
|
1275
1371
|
exit,
|
|
1372
|
+
cwd: cwdFn,
|
|
1276
1373
|
});
|
|
1277
1374
|
}
|
|
@@ -7,11 +7,23 @@
|
|
|
7
7
|
* `compareVersions`; this module is the single authoritative
|
|
8
8
|
* implementation (Story #4048 B3 — multiplied helpers).
|
|
9
9
|
*
|
|
10
|
+
* `resolveConsumerPinVersion` (Story #4530) lives here for the same
|
|
11
|
+
* no-mirror-copies reason rather than in either file directly: `update.js`
|
|
12
|
+
* already imports `registry.js` (for the drift-check fallback), so a
|
|
13
|
+
* function needed by both would otherwise force a two-way circular import
|
|
14
|
+
* between them. This module is a dependency-free leaf both already import.
|
|
15
|
+
*
|
|
10
16
|
* Builtins only — this module is imported from both the CLI surface
|
|
11
17
|
* (`lib/cli/`) and the doctor registry which runs before third-party
|
|
12
18
|
* packages are guaranteed to be present.
|
|
13
19
|
*/
|
|
14
20
|
|
|
21
|
+
import nodeFs from 'node:fs';
|
|
22
|
+
import path from 'node:path';
|
|
23
|
+
|
|
24
|
+
/** The published package whose consumer pin is resolved below. */
|
|
25
|
+
const PACKAGE_NAME = 'mandrel';
|
|
26
|
+
|
|
15
27
|
/**
|
|
16
28
|
* Parse a dotted semver-ish string into a numeric tuple. Non-numeric or
|
|
17
29
|
* missing segments coerce to 0 so a partial version still compares sanely.
|
|
@@ -57,3 +69,122 @@ export function compareVersions(a, b) {
|
|
|
57
69
|
export function crossesMajor(current, target) {
|
|
58
70
|
return parseVersion(target)[0] > parseVersion(current)[0];
|
|
59
71
|
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Does `version` satisfy the range described by `spec` — a
|
|
75
|
+
* `{ operator, version }` pin spec as returned by
|
|
76
|
+
* {@link resolveConsumerPinSpec}?
|
|
77
|
+
*
|
|
78
|
+
* A deliberately minimal caret/tilde/exact implementation rather than a
|
|
79
|
+
* `semver` dependency: `version-helpers.js` is builtins-only (it is imported
|
|
80
|
+
* by the doctor registry, which runs before third-party packages are
|
|
81
|
+
* guaranteed to be present), and the only ranges
|
|
82
|
+
* {@link resolveConsumerPinSpec} ever yields are `^`, `~`, and exact.
|
|
83
|
+
*
|
|
84
|
+
* Semantics match npm's:
|
|
85
|
+
* - `''` (exact) → `version === spec.version`
|
|
86
|
+
* - `~1.2.3` → `>=1.2.3 <1.3.0`
|
|
87
|
+
* - `^1.2.3` → `>=1.2.3 <2.0.0`
|
|
88
|
+
* - `^0.2.3` → `>=0.2.3 <0.3.0` (0.x: the minor axis is the API axis)
|
|
89
|
+
* - `^0.0.3` → `>=0.0.3 <0.0.4`
|
|
90
|
+
*
|
|
91
|
+
* Prerelease tags are ignored on both sides, consistent with
|
|
92
|
+
* {@link parseVersion}'s numeric-tuple contract.
|
|
93
|
+
*
|
|
94
|
+
* @param {string} version
|
|
95
|
+
* @param {{ operator: '^' | '~' | '', version: string }} spec
|
|
96
|
+
* @returns {boolean}
|
|
97
|
+
*/
|
|
98
|
+
export function satisfiesPinSpec(version, spec) {
|
|
99
|
+
if (compareVersions(version, spec.version) < 0) return false;
|
|
100
|
+
const [vMajor, vMinor] = parseVersion(version);
|
|
101
|
+
const [pMajor, pMinor, pPatch] = parseVersion(spec.version);
|
|
102
|
+
|
|
103
|
+
if (spec.operator === '~') {
|
|
104
|
+
return vMajor === pMajor && vMinor === pMinor;
|
|
105
|
+
}
|
|
106
|
+
if (spec.operator === '^') {
|
|
107
|
+
if (pMajor > 0) return vMajor === pMajor;
|
|
108
|
+
if (pMinor > 0) return vMajor === 0 && vMinor === pMinor;
|
|
109
|
+
// ^0.0.x pins the patch axis exactly.
|
|
110
|
+
return compareVersions(version, `0.0.${pPatch}`) === 0;
|
|
111
|
+
}
|
|
112
|
+
return compareVersions(version, spec.version) === 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Read the consumer's declared `mandrel` dependency pin from
|
|
117
|
+
* `<consumerRoot>/package.json` (checking `dependencies` then
|
|
118
|
+
* `devDependencies`), returning both the range `operator` and the base
|
|
119
|
+
* `version` it decorates.
|
|
120
|
+
*
|
|
121
|
+
* Callers that only need to decide whether `npm install mandrel@<target>`
|
|
122
|
+
* would move the declared pin want {@link resolveConsumerPinVersion} (the
|
|
123
|
+
* base version alone). Callers that need to ask whether an installed
|
|
124
|
+
* version is *in range* — `mandrel doctor`'s `pin-current` check — need the
|
|
125
|
+
* operator too, because `^2.1.0` and `2.1.0` make very different claims
|
|
126
|
+
* about an installed 2.4.0.
|
|
127
|
+
*
|
|
128
|
+
* Returns `null` under exactly the same conditions as
|
|
129
|
+
* {@link resolveConsumerPinVersion} — see its contract.
|
|
130
|
+
*
|
|
131
|
+
* @param {string} consumerRoot
|
|
132
|
+
* @param {typeof nodeFs} [fsImpl]
|
|
133
|
+
* @returns {{ operator: '^' | '~' | '', version: string } | null}
|
|
134
|
+
*/
|
|
135
|
+
export function resolveConsumerPinSpec(consumerRoot, fsImpl = nodeFs) {
|
|
136
|
+
let parsed;
|
|
137
|
+
try {
|
|
138
|
+
const raw = fsImpl.readFileSync(
|
|
139
|
+
path.join(consumerRoot, 'package.json'),
|
|
140
|
+
'utf8',
|
|
141
|
+
);
|
|
142
|
+
parsed = JSON.parse(raw);
|
|
143
|
+
} catch {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
const declared =
|
|
147
|
+
parsed?.dependencies?.[PACKAGE_NAME] ??
|
|
148
|
+
parsed?.devDependencies?.[PACKAGE_NAME];
|
|
149
|
+
if (typeof declared !== 'string') return null;
|
|
150
|
+
const match = /^([\^~]?)(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$/.exec(
|
|
151
|
+
declared.trim(),
|
|
152
|
+
);
|
|
153
|
+
if (!match) return null;
|
|
154
|
+
return {
|
|
155
|
+
operator: /** @type {'^' | '~' | ''} */ (match[1]),
|
|
156
|
+
version: match[2],
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Read the consumer's declared `mandrel` dependency pin from
|
|
162
|
+
* `<consumerRoot>/package.json` (checking `dependencies` then
|
|
163
|
+
* `devDependencies`), stripping a leading `^`/`~` range operator to
|
|
164
|
+
* recover a base semver for comparison against the registry's newest
|
|
165
|
+
* published version.
|
|
166
|
+
*
|
|
167
|
+
* A base-version compare against the target is the right question here
|
|
168
|
+
* regardless of what the full range technically already matches: the
|
|
169
|
+
* declared pin is exactly what `npm install mandrel@<target>` (the
|
|
170
|
+
* `mandrel update` `npm-update` step) moves, so comparing its base version
|
|
171
|
+
* decides whether that step needs to run.
|
|
172
|
+
*
|
|
173
|
+
* Returns `null` — never throws — when there is no readable `package.json`,
|
|
174
|
+
* no `mandrel` entry in either dependency block, or the declared value
|
|
175
|
+
* isn't a plain exact/caret/tilde semver (a `workspace:`/`file:`/`git+`
|
|
176
|
+
* specifier, `latest`, `*`, or a comparator range). Callers fall back to
|
|
177
|
+
* resolving the actually-installed version in that case — see
|
|
178
|
+
* `lib/cli/update.js#resolveCurrentVersionForUpdate` and
|
|
179
|
+
* `lib/cli/registry.js`'s `pin-current` doctor check (both Story #4530),
|
|
180
|
+
* which is also how a project with no `mandrel` dependency at all —
|
|
181
|
+
* including mandrel's own repo, which carries no self-dependency —
|
|
182
|
+
* degrades to a clean skip rather than a false failure.
|
|
183
|
+
*
|
|
184
|
+
* @param {string} consumerRoot
|
|
185
|
+
* @param {typeof nodeFs} [fsImpl]
|
|
186
|
+
* @returns {string | null}
|
|
187
|
+
*/
|
|
188
|
+
export function resolveConsumerPinVersion(consumerRoot, fsImpl = nodeFs) {
|
|
189
|
+
return resolveConsumerPinSpec(consumerRoot, fsImpl)?.version ?? null;
|
|
190
|
+
}
|
package/lib/migrations/README.md
CHANGED
|
@@ -9,11 +9,13 @@ filtering, idempotency enforcement, and the actionable per-step log line. The
|
|
|
9
9
|
`migrations` array is the single source of truth for which steps exist and in
|
|
10
10
|
what order they run.
|
|
11
11
|
|
|
12
|
-
>
|
|
13
|
-
>
|
|
14
|
-
>
|
|
15
|
-
>
|
|
16
|
-
>
|
|
12
|
+
> Story #4531 registered the first real step —
|
|
13
|
+
> [`steps/2.1.0-retire-mi-drop-knobs.js`](./steps/2.1.0-retire-mi-drop-knobs.js),
|
|
14
|
+
> which strips the retired `codingGuardrails.miDropMustRefactor` and
|
|
15
|
+
> `autoRefresh.miDropCap` keys from a consumer's `.agentrc.json`. The
|
|
16
|
+
> machinery itself is still exercised independently by fixture steps in
|
|
17
|
+
> [`__tests__/index.test.js`](./__tests__/index.test.js). Add the next real
|
|
18
|
+
> step here (ascending by version) when the next contract cutover lands.
|
|
17
19
|
|
|
18
20
|
## Step shape
|
|
19
21
|
|
package/lib/migrations/index.js
CHANGED
|
@@ -43,18 +43,22 @@
|
|
|
43
43
|
* already in the tree (the consumer was on that version) and is skipped; a
|
|
44
44
|
* step at exactly `toVersion` is the target and runs.
|
|
45
45
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
46
|
+
* v2.0.0's Story-collapse cutover did not register a config migration step
|
|
47
|
+
* (consumers wipe/re-sync `.agents/` and re-seed `.agentrc.json` from the
|
|
48
|
+
* starter). Story #4531 registered the first real step — see
|
|
49
|
+
* `steps/2.1.0-retire-mi-drop-knobs.js` — alongside the fixture steps in
|
|
50
|
+
* `__tests__/index.test.js` that prove the machinery independent of any
|
|
51
|
+
* real step. When the next contract cutover lands, add its step here
|
|
52
|
+
* (ascending by version) with an idempotent `detect`/`apply`.
|
|
52
53
|
*/
|
|
53
54
|
|
|
55
|
+
import { retireMiDropKnobs } from './steps/2.1.0-retire-mi-drop-knobs.js';
|
|
56
|
+
import { retireVerifyConcurrencyCap } from './steps/2.1.0-retire-verify-concurrency-cap.js';
|
|
57
|
+
import { retireEpicAcTags } from './steps/2.2.0-retire-epic-ac-tags.js';
|
|
58
|
+
|
|
54
59
|
/**
|
|
55
60
|
* Ordered registry of migration steps. MUST stay sorted ascending by
|
|
56
|
-
* `version`.
|
|
57
|
-
* here.
|
|
61
|
+
* `version`.
|
|
58
62
|
*
|
|
59
63
|
* @type {Array<{
|
|
60
64
|
* version: string,
|
|
@@ -63,7 +67,11 @@
|
|
|
63
67
|
* apply: (ctx: unknown) => void,
|
|
64
68
|
* }>}
|
|
65
69
|
*/
|
|
66
|
-
export const migrations = [
|
|
70
|
+
export const migrations = [
|
|
71
|
+
retireMiDropKnobs,
|
|
72
|
+
retireVerifyConcurrencyCap,
|
|
73
|
+
retireEpicAcTags,
|
|
74
|
+
];
|
|
67
75
|
|
|
68
76
|
/**
|
|
69
77
|
* Parse a dotted semver-ish string into a numeric tuple for comparison.
|