mandrel 1.77.0 → 1.79.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 (30) hide show
  1. package/.agents/docs/workflows.md +19 -0
  2. package/.agents/schemas/lifecycle/loop.tick.schema.json +20 -0
  3. package/.agents/schemas/loop-unit.schema.json +70 -0
  4. package/.agents/scripts/check-doc-links.js +24 -1
  5. package/.agents/scripts/check-loop-units.js +204 -0
  6. package/.agents/scripts/generate-workflows-doc.js +37 -4
  7. package/.agents/scripts/lib/close-validation/process.js +61 -5
  8. package/.agents/scripts/lib/close-validation/runner.js +17 -1
  9. package/.agents/scripts/lib/loop-units/validate-loop-unit.js +197 -0
  10. package/.agents/scripts/lib/mandrel-catalog.js +36 -0
  11. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/context.js +7 -2
  12. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +15 -36
  13. package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +183 -0
  14. package/.agents/scripts/lib/story-lifecycle.js +12 -4
  15. package/.agents/scripts/lib/templates/decomposer-prompts.js +14 -5
  16. package/.agents/scripts/providers/github/branch-protection.js +1 -1
  17. package/.agents/scripts/providers/github/errors.js +53 -2
  18. package/.agents/scripts/providers/github/labels.js +1 -1
  19. package/.agents/scripts/providers/github/projects-v2-graphql.js +1 -1
  20. package/.agents/scripts/run-lint.js +11 -0
  21. package/.agents/scripts/sync-claude-commands.js +112 -29
  22. package/.agents/scripts/update-maintainability-baseline.js +19 -76
  23. package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +10 -7
  24. package/.agents/workflows/loops/README.md +65 -0
  25. package/.agents/workflows/loops/fix-failing-tests.md +74 -0
  26. package/.agents/workflows/loops/nightly-audit.md +71 -0
  27. package/.agents/workflows/loops/watch-ci.md +68 -0
  28. package/docs/CHANGELOG.md +26 -0
  29. package/package.json +1 -1
  30. package/.agents/scripts/providers/github/transient-retry.js +0 -62
@@ -0,0 +1,71 @@
1
+ ---
2
+ description: >-
3
+ Cron maintenance loop that runs a nightly audit sweep over the repository and
4
+ files actionable findings. Each run executes the audit workflows and routes
5
+ the results; the host (`/schedule` or a cron-driven `/loop`) owns the cadence.
6
+ verify is optional for a cron loop — the scheduler owns iteration, so this
7
+ unit ships the action and goal, not a terminating oracle.
8
+ loop:
9
+ cadence: cron
10
+ goal: >-
11
+ Keep the repository's standing health surfaced by running the audit sweep on
12
+ a nightly schedule and turning each fresh finding into an actionable, deduped
13
+ record so regressions are caught within a day rather than at release time.
14
+ maxRounds: 30
15
+ onExhaust: report
16
+ ---
17
+
18
+ # /loops:nightly-audit — scheduled maintenance audit sweep
19
+
20
+ A **cron maintenance loop**. The host (`/schedule`, or a cron-driven `/loop`)
21
+ owns the cadence and fires this unit once per scheduled window — typically
22
+ overnight. Because the scheduler owns iteration, this unit carries **no
23
+ `verify` oracle**: per the loop-unit schema, `verify` is required only for
24
+ `self-paced` cadence and optional for `interval` / `cron`. Each run is a single
25
+ sweep that observes, records, and yields until the next scheduled tick.
26
+
27
+ ## Action
28
+
29
+ Each scheduled run:
30
+
31
+ 1. **Run the audit sweep.** Execute the relevant audit workflows for the repo
32
+ (`/audit-security`, `/audit-clean-code`, `/audit-dependencies`,
33
+ `/audit-quality`, and any others the project relies on). Each audit writes a
34
+ structured `temp/audits/audit-*-results.md` report — that is the canonical
35
+ artifact this loop consumes, not free-form prose.
36
+ 2. **Diff against the prior night.** Compare the fresh findings against the last
37
+ sweep's reports and against already-open Issues. A finding seen before is
38
+ not new signal; only genuinely fresh or regressed findings warrant a record.
39
+ 3. **Route fresh findings.** Hand the new findings to `/audit-to-stories`, which
40
+ deduplicates against existing Issues by fingerprint and either chains into
41
+ `/plan` or opens standalone Stories. Do not open raw duplicate Issues —
42
+ dedup is the loop's job, not the operator's.
43
+ 4. **Report and yield.** Emit a short digest (sweeps run, new findings, Issues
44
+ opened or updated) and return control to the scheduler, which sleeps until
45
+ the next cron window.
46
+
47
+ ## Goal & done-signal
48
+
49
+ - **Goal:** the repository's health regressions are caught and turned into
50
+ actionable, deduplicated records within a day, without a human remembering to
51
+ run the audits by hand.
52
+ - **Done-signal:** the nightly sweep completed and every fresh finding has been
53
+ routed to a record (or explicitly judged a non-finding). A cron loop has no
54
+ self-evaluated oracle — the scheduler owns whether the loop runs again; this
55
+ unit simply finishes the night's sweep and yields.
56
+ - **Backstop:** `maxRounds: 30`. Roughly a month of nightly runs;
57
+ `onExhaust: report` emits a final digest and stops so a long-lived schedule
58
+ is renewed deliberately rather than running unbounded.
59
+
60
+ ## Stop & escalate
61
+
62
+ - **An audit cannot run** (a required tool is missing, the audit harness errors,
63
+ the working tree is dirty in a way that invalidates the sweep). Report the
64
+ failure for that audit and continue with the others — do not abort the whole
65
+ night because one audit broke.
66
+ - **A finding is high-severity and time-sensitive** (an exposed secret, a
67
+ critical CVE reachable in production). Surface it loudly in the digest rather
68
+ than letting it sit as one row among many — a nightly cadence is too slow for
69
+ an actively-exploitable finding.
70
+ - **`maxRounds` is reached.** Emit a final digest (`onExhaust: report`) so the
71
+ operator can renew or retire the schedule deliberately.
@@ -0,0 +1,68 @@
1
+ ---
2
+ description: >-
3
+ Interval watch loop that polls a pull request's CI checks until they settle.
4
+ Each round runs `gh pr checks` and reports the delta; the host (`/loop 5m`)
5
+ owns the cadence and re-invokes the unit on its schedule. verify is optional
6
+ for an interval loop — the externally-scheduled host owns iteration, so this
7
+ unit ships the action and goal, not a terminating oracle.
8
+ loop:
9
+ cadence: interval
10
+ goal: >-
11
+ Keep an eye on the current pull request's required CI checks each interval,
12
+ surfacing the first failed or stuck check the moment it appears so a human
13
+ can act before the checks finish.
14
+ maxRounds: 60
15
+ onExhaust: report
16
+ ---
17
+
18
+ # /loops:watch-ci — poll a PR's checks until they settle
19
+
20
+ An **interval watch loop**. The host (`/loop <interval> /loops:watch-ci`, e.g.
21
+ `/loop 5m …`) owns the cadence and re-runs this unit on each tick. Because the
22
+ host schedules iteration externally, this unit carries **no `verify` oracle** —
23
+ per the loop-unit schema, `verify` is required only for `self-paced` cadence and
24
+ optional for `interval` / `cron`. The unit's job each round is to observe and
25
+ report, not to converge.
26
+
27
+ ## Action
28
+
29
+ Each interval:
30
+
31
+ 1. **Read the current check state.** Run `gh pr checks` for the PR under watch
32
+ (the host supplies the PR number, or it is inferred from the current
33
+ branch's open PR). Capture the per-check status: pending, passed, or failed.
34
+ 2. **Compute the delta since last round.** Compare against the prior round's
35
+ snapshot. A check that flipped `pending → failed` is the headline; a check
36
+ that flipped `pending → passed` is progress.
37
+ 3. **Surface failures immediately.** On the first failed or cancelled required
38
+ check, report it — name the check, link the run, and quote the first error
39
+ line if cheaply available — so a human can act before the rest of the matrix
40
+ finishes. Do not wait for the whole suite to settle to raise a red check.
41
+ 4. **Report and yield.** Emit a one-line status summary
42
+ (`N passed, M pending, K failed`) and return control to the host, which
43
+ sleeps until the next interval.
44
+
45
+ ## Goal & done-signal
46
+
47
+ - **Goal:** the operator learns about a CI failure on the watched PR as early as
48
+ the polling interval allows, and knows when all required checks have gone
49
+ green.
50
+ - **Done-signal:** all required checks have a terminal status (every check
51
+ passed, or at least one has failed). An interval loop has no self-evaluated
52
+ oracle — the host stops the loop when the operator cancels it, when a failure
53
+ is surfaced and acted on, or when `maxRounds` is reached.
54
+ - **Backstop:** `maxRounds: 60`. At a 5-minute interval that is ~5 hours of
55
+ watching; `onExhaust: report` emits a final status and stops rather than
56
+ polling forever on a wedged check.
57
+
58
+ ## Stop & escalate
59
+
60
+ - **A required check failed.** Surface it and let the operator decide whether to
61
+ keep watching the remaining checks or stop. A failed required check is the
62
+ signal the watch existed to catch.
63
+ - **The PR cannot be resolved** (no open PR for the branch, `gh` not
64
+ authenticated, the PR was merged or closed out from under the watch). Report
65
+ the condition and stop — there is nothing left to watch.
66
+ - **`maxRounds` is reached with checks still pending.** Emit a final summary of
67
+ the stuck checks (`onExhaust: report`) so the operator can investigate the
68
+ wedged run.
package/docs/CHANGELOG.md CHANGED
@@ -2,6 +2,32 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [1.79.0](https://github.com/dsj1984/mandrel/compare/mandrel-v1.78.0...mandrel-v1.79.0) (2026-06-30)
6
+
7
+
8
+ ### Fixed
9
+
10
+ * **decompose-author:** require namespaced [@epic](https://github.com/epic)-&lt;id&gt;-ac-N tag on wave-0 BDD scaffold scenarios (refs [#4301](https://github.com/dsj1984/mandrel/issues/4301)) ([#4304](https://github.com/dsj1984/mandrel/issues/4304)) ([c97d45e](https://github.com/dsj1984/mandrel/commit/c97d45e806a72a8511995d8f606b5be2ef486315))
11
+ * **reconciler:** single-source the parent/Epic body trailer so force re-decompose retains Epic: #&lt;id&gt; (refs [#4300](https://github.com/dsj1984/mandrel/issues/4300)) ([#4303](https://github.com/dsj1984/mandrel/issues/4303)) ([a8e78a9](https://github.com/dsj1984/mandrel/commit/a8e78a9fd946e6f532a59df9cd37237e0d046b2d))
12
+
13
+ ## [1.78.0](https://github.com/dsj1984/mandrel/compare/mandrel-v1.77.0...mandrel-v1.78.0) (2026-06-24)
14
+
15
+
16
+ ### Added
17
+
18
+ * Epic [#4284](https://github.com/dsj1984/mandrel/issues/4284) ([#4297](https://github.com/dsj1984/mandrel/issues/4297)) ([0f7c543](https://github.com/dsj1984/mandrel/commit/0f7c543e1ceb1c38ac425c388e338e3bfaaec49e))
19
+
20
+
21
+ ### Fixed
22
+
23
+ * **baselines:** apply ignoreGlobs in the maintainability baseline CLI diff-scope branch (refs [#4293](https://github.com/dsj1984/mandrel/issues/4293)) ([#4294](https://github.com/dsj1984/mandrel/issues/4294)) ([c0c7dd5](https://github.com/dsj1984/mandrel/commit/c0c7dd5948314800e5a9d5808fb5eb0b01b09833))
24
+ * **close-validation:** treat biome "No files were processed" as a clean format-gate skip (refs [#4292](https://github.com/dsj1984/mandrel/issues/4292)) ([#4295](https://github.com/dsj1984/mandrel/issues/4295)) ([bf6840d](https://github.com/dsj1984/mandrel/commit/bf6840dd69abbece4eb221797e6432519b6614a0))
25
+
26
+
27
+ ### Changed
28
+
29
+ * **github-provider:** unify the two divergent withTransientRetry into one canonical primitive (refs [#4298](https://github.com/dsj1984/mandrel/issues/4298)) ([#4299](https://github.com/dsj1984/mandrel/issues/4299)) ([e5668dc](https://github.com/dsj1984/mandrel/commit/e5668dcca73b37c37d38e52e1eb8d2930a0e1790))
30
+
5
31
  ## [1.77.0](https://github.com/dsj1984/mandrel/compare/mandrel-v1.76.0...mandrel-v1.77.0) (2026-06-24)
6
32
 
7
33
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mandrel",
3
- "version": "1.77.0",
3
+ "version": "1.79.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/",
@@ -1,62 +0,0 @@
1
- /**
2
- * transient-retry — shared retry-with-backoff for GitHub provider calls.
3
- *
4
- * Resilience for flaky connections (e.g. cell hotspots): retry ONLY on
5
- * connectivity blips, never on auth / scope / not-found / already-exists /
6
- * validation errors. Works for both surfaces the provider uses:
7
- * - the gh CLI path — errors carry the Go HTTP error on `err.stderr`
8
- * (e.g. `dial tcp ...: i/o timeout`); and
9
- * - the direct `fetch` path — errors are a `TypeError: fetch failed` with
10
- * the real reason on `err.cause` (e.g. `ETIMEDOUT`, `ENOTFOUND`).
11
- *
12
- * Retrying a non-idempotent create is acceptable for the dominant hotspot
13
- * failure (`dial tcp ... i/o timeout` means the connection never opened, so
14
- * the request never reached GitHub). Callers still gate retry per call so
15
- * the genuinely non-idempotent project-create can opt out.
16
- */
17
-
18
- const TRANSIENT_RE =
19
- /i\/o timeout|dial tcp|TLS handshake timeout|connection reset|connection refused|temporary failure|could not resolve host|no such host|network is unreachable|socket hang up|fetch failed|ConnectTimeoutError|UND_ERR_CONNECT_TIMEOUT|ETIMEDOUT|ECONNRESET|ECONNREFUSED|ENOTFOUND|EAI_AGAIN|\b50[234]\b/i;
20
-
21
- /** True when an error looks like a retryable network/connectivity blip. */
22
- export function isTransientNetworkError(err) {
23
- const hay = [
24
- err?.stderr,
25
- err?.message,
26
- err?.code,
27
- err?.cause?.message,
28
- err?.cause?.code,
29
- ]
30
- .filter(Boolean)
31
- .join(' ');
32
- return TRANSIENT_RE.test(hay);
33
- }
34
-
35
- /**
36
- * Run `fn`, retrying with exponential backoff ONLY on transient network
37
- * errors (1s, 2s, 4s by default). Non-transient errors throw immediately so
38
- * real failures stay loud. `sleep` is injectable for tests.
39
- *
40
- * @template T
41
- * @param {() => Promise<T>} fn
42
- * @param {{ retries?: number, baseDelayMs?: number,
43
- * sleep?: (ms: number) => Promise<void> }} [opts]
44
- * @returns {Promise<T>}
45
- */
46
- export async function withTransientRetry(
47
- fn,
48
- {
49
- retries = 3,
50
- baseDelayMs = 1000,
51
- sleep = (ms) => new Promise((r) => setTimeout(r, ms)),
52
- } = {},
53
- ) {
54
- for (let attempt = 0; ; attempt++) {
55
- try {
56
- return await fn();
57
- } catch (err) {
58
- if (attempt >= retries || !isTransientNetworkError(err)) throw err;
59
- await sleep(baseDelayMs * 2 ** attempt);
60
- }
61
- }
62
- }