mandrel 2.54.0 → 2.55.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 (114) hide show
  1. package/.agents/agents/story-worker.md +24 -23
  2. package/.agents/audit-checklists/accessibility.md +0 -3
  3. package/.agents/audit-checklists/mobile.md +0 -4
  4. package/.agents/docs/agentrc-reference.json +4 -2
  5. package/.agents/docs/configuration.md +2 -0
  6. package/.agents/schemas/agentrc.schema.json +15 -1
  7. package/.agents/schemas/lifecycle/merge.unlanded.schema.json +2 -1
  8. package/.agents/schemas/story-deliver-terminal.schema.json +1 -0
  9. package/.agents/scripts/audit-to-stories.js +158 -7
  10. package/.agents/scripts/check-audit-attribution.js +119 -62
  11. package/.agents/scripts/check-test-portability.js +512 -0
  12. package/.agents/scripts/coverage-capture.js +17 -10
  13. package/.agents/scripts/evidence-gate.js +31 -4
  14. package/.agents/scripts/generate-workflows-doc.js +65 -14
  15. package/.agents/scripts/git-cleanup.js +4 -0
  16. package/.agents/scripts/lib/ITicketingProvider.js +78 -0
  17. package/.agents/scripts/lib/audit-advisories.js +195 -0
  18. package/.agents/scripts/lib/audit-attribution.js +22 -0
  19. package/.agents/scripts/lib/audit-to-stories/dedupe-against-github.js +68 -5
  20. package/.agents/scripts/lib/audit-to-stories/issue-index.js +83 -0
  21. package/.agents/scripts/lib/audit-to-stories/ledger-commit.js +60 -114
  22. package/.agents/scripts/lib/audit-to-stories/ledger-pr.js +347 -0
  23. package/.agents/scripts/lib/audit-to-stories/parse-audit-md.js +169 -44
  24. package/.agents/scripts/lib/baselines/merge-envelopes.js +298 -32
  25. package/.agents/scripts/lib/bootstrap/baseline-merge-driver.js +180 -14
  26. package/.agents/scripts/lib/cli-args.js +26 -0
  27. package/.agents/scripts/lib/close-validation/gates.js +113 -7
  28. package/.agents/scripts/lib/close-validation/process.js +7 -3
  29. package/.agents/scripts/lib/close-validation/runner.js +62 -11
  30. package/.agents/scripts/lib/config/ci.js +28 -9
  31. package/.agents/scripts/lib/config-settings-schema-delivery.js +7 -0
  32. package/.agents/scripts/lib/config-settings-schema.js +19 -1
  33. package/.agents/scripts/lib/coverage-capture-fullscope.js +23 -11
  34. package/.agents/scripts/lib/coverage-capture-incremental.js +22 -16
  35. package/.agents/scripts/lib/coverage-capture-usage.js +5 -1
  36. package/.agents/scripts/lib/coverage-capture.js +77 -3
  37. package/.agents/scripts/lib/findings/route-finding.js +4 -2
  38. package/.agents/scripts/lib/full-suite-lock.js +232 -6
  39. package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
  40. package/.agents/scripts/lib/git/sync-from-base.js +130 -13
  41. package/.agents/scripts/lib/observability/source-classifier.js +1 -0
  42. package/.agents/scripts/lib/orchestration/check-baselines/phases/compare.js +10 -2
  43. package/.agents/scripts/lib/orchestration/check-baselines/phases/refresh-ack.js +75 -15
  44. package/.agents/scripts/lib/orchestration/deliver-recover.js +82 -43
  45. package/.agents/scripts/lib/orchestration/dependency-candidates.js +8 -4
  46. package/.agents/scripts/lib/orchestration/epic-candidates.js +9 -4
  47. package/.agents/scripts/lib/orchestration/epic-container.js +66 -4
  48. package/.agents/scripts/lib/orchestration/epic-rollup.js +233 -84
  49. package/.agents/scripts/lib/orchestration/file-assumptions.js +218 -16
  50. package/.agents/scripts/lib/orchestration/git-cleanup/phases/branches.js +93 -7
  51. package/.agents/scripts/lib/orchestration/git-cleanup/phases/git-probes.js +22 -6
  52. package/.agents/scripts/lib/orchestration/git-cleanup/phases/parse-args.js +26 -5
  53. package/.agents/scripts/lib/orchestration/git-cleanup/phases/phase-drivers.js +13 -2
  54. package/.agents/scripts/lib/orchestration/git-cleanup/phases/render.js +35 -5
  55. package/.agents/scripts/lib/orchestration/merge-block-class.js +18 -3
  56. package/.agents/scripts/lib/orchestration/merge-poll.js +284 -40
  57. package/.agents/scripts/lib/orchestration/plan-persist/epic-adoption.js +49 -2
  58. package/.agents/scripts/lib/orchestration/plan-persist/epic-ops.js +43 -7
  59. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +24 -1
  60. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +5 -0
  61. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +3 -0
  62. package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +63 -0
  63. package/.agents/scripts/lib/orchestration/plan-persist/wave-serialisation.js +110 -0
  64. package/.agents/scripts/lib/orchestration/planning/memory-pool-advisory.js +130 -40
  65. package/.agents/scripts/lib/orchestration/resolve-stories.js +44 -1
  66. package/.agents/scripts/lib/orchestration/review-providers/native.js +31 -11
  67. package/.agents/scripts/lib/orchestration/review-providers/scoped-lint.js +27 -24
  68. package/.agents/scripts/lib/orchestration/run-epilogue.js +59 -38
  69. package/.agents/scripts/lib/orchestration/single-story-close/close-note.js +81 -0
  70. package/.agents/scripts/lib/orchestration/single-story-close/failed-terminal.js +40 -51
  71. package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +10 -2
  72. package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +101 -0
  73. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +351 -28
  74. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +27 -6
  75. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +117 -22
  76. package/.agents/scripts/lib/orchestration/story-close/baseline-upward-writeback.js +94 -12
  77. package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +6 -1
  78. package/.agents/scripts/lib/orchestration/ticket-validator.js +25 -14
  79. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +30 -0
  80. package/.agents/scripts/lib/orchestration/verify-credit.js +37 -0
  81. package/.agents/scripts/lib/pinned-override-notes.js +41 -53
  82. package/.agents/scripts/lib/pinned-override-resolve.js +212 -0
  83. package/.agents/scripts/lib/qa/resolve-qa-contract.js +18 -0
  84. package/.agents/scripts/lib/single-story-sweep/sweep-lock.js +173 -9
  85. package/.agents/scripts/lib/skills/walk-skill-files.js +24 -7
  86. package/.agents/scripts/lib/test-temp.js +167 -30
  87. package/.agents/scripts/lib/validation-evidence.js +37 -0
  88. package/.agents/scripts/lib/wave-runner/footprint.js +167 -14
  89. package/.agents/scripts/lib/wave-runner/live-probe.js +7 -1
  90. package/.agents/scripts/lib/wave-runner/ready-set.js +1 -1
  91. package/.agents/scripts/merge-baseline.js +175 -21
  92. package/.agents/scripts/providers/github/errors.js +22 -1
  93. package/.agents/scripts/providers/github/issues.js +106 -1
  94. package/.agents/scripts/providers/github/sub-issue-add.js +18 -1
  95. package/.agents/scripts/providers/github.js +6 -0
  96. package/.agents/scripts/resolve-stories.js +44 -34
  97. package/.agents/scripts/single-story-close.js +5 -0
  98. package/.agents/scripts/stories-wave-tick.js +37 -13
  99. package/.agents/templates/docs/audit-sweep-runbook.md +41 -7
  100. package/.agents/workflows/audit-accessibility.md +16 -31
  101. package/.agents/workflows/audit-mobile.md +20 -37
  102. package/.agents/workflows/git-cleanup.md +17 -3
  103. package/.agents/workflows/helpers/audit-lens-core.md +45 -0
  104. package/.agents/workflows/helpers/deliver-digest.md +7 -6
  105. package/.agents/workflows/helpers/deliver-reference.md +35 -14
  106. package/.agents/workflows/helpers/deliver-story-reference.md +7 -4
  107. package/.agents/workflows/helpers/deliver-story.md +15 -12
  108. package/.agents/workflows/helpers/plan-reference.md +7 -0
  109. package/.agents/workflows/mandrel-plan.md +4 -7
  110. package/.agents/workflows/memory-consolidate.md +14 -9
  111. package/docs/CHANGELOG.md +27 -0
  112. package/lib/cli/registry.js +64 -21
  113. package/lib/cli/sync.js +27 -2
  114. package/package.json +7 -4
@@ -24,11 +24,14 @@ import process from 'node:process';
24
24
 
25
25
  import {
26
26
  attributionExitCode,
27
+ auditAdvisories,
27
28
  deriveVerdict,
29
+ diffAdvisories,
30
+ renderAdvisoryDetail,
28
31
  renderAttribution,
29
32
  UNKNOWN,
30
33
  } from './lib/audit-attribution.js';
31
- import { execFileCapture, spawnCapture } from './lib/child-exec.js';
34
+ import { execFileCapture } from './lib/child-exec.js';
32
35
  import { runAsCli } from './lib/cli-utils.js';
33
36
  import { Logger } from './lib/Logger.js';
34
37
 
@@ -43,6 +46,10 @@ const HELP = {
43
46
  flags: [
44
47
  ['--base <ref>', 'Base commit or ref to attribute against. Required.'],
45
48
  ['--cwd <dir>', 'Repository root. Default: process.cwd().'],
49
+ [
50
+ '--no-tracking-issue',
51
+ "Skip the `gh issue list` lookup for the nightly sweep's tracking issue.",
52
+ ],
46
53
  ],
47
54
  notes: [
48
55
  'Run it only after the required SCA step has already failed — it re-audits\nthe base to attribute that failure, and reports `unknown` when the head\naudits clean.',
@@ -52,43 +59,20 @@ const HELP = {
52
59
  };
53
60
 
54
61
  export function parseArgs(argv) {
55
- const out = { base: null, cwd: process.cwd() };
62
+ const out = { base: null, cwd: process.cwd(), trackingIssue: true };
56
63
  for (let i = 2; i < argv.length; i += 1) {
57
- const a = argv[i];
58
- if (a === '--base') out.base = argv[++i] ?? null;
59
- else if (a === '--cwd') out.cwd = argv[++i] ?? out.cwd;
64
+ // Read the option KEY, not the dashed spelling: the key is what the
65
+ // negation guard and every reader below name.
66
+ const key = argv[i].replace(/^--/, '');
67
+ if (key === 'base') out.base = argv[++i] ?? null;
68
+ else if (key === 'cwd') out.cwd = argv[++i] ?? out.cwd;
69
+ // A runner with no `gh` credentials would spend a subprocess to fail; the
70
+ // verdict never depended on the lookup, so let the caller skip it.
71
+ else if (key === 'no-tracking-issue') out.trackingIssue = false;
60
72
  }
61
73
  return out;
62
74
  }
63
75
 
64
- /**
65
- * Run `npm audit --audit-level=high` over a dependency manifest pair.
66
- *
67
- * `--package-lock-only` audits the committed lockfile without installing, so
68
- * the probe never touches the job's own `node_modules`: an attribution
69
- * mechanism that could disturb the tree it is reporting on would be a worse
70
- * defect than the one it explains.
71
- *
72
- * @param {string} dir directory holding package.json + package-lock.json
73
- * @returns {{ failed: boolean }}
74
- */
75
- function auditDir(dir) {
76
- const result = spawnCapture(
77
- 'npm',
78
- ['audit', '--audit-level=high', '--package-lock-only'],
79
- { cwd: dir },
80
- );
81
- if (result.status === 0) return { failed: false };
82
- // npm exits non-zero for "advisories found" and for "could not audit"
83
- // alike. Only a real audit verdict carries a report on stdout; anything
84
- // else is a probe failure the caller must read as `unknown`.
85
- if (/vulnerabilit/i.test(String(result.stdout ?? '')))
86
- return { failed: true };
87
- throw new Error(
88
- `npm audit could not evaluate the base tree: ${String(result.stderr ?? '').slice(0, 200)}`,
89
- );
90
- }
91
-
92
76
  /**
93
77
  * Materialize the base commit's manifest pair into a scratch directory.
94
78
  *
@@ -121,8 +105,8 @@ const defaultGit = (cwd, ...args) => execFileCapture('git', args, { cwd });
121
105
  export function runAttribution(argv = process.argv, deps = {}) {
122
106
  const {
123
107
  git = defaultGit,
124
- auditHead = auditDir,
125
- auditBase = auditDir,
108
+ auditHead = auditAdvisories,
109
+ auditBase = auditAdvisories,
126
110
  materialize = materializeBase,
127
111
  lookupTrackingIssue = defaultLookupTrackingIssue,
128
112
  cleanup = (dir) => rmSync(dir, { recursive: true, force: true }),
@@ -139,29 +123,93 @@ export function runAttribution(argv = process.argv, deps = {}) {
139
123
  return { verdict: UNKNOWN, exitCode: 0, lines };
140
124
  }
141
125
 
142
- let headFailed;
143
- try {
144
- headFailed = auditHead(args.cwd).failed;
145
- } catch (err) {
146
- return report({ verdict: UNKNOWN, args, logger, reason: msg(err) });
126
+ const head = auditTheHead({ args, auditHead });
127
+ if (head.reason) {
128
+ return report({ verdict: UNKNOWN, args, logger, reason: head.reason });
147
129
  }
148
- if (!headFailed) {
149
- return report({
150
- verdict: UNKNOWN,
130
+
131
+ const { baseAudit, reason } = auditTheBase({
132
+ args,
133
+ git,
134
+ auditBase,
135
+ materialize,
136
+ cleanup,
137
+ });
138
+ const { introduced, preExisting } = diffAdvisories({
139
+ head: head.advisories,
140
+ base: baseAudit?.advisories ?? null,
141
+ });
142
+
143
+ // Per advisory: the merge base is "already failing for this" exactly when it
144
+ // carries every advisory the head does. A base red for its own advisory must
145
+ // not absolve a diff that added a different one.
146
+ const verdict = deriveVerdict({
147
+ headFailed: true,
148
+ baseAudit: baseAudit && { failed: introduced.length === 0 },
149
+ });
150
+ return report({
151
+ verdict,
152
+ args,
153
+ logger,
154
+ reason,
155
+ trackingIssue: resolveTrackingIssue({
156
+ verdict,
151
157
  args,
152
- logger,
153
- reason: 'the head tree audits clean, so there is nothing to attribute',
154
- });
158
+ lookupTrackingIssue,
159
+ }),
160
+ introduced,
161
+ preExisting,
162
+ });
163
+ }
164
+
165
+ /**
166
+ * Audit the head tree, converting both no-verdict cases — the audit itself
167
+ * broke, or the head is clean — into a `reason` the caller reports as
168
+ * `unknown`. Neither is an accusation, and neither is worth auditing a base for.
169
+ *
170
+ * @param {{ args: object, auditHead: Function }} ctx
171
+ * @returns {{ advisories?: Array<object>, reason: string|null }}
172
+ */
173
+ function auditTheHead({ args, auditHead }) {
174
+ try {
175
+ const head = auditHead(args.cwd);
176
+ return head.failed
177
+ ? { advisories: head.advisories, reason: null }
178
+ : {
179
+ reason:
180
+ 'the head tree audits clean, so there is nothing to attribute',
181
+ };
182
+ } catch (err) {
183
+ return { reason: msg(err) };
155
184
  }
185
+ }
186
+
187
+ /**
188
+ * Look the nightly sweep's tracking issue up, unless there is no verdict to
189
+ * attach it to or `--no-tracking-issue` said not to spend the round-trip.
190
+ *
191
+ * @param {{ verdict: string, args: object, lookupTrackingIssue: Function }} ctx
192
+ * @returns {number|null}
193
+ */
194
+ function resolveTrackingIssue({ verdict, args, lookupTrackingIssue }) {
195
+ if (verdict === UNKNOWN || !args.trackingIssue) return null;
196
+ return safeLookup(lookupTrackingIssue, args.cwd);
197
+ }
156
198
 
199
+ /**
200
+ * Materialize the merge base and audit it, converting every way that can fail
201
+ * into a `reason` rather than a throw. The scratch directory is always removed.
202
+ *
203
+ * @param {object} ctx
204
+ * @returns {{ baseAudit: object|null, reason: string|null }}
205
+ */
206
+ function auditTheBase({ args, git, auditBase, materialize, cleanup }) {
157
207
  let dir = null;
158
- let baseAudit = null;
159
- let reason = null;
160
208
  try {
161
209
  dir = materialize({ cwd: args.cwd, base: args.base, git });
162
- baseAudit = auditBase(dir);
210
+ return { baseAudit: auditBase(dir), reason: null };
163
211
  } catch (err) {
164
- reason = msg(err);
212
+ return { baseAudit: null, reason: msg(err) };
165
213
  } finally {
166
214
  if (dir) {
167
215
  try {
@@ -172,11 +220,6 @@ export function runAttribution(argv = process.argv, deps = {}) {
172
220
  }
173
221
  }
174
222
  }
175
-
176
- const verdict = deriveVerdict({ headFailed, baseAudit });
177
- const trackingIssue =
178
- verdict === UNKNOWN ? null : safeLookup(lookupTrackingIssue, args.cwd);
179
- return report({ verdict, args, logger, reason, trackingIssue });
180
223
  }
181
224
 
182
225
  function msg(err) {
@@ -189,15 +232,29 @@ function report({
189
232
  logger,
190
233
  reason = null,
191
234
  trackingIssue = null,
235
+ introduced = [],
236
+ preExisting = [],
192
237
  }) {
193
- const lines = renderAttribution({
194
- verdict,
195
- baseRef: args.base,
196
- trackingIssue,
197
- reason,
198
- });
238
+ const lines = [
239
+ ...renderAttribution({
240
+ verdict,
241
+ baseRef: args.base,
242
+ trackingIssue,
243
+ reason,
244
+ }),
245
+ // The two lists are reported separately even when the verdict is
246
+ // `introduced-by-this-diff`: an author fixing B still needs to know A is
247
+ // not theirs.
248
+ ...renderAdvisoryDetail({ introduced, preExisting }),
249
+ ];
199
250
  for (const line of lines) logger.info(line);
200
- return { verdict, exitCode: attributionExitCode(verdict), lines };
251
+ return {
252
+ verdict,
253
+ exitCode: attributionExitCode(verdict),
254
+ lines,
255
+ introduced,
256
+ preExisting,
257
+ };
201
258
  }
202
259
 
203
260
  /**