mandrel 1.62.0 → 1.64.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 (44) hide show
  1. package/.agents/scripts/agents-bootstrap-github.js +40 -48
  2. package/.agents/scripts/bootstrap.js +74 -60
  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/bootstrap/branch-protection.js +8 -8
  8. package/.agents/scripts/lib/bootstrap/gh-preflight.js +3 -3
  9. package/.agents/scripts/lib/bootstrap/hitl-confirm.js +2 -2
  10. package/.agents/scripts/lib/bootstrap/merge-methods.js +7 -7
  11. package/.agents/scripts/lib/bootstrap/preflight.js +18 -15
  12. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +5 -5
  13. package/.agents/scripts/lib/bootstrap/prompt.js +5 -1
  14. package/.agents/scripts/lib/detect-package-manager.js +2 -2
  15. package/.agents/scripts/lib/feedback-loop/graduator-core.js +171 -137
  16. package/.agents/scripts/lib/onboard/init-tail.js +60 -69
  17. package/.agents/scripts/lib/orchestration/code-review.js +206 -168
  18. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/creation.js +71 -5
  19. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist.js +16 -2
  20. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/component-drift.js +101 -1
  21. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/crap-drift.js +20 -42
  22. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/maintainability-drift.js +12 -32
  23. package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +97 -60
  24. package/.agents/scripts/lib/orchestration/model-attribution.js +73 -45
  25. package/.agents/scripts/lib/orchestration/review-providers/parse-findings.js +97 -49
  26. package/.agents/scripts/lib/orchestration/story-close/pre-merge-validation.js +73 -69
  27. package/.agents/scripts/lib/orchestration/story-close-recovery.js +109 -79
  28. package/.agents/scripts/lib/signals/detectors/common.js +107 -0
  29. package/.agents/scripts/lib/signals/detectors/hotspot.js +12 -18
  30. package/.agents/scripts/lib/signals/detectors/retry.js +3 -40
  31. package/.agents/scripts/lib/signals/detectors/rework.js +3 -40
  32. package/.agents/scripts/lib/story-body/story-body.js +102 -76
  33. package/.agents/scripts/providers/github/blocked-by-add.js +252 -0
  34. package/.agents/scripts/providers/github/tickets.js +1 -1
  35. package/.agents/scripts/single-story-init.js +16 -3
  36. package/.agents/workflows/audit-architecture.md +9 -0
  37. package/.agents/workflows/helpers/deliver-stories.md +24 -2
  38. package/.agents/workflows/helpers/single-story-deliver.md +84 -1
  39. package/README.md +1 -1
  40. package/docs/CHANGELOG.md +43 -0
  41. package/lib/cli/init.js +66 -21
  42. package/lib/cli/sync.js +3 -3
  43. package/package.json +1 -1
  44. package/.agents/scripts/lib/onboard/detect-stack.js +0 -300
@@ -84,25 +84,22 @@ async function verifyApiAccess(provider) {
84
84
  // target repo. Anything else (auth, scope, transport) is fatal.
85
85
  if (!isApiAccessNotFoundError(err)) {
86
86
  throw new Error(
87
- `[bootstrap] API access verification failed: ${err.message}`,
87
+ `[Bootstrap] API access verification failed: ${err.message}`,
88
88
  );
89
89
  }
90
90
  }
91
91
  }
92
92
 
93
93
  async function ensureLabels(provider, log) {
94
- log(`[bootstrap] Ensuring ${LABEL_TAXONOMY.length} labels...`);
94
+ log(`[Bootstrap] Ensuring ${LABEL_TAXONOMY.length} labels...`);
95
95
  const labels = await provider.ensureLabels(LABEL_TAXONOMY);
96
96
  const missing = Array.isArray(labels.missing) ? labels.missing : [];
97
97
  log(
98
- `[bootstrap] Labels — created: ${labels.created.length}, skipped: ${labels.skipped.length}, missing: ${missing.length}`,
98
+ `[Bootstrap] Labels — created: ${labels.created.length}, skipped: ${labels.skipped.length}, missing: ${missing.length}`,
99
99
  );
100
- if (labels.created.length > 0) {
101
- log(`[bootstrap] Created: ${labels.created.join(', ')}`);
102
- }
103
100
  if (missing.length > 0) {
104
101
  log(
105
- `[bootstrap] ⚠️ ${missing.length} label(s) were reported as created/skipped but are NOT present on the remote: ${missing.join(', ')}. Re-run bootstrap or create them manually with \`gh label create\`.`,
102
+ `[Bootstrap] ⚠️ ${missing.length} label(s) were reported as created/skipped but are NOT present on the remote: ${missing.join(', ')}. Re-run bootstrap or create them manually with \`gh label create\`.`,
106
103
  );
107
104
  }
108
105
  return labels;
@@ -119,19 +116,19 @@ async function resolveProject(provider, providerConfig, log) {
119
116
  const result = await provider.resolveOrCreateProject();
120
117
  if (result.scopesMissing) {
121
118
  log(
122
- `[bootstrap] Projects V2: token lacks the "project" scope — skipping board provisioning. ${PROJECTS_DOC_POINTER}`,
119
+ `[Bootstrap] Projects V2: token lacks the "project" scope — skipping board provisioning. ${PROJECTS_DOC_POINTER}`,
123
120
  );
124
121
  return fallback(true);
125
122
  }
126
123
  const projectNumber = result.projectNumber ?? null;
127
124
  const created = !!result.created;
128
125
  log(
129
- `[bootstrap] ${created ? 'Created' : 'Using'} Project V2 #${projectNumber}.`,
126
+ `[Bootstrap] ${created ? 'Created' : 'Using'} Project V2 #${projectNumber}.`,
130
127
  );
131
128
  return { projectNumber, created, skipped: false, scopesMissing: false };
132
129
  } catch (err) {
133
130
  log(
134
- `[bootstrap] Projects V2 resolution failed: ${err.message}. ${PROJECTS_DOC_POINTER}`,
131
+ `[Bootstrap] Projects V2 resolution failed: ${err.message}. ${PROJECTS_DOC_POINTER}`,
135
132
  );
136
133
  return fallback(false);
137
134
  }
@@ -142,17 +139,17 @@ async function ensureStatusField(provider, log) {
142
139
  const statusField = await provider.ensureStatusField(STATUS_FIELD_OPTIONS);
143
140
  if (statusField.status === 'scopes-missing') {
144
141
  log(
145
- `[bootstrap] Projects V2 Status field: insufficient scopes. ${PROJECTS_DOC_POINTER}`,
142
+ `[Bootstrap] Projects V2 Status field: insufficient scopes. ${PROJECTS_DOC_POINTER}`,
146
143
  );
147
144
  } else {
148
145
  const addedSuffix = statusField.added.length
149
146
  ? ` (added: ${statusField.added.join(', ')})`
150
147
  : '';
151
- log(`[bootstrap] Status field — ${statusField.status}${addedSuffix}`);
148
+ log(`[Bootstrap] Status field — ${statusField.status}${addedSuffix}`);
152
149
  }
153
150
  return statusField;
154
151
  } catch (err) {
155
- log(`[bootstrap] Status field provisioning failed: ${err.message}`);
152
+ log(`[Bootstrap] Status field provisioning failed: ${err.message}`);
156
153
  return { status: 'skipped', added: [] };
157
154
  }
158
155
  }
@@ -162,16 +159,16 @@ async function ensureViews(provider, log) {
162
159
  const views = await provider.ensureProjectViews(PROJECT_VIEW_DEFS);
163
160
  if (views.unavailable) {
164
161
  log(
165
- `[bootstrap] Projects V2 Views unavailable — skipped ${views.skipped.join(', ')}.${views.error ? ` (${views.error})` : ''} ${PROJECTS_DOC_POINTER}`,
162
+ `[Bootstrap] Projects V2 Views unavailable — skipped ${views.skipped.join(', ')}.${views.error ? ` (${views.error})` : ''} ${PROJECTS_DOC_POINTER}`,
166
163
  );
167
164
  } else {
168
165
  log(
169
- `[bootstrap] Views — created: ${views.created.length}, skipped: ${views.skipped.length}`,
166
+ `[Bootstrap] Views — created: ${views.created.length}, skipped: ${views.skipped.length}`,
170
167
  );
171
168
  }
172
169
  return views;
173
170
  } catch (err) {
174
- log(`[bootstrap] Views provisioning failed: ${err.message}`);
171
+ log(`[Bootstrap] Views provisioning failed: ${err.message}`);
175
172
  return { created: [], skipped: [], unavailable: false };
176
173
  }
177
174
  }
@@ -203,13 +200,13 @@ async function auditAndOptionallyReapWorkflows(
203
200
  projectId = await resolveProjectIdByNumber({ provider, projectNumber });
204
201
  } catch (err) {
205
202
  log(
206
- `[bootstrap] Workflow audit: could not resolve project id — ${err.message}.`,
203
+ `[Bootstrap] Workflow audit: could not resolve project id — ${err.message}.`,
207
204
  );
208
205
  return { skipped: true, reason: 'project-id-unresolved' };
209
206
  }
210
207
  if (!projectId) {
211
208
  log(
212
- `[bootstrap] Workflow audit: project #${projectNumber} not visible to viewer — skipping.`,
209
+ `[Bootstrap] Workflow audit: project #${projectNumber} not visible to viewer — skipping.`,
213
210
  );
214
211
  return { skipped: true, reason: 'project-not-visible' };
215
212
  }
@@ -217,45 +214,40 @@ async function auditAndOptionallyReapWorkflows(
217
214
  try {
218
215
  audit = await auditProjectWorkflows({ provider, projectId });
219
216
  } catch (err) {
220
- log(`[bootstrap] Workflow audit failed: ${err.message} — skipping.`);
217
+ log(`[Bootstrap] Workflow audit failed: ${err.message} — skipping.`);
221
218
  return { skipped: true, reason: 'audit-failed', error: err.message };
222
219
  }
223
- log(`[bootstrap] Workflow audit — ${formatAuditSummary(audit)}.`);
220
+ log(`[Bootstrap] Workflow audit — ${formatAuditSummary(audit)}.`);
224
221
  if (audit.conflicting.length === 0) {
225
222
  return { audit, reaped: [], action: 'no-conflicts' };
226
223
  }
227
224
  const names = audit.conflicting.map((w) => w.name).join(', ');
228
225
  if (!reap) {
229
226
  log(
230
- `[bootstrap] ⚠️ Conflicting workflows enabled: ${names}. ` +
231
- `These race against the orchestrator's ColumnSync writes and ` +
232
- `typically leave closed Stories stuck at "In Progress" on the ` +
233
- `board (see Story #2813 for the reproduction). Remediation: ` +
234
- `(a) re-run with --reap-conflicting-workflows to delete them, ` +
235
- `or (b) toggle them off in the GitHub UI under ` +
236
- `Project → Workflows. The orchestrator's post-merge ` +
237
- `resync-status-column.js CLI defends against both unless you ` +
238
- `also disable that step.`,
227
+ `[Bootstrap] ⚠️ Conflicting Projects V2 workflows enabled: ${names}. ` +
228
+ `They can leave closed Stories stuck at "In Progress". Fix: re-run ` +
229
+ `with --reap-conflicting-workflows, or disable them under ` +
230
+ `Project Workflows.`,
239
231
  );
240
232
  return { audit, reaped: [], action: 'warn-only' };
241
233
  }
242
234
  log(
243
- `[bootstrap] Reaping ${audit.conflicting.length} conflicting workflow(s): ${names}...`,
235
+ `[Bootstrap] Reaping ${audit.conflicting.length} conflicting workflow(s): ${names}...`,
244
236
  );
245
237
  const { reaped } = await reapConflictingWorkflows({ provider, audit });
246
238
  log(
247
- `[bootstrap] ✅ Deleted ${reaped.length} workflow(s): ${reaped.map((r) => r.name).join(', ')}.`,
239
+ `[Bootstrap] ✅ Deleted ${reaped.length} workflow(s): ${reaped.map((r) => r.name).join(', ')}.`,
248
240
  );
249
241
  return { audit, reaped, action: 'reaped' };
250
242
  }
251
243
 
252
244
  async function ensureProjectFields(provider, project, log) {
253
245
  log(
254
- `[bootstrap] Ensuring ${PROJECT_FIELD_DEFS.length} project fields on project #${project.projectNumber}...`,
246
+ `[Bootstrap] Ensuring ${PROJECT_FIELD_DEFS.length} project fields on project #${project.projectNumber}...`,
255
247
  );
256
248
  const fields = await provider.ensureProjectFields(PROJECT_FIELD_DEFS);
257
249
  log(
258
- `[bootstrap] Fields — created: ${fields.created.length}, skipped: ${fields.skipped.length}`,
250
+ `[Bootstrap] Fields — created: ${fields.created.length}, skipped: ${fields.skipped.length}`,
259
251
  );
260
252
  return fields;
261
253
  }
@@ -304,7 +296,7 @@ export async function runBootstrap(config, opts = {}) {
304
296
  if (opts.githubAdminApproved !== true) {
305
297
  const skipLog = opts.quiet ? () => {} : Logger.info;
306
298
  skipLog(
307
- '[bootstrap] GitHub-admin mutations skipped: github-admin phase group not approved (explicit opt-in required).',
299
+ '[Bootstrap] GitHub-admin mutations skipped: github-admin phase group not approved (explicit opt-in required).',
308
300
  );
309
301
  return { skipped: true, reason: 'github-admin-not-approved' };
310
302
  }
@@ -315,13 +307,13 @@ export async function runBootstrap(config, opts = {}) {
315
307
  const providerName = config.provider ?? (config.github ? 'github' : null);
316
308
  const providerConfig = providerName ? config[providerName] : null;
317
309
 
318
- log('[bootstrap] Starting idempotent setup...');
319
- log(`[bootstrap] Provider: ${providerName}`);
320
- log(`[bootstrap] Target: ${providerConfig?.owner}/${providerConfig?.repo}`);
310
+ log('[Bootstrap] Starting idempotent setup...');
311
+ log(`[Bootstrap] Provider: ${providerName}`);
312
+ log(`[Bootstrap] Target: ${providerConfig?.owner}/${providerConfig?.repo}`);
321
313
 
322
- log('[bootstrap] Verifying API access...');
314
+ log('[Bootstrap] Verifying API access...');
323
315
  await verifyApiAccess(provider);
324
- log('[bootstrap] API access verified.');
316
+ log('[Bootstrap] API access verified.');
325
317
 
326
318
  const labels = await ensureLabels(provider, log);
327
319
  const project = await resolveProject(provider, providerConfig, log);
@@ -336,7 +328,7 @@ export async function runBootstrap(config, opts = {}) {
336
328
  views = await ensureViews(provider, log);
337
329
  fields = await ensureProjectFields(provider, project, log);
338
330
  } else {
339
- log('[bootstrap] No active project — skipping legacy project-field setup.');
331
+ log('[Bootstrap] No active project — skipping legacy project-field setup.');
340
332
  }
341
333
 
342
334
  // Story #2845 — audit project workflows for the ones that race against
@@ -407,7 +399,7 @@ export async function runBootstrap(config, opts = {}) {
407
399
  log,
408
400
  });
409
401
 
410
- log('[bootstrap] Done.');
402
+ log('[Bootstrap] Done.');
411
403
  return {
412
404
  labels,
413
405
  fields,
@@ -432,14 +424,14 @@ async function main() {
432
424
  // before bootstrap proceeds.
433
425
  try {
434
426
  const { version } = await preflightGh();
435
- Logger.info(`[bootstrap] gh CLI ${version} ready (auth verified).`);
427
+ Logger.info(`[Bootstrap] gh CLI ${version} ready (auth verified).`);
436
428
  } catch (err) {
437
429
  if (
438
430
  err instanceof GhNotInstalledError ||
439
431
  err instanceof GhAuthError ||
440
432
  err instanceof GhVersionError
441
433
  ) {
442
- Logger.error(`[bootstrap] ${err.message}`);
434
+ Logger.error(`[Bootstrap] ${err.message}`);
443
435
  process.exit(1);
444
436
  }
445
437
  throw err;
@@ -453,7 +445,7 @@ async function main() {
453
445
  await preflightRuntimeDeps();
454
446
  } catch (err) {
455
447
  if (err instanceof MissingRuntimeDepsError) {
456
- Logger.error(`[bootstrap] ${err.message}`);
448
+ Logger.error(`[Bootstrap] ${err.message}`);
457
449
  process.exit(1);
458
450
  }
459
451
  throw err;
@@ -467,13 +459,13 @@ async function main() {
467
459
  const config = resolveConfig();
468
460
 
469
461
  if (!config.github) {
470
- throw new Error('[bootstrap] No "github" block found in .agentrc.json.');
462
+ throw new Error('[Bootstrap] No "github" block found in .agentrc.json.');
471
463
  }
472
464
 
473
465
  try {
474
466
  validateOrchestrationConfig(config);
475
467
  } catch (err) {
476
- Logger.error(`[bootstrap] ERROR: ${err.message}`);
468
+ Logger.error(`[Bootstrap] ERROR: ${err.message}`);
477
469
  process.exit(1);
478
470
  }
479
471
 
@@ -513,13 +505,13 @@ async function main() {
513
505
  // detailed summary only when mutations were actually attempted.
514
506
  if (result.skipped) {
515
507
  Logger.info(
516
- `[bootstrap] GitHub-admin step skipped (${result.reason}). Re-run with --approve-github-admin (or --assume-yes) to apply.`,
508
+ `[Bootstrap] GitHub-admin step skipped (${result.reason}). Re-run with --approve-github-admin (or --assume-yes) to apply.`,
517
509
  );
518
510
  } else {
519
511
  printSummary(result);
520
512
  }
521
513
  } catch (err) {
522
- throw new Error(`[bootstrap] runBootstrap failed: ${err.message}`);
514
+ throw new Error(`[Bootstrap] runBootstrap failed: ${err.message}`);
523
515
  }
524
516
  }
525
517