mandrel 1.61.0 → 1.63.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.
Files changed (35) hide show
  1. package/.agents/docs/SDLC.md +10 -3
  2. package/.agents/docs/workflows.md +1 -1
  3. package/.agents/scripts/check-action-pinning.js +260 -0
  4. package/.agents/scripts/check-arch-cycles.js +38 -14
  5. package/.agents/scripts/epic-deliver-prepare.js +149 -104
  6. package/.agents/scripts/lib/baseline-snapshot.js +245 -141
  7. package/.agents/scripts/lib/feedback-loop/graduator-core.js +171 -137
  8. package/.agents/scripts/lib/orchestration/code-review.js +206 -168
  9. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/creation.js +71 -5
  10. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist.js +16 -2
  11. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/component-drift.js +101 -1
  12. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/crap-drift.js +20 -42
  13. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/maintainability-drift.js +12 -32
  14. package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +97 -60
  15. package/.agents/scripts/lib/orchestration/model-attribution.js +73 -45
  16. package/.agents/scripts/lib/orchestration/review-providers/parse-findings.js +97 -49
  17. package/.agents/scripts/lib/orchestration/story-close/pre-merge-validation.js +73 -69
  18. package/.agents/scripts/lib/orchestration/story-close-recovery.js +109 -79
  19. package/.agents/scripts/lib/signals/detectors/common.js +107 -0
  20. package/.agents/scripts/lib/signals/detectors/hotspot.js +12 -18
  21. package/.agents/scripts/lib/signals/detectors/retry.js +3 -40
  22. package/.agents/scripts/lib/signals/detectors/rework.js +3 -40
  23. package/.agents/scripts/lib/story-body/story-body.js +102 -76
  24. package/.agents/scripts/providers/github/blocked-by-add.js +252 -0
  25. package/.agents/scripts/single-story-init.js +16 -3
  26. package/.agents/workflows/audit-architecture.md +9 -0
  27. package/.agents/workflows/deliver.md +87 -26
  28. package/.agents/workflows/helpers/deliver-epic.md +12 -5
  29. package/.agents/workflows/helpers/deliver-stories.md +13 -7
  30. package/.agents/workflows/plan.md +3 -1
  31. package/README.md +1 -1
  32. package/docs/CHANGELOG.md +40 -0
  33. package/lib/cli/registry.js +1 -1
  34. package/lib/cli/update.js +114 -8
  35. package/package.json +1 -1
@@ -14,9 +14,14 @@ description: >-
14
14
 
15
15
  ## Overview
16
16
 
17
- `/deliver` is the **single SDL execution command** in the 5.40 surface.
18
- It opens a PR against `main` and auto-merges when every signal certifies a
19
- clean run; otherwise it falls back to the operator-merges-button path.
17
+ This helper is the **Epic delivery path** behind `/deliver` — the router
18
+ delegates to it once per Epic ID, either as the sole route (single-Epic
19
+ input) or as one **Epic segment** of the sequential segment plan `/deliver`
20
+ composes over mixed Epic / standalone-Story input (Epic segments run in
21
+ input order, after the standalone segment; see
22
+ [`deliver.md`](../deliver.md)). Each invocation opens a PR against `main`
23
+ and auto-merges when every signal certifies a clean run; otherwise it falls
24
+ back to the operator-merges-button path.
20
25
 
21
26
  ```text
22
27
  /deliver <epicId>
@@ -32,8 +37,10 @@ clean run; otherwise it falls back to the operator-merges-button path.
32
37
  → Phase 9 — cleanup (BranchCleaner + Cleaner lifecycle listeners on epic.cleanup.start / epic.merge.armed; fire via lifecycle-emit → epic.merge.armed)
33
38
  ```
34
39
 
35
- The argument is always an Epic ID (`type::epic`). Story IDs go to
36
- [`/deliver`](deliver-stories.md) (standalone) or the
40
+ The argument is always a single Epic ID (`type::epic`) multi-Epic or
41
+ mixed input is segmented by the `/deliver` router before this helper runs.
42
+ Story IDs go to
43
+ [`helpers/deliver-stories`](deliver-stories.md) (standalone) or the
37
44
  [`helpers/epic-deliver-story`](epic-deliver-story.md) helper
38
45
  (Epic-attached, invoked by this workflow's fan-out); Tasks are not directly
39
46
  executable.
@@ -11,10 +11,14 @@ description: >-
11
11
 
12
12
  ## Overview
13
13
 
14
- `/deliver` is the **operator-facing multi-Story delivery command**. It
15
- takes one or more Story IDs, builds a dependency-aware wave plan, optionally
16
- confirms it with the operator, and fans out one Agent call per Story per wave
17
- parallel within each wave, serialised across waves.
14
+ This helper is the **standalone multi-Story delivery path** behind
15
+ `/deliver`. The router delegates to it whenever the supplied IDs include
16
+ standalone Stories either as the sole route (Story-only input) or as the
17
+ **standalone segment** of a mixed segment plan (run first, before any Epic
18
+ segments; see [`deliver.md`](../deliver.md)). It takes one or more Story
19
+ IDs, builds a dependency-aware wave plan, optionally confirms it with the
20
+ operator, and fans out one Agent call per Story per wave — parallel within
21
+ each wave, serialised across waves.
18
22
 
19
23
  ```text
20
24
  /deliver 101 102 103
@@ -33,11 +37,13 @@ confirms it with the operator, and fans out one Agent call per Story per wave
33
37
  | 1+ standalone Stories (no `Epic: #N` in body) | `/deliver <id> [<id>...]` |
34
38
  | Exactly one standalone Story (lighter path) | `/single-story-deliver <id>` |
35
39
  | Epic-attached Stories (have `Epic: #N`) | `/deliver <epicId>` |
40
+ | Mixed Epics + standalone Stories | `/deliver <ids...>` — the router composes a sequential segment plan; this helper delivers the standalone segment first |
36
41
 
37
- `/deliver` **refuses** Stories that carry an `Epic: #N` reference in
42
+ This helper **refuses** Stories that carry an `Epic: #N` reference in
38
43
  their body. Those Stories belong to an Epic's dispatch manifest and must flow
39
- through `/deliver`. Use `/single-story-deliver` for a single Epic-free
40
- Story when you want the leaner one-story path without wave machinery.
44
+ through `/deliver <epicId>`. Use `/single-story-deliver` for a single
45
+ Epic-free Story when you want the leaner one-story path without wave
46
+ machinery.
41
47
 
42
48
  > **Concurrency cap.** The cap is resolved **deterministically in code** by
43
49
  > `stories-wave-tick.js` (Phase 1a) — the same `resolveConfig` + `getRunners`
@@ -126,6 +126,8 @@ stubbed docs, or an unready doctor verdict).
126
126
 
127
127
  ## See also
128
128
 
129
- - [`/deliver`](deliver.md) — the unified delivery entry point.
129
+ - [`/deliver`](deliver.md) — the unified delivery entry point. Accepts a
130
+ single Epic, one or more standalone Stories, or any mix of ≥1 Epics and
131
+ standalone Stories — mixed sets compose a sequential segment plan.
130
132
  - [`helpers/plan-epic.md`](helpers/plan-epic.md) /
131
133
  [`helpers/plan-story.md`](helpers/plan-story.md) — the path helpers.
package/README.md CHANGED
@@ -163,7 +163,7 @@ Deeper reference material lives in `docs/` rather than inline here:
163
163
  - [`.agents/docs/workflows.md`](.agents/docs/workflows.md) — slash-command
164
164
  index (auto-generated from the workflow set).
165
165
  - [`docs/CHANGELOG.md`](docs/CHANGELOG.md) — release history.
166
- - [`AGENTS.md`](AGENTS.md) — repository onboarding, the two-package release
166
+ - [`AGENTS.md`](AGENTS.md) — repository onboarding, the single-package release
167
167
  topology, PAT / npm-token setup, and major-version policy. Releases are
168
168
  automated by `release-please`: land Conventional Commits on `main` and it
169
169
  opens a combined `chore: release main` PR that squash-merges itself once
package/docs/CHANGELOG.md CHANGED
@@ -2,6 +2,46 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [1.63.0](https://github.com/dsj1984/mandrel/compare/mandrel-v1.62.0...mandrel-v1.63.0) (2026-06-13)
6
+
7
+
8
+ ### Added
9
+
10
+ * **decompose:** set native GitHub blocked-by dependencies from Story depends_on graph (refs [#4067](https://github.com/dsj1984/mandrel/issues/4067)) ([#4068](https://github.com/dsj1984/mandrel/issues/4068)) ([1799659](https://github.com/dsj1984/mandrel/commit/1799659a84b06555c9caa1193aec59113c943b78))
11
+
12
+
13
+ ### Fixed
14
+
15
+ * **audit-architecture:** add Impact severity field to Detailed Findings (refs [#4085](https://github.com/dsj1984/mandrel/issues/4085)) ([#4096](https://github.com/dsj1984/mandrel/issues/4096)) ([d72c109](https://github.com/dsj1984/mandrel/commit/d72c109195955eff70d7d926abb36a88e32d3f6e))
16
+ * fix stale architecture.md/README docs and broaden the import-cycle gate scan root ([#4071](https://github.com/dsj1984/mandrel/issues/4071)) ([#4090](https://github.com/dsj1984/mandrel/issues/4090)) ([2e97b45](https://github.com/dsj1984/mandrel/commit/2e97b45f9ed43398dc703dd735394c7483329033))
17
+
18
+
19
+ ### Performance
20
+
21
+ * **decompose:** parallelize blocked-by edge creation with bounded concurrentMap (refs [#4082](https://github.com/dsj1984/mandrel/issues/4082)) ([#4094](https://github.com/dsj1984/mandrel/issues/4094)) ([a9f38da](https://github.com/dsj1984/mandrel/commit/a9f38da2d44941a164865a6ad291c2da01ee4ed6))
22
+
23
+
24
+ ### Changed
25
+
26
+ * `parseProviderFindings` is a CC-30 ternary thicket ([#4074](https://github.com/dsj1984/mandrel/issues/4074)) ([#4089](https://github.com/dsj1984/mandrel/issues/4089)) ([5ed693c](https://github.com/dsj1984/mandrel/commit/5ed693c3325c05a36251b859be5873ab0378031c))
27
+ * extract shared createDriftDetector skeleton for CRAP and MI drift detectors (refs [#4076](https://github.com/dsj1984/mandrel/issues/4076)) ([#4091](https://github.com/dsj1984/mandrel/issues/4091)) ([e8a81a8](https://github.com/dsj1984/mandrel/commit/e8a81a82307a12c3d6e58275d4c4554b07659f4f))
28
+ * **scripts:** reduce CC of CLI orchestration bodies below the must-fix band (refs [#4075](https://github.com/dsj1984/mandrel/issues/4075)) ([#4093](https://github.com/dsj1984/mandrel/issues/4093)) ([53519de](https://github.com/dsj1984/mandrel/commit/53519de7968e59c08479c886ca3713f53f5c039d))
29
+ * **signals:** hoist shared detector validation preamble into common.js (refs [#4077](https://github.com/dsj1984/mandrel/issues/4077)) ([#4092](https://github.com/dsj1984/mandrel/issues/4092)) ([2f6fe7b](https://github.com/dsj1984/mandrel/commit/2f6fe7b103c208fbd3ea4cfedf63d2a7aa412e24))
30
+ * **single-story-init:** inject spawnSync boundary into makeGhRunner (refs [#4073](https://github.com/dsj1984/mandrel/issues/4073)) ([#4087](https://github.com/dsj1984/mandrel/issues/4087)) ([8fae355](https://github.com/dsj1984/mandrel/commit/8fae35568523e3997d8f7e79580c0cc8e0625072))
31
+ * **story-body:** extract per-section sub-parsers from parse() (refs [#4072](https://github.com/dsj1984/mandrel/issues/4072)) ([#4088](https://github.com/dsj1984/mandrel/issues/4088)) ([aa9e472](https://github.com/dsj1984/mandrel/commit/aa9e47204728da0ab4f547927af251fce6a85b69))
32
+
33
+ ## [1.62.0](https://github.com/dsj1984/mandrel/compare/mandrel-v1.61.0...mandrel-v1.62.0) (2026-06-12)
34
+
35
+
36
+ ### Added
37
+
38
+ * /deliver composes a sequential segment plan over mixed Epic and standalone-Story ID sets (refs [#4062](https://github.com/dsj1984/mandrel/issues/4062)) ([#4063](https://github.com/dsj1984/mandrel/issues/4063)) ([a83d29e](https://github.com/dsj1984/mandrel/commit/a83d29e032b7f6b6846d7988f071d6b6416df2f9))
39
+
40
+
41
+ ### Fixed
42
+
43
+ * make mandrel update no-op short-circuit drift-aware (refs [#4065](https://github.com/dsj1984/mandrel/issues/4065)) ([#4066](https://github.com/dsj1984/mandrel/issues/4066)) ([693f1cf](https://github.com/dsj1984/mandrel/commit/693f1cf5bf8fd7d29fed910659708ffceb7a54cb))
44
+
5
45
  ## [1.61.0](https://github.com/dsj1984/mandrel/compare/mandrel-v1.60.0...mandrel-v1.61.0) (2026-06-12)
6
46
 
7
47
 
@@ -483,7 +483,7 @@ function runAgentsMaterialized({ cwd, existsSync, resolvePackage } = {}) {
483
483
  * }} [opts]
484
484
  * @returns {{ ok: boolean, detail: string, remedy?: string }}
485
485
  */
486
- function runAgentsDrift({ cwd, fsImpl = fs, resolvePackageRoot } = {}) {
486
+ export function runAgentsDrift({ cwd, fsImpl = fs, resolvePackageRoot } = {}) {
487
487
  const getCwd = cwd ?? (() => process.cwd());
488
488
  const resolveRoot = resolvePackageRoot ?? defaultResolvePackageRoot;
489
489
  const projectRoot = getCwd();
package/lib/cli/update.js CHANGED
@@ -12,7 +12,10 @@
12
12
  * ## Ordered cycle (happy path)
13
13
  *
14
14
  * 1. resolve target version (newest published) and the current version
15
- * 2. no-op short-circuit — already on the newest version ⇒ nothing to do
15
+ * 2. drift-aware short-circuit — version check gates on installed version AND
16
+ * materialized `.agents/` state (Story #4065). When already on newest AND
17
+ * no drift: nothing to do (true no-op). When already on newest BUT drift
18
+ * detected: skip npm-update/migrations, run sync + sync-commands to heal.
16
19
  * 3. install — bump the dependency (lockfile bump left STAGED).
17
20
  * The package manager is auto-detected from the lockfile in the project
18
21
  * root: `pnpm-lock.yaml` ⇒ `pnpm add -D …` (with `-w` at a
@@ -85,6 +88,11 @@
85
88
  * - `argv` — subcommand args (after `mandrel update`)
86
89
  * - `currentVersion` — the installed `mandrel` version string
87
90
  * - `resolveTargetVersion`— async, returns the newest published version
91
+ * - `checkDrift` — sync or async, returns `true` when `.agents/`
92
+ * differs from the installed payload. Used by the
93
+ * drift-aware no-op short-circuit (Story #4065).
94
+ * Defaults to `() => !runAgentsDrift().ok`, which
95
+ * reuses the same `agents-drift` doctor signal.
88
96
  * - `npmUpdate` — async, performs the dependency bump (no git);
89
97
  * receives `(target, { installCmd })`
90
98
  * - `spawnPhase` — async, spawns a post-install phase from the new
@@ -140,7 +148,7 @@ import { fileURLToPath } from 'node:url';
140
148
  import { detectPackageManagerWithWorkspace } from '../../.agents/scripts/lib/detect-package-manager.js';
141
149
  import { runInstallCommand } from '../../.agents/scripts/lib/install-cmd-parser.js';
142
150
  import { runMigrations as defaultRunMigrations } from '../migrations/index.js';
143
- import { registry } from './registry.js';
151
+ import { registry, runAgentsDrift } from './registry.js';
144
152
  import { runSync as defaultRunSync } from './sync.js';
145
153
  import { isStale } from './version-check.js';
146
154
  import { compareVersions } from './version-helpers.js';
@@ -761,6 +769,7 @@ function parseInstallCmdFlag(argv) {
761
769
  * currentVersion?: string | (() => string),
762
770
  * resolveTargetVersion?: () => (string | Promise<string>),
763
771
  * npmUpdate?: (version: string, opts: { installCmd?: string }) => unknown | Promise<unknown>,
772
+ * checkDrift?: () => (boolean | Promise<boolean>),
764
773
  * spawnPhase?: (phase: string, args: string[], opts: { binPath: string, cwd: string, write: (s: string) => void, writeErr: (s: string) => void }) => Promise<{ ok: boolean, stdout: string, stderr: string }> | { ok: boolean, stdout: string, stderr: string },
765
774
  * runSync?: typeof defaultRunSync,
766
775
  * runMigrations?: typeof defaultRunMigrations,
@@ -773,7 +782,7 @@ function parseInstallCmdFlag(argv) {
773
782
  * }} [opts]
774
783
  * @returns {Promise<{
775
784
  * ok: boolean,
776
- * action: 'updated' | 'dry-run' | 'up-to-date' | 'doctor-failed',
785
+ * action: 'updated' | 'resynced' | 'dry-run' | 'up-to-date' | 'doctor-failed',
777
786
  * currentVersion: string,
778
787
  * targetVersion: string | null,
779
788
  * stepsRun: string[],
@@ -785,6 +794,7 @@ export async function runUpdate({
785
794
  currentVersion,
786
795
  resolveTargetVersion,
787
796
  npmUpdate,
797
+ checkDrift,
788
798
  spawnPhase,
789
799
  runSync = defaultRunSync,
790
800
  runMigrations = defaultRunMigrations,
@@ -811,16 +821,112 @@ export async function runUpdate({
811
821
  const target = String(await resolveTargetVersion());
812
822
 
813
823
  // --- No-op short-circuit --------------------------------------------------
814
- // Already on (or ahead of) the newest version: nothing to apply.
824
+ // Already on (or ahead of) the newest version: check whether .agents/ is
825
+ // actually materialized to the installed payload (agents-drift). When drift
826
+ // is present, fall through to a sync-only heal path even though the package
827
+ // version is unchanged. Only emit "Already up to date" when the version is
828
+ // current AND the payload matches (Story #4065).
815
829
  if (compareVersions(target, current) <= 0) {
816
- write(`✅ Already up to date (v${current} is the newest version).\n`);
830
+ // Resolve the drift probe: prefer the injected checkDrift seam (unit-test
831
+ // friendly); fall back to the production runAgentsDrift helper.
832
+ const driftProbe =
833
+ typeof checkDrift === 'function'
834
+ ? checkDrift
835
+ : () => !runAgentsDrift().ok;
836
+ const hasDrift = await driftProbe();
837
+
838
+ if (!hasDrift) {
839
+ write(`✅ Already up to date (v${current} is the newest version).\n`);
840
+ return {
841
+ ok: true,
842
+ action: 'up-to-date',
843
+ currentVersion: current,
844
+ targetVersion: target,
845
+ stepsRun: [],
846
+ dryRun,
847
+ };
848
+ }
849
+
850
+ // Drift detected while version is already current — heal by re-syncing
851
+ // without bumping the package (no npm-update, no migrations needed since
852
+ // the installed version did not change).
853
+ if (dryRun) {
854
+ write(
855
+ `mandrel update — drift detected, sync heal planned (v${current} is already current)\n`,
856
+ );
857
+ write(
858
+ ' 1. runSync — re-materialize .agents/ from installed payload\n',
859
+ );
860
+ write(
861
+ ' 2. sync-commands — regenerate .claude/commands/ from .agents/workflows/\n',
862
+ );
863
+ write('Dry run: no files written.\n');
864
+ return {
865
+ ok: true,
866
+ action: 'dry-run',
867
+ currentVersion: current,
868
+ targetVersion: target,
869
+ stepsRun: [],
870
+ dryRun: true,
871
+ };
872
+ }
873
+
874
+ write(
875
+ `Healing .agents/ drift (v${current} is already current, but .agents/ is stale)…\n`,
876
+ );
877
+ const stepsRun = [];
878
+
879
+ const useReExec = typeof spawnPhase === 'function';
880
+
881
+ if (useReExec) {
882
+ const projectRoot = cwd();
883
+ const binPath = resolveNewBinPath(projectRoot);
884
+
885
+ const syncResult = await spawnPhase('sync', [], {
886
+ binPath,
887
+ cwd: projectRoot,
888
+ write,
889
+ writeErr,
890
+ });
891
+ if (!syncResult.ok) {
892
+ throw new Error(
893
+ `mandrel update: \`mandrel sync\` from installed binary exited non-zero — ` +
894
+ 'the .agents/ materialization may be incomplete. ' +
895
+ 'Run `mandrel sync` manually to restore.',
896
+ );
897
+ }
898
+ stepsRun.push('runSync');
899
+
900
+ const syncCommandsResult = await spawnPhase('sync-commands', [], {
901
+ binPath,
902
+ cwd: projectRoot,
903
+ write,
904
+ writeErr,
905
+ });
906
+ if (!syncCommandsResult.ok) {
907
+ throw new Error(
908
+ `mandrel update: \`mandrel sync-commands\` from installed binary exited non-zero — ` +
909
+ 'the .claude/commands/ tree may be out of sync. ' +
910
+ 'Run `npm run sync:commands` manually to restore.',
911
+ );
912
+ }
913
+ stepsRun.push('sync-commands');
914
+ } else {
915
+ // In-process backward-compat path.
916
+ runSync({ argv: [] });
917
+ stepsRun.push('runSync');
918
+ }
919
+
920
+ write(
921
+ `✅ Healed .agents/ drift (v${current}). The materialized payload is now current.\n`,
922
+ );
817
923
  return {
818
924
  ok: true,
819
- action: 'up-to-date',
925
+ action: 'resynced',
820
926
  currentVersion: current,
821
927
  targetVersion: target,
822
- stepsRun: [],
823
- dryRun,
928
+ stepsRun,
929
+ dryRun: false,
824
930
  };
825
931
  }
826
932
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mandrel",
3
- "version": "1.61.0",
3
+ "version": "1.63.0",
4
4
  "description": "Claude Code-first opinionated workflow framework: instructions, personas, skills, and SDLC workflows that govern AI coding assistants.",
5
5
  "files": [
6
6
  ".agents/",