mandrel 1.92.0 → 1.94.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 (144) hide show
  1. package/.agents/agents/acceptance-critic.md +129 -0
  2. package/.agents/agents/retro.md +42 -0
  3. package/.agents/agents/story-worker.md +162 -0
  4. package/.agents/docs/configuration.md +7 -1
  5. package/.agents/docs/execution-reference.md +27 -2
  6. package/.agents/instructions.md +43 -33
  7. package/.agents/personas/engineer.md +26 -112
  8. package/.agents/personas/security-engineer.md +1 -2
  9. package/.agents/rules/git-conventions-reference.md +225 -0
  10. package/.agents/rules/git-conventions.md +25 -200
  11. package/.agents/rules/security-baseline.md +5 -0
  12. package/.agents/rules/testing-standards.md +106 -13
  13. package/.agents/schemas/agentrc.schema.json +31 -1
  14. package/.agents/schemas/lifecycle/slice.end.schema.json +21 -0
  15. package/.agents/schemas/lifecycle/slice.heartbeat.schema.json +20 -0
  16. package/.agents/schemas/lifecycle/slice.start.schema.json +17 -0
  17. package/.agents/scripts/acceptance-eval.js +62 -18
  18. package/.agents/scripts/agents-bootstrap-github.js +1 -1
  19. package/.agents/scripts/bookkeeping-reconcile.js +117 -0
  20. package/.agents/scripts/check-context-budget.js +62 -5
  21. package/.agents/scripts/diagnose-friction.js +0 -6
  22. package/.agents/scripts/epic-deliver-prepare.js +272 -10
  23. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +56 -18
  24. package/.agents/scripts/lib/close-validation/gates.js +159 -21
  25. package/.agents/scripts/lib/config/acceptance-eval.js +52 -5
  26. package/.agents/scripts/lib/config/delivery-routing.js +87 -0
  27. package/.agents/scripts/lib/config/explain.js +2 -0
  28. package/.agents/scripts/lib/config-resolver.js +1 -1
  29. package/.agents/scripts/lib/config-settings-schema-delivery.js +37 -3
  30. package/.agents/scripts/lib/config-settings-schema-quality.js +9 -0
  31. package/.agents/scripts/lib/doc-tiers.js +37 -2
  32. package/.agents/scripts/lib/observability/active-story-env.js +111 -2
  33. package/.agents/scripts/lib/observability/hook-heartbeat.js +219 -0
  34. package/.agents/scripts/lib/observability/tool-trace-hook.js +15 -4
  35. package/.agents/scripts/lib/orchestration/acceptance-clusters.js +111 -0
  36. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +32 -4
  37. package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +270 -0
  38. package/.agents/scripts/lib/orchestration/ceremony-routing.js +141 -0
  39. package/.agents/scripts/lib/orchestration/context-hydration-engine.js +3 -124
  40. package/.agents/scripts/lib/orchestration/deliver-route.js +173 -0
  41. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/authoring-context.js +1 -1
  42. package/.agents/scripts/lib/orchestration/epic-run-state-store.js +233 -0
  43. package/.agents/scripts/lib/orchestration/file-assumptions.js +68 -7
  44. package/.agents/scripts/lib/orchestration/lifecycle/emit-slice-lifecycle.js +270 -0
  45. package/.agents/scripts/lib/orchestration/lifecycle/listeners/acceptance-reconciler.js +83 -2
  46. package/.agents/scripts/lib/orchestration/lifecycle/listeners/checkpoint-pointer-writer.js +6 -0
  47. package/.agents/scripts/lib/orchestration/plan-context.js +189 -3
  48. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +3 -2
  49. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +99 -0
  50. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +38 -1
  51. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +16 -1
  52. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +1 -0
  53. package/.agents/scripts/lib/orchestration/story-close/pre-merge-validation.js +1 -0
  54. package/.agents/scripts/lib/orchestration/ticket-validator.js +19 -2
  55. package/.agents/scripts/lib/provider-factory.js +1 -1
  56. package/.agents/scripts/lib/templates/decomposer-prompts.js +1 -1
  57. package/.agents/scripts/plan-context.js +28 -10
  58. package/.agents/scripts/post-structured-comment.js +38 -0
  59. package/.agents/scripts/slice-phase.js +361 -0
  60. package/.agents/scripts/sync-claude-agents.js +165 -0
  61. package/.agents/scripts/update-ticket-state.js +31 -0
  62. package/.agents/scripts/wave-tick.js +138 -9
  63. package/.agents/skills/core/api-and-interface-design/SKILL.md +5 -3
  64. package/.agents/skills/core/code-review-and-quality/SKILL.md +63 -7
  65. package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +1 -1
  66. package/.agents/skills/core/epic-plan-consolidate/SKILL.md +5 -5
  67. package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +8 -8
  68. package/.agents/skills/core/epic-plan-premortem/SKILL.md +4 -4
  69. package/.agents/skills/core/epic-plan-spec-author/SKILL.md +26 -56
  70. package/.agents/skills/core/gates-and-baselines/SKILL.md +149 -0
  71. package/.agents/skills/core/idea-refinement/SKILL.md +2 -8
  72. package/.agents/skills/core/qa-coverage-mapping/SKILL.md +7 -7
  73. package/.agents/skills/skills.index.json +11 -381
  74. package/.agents/workflows/deliver.md +47 -4
  75. package/.agents/workflows/helpers/acceptance-self-eval.md +38 -13
  76. package/.agents/workflows/helpers/deliver-epic-reference.md +18 -5
  77. package/.agents/workflows/helpers/deliver-epic-single.md +331 -0
  78. package/.agents/workflows/helpers/deliver-epic.md +51 -8
  79. package/.agents/workflows/helpers/deliver-stories.md +15 -5
  80. package/.agents/workflows/helpers/epic-deliver-story.md +12 -3
  81. package/.agents/workflows/helpers/mandrel-sync-config.md +1 -1
  82. package/.agents/workflows/helpers/plan-epic-reference.md +19 -8
  83. package/.agents/workflows/helpers/plan-epic.md +95 -27
  84. package/.agents/workflows/helpers/scope-triage-gate.md +9 -0
  85. package/.agents/workflows/mandrel-update.md +1 -1
  86. package/.agents/workflows/plan.md +16 -4
  87. package/docs/CHANGELOG.md +23 -0
  88. package/lib/cli/registry.js +95 -0
  89. package/package.json +4 -2
  90. package/.agents/personas/engineer-mobile.md +0 -120
  91. package/.agents/personas/engineer-web.md +0 -111
  92. package/.agents/personas/product.md +0 -94
  93. package/.agents/personas/refactorer.md +0 -113
  94. package/.agents/personas/sre.md +0 -86
  95. package/.agents/personas/ux-designer.md +0 -95
  96. package/.agents/scripts/epic-plan-decompose.js +0 -54
  97. package/.agents/scripts/epic-plan-spec.js +0 -64
  98. package/.agents/scripts/lib/orchestration/skill-capsule-loader.js +0 -109
  99. package/.agents/scripts/plan-critics.js +0 -227
  100. package/.agents/skills/core/baseline-refresh/SKILL.md +0 -181
  101. package/.agents/skills/core/ci-cd-and-automation/SKILL.md +0 -274
  102. package/.agents/skills/core/ci-cd-and-automation/examples.md +0 -211
  103. package/.agents/skills/core/code-simplification/SKILL.md +0 -389
  104. package/.agents/skills/core/context-engineering/SKILL.md +0 -309
  105. package/.agents/skills/core/context-engineering/examples.md +0 -58
  106. package/.agents/skills/core/deprecation-and-migration/SKILL.md +0 -250
  107. package/.agents/skills/core/frontend-ui-engineering/SKILL.md +0 -357
  108. package/.agents/skills/core/hydrate-context/SKILL.md +0 -123
  109. package/.agents/skills/core/idea-refinement/examples.md +0 -437
  110. package/.agents/skills/core/idea-refinement/frameworks.md +0 -135
  111. package/.agents/skills/core/incremental-implementation/SKILL.md +0 -271
  112. package/.agents/skills/core/introducing-a-baseline-gate/SKILL.md +0 -213
  113. package/.agents/skills/core/knowledge-transfer/SKILL.md +0 -180
  114. package/.agents/skills/core/mutation-survivor-remediation/SKILL.md +0 -117
  115. package/.agents/skills/core/performance-optimization/SKILL.md +0 -314
  116. package/.agents/skills/core/planning-and-task-breakdown/SKILL.md +0 -277
  117. package/.agents/skills/core/property-based-testing/SKILL.md +0 -148
  118. package/.agents/skills/core/refactoring-discipline/SKILL.md +0 -111
  119. package/.agents/skills/core/shipping-and-launch/SKILL.md +0 -328
  120. package/.agents/skills/core/spec-driven-development/SKILL.md +0 -252
  121. package/.agents/skills/core/test-driven-development/SKILL.md +0 -475
  122. package/.agents/skills/core/using-agent-skills/SKILL.md +0 -232
  123. package/.agents/skills/stack/architecture/monorepo-path-strategist/SKILL.md +0 -31
  124. package/.agents/skills/stack/architecture/structured-output-zod/SKILL.md +0 -51
  125. package/.agents/skills/stack/architecture/subagent-orchestration/SKILL.md +0 -76
  126. package/.agents/skills/stack/backend/cloudflare-hono-architect/SKILL.md +0 -31
  127. package/.agents/skills/stack/backend/cloudflare-hono-architect/examples/route-template.ts +0 -33
  128. package/.agents/skills/stack/backend/cloudflare-queue-manager/SKILL.md +0 -31
  129. package/.agents/skills/stack/backend/cloudflare-workers/SKILL.md +0 -51
  130. package/.agents/skills/stack/backend/highlevel-crm/SKILL.md +0 -54
  131. package/.agents/skills/stack/backend/sqlite-drizzle-expert/SKILL.md +0 -29
  132. package/.agents/skills/stack/backend/sqlite-drizzle-expert/examples/schema-template.ts +0 -30
  133. package/.agents/skills/stack/backend/stripe-integration/SKILL.md +0 -57
  134. package/.agents/skills/stack/backend/stripe-integration/scripts/listen-stripe.sh +0 -9
  135. package/.agents/skills/stack/backend/turso-sqlite/SKILL.md +0 -48
  136. package/.agents/skills/stack/frontend/astro/SKILL.md +0 -62
  137. package/.agents/skills/stack/frontend/astro-react-island-strategist/SKILL.md +0 -30
  138. package/.agents/skills/stack/frontend/expo-react-native-developer/SKILL.md +0 -29
  139. package/.agents/skills/stack/frontend/google-analytics-v4/SKILL.md +0 -50
  140. package/.agents/skills/stack/frontend/tailwind-v4/SKILL.md +0 -58
  141. package/.agents/skills/stack/frontend/ui-accessibility-engineer/SKILL.md +0 -34
  142. package/.agents/skills/stack/qa/audit-accessibility/SKILL.md +0 -51
  143. package/.agents/skills/stack/qa/lighthouse-baseline/SKILL.md +0 -199
  144. package/.agents/skills/stack/security/backend-security-patterns/SKILL.md +0 -68
@@ -124,6 +124,66 @@ export function readLedgerLastEvents(ledgerPath) {
124
124
  return out;
125
125
  }
126
126
 
127
+ /**
128
+ * Single-delivery analogue of {@link readLedgerLastEvents} (Epic #4476, M5).
129
+ * A single-delivery run has no Story fan-out; the whole Epic is one long
130
+ * guarded session walking the Delivery Slicing table, and its liveness signal
131
+ * is `slice.heartbeat` (emitted off the token stream by the PostToolUse hook).
132
+ * This scans the ledger for `slice.*` events, treats a slice carrying a
133
+ * `slice.start` without a matching `slice.end` as in-flight, and returns each
134
+ * in-flight slice's most recent event timestamp — the same
135
+ * started-without-ended shape the Story reader uses, keyed by the string
136
+ * `sliceId`. Any `slice.*` event (notably `slice.heartbeat`) refreshes the
137
+ * timestamp.
138
+ *
139
+ * Pure helper — exported for unit tests.
140
+ *
141
+ * @param {string} ledgerPath Absolute path to `lifecycle.ndjson`.
142
+ * @returns {Map<string, string>} `sliceId → ISO-8601 timestamp`. Empty when
143
+ * the ledger is missing, unreadable, or carries no in-flight slices.
144
+ */
145
+ export function readLedgerSliceEvents(ledgerPath) {
146
+ if (!ledgerPath || !existsSync(ledgerPath)) return new Map();
147
+ let raw;
148
+ try {
149
+ raw = readFileSync(ledgerPath, 'utf8');
150
+ } catch {
151
+ return new Map();
152
+ }
153
+ if (!raw) return new Map();
154
+ const started = new Set();
155
+ const ended = new Set();
156
+ const lastTs = new Map();
157
+ for (const line of raw.split(/\r?\n/)) {
158
+ if (!line) continue;
159
+ let record;
160
+ try {
161
+ record = JSON.parse(line);
162
+ } catch {
163
+ continue;
164
+ }
165
+ if (!record || record.kind !== 'emitted') continue;
166
+ const event = typeof record.event === 'string' ? record.event : '';
167
+ if (!event.startsWith('slice.')) continue;
168
+ const sliceId = record.payload?.sliceId;
169
+ if (typeof sliceId !== 'string' || sliceId.length === 0) continue;
170
+ if (event === 'slice.start') started.add(sliceId);
171
+ else if (event === 'slice.end') ended.add(sliceId);
172
+ const ts = typeof record.ts === 'string' ? record.ts : null;
173
+ if (ts) {
174
+ const prior = lastTs.get(sliceId);
175
+ if (!prior || prior < ts) lastTs.set(sliceId, ts);
176
+ }
177
+ }
178
+ const out = new Map();
179
+ for (const id of started) {
180
+ if (ended.has(id)) continue;
181
+ const ts = lastTs.get(id);
182
+ if (ts) out.set(id, ts);
183
+ }
184
+ return out;
185
+ }
186
+
127
187
  /**
128
188
  * Deterministic branch-activity resolver (Story #3900). Returns the epoch-ms
129
189
  * timestamp of the most recent commit on `story-<storyId>` via
@@ -139,18 +199,25 @@ export function readLedgerLastEvents(ledgerPath) {
139
199
  * and injectable so unit tests never spawn a subprocess.
140
200
  *
141
201
  * @param {number} storyId
142
- * @param {{ cwd?: string, exec?: typeof execFileSync }} [deps]
202
+ * @param {{ cwd?: string, exec?: typeof execFileSync, branch?: string }} [deps]
203
+ * `branch` overrides the default `story-<storyId>` ref — the single-delivery
204
+ * watchdog passes `epic-<id>` (Epic #4476, M5) to consult the one
205
+ * integration branch instead of a per-Story branch.
143
206
  * @returns {number|null} epoch-ms of the last commit, or null.
144
207
  */
145
208
  export function branchLastCommitMs(storyId, deps = {}) {
146
209
  const exec = deps.exec ?? execFileSync;
147
210
  const cwd = deps.cwd ?? process.cwd();
211
+ const branch =
212
+ typeof deps.branch === 'string' && deps.branch
213
+ ? deps.branch
214
+ : `story-${storyId}`;
148
215
  try {
149
- const out = exec(
150
- 'git',
151
- ['log', '-1', '--format=%cI', `story-${storyId}`, '--'],
152
- { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] },
153
- ).trim();
216
+ const out = exec('git', ['log', '-1', '--format=%cI', branch, '--'], {
217
+ cwd,
218
+ encoding: 'utf8',
219
+ stdio: ['ignore', 'pipe', 'ignore'],
220
+ }).trim();
154
221
  if (!out) return null;
155
222
  const ms = Date.parse(out);
156
223
  return Number.isFinite(ms) ? ms : null;
@@ -180,13 +247,26 @@ export function branchLastCommitMs(storyId, deps = {}) {
180
247
  * @param {(storyId: number) => (number|null)} [args.branchActivity]
181
248
  * Resolver for the last commit epoch-ms on `story-<id>`. Defaults to
182
249
  * {@link branchLastCommitMs}. Injected for tests.
250
+ * @param {Map<string, string>} [args.sliceEvents] `sliceId → ISO-8601 ts`
251
+ * from {@link readLedgerSliceEvents} (Epic #4476, M5). Single-delivery has
252
+ * no Story fan-out; its liveness signal is `slice.heartbeat`. When
253
+ * non-empty, in-flight slices are surfaced under the additive
254
+ * `inFlightSlices` / `stalledSlices` fields — the story fields are
255
+ * untouched, so a fan-out run's envelope is byte-for-byte unchanged.
256
+ * @param {(epicId: number) => (number|null)} [args.epicBranchActivity]
257
+ * Resolver for the last commit epoch-ms on the single-delivery integration
258
+ * branch `epic-<id>` — the deterministic forward-progress signal for the
259
+ * one long session, mirroring the per-Story branch check. Injected for
260
+ * tests.
183
261
  * @returns {{
184
262
  * kind: 'wave-stall',
185
263
  * epicId: number,
186
264
  * thresholdMinutes: number,
187
265
  * checkedAt: string,
188
266
  * stalled: Array<{storyId: number, lastEventAt: string, idleMinutes: number}>,
189
- * inFlight: number[]
267
+ * inFlight: number[],
268
+ * stalledSlices: Array<{sliceId: string, lastEventAt: string, idleMinutes: number}>,
269
+ * inFlightSlices: string[]
190
270
  * }}
191
271
  */
192
272
  export function buildWaveStallEnvelope({
@@ -195,6 +275,9 @@ export function buildWaveStallEnvelope({
195
275
  lastEvents,
196
276
  now,
197
277
  branchActivity = branchLastCommitMs,
278
+ sliceEvents = new Map(),
279
+ epicBranchActivity = (eid) =>
280
+ branchLastCommitMs(0, { branch: `epic-${eid}` }),
198
281
  }) {
199
282
  const checkedAt = now.toISOString();
200
283
  const thresholdMs = thresholdMinutes * 60 * 1000;
@@ -227,6 +310,37 @@ export function buildWaveStallEnvelope({
227
310
  }
228
311
  inFlight.sort((a, b) => a - b);
229
312
  stalled.sort((a, b) => a.storyId - b.storyId);
313
+
314
+ // Single-delivery slice liveness (additive). A slice that has gone silent
315
+ // past the threshold is a stall UNLESS the `epic-<id>` branch carries a
316
+ // recent commit — the same forward-progress carve-out the Story path uses.
317
+ const stalledSlices = [];
318
+ const inFlightSlices = [];
319
+ for (const [sliceId, ts] of sliceEvents) {
320
+ inFlightSlices.push(sliceId);
321
+ const eventMs = Date.parse(ts);
322
+ if (!Number.isFinite(eventMs)) continue;
323
+ const idleMs = now.getTime() - eventMs;
324
+ if (idleMs < thresholdMs) continue;
325
+
326
+ const commitMs = epicBranchActivity(epicId);
327
+ if (
328
+ typeof commitMs === 'number' &&
329
+ Number.isFinite(commitMs) &&
330
+ now.getTime() - commitMs < thresholdMs
331
+ ) {
332
+ continue;
333
+ }
334
+
335
+ stalledSlices.push({
336
+ sliceId,
337
+ lastEventAt: ts,
338
+ idleMinutes: Math.floor(idleMs / 60000),
339
+ });
340
+ }
341
+ inFlightSlices.sort();
342
+ stalledSlices.sort((a, b) => a.sliceId.localeCompare(b.sliceId));
343
+
230
344
  return {
231
345
  kind: 'wave-stall',
232
346
  epicId,
@@ -234,6 +348,8 @@ export function buildWaveStallEnvelope({
234
348
  checkedAt,
235
349
  stalled,
236
350
  inFlight,
351
+ stalledSlices,
352
+ inFlightSlices,
237
353
  };
238
354
  }
239
355
 
@@ -251,7 +367,13 @@ export function buildWaveStallEnvelope({
251
367
  * @param {(storyId: number) => (number|null)} [args.branchActivity]
252
368
  * Branch-liveness resolver; defaults to {@link branchLastCommitMs}.
253
369
  * Injected for tests so the watchdog never spawns git.
254
- * @returns {{envelope: object, stalledCount: number}}
370
+ * @param {(epicId: number) => (number|null)} [args.epicBranchActivity]
371
+ * Single-delivery integration-branch (`epic-<id>`) liveness resolver;
372
+ * injected for tests. Defaults to {@link branchLastCommitMs} with the
373
+ * `epic-<id>` ref.
374
+ * @returns {{envelope: object, stalledCount: number}} `stalledCount` counts
375
+ * both stalled Stories (fan-out) and stalled slices (single delivery), so a
376
+ * dead single-delivery session still exits the CLI non-zero.
255
377
  */
256
378
  export function runCheckIdle({
257
379
  epicId,
@@ -260,6 +382,7 @@ export function runCheckIdle({
260
382
  now = new Date(),
261
383
  config,
262
384
  branchActivity = branchLastCommitMs,
385
+ epicBranchActivity,
263
386
  }) {
264
387
  if (!Number.isInteger(epicId) || epicId <= 0) {
265
388
  throw new TypeError('runCheckIdle: epicId must be a positive integer');
@@ -271,14 +394,20 @@ export function runCheckIdle({
271
394
  }
272
395
  const resolvedPath = ledgerPath ?? epicLedgerPath(epicId, config);
273
396
  const lastEvents = readLedgerLastEvents(resolvedPath);
397
+ const sliceEvents = readLedgerSliceEvents(resolvedPath);
274
398
  const envelope = buildWaveStallEnvelope({
275
399
  epicId,
276
400
  thresholdMinutes,
277
401
  lastEvents,
278
402
  now,
279
403
  branchActivity,
404
+ sliceEvents,
405
+ ...(epicBranchActivity ? { epicBranchActivity } : {}),
280
406
  });
281
- return { envelope, stalledCount: envelope.stalled.length };
407
+ return {
408
+ envelope,
409
+ stalledCount: envelope.stalled.length + envelope.stalledSlices.length,
410
+ };
282
411
  }
283
412
 
284
413
  async function main(argv) {
@@ -19,7 +19,7 @@ description:
19
19
  - Define the contract before implementing — interfaces are the spec; implementation follows.
20
20
  - Validate at system **boundaries** (API routes, form handlers, env-var loaders, third-party responses) using a strict schema. After validation, internal code trusts the types; do not re-validate between internal functions.
21
21
  - Treat third-party API responses as untrusted data — validate shape and content before using them in any decision, render, or logic path.
22
- - Prefer **addition over modification**: extend interfaces with optional fields rather than changing existing types or removing fields; reach for the deprecation playbook (see `deprecation-and-migration`) when removal is unavoidable.
22
+ - Prefer **addition over modification**: extend interfaces with optional fields rather than changing existing types or removing fields. When removal is unavoidable, use an expand–contract migration ship the replacement, migrate consumers, then remove the old surface in a later release.
23
23
  - Follow REST resource conventions (`GET/POST/PATCH/DELETE /resource`, sub-resources at `/resource/:id/child`) and paginate every list endpoint with `page` + `pageSize` query params and a `pagination` envelope.
24
24
  - Security input-validation and test-tier MUSTs come from `.agents/rules/security-baseline.md` and `.agents/rules/testing-standards.md` respectively — apply both, and never put DB/wire-shape assertions outside the contract tier.
25
25
 
@@ -55,8 +55,10 @@ ordering — becomes a de facto contract once users depend on it. Implications:
55
55
  potential commitment.
56
56
  - **Don't leak implementation details.** If users can observe it, they will
57
57
  depend on it.
58
- - **Plan for deprecation at design time.** See `deprecation-and-migration`
59
- for how to safely remove things users depend on.
58
+ - **Plan for deprecation at design time.** Remove things users depend on via
59
+ expand–contract: add the replacement, migrate consumers behind a deprecation
60
+ window, then delete the old surface — never break a published contract in one
61
+ step.
60
62
  - **Tests are not enough.** Even with perfect contract tests, "safe" changes
61
63
  can break real users who depend on undocumented behavior.
62
64
 
@@ -1,10 +1,11 @@
1
1
  ---
2
2
  name: code-review-and-quality
3
3
  description:
4
- Conducts multi-axis code review. Use before merging any change. Use when
5
- reviewing code written by yourself, another agent, or a human. Use when you
6
- need to assess code quality across multiple dimensions before it enters the
7
- main branch.
4
+ Conducts multi-axis code review and runs the disciplined post-green refactor
5
+ pass. Use before merging any change, when reviewing code written by yourself,
6
+ another agent, or a human, or when the opt-in `delivery.refactorStage`
7
+ checkpoint asks for a behaviour-preserving CRAP/duplication pass after the
8
+ suite is green.
8
9
  ---
9
10
 
10
11
  # Code Review and Quality
@@ -18,9 +19,11 @@ description:
18
19
  - Reject "clever" code in favour of the boring, obvious solution. Abstractions MUST earn their complexity (no generalizing before the third use case).
19
20
  - Flag dead code artifacts (`_unused` vars, backwards-compat shims, `// removed` comments) and require their removal before merge.
20
21
  - Defer to `.agents/rules/security-baseline.md` and the `security-and-hardening` skill for security review; explicit checks include input validation, no hardcoded secrets, parameterized queries, encoded output, authn+authz, and treating external data as untrusted.
21
- - Defer to `performance-optimization` for perf review; explicit checks include no N+1 queries, no unbounded fetches, no blocking sync work, no obviously oversized bundles.
22
+ - Review performance on the hot path only, and measure before optimizing; explicit checks include no N+1 queries, no unbounded fetches, no blocking sync work, no obviously oversized bundles.
22
23
  - Disallow scope creep in a PR: drive-by cleanups, adjacent refactors, and "while I'm here" edits should be split into a separate change.
23
24
  - Bug-fix reviews cover **both** the fix and the regression test; a fix without a failing-then-passing test is not approvable.
25
+ - **Refactoring is post-green and behaviour-preserving.** Run the refactor pass only when the suite is already green and gates pass; inputs, outputs, side effects, error semantics, and ordering MUST be identical before and after, and existing tests MUST keep passing **without modification** (if a test had to change, behaviour changed — revert).
26
+ - **Lower CRAP by lowering complexity, and remove duplication at the root.** Target the highest-CRAP well-covered functions and the largest verbatim duplications by measurement (`check-baselines.js`), not by smell; extract one well-named helper rather than leaving near-copies. CRAP must not rise and maintainability must not fall on any touched file, and no gate, floor, or threshold may be retuned to make the pass "succeed".
24
27
 
25
28
  ## Overview
26
29
 
@@ -105,8 +108,8 @@ introduce vulnerabilities?
105
108
 
106
109
  ### 5. Performance
107
110
 
108
- For detailed profiling and optimization, see `performance-optimization`. Does
109
- the change introduce performance problems?
111
+ Measure before optimizing, and only on the hot path. Does the change introduce
112
+ performance problems?
110
113
 
111
114
  - Any N+1 query patterns?
112
115
  - Any unbounded loops or unconstrained data fetching?
@@ -409,6 +412,50 @@ Every dependency is a liability.
409
412
  optional
410
413
  - Accepting "I'll fix it later" — it never happens
411
414
 
415
+ ## Post-Green Refactor Pass
416
+
417
+ Review's sibling discipline is the **behaviour-preserving refactor** that drives
418
+ CRAP (Change Risk Anti-Patterns) and duplication down _after_ the code is green
419
+ — the pass wired in as the opt-in `delivery.refactorStage` checkpoint. It
420
+ optimises for the measured CRAP and duplication axes the baselines ratchet, not
421
+ for subjective readability, and it never runs from red.
422
+
423
+ **When to run:** a baseline report flags a high-CRAP function that is already
424
+ well-covered (so the lever is complexity, not coverage); the same logic is
425
+ duplicated across two or more files and the copies are drifting; or the
426
+ `delivery.refactorStage` checkpoint fires after gates pass and before a Story
427
+ closes. **When NOT to:** before tests are green (write/fix tests first), or when
428
+ a function's CRAP is high because it is _uncovered_ — that is a missing-test
429
+ problem, so drive it with the TDD cycle in
430
+ [`.agents/rules/testing-standards.md`](../../../rules/testing-standards.md).
431
+
432
+ ```text
433
+ Confirm green + baseline → pick highest-CRAP / largest dup
434
+ ▲ │
435
+ │ ▼
436
+ re-run gates, confirm ◄── refactor ◄── comprehend the target
437
+ CRAP↓ / maint↑, tests pass (one change) (call sites + tests)
438
+ ```
439
+
440
+ 1. **Baseline.** Confirm green and capture current CRAP and maintainability
441
+ (`node .agents/scripts/check-baselines.js`) — your do-no-harm reference.
442
+ 2. **Target by measurement.** Pick the highest-CRAP well-covered function, or
443
+ the largest verbatim duplication. Work worst-first.
444
+ 3. **Comprehend before you touch (Chesterton's Fence).** Read the call sites and
445
+ the tests that pin the behaviour first; an "unnecessary" abstraction may
446
+ exist for testability or extensibility.
447
+ 4. **Refactor one step.** Extract a focused function, flatten nesting into guard
448
+ clauses, replace a nested ternary / boolean-flag param, or extract a single
449
+ shared helper for the duplication (dedup the _behaviour_, not just the text —
450
+ look-alike blocks that encode different responsibilities stay separate).
451
+ 5. **Verify.** Re-run the affected tests (unmodified) and the baselines. CRAP
452
+ must not rise, maintainability must not fall, tests must pass. If a test
453
+ needed editing, revert — behaviour changed.
454
+
455
+ Keep each refactor an isolated, reviewable commit separate from feature or fix
456
+ work, refactor only the targeted functions/duplications (no drive-by rewrites),
457
+ and leave no dead code behind (unused imports, orphaned helpers).
458
+
412
459
  ## Verification
413
460
 
414
461
  After review is complete:
@@ -419,3 +466,12 @@ After review is complete:
419
466
  - [ ] Tests pass
420
467
  - [ ] Build succeeds
421
468
  - [ ] The verification story is documented (what changed, how it was verified)
469
+
470
+ After a post-green refactor pass:
471
+
472
+ - [ ] The suite was green before and after, with **no test modifications**
473
+ - [ ] CRAP did not rise and maintainability did not fall on any touched file
474
+ (baselines re-run and compared)
475
+ - [ ] No gate, floor, or coverage threshold was loosened
476
+ - [ ] Duplication was removed at the root (one helper), not just locally patched
477
+ - [ ] Each refactor is an isolated commit, separate from feature or fix work
@@ -15,7 +15,7 @@ description:
15
15
  - Follow the triage checklist in order — **Reproduce → Localize → Diagnose → Fix → Verify → Guard** — and never skip steps.
16
16
  - A bug you cannot reproduce reliably is a bug you cannot fix with confidence. Invest in reproduction before patching.
17
17
  - Fix the **root cause**, not the symptom. Suppressing an error, swallowing an exception, or stubbing an assertion is not a fix.
18
- - Every bug fix ships with a **failing-then-passing regression test** (the Prove-It Pattern from `test-driven-development`). A fix without a guard test is incomplete.
18
+ - Every bug fix ships with a **failing-then-passing regression test** (the Prove-It Pattern in `.agents/rules/testing-standards.md`). A fix without a guard test is incomplete.
19
19
  - Apply the **Anti-Thrashing** rule: if you have applied the same kind of fix more than once and the failure mode hasn't changed, the diagnosis is wrong — re-plan.
20
20
  - For non-reproducible bugs, classify them (timing / environment / state / random), add targeted instrumentation, and document conditions instead of chasing in the dark.
21
21
  - Bisect history with `git bisect` (or equivalent) when "something used to work" and you cannot localize from the diff.
@@ -4,7 +4,7 @@ description: >-
4
4
  Run a holistic, pre-persist consolidation pass over the draft Story
5
5
  ticket array an Epic's decompose phase produced. Use during Phase 8 of
6
6
  `/plan`, after `epic-plan-decompose-author` writes
7
- `temp/epic-<Epic_ID>/tickets.json` and before `epic-plan-decompose.js`
7
+ `temp/epic-<Epic_ID>/tickets.json` and before `plan-persist.js`
8
8
  validates and persists it. Reconciles the draft against the Tech Spec
9
9
  "Delivery Slicing" ceiling via scope-preserving operations only.
10
10
  allowed_tools:
@@ -48,7 +48,7 @@ critic cannot grade its own homework.
48
48
  `/plan` Phase 8, as the **8.3 — Holistic Consolidation** sub-step:
49
49
  immediately after `epic-plan-decompose-author` writes
50
50
  `temp/epic-<Epic_ID>/tickets.json` and **before**
51
- `epic-plan-decompose.js --tickets …` validates and persists. The pass operates
51
+ `plan-persist.js --tickets …` validates and persists. The pass operates
52
52
  on the temp artifact so the holistic adjustment happens before the GitHub
53
53
  write; the deterministic validator runs *after* it, so the critic can never
54
54
  emit a plan the validator would reject.
@@ -61,7 +61,7 @@ passing the Epic ID as the Skill argument. The Skill itself reads:
61
61
  - `temp/epic-<Epic_ID>/tickets.json` — the **draft** Story array the
62
62
  `epic-plan-decompose-author` Skill wrote. This is the consolidation input.
63
63
  - `temp/epic-<Epic_ID>/decomposer-context.json` — the authoring envelope
64
- emitted by `epic-plan-decompose.js --emit-context`. Read `epicBody`
64
+ emitted by `plan-context.js --epic <Epic_ID>`. Read `epicBody`
65
65
  from it — the sectioned Epic body carrying the folded Tech Spec
66
66
  sections (there is no separate `techSpec` key — Story #4324). The
67
67
  **"Delivery Slicing"**
@@ -74,7 +74,7 @@ passing the Epic ID as the Skill argument. The Skill itself reads:
74
74
  - `temp/epic-<Epic_ID>/tickets.json` — the **consolidated** array, overwriting
75
75
  the draft. Same schema as the author skill emits (flat Story array; Stories
76
76
  carry top-level `acceptance[]` / `verify[]`; `body` is a serialized string).
77
- The downstream `epic-plan-decompose.js --tickets …` validator is the final
77
+ The downstream `plan-persist.js --tickets …` validator is the final
78
78
  gate — author for its rules, not for "looks right."
79
79
  - `temp/epic-<Epic_ID>/consolidation-report.md` — a human-readable
80
80
  rationale + before/after diff. This is the artifact the workflow shows the
@@ -152,7 +152,7 @@ so the operator sees the coarsening at the Phase 8.3 advisory diff.
152
152
 
153
153
  Return control. The workflow shows the operator the consolidation report at the
154
154
  HITL diff gate; on approval it runs
155
- `node .agents/scripts/epic-plan-decompose.js --epic <Epic_ID> --tickets
155
+ `node .agents/scripts/plan-persist.js --epic <Epic_ID> --tickets
156
156
  temp/epic-<Epic_ID>/tickets.json`, which validates the consolidated array,
157
157
  persists the hierarchy, and flips the Epic to `agent::ready`.
158
158
 
@@ -2,9 +2,9 @@
2
2
  name: epic-plan-decompose-author
3
3
  description: >-
4
4
  Author the Story ticket JSON for an Epic from the decomposer
5
- authoring context emitted by `epic-plan-decompose.js --emit-context`. Use
5
+ authoring context emitted by `plan-context.js --epic <Epic_ID>`. Use
6
6
  during Phase 8 of `/plan` when the host LLM needs to write the ticket
7
- array before `epic-plan-decompose.js` validates and persists it.
7
+ array before `plan-persist.js` validates and persists it.
8
8
  allowed_tools:
9
9
  - Read
10
10
  - Write
@@ -15,11 +15,11 @@ allowed_tools:
15
15
 
16
16
  ## Policy Capsule
17
17
 
18
- - Run only after `epic-plan-decompose.js --emit-context` has written `temp/epic-<Epic_ID>/decomposer-context.json`; fail loudly if the file is missing.
18
+ - Run only after `plan-context.js --epic <Epic_ID>` has written `temp/epic-<Epic_ID>/decomposer-context.json`; fail loudly if the file is missing.
19
19
  - Emit exactly one artifact: `temp/epic-<Epic_ID>/tickets.json` (a JSON array). Do not write anywhere else, and never call the GitHub API from this Skill — persistence belongs to the script.
20
20
  - Output is JSON only — no prose, no Markdown fence. The downstream validator (`lib/orchestration/ticket-validator.js`) is the authoritative gate.
21
21
  - **Re-emit rule (amend, don't regenerate — Story #4431).** On a re-emit — the validator rejecting the draft, or a Phase 8.3/8.4/8.5 critic flagging specific Stories — apply **targeted edits** to the existing `temp/epic-<Epic_ID>/tickets.json`, fixing only the rejected or flagged Stories, rather than re-authoring the whole array from scratch. `helpers/plan-epic.md` bounds each critic's feedback to **one refinement pass** — apply the edit and re-run the downstream step once; do not loop.
22
- - Treat **`maxTickets`** from the context envelope as a **reviewability budget**, not a hard authoring cap (Story #2798). Merge narrow, single-module Stories into their capability first; if the plan genuinely needs more, emit the full plan and add a compact `over_budget_rationale` note inside the first Story's `## Goal` section explaining why the plan exceeds the budget. Operator persistence then requires the explicit `--allow-over-budget` override on `epic-plan-decompose.js`; without it the persist step rejects the over-budget array. Never truncate the JSON array to fit.
22
+ - Treat **`maxTickets`** from the context envelope as a **reviewability budget**, not a hard authoring cap (Story #2798). Merge narrow, single-module Stories into their capability first; if the plan genuinely needs more, emit the full plan and add a compact `over_budget_rationale` note inside the first Story's `## Goal` section explaining why the plan exceeds the budget. Operator persistence then requires the explicit `--allow-over-budget` override on `plan-persist.js`; without it the persist step rejects the over-budget array. Never truncate the JSON array to fit.
23
23
  - Honour the 2-tier hierarchy: every ticket is a **Story** attached directly to the Epic. Stories carry the implementation scope inline; no Feature and no lower ticket tier exists. Thematic grouping is prose in the Epic body / Tech Spec, never a ticket.
24
24
  - **Decompose at deliverable granularity, not module/task level.** A Story is a capability slice a frontier model delivers and self-verifies in one pass — a shippable slice a reviewer would accept as a single PR — not a single module or file. See the STORY SIZING section for the full guidance and the single-consumer merge rule.
25
25
  - Every ticket carries `type::story` and `persona::*` labels. Every Story ticket object MUST carry top-level `acceptance: string[]` and `verify: string[]` arrays (read by `hasInlineAcceptanceAndVerify` in the validator) and `body` MUST be a **string** produced by `serialize()` from `lib/story-body/story-body.js` — an object body causes `createOp` in `epic-spec-reconciler-ops.js` to throw `StoryBodyParseError` (Story #3302), and is also silently discarded by `composeStoryBody` in the GitHub provider, producing an empty issue body.
@@ -41,7 +41,7 @@ orchestrator can execute autonomously.
41
41
  ## When to use
42
42
 
43
43
  `/plan` Phase 8, immediately after
44
- `epic-plan-decompose.js --emit-context` writes
44
+ `plan-context.js --epic <Epic_ID>` writes
45
45
  `temp/epic-<Epic_ID>/decomposer-context.json`. The Skill replaces the
46
46
  inline "Author the Ticket Array" step in the legacy workflow body —
47
47
  the caller dispatches this Skill via the `Skill` tool, supplies the Epic
@@ -54,7 +54,7 @@ The dispatcher passes the Epic ID as the Skill argument. The Skill itself
54
54
  reads:
55
55
 
56
56
  - `temp/epic-<Epic_ID>/decomposer-context.json` — produced by
57
- `node .agents/scripts/epic-plan-decompose.js --epic <Epic_ID> --emit-context`.
57
+ `node .agents/scripts/plan-context.js --epic <Epic_ID>`.
58
58
  Fields:
59
59
  - `epic.id`, `epic.title`
60
60
  - `epicBody` — the sectioned Epic body. It carries everything the
@@ -93,7 +93,7 @@ reads:
93
93
  objects conforming to the schema in this Skill's body.
94
94
 
95
95
  The file MUST exist before the Skill returns. The caller will then run
96
- `node .agents/scripts/epic-plan-decompose.js --epic <Epic_ID> --tickets
96
+ `node .agents/scripts/plan-persist.js --epic <Epic_ID> --tickets
97
97
  temp/epic-<Epic_ID>/tickets.json`, which validates the array, persists
98
98
  the hierarchy as GitHub issues, and transitions the Epic to
99
99
  `agent::ready`. The script's validator is the final gate — author for
@@ -140,7 +140,7 @@ machine-consumed.
140
140
  ### Step 4 — Hand back to `/plan`
141
141
 
142
142
  Return control. The caller invokes
143
- `node .agents/scripts/epic-plan-decompose.js --epic <Epic_ID> --tickets
143
+ `node .agents/scripts/plan-persist.js --epic <Epic_ID> --tickets
144
144
  temp/epic-<Epic_ID>/tickets.json`, which validates, persists, and flips
145
145
  the Epic to `agent::ready`.
146
146
 
@@ -4,7 +4,7 @@ description: >-
4
4
  Run a fresh-context, code-reading pre-mortem critic over the draft Story
5
5
  ticket array an Epic's decompose phase produced. Use during Phase 8 of
6
6
  `/plan`, after `epic-plan-decompose-author` / `epic-plan-consolidate` write
7
- `temp/epic-<Epic_ID>/tickets.json` and before `epic-plan-decompose.js`
7
+ `temp/epic-<Epic_ID>/tickets.json` and before `plan-persist.js`
8
8
  validates and persists it. Reads the sectioned Epic body AND the cited
9
9
  code surfaces, then emits predicted-rework findings before any GitHub write.
10
10
  allowed_tools:
@@ -49,7 +49,7 @@ independent of the draft it grades.
49
49
  after `epic-plan-decompose-author` writes (and, when present,
50
50
  `epic-plan-consolidate` consolidates) `temp/epic-<Epic_ID>/tickets.json`, after
51
51
  the reachability completeness critic (8.4), and **before**
52
- `epic-plan-decompose.js --tickets …` validates and persists. The pass operates
52
+ `plan-persist.js --tickets …` validates and persists. The pass operates
53
53
  on the temp artifact and emits a report so the operator sees predicted rework in
54
54
  the Phase 8 HITL diff before the GitHub write; the author re-runs on the
55
55
  findings and the deterministic validator runs *after*, so nothing this critic
@@ -63,7 +63,7 @@ passing the Epic ID as the Skill argument. The Skill itself reads:
63
63
  - `temp/epic-<Epic_ID>/tickets.json` — the **draft** (or consolidated) Story
64
64
  array. This is the pre-mortem subject.
65
65
  - `temp/epic-<Epic_ID>/decomposer-context.json` — the authoring envelope emitted
66
- by `epic-plan-decompose.js --emit-context`. Read `epicBody` from it —
66
+ by `plan-context.js --epic <Epic_ID>`. Read `epicBody` from it —
67
67
  the sectioned Epic body carrying the folded Tech Spec sections
68
68
  (there is no separate `techSpec` key — Story #4324).
69
69
  - **The repository working tree** — the actual files each Story's `changes[]` /
@@ -126,7 +126,7 @@ line. Paste identifiers and short rationale only — never full source bodies.
126
126
  Return control. The workflow shows the operator the pre-mortem report at the
127
127
  Phase 8 HITL diff; on operator approval it re-runs
128
128
  `epic-plan-decompose-author` on the findings **before** the persist call
129
- (`node .agents/scripts/epic-plan-decompose.js --epic <Epic_ID> --tickets
129
+ (`node .agents/scripts/plan-persist.js --epic <Epic_ID> --tickets
130
130
  temp/epic-<Epic_ID>/tickets.json`). This Skill itself persists nothing.
131
131
 
132
132
  ## Constraints