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/registry.js
CHANGED
|
@@ -31,12 +31,17 @@ import {
|
|
|
31
31
|
satisfiesNodeEngine,
|
|
32
32
|
} from '../../.agents/scripts/lib/bootstrap/project-bootstrap.js';
|
|
33
33
|
import { isCommandExcluded } from '../../.agents/scripts/lib/command-header.js';
|
|
34
|
+
import { getDeliveryRouting } from '../../.agents/scripts/lib/config/delivery-routing.js';
|
|
34
35
|
import {
|
|
35
36
|
defaultResolvePackageRoot,
|
|
36
37
|
listFiles as listPayloadFiles,
|
|
37
38
|
} from './sync.js';
|
|
38
39
|
import { readCache } from './version-check.js';
|
|
39
|
-
import {
|
|
40
|
+
import {
|
|
41
|
+
compareVersions as compareSemver,
|
|
42
|
+
resolveConsumerPinSpec,
|
|
43
|
+
satisfiesPinSpec,
|
|
44
|
+
} from './version-helpers.js';
|
|
40
45
|
|
|
41
46
|
// ---------------------------------------------------------------------------
|
|
42
47
|
// Internal helpers
|
|
@@ -309,6 +314,60 @@ function runCommandsInSync({ projectRoot, cwd, readDir, readFile } = {}) {
|
|
|
309
314
|
// check: agents-in-sync
|
|
310
315
|
// ---------------------------------------------------------------------------
|
|
311
316
|
|
|
317
|
+
/**
|
|
318
|
+
* Best-effort raw JSON read — no AJV, no merge validation. Absent, unreadable,
|
|
319
|
+
* or malformed files degrade to `null` so a config problem never crashes a
|
|
320
|
+
* doctor check; the caller applies framework defaults on `null`.
|
|
321
|
+
*
|
|
322
|
+
* @param {string} absPath
|
|
323
|
+
* @param {typeof fs} fsImpl
|
|
324
|
+
* @returns {object | null}
|
|
325
|
+
*/
|
|
326
|
+
function readJsonSafe(absPath, fsImpl) {
|
|
327
|
+
try {
|
|
328
|
+
return JSON.parse(fsImpl.readFileSync(absPath, 'utf8'));
|
|
329
|
+
} catch {
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Resolve `delivery.routing.roleScopedAgents` (default `true`) for
|
|
336
|
+
* `agents-in-sync`'s fatal/advisory branch below, WITHOUT importing the full
|
|
337
|
+
* `resolveConfig()` chain — that chain pulls in AJV (`config-schema.js`),
|
|
338
|
+
* which would violate this module's own "Node built-ins only" contract (see
|
|
339
|
+
* the module doc: registry.js must load inside the preflight guard, before
|
|
340
|
+
* any third-party package is guaranteed present).
|
|
341
|
+
*
|
|
342
|
+
* Reads `.agentrc.json` and `.agentrc.local.json` directly and shallow-merges
|
|
343
|
+
* only the `delivery.routing` block (local wins per-key) — narrower than the
|
|
344
|
+
* resolver's full deep-merge, but sufficient for this one boolean, and it
|
|
345
|
+
* still honours a local override rather than silently ignoring it. Delegates
|
|
346
|
+
* the actual default/coercion to `getDeliveryRouting` (`config/delivery-
|
|
347
|
+
* routing.js`), which has zero imports of its own and is already the single
|
|
348
|
+
* source of truth for this default elsewhere in the framework.
|
|
349
|
+
*
|
|
350
|
+
* @param {string} projectRoot
|
|
351
|
+
* @param {typeof fs} fsImpl
|
|
352
|
+
* @returns {boolean}
|
|
353
|
+
*/
|
|
354
|
+
function resolveRoleScopedAgentsFlag(projectRoot, fsImpl) {
|
|
355
|
+
const base = readJsonSafe(path.join(projectRoot, '.agentrc.json'), fsImpl);
|
|
356
|
+
const local = readJsonSafe(
|
|
357
|
+
path.join(projectRoot, '.agentrc.local.json'),
|
|
358
|
+
fsImpl,
|
|
359
|
+
);
|
|
360
|
+
const merged = {
|
|
361
|
+
delivery: {
|
|
362
|
+
routing: {
|
|
363
|
+
...(base?.delivery?.routing ?? {}),
|
|
364
|
+
...(local?.delivery?.routing ?? {}),
|
|
365
|
+
},
|
|
366
|
+
},
|
|
367
|
+
};
|
|
368
|
+
return getDeliveryRouting(merged).roleScopedAgents;
|
|
369
|
+
}
|
|
370
|
+
|
|
312
371
|
/**
|
|
313
372
|
* Dry-run the sync-claude-agents logic: compare `.agents/agents/*.md` sources
|
|
314
373
|
* to the generated `.claude/agents/*.md` role-agent tree and report parity.
|
|
@@ -326,30 +385,52 @@ function runCommandsInSync({ projectRoot, cwd, readDir, readFile } = {}) {
|
|
|
326
385
|
* A repo with no `.agents/agents/` sources and no `.claude/agents/` tree is a
|
|
327
386
|
* clean no-op (0 sources, 0 dests → "up to date").
|
|
328
387
|
*
|
|
329
|
-
* **
|
|
330
|
-
*
|
|
331
|
-
*
|
|
332
|
-
*
|
|
333
|
-
*
|
|
334
|
-
*
|
|
335
|
-
*
|
|
336
|
-
*
|
|
388
|
+
* **Tightened from advisory to fatal (Story #4530, M7-B).** Prior to this
|
|
389
|
+
* Story, a **never materialized** agent tree (sources present, `.claude/
|
|
390
|
+
* agents/` empty) was reported as advisory — "inert", because no workflow
|
|
391
|
+
* spawned the role agents yet. M7-B has landed: `helpers/deliver-story` Step
|
|
392
|
+
* 1a dispatches `subagent_type: acceptance-critic` on the **default** path
|
|
393
|
+
* (`roleScopedAgents` defaults `true`). An unmaterialized tree under that
|
|
394
|
+
* default is no longer a benign scaffolding gap — it is the acceptance
|
|
395
|
+
* ceremony's fresh-context critic silently failing to spawn and falling back
|
|
396
|
+
* to a weaker inline critic, with doctor reporting green throughout. This
|
|
397
|
+
* check now fails in exactly that case. When `roleScopedAgents` is `false`
|
|
398
|
+
* (the operator kill-switch), an unmaterialized tree is expected — the check
|
|
399
|
+
* stays advisory. Once the tree HAS been materialized, drift (a source
|
|
400
|
+
* missing from the dest, or a stale dest file) fails the check exactly like
|
|
401
|
+
* `commands-in-sync`, unconditionally, exactly as before this Story.
|
|
337
402
|
*
|
|
338
403
|
* Injectable seams (used by tests so no real filesystem is touched):
|
|
339
404
|
* - `cwd()` replaces `process.cwd` so tests can pin the consumer root.
|
|
340
405
|
* - `readDir` replaces `fs.readdirSync`.
|
|
406
|
+
* - `fsImpl` replaces the `node:fs` surface used for the `.agentrc.json` /
|
|
407
|
+
* `.agentrc.local.json` reads.
|
|
408
|
+
* - `roleScopedAgents` overrides the resolved flag directly, bypassing the
|
|
409
|
+
* config reads entirely.
|
|
341
410
|
*
|
|
342
|
-
* @param {{
|
|
411
|
+
* @param {{
|
|
412
|
+
* projectRoot?: string,
|
|
413
|
+
* cwd?: () => string,
|
|
414
|
+
* readDir?: (dir: string) => string[],
|
|
415
|
+
* fsImpl?: typeof fs,
|
|
416
|
+
* roleScopedAgents?: boolean,
|
|
417
|
+
* }} [opts]
|
|
343
418
|
* @returns {{ ok: boolean, detail: string, remedy?: string }}
|
|
344
419
|
*/
|
|
345
|
-
function runAgentsInSync({
|
|
420
|
+
function runAgentsInSync({
|
|
421
|
+
projectRoot,
|
|
422
|
+
cwd,
|
|
423
|
+
readDir,
|
|
424
|
+
fsImpl = fs,
|
|
425
|
+
roleScopedAgents,
|
|
426
|
+
} = {}) {
|
|
346
427
|
const getCwd = cwd ?? (() => process.cwd());
|
|
347
428
|
const root = projectRoot ?? getCwd();
|
|
348
429
|
const listDir =
|
|
349
430
|
readDir ??
|
|
350
431
|
((dir) => {
|
|
351
432
|
try {
|
|
352
|
-
return
|
|
433
|
+
return fsImpl.readdirSync(dir).filter((f) => f.endsWith('.md'));
|
|
353
434
|
} catch {
|
|
354
435
|
return [];
|
|
355
436
|
}
|
|
@@ -365,14 +446,26 @@ function runAgentsInSync({ projectRoot, cwd, readDir } = {}) {
|
|
|
365
446
|
.filter((f) => !f.startsWith('.'))
|
|
366
447
|
.sort();
|
|
367
448
|
|
|
368
|
-
// Never-materialized agent tree: inert, so advisory (ok:true), not fatal.
|
|
369
449
|
if (dests.length === 0) {
|
|
450
|
+
if (sources.length === 0) {
|
|
451
|
+
return { ok: true, detail: '0 agents up to date' };
|
|
452
|
+
}
|
|
453
|
+
const resolvedFlag =
|
|
454
|
+
typeof roleScopedAgents === 'boolean'
|
|
455
|
+
? roleScopedAgents
|
|
456
|
+
: resolveRoleScopedAgentsFlag(root, fsImpl);
|
|
457
|
+
if (!resolvedFlag) {
|
|
458
|
+
// Kill-switch off: an unmaterialized tree is expected, not a defect.
|
|
459
|
+
return {
|
|
460
|
+
ok: true,
|
|
461
|
+
detail: `${sources.length} agent def(s) not materialized (roleScopedAgents is disabled)`,
|
|
462
|
+
};
|
|
463
|
+
}
|
|
370
464
|
return {
|
|
371
|
-
ok:
|
|
372
|
-
detail:
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
: `${sources.length} agent def(s) not yet materialized (inert)`,
|
|
465
|
+
ok: false,
|
|
466
|
+
detail: `${sources.length} agent def(s) not yet materialized in .claude/agents/`,
|
|
467
|
+
remedy:
|
|
468
|
+
'Run `mandrel sync-agents` to regenerate the `.claude/agents/` tree.',
|
|
376
469
|
};
|
|
377
470
|
}
|
|
378
471
|
|
|
@@ -392,7 +485,7 @@ function runAgentsInSync({ projectRoot, cwd, readDir } = {}) {
|
|
|
392
485
|
ok: false,
|
|
393
486
|
detail: parts.join(', '),
|
|
394
487
|
remedy:
|
|
395
|
-
'Run `
|
|
488
|
+
'Run `mandrel sync-agents` to regenerate the `.claude/agents/` tree.',
|
|
396
489
|
};
|
|
397
490
|
}
|
|
398
491
|
|
|
@@ -705,6 +798,149 @@ export function runAgentsDrift({ cwd, fsImpl = fs, resolvePackageRoot } = {}) {
|
|
|
705
798
|
};
|
|
706
799
|
}
|
|
707
800
|
|
|
801
|
+
// ---------------------------------------------------------------------------
|
|
802
|
+
// check: pin-current
|
|
803
|
+
// ---------------------------------------------------------------------------
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* Fatal doctor check (Story #4525 / #4530): is the version actually
|
|
807
|
+
* resolvable in `node_modules` **consistent with** the consumer's declared
|
|
808
|
+
* `mandrel` dependency range (`package.json`)?
|
|
809
|
+
*
|
|
810
|
+
* This is a **different** signal from `agents-drift` (are the materialized
|
|
811
|
+
* `.agents/` file *contents* byte-identical to the installed payload) and
|
|
812
|
+
* from `version-current` below (is a *newer* version *published*). Neither
|
|
813
|
+
* of those catches the shape of bug #4525 reported: a `package.json` pin
|
|
814
|
+
* that has fallen out of agreement with what `node_modules` actually
|
|
815
|
+
* resolves to — e.g. an out-of-band symlink, or `npm install mandrel@latest
|
|
816
|
+
* --no-save` — which `mandrel doctor` graded as fully green before this
|
|
817
|
+
* check existed, because the only version `defaultCurrentVersion` ever read
|
|
818
|
+
* WAS the inflated `node_modules` one.
|
|
819
|
+
*
|
|
820
|
+
* **Range-aware.** The declared pin is a *range*, not an equality, so the
|
|
821
|
+
* question this check asks is satisfaction, not identity. A consumer who
|
|
822
|
+
* declares `^2.1.0` and runs a routine `npm update` gets 2.4.0 in
|
|
823
|
+
* `node_modules` with `package.json` untouched — that is npm behaving
|
|
824
|
+
* exactly as documented, and under this project's `always-bump-minor`
|
|
825
|
+
* release cadence it is the *normal* steady state, not a defect. Grading it
|
|
826
|
+
* fatal (as this check originally did, comparing only the pin's base
|
|
827
|
+
* version) made `mandrel doctor` cry wolf on healthy installs.
|
|
828
|
+
*
|
|
829
|
+
* The graded contract:
|
|
830
|
+
* - installed **satisfies** the declared range and equals its base version
|
|
831
|
+
* → pass, quietly.
|
|
832
|
+
* - installed **satisfies** the declared range but is newer than its base
|
|
833
|
+
* (`^2.1.0` + 2.4.0) → pass with an **advisory** `detail`. Re-pinning via
|
|
834
|
+
* `mandrel update` is tidier, but nothing is broken. This mirrors how
|
|
835
|
+
* `version-current` stays non-fatal: `ok: true` with the advisory carried
|
|
836
|
+
* in `detail` (the registry's `advisory: true` flag documents an entry
|
|
837
|
+
* whose `run()` *never* fails, which is not true of this check).
|
|
838
|
+
* - installed does **not** satisfy the declared range (`^2.1.0` + 3.0.0, or
|
|
839
|
+
* an exact `2.1.0` pin + 2.4.0) → **fail**. The declared dependency does
|
|
840
|
+
* not describe the code that is running.
|
|
841
|
+
* - installed is **behind** the pin's base version (`^2.4.0` + 2.1.0) →
|
|
842
|
+
* **fail**, and note that `<` the base never satisfies any of the ranges
|
|
843
|
+
* `resolveConsumerPinSpec` yields, so this is a sub-case of the above
|
|
844
|
+
* given a distinct, more actionable message.
|
|
845
|
+
*
|
|
846
|
+
* Skips cleanly (not a failure) when there is no resolvable pin at all — no
|
|
847
|
+
* `package.json`, no `mandrel` entry in `dependencies`/`devDependencies`, or
|
|
848
|
+
* a non-semver specifier (`workspace:`, `git+`, `latest`, a comparator
|
|
849
|
+
* range) — via {@link resolveConsumerPinSpec}'s own `null` contract. This
|
|
850
|
+
* is also how mandrel's own repo (no self-dependency) and workspace/npx
|
|
851
|
+
* consumers degrade to a clean pass rather than a false failure.
|
|
852
|
+
*
|
|
853
|
+
* Injectable seams (used by tests so no real filesystem or package is
|
|
854
|
+
* needed):
|
|
855
|
+
* - `cwd()` — replaces `process.cwd`, anchoring both the pin read and the
|
|
856
|
+
* `node_modules` resolution at the same consumer root (mirrors
|
|
857
|
+
* `agents-drift` / `agents-in-sync`).
|
|
858
|
+
* - `fsImpl` — replaces the `node:fs` surface.
|
|
859
|
+
* - `resolvePackageRoot(fromDir)` — replaces `mandrel` resolution; throws
|
|
860
|
+
* when the package is not installed.
|
|
861
|
+
*
|
|
862
|
+
* @param {{
|
|
863
|
+
* cwd?: () => string,
|
|
864
|
+
* fsImpl?: typeof fs,
|
|
865
|
+
* resolvePackageRoot?: (fromDir: string) => string,
|
|
866
|
+
* }} [opts]
|
|
867
|
+
* @returns {{ ok: boolean, detail: string, remedy?: string }}
|
|
868
|
+
*/
|
|
869
|
+
export function runPinCurrent({ cwd, fsImpl = fs, resolvePackageRoot } = {}) {
|
|
870
|
+
const getCwd = cwd ?? (() => process.cwd());
|
|
871
|
+
const resolveRoot = resolvePackageRoot ?? defaultResolvePackageRoot;
|
|
872
|
+
const projectRoot = getCwd();
|
|
873
|
+
|
|
874
|
+
const spec = resolveConsumerPinSpec(projectRoot, fsImpl);
|
|
875
|
+
if (!spec) {
|
|
876
|
+
return {
|
|
877
|
+
ok: true,
|
|
878
|
+
detail: 'no resolvable mandrel dependency pin — skipped',
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
let installedRoot;
|
|
883
|
+
try {
|
|
884
|
+
installedRoot = resolveRoot(projectRoot);
|
|
885
|
+
} catch {
|
|
886
|
+
// No baseline to compare the pin against; agents-materialized /
|
|
887
|
+
// runtime-deps own the "not installed" remedy.
|
|
888
|
+
return {
|
|
889
|
+
ok: true,
|
|
890
|
+
detail: 'mandrel not installed — pin check skipped',
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
let installed;
|
|
895
|
+
try {
|
|
896
|
+
const parsed = JSON.parse(
|
|
897
|
+
fsImpl.readFileSync(path.join(installedRoot, 'package.json'), 'utf8'),
|
|
898
|
+
);
|
|
899
|
+
installed = String(parsed.version);
|
|
900
|
+
} catch {
|
|
901
|
+
return {
|
|
902
|
+
ok: true,
|
|
903
|
+
detail: 'installed version unreadable — pin check skipped',
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
const declared = `${spec.operator}${spec.version}`;
|
|
908
|
+
|
|
909
|
+
if (compareSemver(installed, spec.version) < 0) {
|
|
910
|
+
return {
|
|
911
|
+
ok: false,
|
|
912
|
+
detail: `package.json pins ${declared} but an older v${installed} is installed`,
|
|
913
|
+
remedy:
|
|
914
|
+
'Run `npm install` to install a version satisfying the package.json pin.',
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
if (!satisfiesPinSpec(installed, spec)) {
|
|
919
|
+
return {
|
|
920
|
+
ok: false,
|
|
921
|
+
detail: `package.json pins ${declared} but v${installed} is installed — outside the declared range`,
|
|
922
|
+
remedy:
|
|
923
|
+
'Run `mandrel update` to reconcile the package.json pin with the installed version.',
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
if (compareSemver(installed, spec.version) > 0) {
|
|
928
|
+
// In range, just newer than the declared base — the ordinary result of an
|
|
929
|
+
// `npm update` under a caret/tilde pin. Healthy, so `ok: true`; the
|
|
930
|
+
// advisory rides in `detail` (the doctor runner only prints `remedy` for
|
|
931
|
+
// failures).
|
|
932
|
+
return {
|
|
933
|
+
ok: true,
|
|
934
|
+
detail: `v${installed} satisfies the ${declared} pin but is newer than it — run \`mandrel update\` to re-pin (advisory)`,
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
return {
|
|
939
|
+
ok: true,
|
|
940
|
+
detail: `pin ${declared} matches the installed version`,
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
|
|
708
944
|
// ---------------------------------------------------------------------------
|
|
709
945
|
// check: version-current
|
|
710
946
|
// ---------------------------------------------------------------------------
|
|
@@ -870,6 +1106,13 @@ export const registry = [
|
|
|
870
1106
|
name: 'agents-drift',
|
|
871
1107
|
run: (opts) => runAgentsDrift(opts),
|
|
872
1108
|
},
|
|
1109
|
+
{
|
|
1110
|
+
name: 'pin-current',
|
|
1111
|
+
// Fatal, unlike version-current below (Story #4525/#4530): a pin/install
|
|
1112
|
+
// disagreement means the declared dependency does not describe the code
|
|
1113
|
+
// that is running.
|
|
1114
|
+
run: (opts) => runPinCurrent(opts),
|
|
1115
|
+
},
|
|
873
1116
|
{
|
|
874
1117
|
name: 'version-current',
|
|
875
1118
|
// Non-fatal: surfaces a cache-only stale-version advisory. `run()` always
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// lib/cli/sync-agents.js
|
|
2
|
+
/**
|
|
3
|
+
* `mandrel sync-agents` subcommand (Story #4528 / #4530).
|
|
4
|
+
*
|
|
5
|
+
* Thin wrapper that delegates to the canonical sync script
|
|
6
|
+
* (.agents/scripts/sync-claude-agents.js), projecting `.agents/agents/` into
|
|
7
|
+
* a flat `.claude/agents/` tree. Exact sibling of `sync-commands.js` —
|
|
8
|
+
* same delegation shape, same marker-gated refusal check, same seams —
|
|
9
|
+
* targeting the role-agent tree instead of the command tree. The sync
|
|
10
|
+
* script owns all sync logic; this module exists only to expose it through
|
|
11
|
+
* the mandrel CLI surface without reimplementing it.
|
|
12
|
+
*
|
|
13
|
+
* `sync-claude-agents.js` uses top-level await and has no exported `main()`
|
|
14
|
+
* function (marked `cli-opt-out`), so delegation runs via a child process
|
|
15
|
+
* rather than a direct import. Exit code and all output are forwarded
|
|
16
|
+
* verbatim to the caller so `mandrel sync-agents` is transparent to scripts
|
|
17
|
+
* that check $?.
|
|
18
|
+
*
|
|
19
|
+
* ## Prior to this Story
|
|
20
|
+
*
|
|
21
|
+
* `sync-claude-agents.js` was invoked on the **bootstrap** path
|
|
22
|
+
* (`project-bootstrap.js`'s `sync:agents` script, `prepare`, and
|
|
23
|
+
* `runSyncCommands`) but had no `lib/cli/` subcommand counterpart and was
|
|
24
|
+
* never run by `mandrel sync` / `mandrel update` on the CLI path — the gap
|
|
25
|
+
* #4528 reported. See `update.js`'s `driftHealSteps()` /
|
|
26
|
+
* `fullUpgradeSteps()`, which now spawn a `sync-agents` phase alongside
|
|
27
|
+
* `sync-commands` so `.claude/agents/` materializes on that path too.
|
|
28
|
+
*
|
|
29
|
+
* ## Marker-gated refusal (Story #4526 / #4530) — same shape as sync-commands
|
|
30
|
+
*
|
|
31
|
+
* Before delegating, this wrapper checks whether the `.agents/` tree it is
|
|
32
|
+
* about to project agents FROM actually matches the running CLI's own
|
|
33
|
+
* payload version, exactly as `sync-commands.js` does for the command tree.
|
|
34
|
+
* See that module's doc comment for the full rationale and the anchor-trap
|
|
35
|
+
* warning — it applies identically here (`PROJECT_ROOT` is deliberately the
|
|
36
|
+
* package root, used only to read the running CLI's OWN version; the marker
|
|
37
|
+
* read is anchored at `cwd()`, never at `PROJECT_ROOT`).
|
|
38
|
+
*
|
|
39
|
+
* Gate order:
|
|
40
|
+
* 1. Marker present (`.agents/.mandrel-version`) → compare it to the
|
|
41
|
+
* running CLI's own package version. Mismatch → refuse.
|
|
42
|
+
* 2. Marker absent (a pre-marker install) → fall back to the existing
|
|
43
|
+
* `agents-drift` content-hash check. Drift detected → refuse.
|
|
44
|
+
* 3. Clean either way → project as before.
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
import { spawnSync } from 'node:child_process';
|
|
48
|
+
import nodeFs from 'node:fs';
|
|
49
|
+
import path from 'node:path';
|
|
50
|
+
import { fileURLToPath } from 'node:url';
|
|
51
|
+
|
|
52
|
+
import { runAgentsDrift } from './registry.js';
|
|
53
|
+
import { readVersionMarker } from './sync.js';
|
|
54
|
+
|
|
55
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
56
|
+
// lib/cli/ → lib/ → project root
|
|
57
|
+
const PROJECT_ROOT = path.resolve(__dirname, '..', '..');
|
|
58
|
+
const SYNC_SCRIPT = path.join(
|
|
59
|
+
PROJECT_ROOT,
|
|
60
|
+
'.agents',
|
|
61
|
+
'scripts',
|
|
62
|
+
'sync-claude-agents.js',
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Resolve the running CLI's own package version from `PROJECT_ROOT`'s
|
|
67
|
+
* `package.json`. See `sync-commands.js`'s "anchor trap" module doc: this is
|
|
68
|
+
* the ONE place `PROJECT_ROOT` (the package's own two-dirs-up root) is the
|
|
69
|
+
* correct anchor.
|
|
70
|
+
*
|
|
71
|
+
* @param {typeof nodeFs} fsImpl
|
|
72
|
+
* @returns {string}
|
|
73
|
+
*/
|
|
74
|
+
function resolveOwnPackageVersion(fsImpl) {
|
|
75
|
+
const parsed = JSON.parse(
|
|
76
|
+
fsImpl.readFileSync(path.join(PROJECT_ROOT, 'package.json'), 'utf8'),
|
|
77
|
+
);
|
|
78
|
+
return String(parsed.version);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Run the sync-claude-agents script and forward its output + exit code —
|
|
83
|
+
* unless the marker-gated refusal check (see module doc) finds the
|
|
84
|
+
* `.agents/` tree mismatched against the running CLI, in which case it
|
|
85
|
+
* refuses and exits non-zero before spawning anything.
|
|
86
|
+
*
|
|
87
|
+
* Injectable seams — identical shape to `sync-commands.js`:
|
|
88
|
+
* - `runner` — replaces `spawnSync` for the real delegation.
|
|
89
|
+
* - `cwd()` — replaces `process.cwd`; anchors the marker read at the
|
|
90
|
+
* consumer project root.
|
|
91
|
+
* - `fs` — replaces the `node:fs` surface for both the marker read and the
|
|
92
|
+
* own-version read.
|
|
93
|
+
* - `ownVersion` — overrides the running CLI's own resolved version
|
|
94
|
+
* directly, bypassing `resolveOwnPackageVersion`.
|
|
95
|
+
* - `checkAgentsDrift` — overrides the fallback drift probe used when the
|
|
96
|
+
* marker is absent; defaults to `runAgentsDrift({ cwd })`.
|
|
97
|
+
* - `writeErr` / `exit` — replace the corresponding process surfaces.
|
|
98
|
+
*
|
|
99
|
+
* @param {string[]} _argv - Unused; reserved for future flags.
|
|
100
|
+
* @param {{
|
|
101
|
+
* runner?: typeof spawnSync,
|
|
102
|
+
* cwd?: () => string,
|
|
103
|
+
* fs?: typeof nodeFs,
|
|
104
|
+
* ownVersion?: string,
|
|
105
|
+
* checkAgentsDrift?: () => { ok: boolean, detail: string },
|
|
106
|
+
* writeErr?: (s: string) => void,
|
|
107
|
+
* exit?: (code: number) => void,
|
|
108
|
+
* }} [opts]
|
|
109
|
+
* @returns {void}
|
|
110
|
+
*/
|
|
111
|
+
export default function run(
|
|
112
|
+
_argv = [],
|
|
113
|
+
{
|
|
114
|
+
runner = spawnSync,
|
|
115
|
+
cwd = () => process.cwd(),
|
|
116
|
+
fs = nodeFs,
|
|
117
|
+
ownVersion,
|
|
118
|
+
checkAgentsDrift,
|
|
119
|
+
writeErr = (s) => process.stderr.write(s),
|
|
120
|
+
exit = (code) => process.exit(code),
|
|
121
|
+
} = {},
|
|
122
|
+
) {
|
|
123
|
+
const projectRoot = cwd();
|
|
124
|
+
const resolvedOwnVersion = ownVersion ?? resolveOwnPackageVersion(fs);
|
|
125
|
+
const marker = readVersionMarker(projectRoot, fs);
|
|
126
|
+
|
|
127
|
+
if (marker) {
|
|
128
|
+
if (marker !== resolvedOwnVersion) {
|
|
129
|
+
writeErr(
|
|
130
|
+
`mandrel sync-agents: the materialized .agents/ tree is v${marker} but the running CLI is v${resolvedOwnVersion} — refusing to project .claude/agents/ from a mismatched tree.\n` +
|
|
131
|
+
' → Run `mandrel sync` to re-materialize .agents/ to the current version, then re-run.\n',
|
|
132
|
+
);
|
|
133
|
+
exit(1);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
const drift = (checkAgentsDrift ?? (() => runAgentsDrift({ cwd })))();
|
|
138
|
+
if (!drift.ok) {
|
|
139
|
+
writeErr(
|
|
140
|
+
`mandrel sync-agents: .agents/ appears to have drifted from the installed package payload (${drift.detail}) — refusing to project .claude/agents/ from a mismatched tree.\n` +
|
|
141
|
+
' → Run `mandrel sync` to restore the materialized .agents/ payload, then re-run.\n',
|
|
142
|
+
);
|
|
143
|
+
exit(1);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const result = runner(process.execPath, [SYNC_SCRIPT], {
|
|
149
|
+
stdio: 'inherit',
|
|
150
|
+
env: process.env,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const exitCode = result.status ?? 1;
|
|
154
|
+
if (exitCode !== 0) {
|
|
155
|
+
exit(exitCode);
|
|
156
|
+
}
|
|
157
|
+
}
|
package/lib/cli/sync-commands.js
CHANGED
|
@@ -11,12 +11,49 @@
|
|
|
11
11
|
* (marked `cli-opt-out`), so delegation runs via a child process rather than
|
|
12
12
|
* a direct import. Exit code and all output are forwarded verbatim to the
|
|
13
13
|
* caller so `mandrel sync-commands` is transparent to scripts that check $?.
|
|
14
|
+
*
|
|
15
|
+
* ## Marker-gated refusal (Story #4526 / #4530)
|
|
16
|
+
*
|
|
17
|
+
* Before delegating, this wrapper checks whether the `.agents/` tree it is
|
|
18
|
+
* about to project commands FROM actually matches the running CLI's own
|
|
19
|
+
* payload version. Without this gate, a consumer whose `.agents/` tree lags
|
|
20
|
+
* the installed package (see #4525's pin-skew scenario) gets commands
|
|
21
|
+
* faithfully projected from a **stale** tree — describing a workflow whose
|
|
22
|
+
* backing scripts are not installed. Because `.claude/*` is conventionally
|
|
23
|
+
* gitignored, that wrong projection leaves no diff and no signal until an
|
|
24
|
+
* agent hits a missing-module error mid-task, far from the actual cause.
|
|
25
|
+
*
|
|
26
|
+
* Gate order:
|
|
27
|
+
* 1. Marker present (`.agents/.mandrel-version`, Story #4530) → compare it
|
|
28
|
+
* to the running CLI's own package version. Mismatch → refuse.
|
|
29
|
+
* 2. Marker absent (a pre-marker install with no baseline to compare) →
|
|
30
|
+
* fall back to the existing `agents-drift` content-hash check
|
|
31
|
+
* (`registry.js`). Drift detected → refuse.
|
|
32
|
+
* 3. Clean either way → project as before.
|
|
33
|
+
*
|
|
34
|
+
* ## Anchor trap (do not repeat)
|
|
35
|
+
*
|
|
36
|
+
* `PROJECT_ROOT` below is deliberately the **package** root — resolved from
|
|
37
|
+
* this module's own two-dirs-up location — because it is used ONLY to read
|
|
38
|
+
* the running CLI's OWN version, which is exactly what that self-referential
|
|
39
|
+
* pattern correctly answers (contrast with the #4525 bug in `update.js`,
|
|
40
|
+
* where the same pattern was wrongly used to answer a *consumer*-pin
|
|
41
|
+
* question). The marker read is a SEPARATE resolution, anchored at `cwd()`
|
|
42
|
+
* (the consumer project root) via `readVersionMarker` — never at
|
|
43
|
+
* `PROJECT_ROOT`. Comparing the marker to `PROJECT_ROOT`'s own version would
|
|
44
|
+
* make the check compare the package against itself and could never fire —
|
|
45
|
+
* the exact trap `registry.js`'s `agents-in-sync` check documents for the
|
|
46
|
+
* same reason.
|
|
14
47
|
*/
|
|
15
48
|
|
|
16
49
|
import { spawnSync } from 'node:child_process';
|
|
50
|
+
import nodeFs from 'node:fs';
|
|
17
51
|
import path from 'node:path';
|
|
18
52
|
import { fileURLToPath } from 'node:url';
|
|
19
53
|
|
|
54
|
+
import { runAgentsDrift } from './registry.js';
|
|
55
|
+
import { readVersionMarker } from './sync.js';
|
|
56
|
+
|
|
20
57
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
21
58
|
// lib/cli/ → lib/ → project root
|
|
22
59
|
const PROJECT_ROOT = path.resolve(__dirname, '..', '..');
|
|
@@ -28,16 +65,88 @@ const SYNC_SCRIPT = path.join(
|
|
|
28
65
|
);
|
|
29
66
|
|
|
30
67
|
/**
|
|
31
|
-
*
|
|
68
|
+
* Resolve the running CLI's own package version from `PROJECT_ROOT`'s
|
|
69
|
+
* `package.json`. See the "anchor trap" module doc: this is the ONE place
|
|
70
|
+
* `PROJECT_ROOT` (the package's own two-dirs-up root) is the correct anchor.
|
|
71
|
+
*
|
|
72
|
+
* @param {typeof nodeFs} fsImpl
|
|
73
|
+
* @returns {string}
|
|
74
|
+
*/
|
|
75
|
+
function resolveOwnPackageVersion(fsImpl) {
|
|
76
|
+
const parsed = JSON.parse(
|
|
77
|
+
fsImpl.readFileSync(path.join(PROJECT_ROOT, 'package.json'), 'utf8'),
|
|
78
|
+
);
|
|
79
|
+
return String(parsed.version);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Run the sync-claude-commands script and forward its output + exit code —
|
|
84
|
+
* unless the marker-gated refusal check (see module doc) finds the
|
|
85
|
+
* `.agents/` tree mismatched against the running CLI, in which case it
|
|
86
|
+
* refuses and exits non-zero before spawning anything.
|
|
32
87
|
*
|
|
33
|
-
* Injectable
|
|
34
|
-
*
|
|
88
|
+
* Injectable seams (used by tests so no real filesystem, child process, or
|
|
89
|
+
* package resolution occurs on the gate path):
|
|
90
|
+
* - `runner` — replaces `spawnSync` for the real delegation.
|
|
91
|
+
* - `cwd()` — replaces `process.cwd`; anchors the marker read at the
|
|
92
|
+
* consumer project root.
|
|
93
|
+
* - `fs` — replaces the `node:fs` surface for both the marker read and the
|
|
94
|
+
* own-version read.
|
|
95
|
+
* - `ownVersion` — overrides the running CLI's own resolved version
|
|
96
|
+
* directly, bypassing `resolveOwnPackageVersion`.
|
|
97
|
+
* - `checkAgentsDrift` — overrides the fallback drift probe used when the
|
|
98
|
+
* marker is absent; defaults to `runAgentsDrift({ cwd })`.
|
|
99
|
+
* - `writeErr` / `exit` — replace the corresponding process surfaces.
|
|
35
100
|
*
|
|
36
101
|
* @param {string[]} _argv - Unused; reserved for future flags.
|
|
37
|
-
* @param {{
|
|
102
|
+
* @param {{
|
|
103
|
+
* runner?: typeof spawnSync,
|
|
104
|
+
* cwd?: () => string,
|
|
105
|
+
* fs?: typeof nodeFs,
|
|
106
|
+
* ownVersion?: string,
|
|
107
|
+
* checkAgentsDrift?: () => { ok: boolean, detail: string },
|
|
108
|
+
* writeErr?: (s: string) => void,
|
|
109
|
+
* exit?: (code: number) => void,
|
|
110
|
+
* }} [opts]
|
|
38
111
|
* @returns {void}
|
|
39
112
|
*/
|
|
40
|
-
export default function run(
|
|
113
|
+
export default function run(
|
|
114
|
+
_argv = [],
|
|
115
|
+
{
|
|
116
|
+
runner = spawnSync,
|
|
117
|
+
cwd = () => process.cwd(),
|
|
118
|
+
fs = nodeFs,
|
|
119
|
+
ownVersion,
|
|
120
|
+
checkAgentsDrift,
|
|
121
|
+
writeErr = (s) => process.stderr.write(s),
|
|
122
|
+
exit = (code) => process.exit(code),
|
|
123
|
+
} = {},
|
|
124
|
+
) {
|
|
125
|
+
const projectRoot = cwd();
|
|
126
|
+
const resolvedOwnVersion = ownVersion ?? resolveOwnPackageVersion(fs);
|
|
127
|
+
const marker = readVersionMarker(projectRoot, fs);
|
|
128
|
+
|
|
129
|
+
if (marker) {
|
|
130
|
+
if (marker !== resolvedOwnVersion) {
|
|
131
|
+
writeErr(
|
|
132
|
+
`mandrel sync-commands: the materialized .agents/ tree is v${marker} but the running CLI is v${resolvedOwnVersion} — refusing to project .claude/commands/ from a mismatched tree.\n` +
|
|
133
|
+
' → Run `mandrel sync` to re-materialize .agents/ to the current version, then re-run.\n',
|
|
134
|
+
);
|
|
135
|
+
exit(1);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
const drift = (checkAgentsDrift ?? (() => runAgentsDrift({ cwd })))();
|
|
140
|
+
if (!drift.ok) {
|
|
141
|
+
writeErr(
|
|
142
|
+
`mandrel sync-commands: .agents/ appears to have drifted from the installed package payload (${drift.detail}) — refusing to project .claude/commands/ from a mismatched tree.\n` +
|
|
143
|
+
' → Run `mandrel sync` to restore the materialized .agents/ payload, then re-run.\n',
|
|
144
|
+
);
|
|
145
|
+
exit(1);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
41
150
|
const result = runner(process.execPath, [SYNC_SCRIPT], {
|
|
42
151
|
stdio: 'inherit',
|
|
43
152
|
env: process.env,
|
|
@@ -45,6 +154,6 @@ export default function run(_argv = [], { runner = spawnSync } = {}) {
|
|
|
45
154
|
|
|
46
155
|
const exitCode = result.status ?? 1;
|
|
47
156
|
if (exitCode !== 0) {
|
|
48
|
-
|
|
157
|
+
exit(exitCode);
|
|
49
158
|
}
|
|
50
159
|
}
|