mandrel 2.64.0 → 2.66.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 (105) hide show
  1. package/.agents/agents/acceptance-critic.md +8 -7
  2. package/.agents/agents/auditor.md +20 -20
  3. package/.agents/agents/plan-critic.md +8 -7
  4. package/.agents/agents/story-worker.md +7 -7
  5. package/.agents/audit-checklists/quality.md +3 -0
  6. package/.agents/docs/agentrc-reference.json +1 -9
  7. package/.agents/docs/configuration.md +8 -7
  8. package/.agents/docs/execution-reference.md +27 -5
  9. package/.agents/instructions.md +10 -12
  10. package/.agents/rules/ci-remediation.md +3 -3
  11. package/.agents/rules/gherkin-standards.md +3 -2
  12. package/.agents/rules/git-conventions-reference.md +12 -3
  13. package/.agents/rules/git-conventions.md +9 -7
  14. package/.agents/rules/testing-standards.md +8 -7
  15. package/.agents/runtime-deps.json +1 -1
  16. package/.agents/schemas/agentrc.schema.json +6 -13
  17. package/.agents/schemas/audit-rules.schema.json +1 -1
  18. package/.agents/schemas/story-deliver-terminal.schema.json +5 -0
  19. package/.agents/scripts/bootstrap.js +102 -91
  20. package/.agents/scripts/check-context-budget.js +1 -1
  21. package/.agents/scripts/lib/ITicketingProvider.js +1 -3
  22. package/.agents/scripts/lib/audit-suite/findings.js +1 -17
  23. package/.agents/scripts/lib/audit-suite/frontmatter.js +0 -28
  24. package/.agents/scripts/lib/audit-suite/index.js +0 -6
  25. package/.agents/scripts/lib/audit-suite/selector.js +0 -31
  26. package/.agents/scripts/lib/baselines/duplication-scanner.js +17 -7
  27. package/.agents/scripts/lib/bootstrap/agents-md-fold.js +156 -0
  28. package/.agents/scripts/lib/bootstrap/commit-push.js +1 -1
  29. package/.agents/scripts/lib/bootstrap/manifest.js +2 -2
  30. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +91 -107
  31. package/.agents/scripts/lib/cli/standard-args.js +60 -76
  32. package/.agents/scripts/lib/cli-args.js +26 -0
  33. package/.agents/scripts/lib/config/gates/shared.js +3 -3
  34. package/.agents/scripts/lib/config/review-chain-default.js +13 -0
  35. package/.agents/scripts/lib/config-settings-schema-delivery.js +2 -2
  36. package/.agents/scripts/lib/config-settings-schema-quality.js +11 -13
  37. package/.agents/scripts/lib/doc-tiers.js +25 -6
  38. package/.agents/scripts/lib/feedback-loop/graduate-steps.js +205 -0
  39. package/.agents/scripts/lib/feedback-loop/graduator-core.js +47 -782
  40. package/.agents/scripts/lib/feedback-loop/graduator-gh.js +449 -0
  41. package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
  42. package/.agents/scripts/lib/observability/close-telemetry.js +330 -0
  43. package/.agents/scripts/lib/observability/metrics-ledger.js +0 -72
  44. package/.agents/scripts/lib/observability/runtime-friction.js +2 -0
  45. package/.agents/scripts/lib/observability/signal-validator.js +17 -5
  46. package/.agents/scripts/lib/orchestration/code-review.js +33 -6
  47. package/.agents/scripts/lib/orchestration/epic-rollup.js +29 -12
  48. package/.agents/scripts/lib/orchestration/merge-block-class.js +20 -4
  49. package/.agents/scripts/lib/orchestration/merge-poll.js +41 -22
  50. package/.agents/scripts/lib/orchestration/plan-metrics.js +76 -63
  51. package/.agents/scripts/lib/orchestration/required-checks.js +147 -0
  52. package/.agents/scripts/lib/orchestration/review-providers/code-review.js +203 -0
  53. package/.agents/scripts/lib/orchestration/review-providers/review-provider-factory.js +29 -4
  54. package/.agents/scripts/lib/orchestration/review-providers/security-review.js +3 -2
  55. package/.agents/scripts/lib/orchestration/run-epilogue.js +6 -0
  56. package/.agents/scripts/lib/orchestration/single-story-close/failed-terminal.js +1 -0
  57. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +2 -12
  58. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +370 -268
  59. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +21 -7
  60. package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +112 -82
  61. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-override.js +4 -0
  62. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +393 -313
  63. package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +12 -87
  64. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +3 -0
  65. package/.agents/scripts/lib/orchestration/ticket-validator.js +19 -36
  66. package/.agents/scripts/lib/signals/detectors/common.js +63 -51
  67. package/.agents/scripts/lib/templates/decomposer-prompts.js +5 -24
  68. package/.agents/scripts/lib/transpile.js +28 -3
  69. package/.agents/scripts/providers/github/issues.js +14 -23
  70. package/.agents/scripts/single-story-close.js +10 -2
  71. package/.agents/scripts/single-story-confirm-merge.js +267 -238
  72. package/.agents/scripts/sync-claude-agents.js +1 -1
  73. package/.agents/skills/core/idea-refinement/SKILL.md +6 -6
  74. package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -2
  75. package/.agents/workflows/audit-architecture.md +5 -4
  76. package/.agents/workflows/audit-documentation.md +5 -5
  77. package/.agents/workflows/audit-performance.md +10 -10
  78. package/.agents/workflows/audit-quality.md +42 -7
  79. package/.agents/workflows/helpers/acceptance-self-eval.md +9 -9
  80. package/.agents/workflows/helpers/audit-lens-core.md +30 -57
  81. package/.agents/workflows/helpers/code-review.md +15 -38
  82. package/.agents/workflows/helpers/deliver-digest.md +2 -2
  83. package/.agents/workflows/helpers/deliver-reference.md +7 -3
  84. package/.agents/workflows/helpers/deliver-story.md +9 -1
  85. package/.agents/workflows/helpers/parallel-tooling.md +16 -18
  86. package/.agents/workflows/helpers/plan-reference.md +9 -8
  87. package/.agents/workflows/mandrel-deliver.md +3 -2
  88. package/.agents/workflows/mandrel-plan.md +11 -7
  89. package/.agents/workflows/mandrel-update.md +5 -3
  90. package/docs/CHANGELOG.md +57 -0
  91. package/lib/cli/claude-code-version.js +73 -0
  92. package/lib/cli/doctor.js +2 -2
  93. package/lib/cli/guarded-sync.js +87 -0
  94. package/lib/cli/registry.js +9 -0
  95. package/lib/cli/sync-agents.js +9 -92
  96. package/lib/cli/sync-commands.js +9 -101
  97. package/lib/cli/uninstall.js +37 -9
  98. package/lib/migrations/index.js +2 -0
  99. package/lib/migrations/steps/2.65.0-fold-claude-md-into-agents-md.js +38 -0
  100. package/package.json +3 -2
  101. package/.agents/scripts/lib/audit-suite/lens-diff-floor.js +0 -99
  102. package/.agents/scripts/lib/audit-suite/runner.js +0 -205
  103. package/.agents/scripts/lib/audit-suite/substitutions.js +0 -96
  104. package/.agents/scripts/lib/audit-suite/workflow-loader.js +0 -37
  105. package/.agents/scripts/lib/orchestration/story-close/phases/local-lens-review.js +0 -234
@@ -25,6 +25,17 @@ function resolveFlag(paramValue, parsedValue) {
25
25
  return paramValue ?? parsedValue;
26
26
  }
27
27
 
28
+ /**
29
+ * A boolean flag: param beats parsed arg, absent reads as `false`.
30
+ *
31
+ * @param {unknown} paramValue
32
+ * @param {unknown} parsedValue
33
+ * @returns {boolean}
34
+ */
35
+ function booleanFlag(paramValue, parsedValue) {
36
+ return !!resolveFlag(paramValue, parsedValue);
37
+ }
38
+
28
39
  /**
29
40
  * Junk reads as absent (so the config default applies), never coerced.
30
41
  *
@@ -88,7 +99,7 @@ const RETIRED_FLAGS = Object.freeze({
88
99
  * @param {string[]} argv
89
100
  * @throws {Error}
90
101
  */
91
- function assertNoRetiredFlags(argv) {
102
+ export function assertNoRetiredFlags(argv) {
92
103
  for (const [flag, why] of Object.entries(RETIRED_FLAGS)) {
93
104
  const present = argv.some((a) => a === flag || a.startsWith(`${flag}=`));
94
105
  if (!present) continue;
@@ -102,8 +113,8 @@ function assertNoRetiredFlags(argv) {
102
113
  /**
103
114
  * Returns raw wait-for-merge intent; the runner resolves it after the arm.
104
115
  *
105
- * @param {{ storyIdParam, cwdParam, skipValidationParam, skipSyncParam, noAutoMergeParam, waitForMergeParam, noWaitForMergeParam, maxWaitSecondsParam, mergeWatchModeParam, rerunAdvisoryParam, overrideReviewBlockParam }} raw
106
- * @returns {{ storyId, cwd, skipValidation, skipSync, noAutoMerge, waitForMergeExplicit, noWaitForMerge, maxWaitSeconds, mergeWatchMode, rerunAdvisory, overrideReviewBlock }}
116
+ * @param {{ storyIdParam, cwdParam, skipValidationParam, skipSyncParam, noAutoMergeParam, waitForMergeParam, noWaitForMergeParam, maxWaitSecondsParam, mergeWatchModeParam, rerunAdvisoryParam, overrideReviewBlockParam, workerTokensParam }} raw
117
+ * @returns {{ storyId, cwd, skipValidation, skipSync, noAutoMerge, waitForMergeExplicit, noWaitForMerge, maxWaitSeconds, mergeWatchMode, rerunAdvisory, overrideReviewBlock, workerTokens }}
107
118
  */
108
119
  export function parseCloseOptions({
109
120
  storyIdParam,
@@ -117,6 +128,7 @@ export function parseCloseOptions({
117
128
  mergeWatchModeParam,
118
129
  rerunAdvisoryParam,
119
130
  overrideReviewBlockParam,
131
+ workerTokensParam,
120
132
  }) {
121
133
  // An injecting caller never reads argv (the host's flags are not its
122
134
  // business), so `parsed` stays empty and the retired-flag guard is skipped.
@@ -145,17 +157,19 @@ export function parseCloseOptions({
145
157
  resolveFlag(mergeWatchModeParam, parsed.mergeWatchMode),
146
158
  ),
147
159
  rerunAdvisory: intAtLeast(rerunAdvisory, 0),
148
- skipValidation: !!resolveFlag(skipValidationParam, parsed.skipValidation),
149
- skipSync: !!resolveFlag(skipSyncParam, parsed.skipSync),
150
- noAutoMerge: !!resolveFlag(noAutoMergeParam, parsed.noAutoMerge),
160
+ skipValidation: booleanFlag(skipValidationParam, parsed.skipValidation),
161
+ skipSync: booleanFlag(skipSyncParam, parsed.skipSync),
162
+ noAutoMerge: booleanFlag(noAutoMergeParam, parsed.noAutoMerge),
151
163
  waitForMergeExplicit:
152
164
  typeof waitForMergeExplicit === 'boolean'
153
165
  ? waitForMergeExplicit
154
166
  : undefined,
155
- noWaitForMerge: !!resolveFlag(noWaitForMergeParam, parsed.noWaitForMerge),
167
+ noWaitForMerge: booleanFlag(noWaitForMergeParam, parsed.noWaitForMerge),
156
168
  // Both doors validate, so neither can arm a reasonless override.
157
169
  overrideReviewBlock: parseOverrideReviewBlock(
158
170
  resolveFlag(overrideReviewBlockParam, parsed.overrideReviewBlock),
159
171
  ),
172
+ // Raw: validated best-effort by the runner, never thrown on.
173
+ workerTokens: resolveFlag(workerTokensParam, parsed.workerTokens),
160
174
  };
161
175
  }
@@ -282,11 +282,61 @@ async function stepPlanRunLabelReap({
282
282
  }
283
283
 
284
284
  /**
285
- * Run the post-land tail, sequentially. The ref reap and fast-forward (the
286
- * local-checkout mutations, reap first) run under a cross-process lock on the
287
- * main checkout, since concurrent closes race on the base ref and worktree
288
- * registry; GitHub steps stay outside it. The lock is best-effort: on
289
- * timeout the mutations run anyway.
285
+ * The local-checkout mutations (ref reap first, then fast-forward) under a
286
+ * cross-process lock on the main checkout, since concurrent closes race on
287
+ * the base ref and worktree registry. Best-effort: on timeout they run anyway.
288
+ */
289
+ async function runLockedLocalSteps({
290
+ storyId,
291
+ storyBranch,
292
+ baseBranch,
293
+ cwd,
294
+ config,
295
+ progress,
296
+ gitSpawnFn,
297
+ planFastForwardFn,
298
+ executeFastForwardFn,
299
+ acquireLockWithWaitFn,
300
+ }) {
301
+ const lockCfg = config?.delivery?.postLandLock ?? {};
302
+ const lock = await acquireLockWithWaitFn({
303
+ lockPath: postLandLockPath(cwd),
304
+ waitMs: lockCfg.waitMs,
305
+ pollMs: lockCfg.pollMs,
306
+ timeoutMs: lockCfg.timeoutMs,
307
+ ownerId: `post-land-${storyId}`,
308
+ });
309
+ if (!lock.acquired) {
310
+ progress?.(
311
+ 'POST-LAND',
312
+ `⚠️ post-land lock not acquired (${lock.reason}); proceeding unserialized.`,
313
+ );
314
+ }
315
+ try {
316
+ const refCleanup = await step(
317
+ () => stepRefCleanup({ cwd, storyBranch, progress, gitSpawnFn }),
318
+ { name: 'local ref cleanup', progress },
319
+ );
320
+ const baseFastForward = await step(
321
+ () =>
322
+ stepBaseFastForward({
323
+ cwd,
324
+ baseBranch,
325
+ progress,
326
+ planFastForwardFn,
327
+ executeFastForwardFn,
328
+ }),
329
+ { name: 'base fast-forward', progress },
330
+ );
331
+ return { refCleanup, baseFastForward };
332
+ } finally {
333
+ if (lock.acquired) lock.release();
334
+ }
335
+ }
336
+
337
+ /**
338
+ * Run the post-land tail: friction markers, then the GitHub steps and the
339
+ * locked local steps concurrently, then temp purge, then lease release.
290
340
  *
291
341
  * @param {object} args
292
342
  * @param {number} args.storyId
@@ -348,87 +398,67 @@ export async function runPostLandTail({
348
398
  config,
349
399
  });
350
400
 
351
- const followUps = await step(
352
- () =>
353
- stepFollowUps({
354
- storyId,
355
- provider,
356
- config,
357
- cwd,
358
- progress,
359
- captureStoryFollowUpsFn,
360
- }),
361
- { name: 'follow-up capture', progress },
362
- );
363
- const statusResync = await step(
364
- () =>
365
- stepStatusResync({
366
- storyId,
367
- provider,
368
- config,
369
- progress,
370
- reassertStatusColumnFn,
371
- }),
372
- { name: 'status-column resync', progress },
373
- );
374
- await step(
375
- () =>
376
- stepPlanRunLabelReap({
377
- storyId,
378
- provider,
379
- progress,
380
- reapPlanRunLabelsForStoryFn,
381
- }),
382
- { name: 'plan-run label reap', progress },
383
- );
384
-
385
- const epicRollup = await step(
386
- () =>
387
- stepEpicRollup({
388
- storyId,
389
- provider,
390
- config,
391
- progress,
392
- rollUpEpicForStoryFn,
393
- }),
394
- { name: 'epic rollup', progress },
395
- );
396
-
397
- const lockCfg = config?.delivery?.postLandLock ?? {};
398
- const lock = await acquireLockWithWaitFn({
399
- lockPath: postLandLockPath(cwd),
400
- waitMs: lockCfg.waitMs,
401
- pollMs: lockCfg.pollMs,
402
- timeoutMs: lockCfg.timeoutMs,
403
- ownerId: `post-land-${storyId}`,
404
- });
405
- if (!lock.acquired) {
406
- progress?.(
407
- 'POST-LAND',
408
- `⚠️ post-land lock not acquired (${lock.reason}); proceeding unserialized.`,
409
- );
410
- }
411
- let refCleanup;
412
- let baseFastForward;
413
- try {
414
- refCleanup = await step(
415
- () => stepRefCleanup({ cwd, storyBranch, progress, gitSpawnFn }),
416
- { name: 'local ref cleanup', progress },
417
- );
418
- baseFastForward = await step(
401
+ // The GitHub steps are independent of each other and of the local
402
+ // lock-held mutations, so all of them run concurrently.
403
+ const [followUps, statusResync, , epicRollup, local] = await Promise.all([
404
+ step(
419
405
  () =>
420
- stepBaseFastForward({
406
+ stepFollowUps({
407
+ storyId,
408
+ provider,
409
+ config,
421
410
  cwd,
422
- baseBranch,
423
411
  progress,
424
- planFastForwardFn,
425
- executeFastForwardFn,
412
+ captureStoryFollowUpsFn,
426
413
  }),
427
- { name: 'base fast-forward', progress },
428
- );
429
- } finally {
430
- if (lock.acquired) lock.release();
431
- }
414
+ { name: 'follow-up capture', progress },
415
+ ),
416
+ step(
417
+ () =>
418
+ stepStatusResync({
419
+ storyId,
420
+ provider,
421
+ config,
422
+ progress,
423
+ reassertStatusColumnFn,
424
+ }),
425
+ { name: 'status-column resync', progress },
426
+ ),
427
+ step(
428
+ () =>
429
+ stepPlanRunLabelReap({
430
+ storyId,
431
+ provider,
432
+ progress,
433
+ reapPlanRunLabelsForStoryFn,
434
+ }),
435
+ { name: 'plan-run label reap', progress },
436
+ ),
437
+ step(
438
+ () =>
439
+ stepEpicRollup({
440
+ storyId,
441
+ provider,
442
+ config,
443
+ progress,
444
+ rollUpEpicForStoryFn,
445
+ }),
446
+ { name: 'epic rollup', progress },
447
+ ),
448
+ runLockedLocalSteps({
449
+ storyId,
450
+ storyBranch,
451
+ baseBranch,
452
+ cwd,
453
+ config,
454
+ progress,
455
+ gitSpawnFn,
456
+ planFastForwardFn,
457
+ executeFastForwardFn,
458
+ acquireLockWithWaitFn,
459
+ }),
460
+ ]);
461
+ const { refCleanup, baseFastForward } = local;
432
462
 
433
463
  // After every step that reads the temp artifacts; `signals.ndjson` survives.
434
464
  const tempPurge = await step(
@@ -65,6 +65,7 @@ async function postAuditRecord({ post, surface }) {
65
65
  * prUrl: string,
66
66
  * prNumber: number|null,
67
67
  * criticalCount: number,
68
+ * criticalByProvider?: Record<string, number>,
68
69
  * reason: string,
69
70
  * config?: object,
70
71
  * emitFrictionFn?: typeof emitRuntimeFriction,
@@ -77,6 +78,7 @@ export async function handleOverriddenReviewBlock({
77
78
  prUrl,
78
79
  prNumber,
79
80
  criticalCount,
81
+ criticalByProvider = {},
80
82
  reason,
81
83
  config,
82
84
  emitFrictionFn = emitRuntimeFriction,
@@ -104,6 +106,8 @@ export async function handleOverriddenReviewBlock({
104
106
  details: {
105
107
  prUrl,
106
108
  criticalCount,
109
+ // The false-positive proxy: attributed like the halt it rejected.
110
+ criticalByProvider,
107
111
  reason: reason.slice(0, REASON_SIGNAL_LIMIT),
108
112
  },
109
113
  config,