@open-agent-toolkit/cli 0.2.77 → 0.3.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 (73) hide show
  1. package/assets/agents/oat-phase-implementer.md +5 -5
  2. package/assets/agents/oat-reviewer.md +15 -15
  3. package/assets/agents/recon-worker.md +12 -12
  4. package/assets/bundle-metadata.json +1 -1
  5. package/assets/docs/cli-utilities/workflow-gates.md +1 -1
  6. package/assets/docs/workflows/projects/artifacts.md +2 -2
  7. package/assets/docs/workflows/projects/autonomy.md +1 -1
  8. package/assets/docs/workflows/projects/implementation-execution.md +4 -4
  9. package/assets/docs/workflows/projects/lifecycle.md +1 -1
  10. package/assets/docs/workflows/projects/reviews.md +11 -11
  11. package/assets/docs/workflows/skills/recon.md +53 -27
  12. package/assets/public-package-versions.json +4 -4
  13. package/assets/skills/oat-agent-instructions-analyze/SKILL.md +3 -3
  14. package/assets/skills/oat-docs-analyze/SKILL.md +3 -3
  15. package/assets/skills/oat-project-autonomous/SKILL.md +2 -2
  16. package/assets/skills/oat-project-autonomous/references/gate-inventory.md +14 -14
  17. package/assets/skills/oat-project-design/SKILL.md +2 -2
  18. package/assets/skills/oat-project-discover/SKILL.md +2 -2
  19. package/assets/skills/oat-project-document/SKILL.md +1 -1
  20. package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +14 -14
  21. package/assets/skills/oat-project-implement/SKILL.md +2 -2
  22. package/assets/skills/oat-project-implement/references/completion-and-closeout.md +1 -1
  23. package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +14 -14
  24. package/assets/skills/oat-project-implement/references/phase-execution.md +3 -3
  25. package/assets/skills/oat-project-implement/references/plan-and-resume.md +2 -2
  26. package/assets/skills/oat-project-import-plan/SKILL.md +2 -2
  27. package/assets/skills/oat-project-lite/SKILL.md +1 -1
  28. package/assets/skills/oat-project-lite/references/docs/autonomy-contract.md +14 -14
  29. package/assets/skills/oat-project-plan/SKILL.md +2 -2
  30. package/assets/skills/oat-project-plan-writing/SKILL.md +4 -4
  31. package/assets/skills/oat-project-pr-final/SKILL.md +1 -1
  32. package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +14 -14
  33. package/assets/skills/oat-project-quick-start/SKILL.md +2 -2
  34. package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +14 -14
  35. package/assets/skills/oat-project-review-provide/SKILL.md +14 -14
  36. package/assets/skills/oat-project-review-provide-remote/SKILL.md +10 -10
  37. package/assets/skills/oat-project-review-receive/SKILL.md +64 -45
  38. package/assets/skills/oat-project-review-receive-remote/SKILL.md +6 -6
  39. package/assets/skills/oat-project-revise/SKILL.md +2 -2
  40. package/assets/skills/oat-review-provide/SKILL.md +2 -2
  41. package/assets/skills/oat-review-provide/references/review-artifact-template.md +3 -3
  42. package/assets/skills/oat-review-provide-remote/SKILL.md +11 -11
  43. package/assets/skills/oat-review-receive/SKILL.md +18 -17
  44. package/assets/skills/oat-review-receive-remote/SKILL.md +13 -13
  45. package/assets/skills/oat-wave-execute/SKILL.md +2 -2
  46. package/assets/skills/recon/SKILL.md +37 -19
  47. package/assets/skills/recon/references/packet-contract.md +33 -30
  48. package/assets/skills/recon/references/profiles.md +18 -15
  49. package/assets/skills/recon/references/worker-contract.md +87 -7
  50. package/assets/skills/recon/scripts/create-review-brief.mjs +2 -25
  51. package/assets/skills/recon/scripts/lib/cli-entry.mjs +21 -0
  52. package/assets/skills/recon/scripts/lib/contracts.mjs +144 -38
  53. package/assets/skills/recon/scripts/lib/routing.mjs +2 -5
  54. package/assets/skills/recon/scripts/lib/validated-run.mjs +1 -1
  55. package/assets/skills/recon/scripts/prepare-routing.mjs +2 -26
  56. package/assets/skills/recon/scripts/reconcile-ledger.mjs +269 -2
  57. package/assets/skills/recon/scripts/render-packet.mjs +2 -25
  58. package/assets/skills/recon/scripts/validate-artifact.mjs +2 -25
  59. package/assets/skills/recon/scripts/validate-packet.mjs +73 -43
  60. package/assets/templates/plan.md +1 -1
  61. package/dist/commands/gate/__fixtures__/fake-runtime.mjs +4 -4
  62. package/dist/commands/gate/index.d.ts.map +1 -1
  63. package/dist/commands/gate/index.js +20 -11
  64. package/dist/commands/gate/review-verdict.d.ts +2 -2
  65. package/dist/commands/gate/review-verdict.d.ts.map +1 -1
  66. package/dist/commands/gate/review-verdict.js +223 -57
  67. package/dist/review-remote/body-builder.d.ts +3 -3
  68. package/dist/review-remote/body-builder.d.ts.map +1 -1
  69. package/dist/review-remote/body-builder.js +12 -12
  70. package/dist/review-remote/reviewer-dispatch.d.ts +2 -2
  71. package/dist/review-remote/reviewer-dispatch.d.ts.map +1 -1
  72. package/dist/review-remote/reviewer-dispatch.js +15 -7
  73. package/package.json +2 -2
@@ -2,17 +2,12 @@ import { createHash } from 'node:crypto';
2
2
  import { readFile, writeFile } from 'node:fs/promises';
3
3
  import { getFrontmatterBlock } from '../shared/frontmatter.js';
4
4
  import YAML from 'yaml';
5
- const SEVERITIES = [
6
- 'critical',
7
- 'important',
8
- 'medium',
9
- 'minor',
10
- ];
5
+ const SEVERITIES = ['critical', 'high', 'medium', 'low'];
11
6
  const FRONTMATTER_COUNT_KEYS = {
12
7
  critical: ['oat_review_critical_count', 'critical'],
13
- important: ['oat_review_important_count', 'important'],
8
+ high: ['oat_review_high_count', 'high'],
14
9
  medium: ['oat_review_medium_count', 'medium'],
15
- minor: ['oat_review_minor_count', 'minor'],
10
+ low: ['oat_review_low_count', 'low'],
16
11
  };
17
12
  function artifactContentSignature(content) {
18
13
  return createHash('sha256').update(content).digest('hex');
@@ -59,6 +54,9 @@ function readGateInvocation(frontmatter) {
59
54
  };
60
55
  }
61
56
  function parseCountValue(value) {
57
+ if (typeof value === 'string' && value.trim().length === 0) {
58
+ return null;
59
+ }
62
60
  const numberValue = typeof value === 'number'
63
61
  ? value
64
62
  : typeof value === 'string'
@@ -69,40 +67,58 @@ function parseCountValue(value) {
69
67
  }
70
68
  return numberValue;
71
69
  }
72
- function readFrontmatterCounts(frontmatter) {
70
+ function readFrontmatterCounts(frontmatter, artifactPath) {
71
+ const hasNestedCounts = Object.hasOwn(frontmatter, 'oat_review_counts');
72
+ if (hasNestedCounts &&
73
+ (typeof frontmatter['oat_review_counts'] !== 'object' ||
74
+ frontmatter['oat_review_counts'] === null ||
75
+ Array.isArray(frontmatter['oat_review_counts']))) {
76
+ throw new Error(`Review artifact at ${artifactPath} declares an invalid oat_review_counts block; it must be an object of non-negative integer counts.`);
77
+ }
73
78
  const nestedCounts = typeof frontmatter['oat_review_counts'] === 'object' &&
74
79
  frontmatter['oat_review_counts'] !== null &&
75
80
  !Array.isArray(frontmatter['oat_review_counts'])
76
81
  ? frontmatter['oat_review_counts']
77
82
  : null;
78
- const counts = {
79
- critical: 0,
80
- important: 0,
81
- medium: 0,
82
- minor: 0,
83
- };
83
+ const parsed = new Map();
84
+ // Validate every supplied alias before deciding whether the block is
85
+ // complete. A partial block must not be able to hide a conflicting pair and
86
+ // fall through to a summary line that reports fewer findings.
84
87
  for (const severity of SEVERITIES) {
85
88
  const candidateValues = [
86
- ...FRONTMATTER_COUNT_KEYS[severity].map((key) => frontmatter[key]),
89
+ ...FRONTMATTER_COUNT_KEYS[severity].flatMap((key) => Object.hasOwn(frontmatter, key) ? [frontmatter[key]] : []),
87
90
  ...(nestedCounts
88
- ? FRONTMATTER_COUNT_KEYS[severity].map((key) => nestedCounts[key])
91
+ ? FRONTMATTER_COUNT_KEYS[severity].flatMap((key) => Object.hasOwn(nestedCounts, key) ? [nestedCounts[key]] : [])
89
92
  : []),
90
- ].filter((value) => value !== undefined && value !== null);
91
- if (candidateValues.length === 0) {
92
- return null;
93
+ ];
94
+ const hasInvalidValue = candidateValues.some((value) => parseCountValue(value) === null);
95
+ if (hasInvalidValue) {
96
+ throw new Error(`Review artifact at ${artifactPath} declares an invalid ${severity} count; counts must be non-negative integers.`);
93
97
  }
94
- const parsedCount = candidateValues.reduce((parsed, value) => {
95
- if (parsed !== null) {
96
- return parsed;
97
- }
98
- return parseCountValue(value);
99
- }, null);
100
- if (parsedCount === null) {
101
- return null;
98
+ const distinct = [
99
+ ...new Set(candidateValues
100
+ .map((value) => parseCountValue(value))
101
+ .filter((value) => value !== null)),
102
+ ];
103
+ if (distinct.length > 1) {
104
+ throw new Error(`Review artifact at ${artifactPath} declares conflicting ${severity} counts (${distinct.join(', ')}); resolve the frontmatter before the gate can evaluate the review.`);
105
+ }
106
+ if (distinct.length === 1) {
107
+ parsed.set(severity, distinct[0]);
102
108
  }
103
- counts[severity] = parsedCount;
104
109
  }
105
- return counts;
110
+ if (parsed.size === 0) {
111
+ return null;
112
+ }
113
+ return {
114
+ counts: {
115
+ critical: parsed.get('critical') ?? 0,
116
+ high: parsed.get('high') ?? 0,
117
+ medium: parsed.get('medium') ?? 0,
118
+ low: parsed.get('low') ?? 0,
119
+ },
120
+ tiers: [...parsed.keys()],
121
+ };
106
122
  }
107
123
  function sectionContentIsEmpty(content) {
108
124
  const cleaned = content
@@ -146,15 +162,25 @@ function markdownLines(content) {
146
162
  return lines;
147
163
  }
148
164
  function fenceMarker(line) {
149
- const match = line.match(/^\s*(`{3,}|~{3,})/);
165
+ const match = line.match(/^\s*(`{3,}|~{3,})(.*)$/);
150
166
  if (!match?.[1]) {
151
167
  return null;
152
168
  }
153
169
  return {
154
170
  marker: match[1].startsWith('`') ? '`' : '~',
155
171
  length: match[1].length,
172
+ trailing: match[2] ?? '',
156
173
  };
157
174
  }
175
+ /**
176
+ * Lines outside fenced code blocks.
177
+ *
178
+ * An opening fence may carry an info string (```ts), but a closing fence may
179
+ * not: a marker run followed by other text is fence content, not a delimiter.
180
+ * Treating such a line as a closer ends the fence early and exposes quoted
181
+ * examples to the count scans, which is the failure the fence-awareness is
182
+ * there to prevent.
183
+ */
158
184
  function linesOutsideFences(content) {
159
185
  const outsideFenceLines = [];
160
186
  let activeFence = null;
@@ -165,7 +191,8 @@ function linesOutsideFences(content) {
165
191
  activeFence = marker;
166
192
  }
167
193
  else if (marker.marker === activeFence.marker &&
168
- marker.length >= activeFence.length) {
194
+ marker.length >= activeFence.length &&
195
+ marker.trailing.trim() === '') {
169
196
  activeFence = null;
170
197
  }
171
198
  continue;
@@ -211,39 +238,114 @@ function missingSeverityHeadings(severityHeadings) {
211
238
  const seenSeverities = new Set(severityHeadings.map((heading) => heading.severity));
212
239
  return SEVERITIES.filter((severity) => !seenSeverities.has(severity));
213
240
  }
214
- function parseFindingsSummaryCounts(content) {
215
- const match = content.match(/^Findings:\s*(\d+)\s+critical,\s*(\d+)\s+important,\s*(\d+)\s+medium,\s*(\d+)\s+minor\s*$/im);
216
- if (!match) {
241
+ const FINDINGS_SUMMARY_PATTERN = /^Findings by severity:\s*(\d+)\s+critical,\s*(\d+)\s+high,\s*(\d+)\s+medium,\s*(\d+)\s+low\s*$/i;
242
+ /**
243
+ * Read the canonical count line. Only lines outside fenced code blocks count,
244
+ * so a quoted example cannot be mistaken for the artifact's own counts, and two
245
+ * disagreeing count lines are rejected rather than silently resolved in favor
246
+ * of whichever appears first.
247
+ */
248
+ function parseFindingsSummaryCounts(content, artifactPath) {
249
+ const found = [];
250
+ for (const line of linesOutsideFences(content)) {
251
+ const match = FINDINGS_SUMMARY_PATTERN.exec(line.text.trim());
252
+ if (!match) {
253
+ continue;
254
+ }
255
+ found.push({
256
+ critical: Number.parseInt(match[1] ?? '0', 10),
257
+ high: Number.parseInt(match[2] ?? '0', 10),
258
+ medium: Number.parseInt(match[3] ?? '0', 10),
259
+ low: Number.parseInt(match[4] ?? '0', 10),
260
+ });
261
+ }
262
+ if (found.length === 0) {
217
263
  return null;
218
264
  }
219
- return {
220
- critical: Number.parseInt(match[1] ?? '0', 10),
221
- important: Number.parseInt(match[2] ?? '0', 10),
222
- medium: Number.parseInt(match[3] ?? '0', 10),
223
- minor: Number.parseInt(match[4] ?? '0', 10),
224
- };
265
+ const distinct = new Map(found.map((counts) => [JSON.stringify(counts), counts]));
266
+ if (distinct.size > 1) {
267
+ throw new Error(`Review artifact at ${artifactPath} contains ${found.length} conflicting "Findings by severity" count lines; keep exactly one so the gate can evaluate the review.`);
268
+ }
269
+ return found[0];
225
270
  }
226
- function parseFindingsSectionCounts(content, artifactPath) {
271
+ /**
272
+ * Tier spellings retired by the severity rename. They are no longer read, and
273
+ * detecting them fails the artifact with a migration error rather than letting
274
+ * it parse. A retired heading that parsed alongside canonical ones would have
275
+ * its findings attributed to whichever canonical section precedes it, which can
276
+ * move a finding below the blocking threshold.
277
+ */
278
+ function describeLegacySeverityUsage(content, frontmatter) {
279
+ const found = new Set();
280
+ const findingsSection = findFindingsSection(content);
281
+ for (const line of linesOutsideFences(findingsSection?.content ?? '')) {
282
+ if (/^#{1,6}\s+Important\s*#*\s*$/i.test(line.text)) {
283
+ found.add('a `### Important` heading');
284
+ }
285
+ if (/^#{1,6}\s+Minor\s*#*\s*$/i.test(line.text)) {
286
+ found.add('a `### Minor` heading');
287
+ }
288
+ }
289
+ for (const line of linesOutsideFences(content)) {
290
+ if (/^Findings:\s*\d+\s+critical,/i.test(line.text.trim())) {
291
+ found.add('a legacy `Findings:` count line');
292
+ }
293
+ }
294
+ const nested = typeof frontmatter['oat_review_counts'] === 'object' &&
295
+ frontmatter['oat_review_counts'] !== null &&
296
+ !Array.isArray(frontmatter['oat_review_counts'])
297
+ ? frontmatter['oat_review_counts']
298
+ : {};
299
+ for (const key of [...Object.keys(frontmatter), ...Object.keys(nested)]) {
300
+ if (/^(oat_review_)?(important|minor)(_count)?$/.test(key)) {
301
+ found.add(`the \`${key}\` count key`);
302
+ }
303
+ }
304
+ return [...found];
305
+ }
306
+ function legacySeverityError(artifactPath, legacyUsage) {
307
+ if (legacyUsage.length === 0) {
308
+ return null;
309
+ }
310
+ return new Error(`Review artifact at ${artifactPath} uses retired severity tiers (${legacyUsage.join(', ')}). The tiers are now Critical, High, Medium, and Low: rename \`### Important\` to \`### High\` and \`### Minor\` to \`### Low\`, and \`oat_review_important_count\`/\`oat_review_minor_count\` to \`oat_review_high_count\`/\`oat_review_low_count\`, or re-run the review. If the reviewer that wrote this artifact still emits the retired tiers, its installed instructions are stale: run \`oat tools update\` to refresh the installed tools and their provider projections.`);
311
+ }
312
+ /**
313
+ * Per-tier counts for every severity heading the body actually has.
314
+ *
315
+ * Deliberately tolerant: it counts what is present without requiring the full
316
+ * canonical heading set, so it can cross-check explicit counts without changing
317
+ * how a body-only artifact is validated.
318
+ */
319
+ function tallySeveritySections(content) {
227
320
  const findingsSection = findFindingsSection(content);
228
321
  const severityHeadings = findSeverityHeadings(content);
229
322
  if (severityHeadings.length === 0) {
230
323
  return null;
231
324
  }
232
- const missingSeverities = missingSeverityHeadings(severityHeadings);
233
- if (missingSeverities.length > 0) {
234
- throw new Error(`Review artifact at ${artifactPath} has an incomplete Findings section; expected headings for Critical, Important, Medium, and Minor. Missing: ${missingSeverities.join(', ')}.`);
235
- }
236
325
  const counts = {
237
326
  critical: 0,
238
- important: 0,
327
+ high: 0,
239
328
  medium: 0,
240
- minor: 0,
329
+ low: 0,
241
330
  };
242
331
  for (const [offset, heading] of severityHeadings.entries()) {
243
332
  const nextHeading = severityHeadings[offset + 1];
244
333
  const sectionStart = heading.index + heading.headingLength;
245
334
  const sectionEnd = nextHeading?.index ?? findingsSection?.end ?? content.length;
246
- counts[heading.severity] = countFindingsInSection(content.slice(sectionStart, sectionEnd));
335
+ // Accumulate rather than assign: a repeated heading for one tier must not
336
+ // erase findings already counted under an earlier occurrence.
337
+ counts[heading.severity] += countFindingsInSection(content.slice(sectionStart, sectionEnd));
338
+ }
339
+ return counts;
340
+ }
341
+ function parseFindingsSectionCounts(content, artifactPath) {
342
+ const counts = tallySeveritySections(content);
343
+ if (counts === null) {
344
+ return null;
345
+ }
346
+ const missingSeverities = missingSeverityHeadings(findSeverityHeadings(content));
347
+ if (missingSeverities.length > 0) {
348
+ throw new Error(`Review artifact at ${artifactPath} has an incomplete Findings section; expected headings for ${SEVERITIES.map(severityDisplayName).join(', ')}. Missing: ${missingSeverities.join(', ')}.`);
247
349
  }
248
350
  return counts;
249
351
  }
@@ -263,7 +365,7 @@ function parseFrontmatterObject(frontmatter, artifactPath) {
263
365
  }
264
366
  }
265
367
  function hasBlockingFindings(counts) {
266
- return counts.critical > 0 || counts.important > 0;
368
+ return counts.critical > 0 || counts.high > 0;
267
369
  }
268
370
  function insertionTextForSeverity(severity) {
269
371
  return `\n### ${severityDisplayName(severity)}\n\nNone\n`;
@@ -307,16 +409,72 @@ async function normalizeMissingEmptySeveritySections(artifactPath, content, coun
307
409
  persisted: insertedSeverities.length > 0 && persist,
308
410
  };
309
411
  }
310
- function resolveCounts(content, frontmatter) {
311
- const frontmatterCounts = readFrontmatterCounts(frontmatter);
412
+ function formatCounts(counts) {
413
+ return SEVERITIES.map((severity) => `${counts[severity]} ${severity}`).join(', ');
414
+ }
415
+ /**
416
+ * Fail closed when two count sources disagree.
417
+ *
418
+ * No single source may win by precedence: a stale all-zero frontmatter block or
419
+ * count line would otherwise mask real findings written in the body, and the
420
+ * gate would pass a review that clearly reports a blocking finding.
421
+ */
422
+ function assertAgreeingCounts(artifactPath, left, right, tiers = SEVERITIES) {
423
+ const differing = tiers.filter((severity) => left.counts[severity] !== right.counts[severity]);
424
+ if (differing.length === 0) {
425
+ return;
426
+ }
427
+ throw new Error(`Review artifact at ${artifactPath} contradicts itself about finding counts (${differing.join(', ')}): declared counts are ${formatCounts(left.counts)} (${left.name}) but ${formatCounts(right.counts)} (${right.name}). Reconcile them before the gate can evaluate the review.`);
428
+ }
429
+ /**
430
+ * Resolve the artifact's counts, cross-checking every source that is present.
431
+ *
432
+ * Returns `null` when only the body carries counts, so the caller keeps the
433
+ * completeness-checked body path for body-only artifacts.
434
+ */
435
+ function resolveCounts(content, frontmatter, artifactPath) {
436
+ const explicitSources = [];
437
+ const frontmatterCounts = readFrontmatterCounts(frontmatter, artifactPath);
312
438
  if (frontmatterCounts) {
313
- return frontmatterCounts;
439
+ explicitSources.push({
440
+ name: 'the frontmatter count fields',
441
+ counts: frontmatterCounts.counts,
442
+ tiers: frontmatterCounts.tiers,
443
+ });
314
444
  }
315
- const summaryCounts = parseFindingsSummaryCounts(content);
445
+ const summaryCounts = parseFindingsSummaryCounts(content, artifactPath);
316
446
  if (summaryCounts) {
317
- return summaryCounts;
447
+ explicitSources.push({
448
+ name: 'the "Findings by severity" count line',
449
+ counts: summaryCounts,
450
+ tiers: SEVERITIES,
451
+ });
318
452
  }
319
- return null;
453
+ const [primarySource, ...otherSources] = explicitSources;
454
+ for (const source of otherSources) {
455
+ const comparableTiers = primarySource.tiers.filter((severity) => source.tiers.includes(severity));
456
+ assertAgreeingCounts(artifactPath, primarySource, source, comparableTiers);
457
+ }
458
+ const bodyCounts = tallySeveritySections(content);
459
+ if (bodyCounts) {
460
+ // Only tiers the body actually has a heading for are comparable. A missing
461
+ // heading is not a claim of zero findings, it is a malformed body, and the
462
+ // completeness and normalization checks own that diagnosis.
463
+ const bodyTiers = [
464
+ ...new Set(findSeverityHeadings(content).map((heading) => heading.severity)),
465
+ ];
466
+ const bodySource = {
467
+ name: 'the Findings sections',
468
+ counts: bodyCounts,
469
+ tiers: bodyTiers,
470
+ };
471
+ for (const source of explicitSources) {
472
+ const comparableTiers = source.tiers.filter((severity) => bodyTiers.includes(severity));
473
+ assertAgreeingCounts(artifactPath, source, bodySource, comparableTiers);
474
+ }
475
+ }
476
+ return (explicitSources.find((source) => source.tiers.length === SEVERITIES.length)
477
+ ?.counts ?? null);
320
478
  }
321
479
  export async function parseReviewGateVerdict(artifactPath, options = {}) {
322
480
  let content;
@@ -340,7 +498,15 @@ export async function parseReviewGateVerdict(artifactPath, options = {}) {
340
498
  const frontmatter = frontmatterBlock
341
499
  ? parseFrontmatterObject(frontmatterBlock, artifactPath)
342
500
  : {};
343
- let counts = resolveCounts(content, frontmatter);
501
+ // Retired tiers fail the artifact outright rather than being ignored, and do
502
+ // so before normalization can rewrite the file. Ignoring them is not safe: a
503
+ // retired heading that parses alongside canonical ones has its findings
504
+ // attributed to whichever canonical section precedes it.
505
+ const retiredTierError = legacySeverityError(artifactPath, describeLegacySeverityUsage(content, frontmatter));
506
+ if (retiredTierError) {
507
+ throw retiredTierError;
508
+ }
509
+ let counts = resolveCounts(content, frontmatter, artifactPath);
344
510
  let insertedSeverities = [];
345
511
  let normalizationPersisted = false;
346
512
  if (counts && options.normalizeMissingEmptySeveritySections) {
@@ -5,12 +5,12 @@
5
5
  *
6
6
  * The body is the durable handoff to `*-receive-remote`: a leading
7
7
  * HTML-comment marker block (parsed back by {@link parseMarkerBlock}) followed
8
- * by human-readable prose (summary, severity counts, optional minor-fix nudge,
8
+ * by human-readable prose (summary, severity counts, optional low-fix nudge,
9
9
  * optional verification commands).
10
10
  */
11
11
  import { type ReviewInvocation } from './marker-parser.js';
12
12
  export type ReviewVerdict = 'REQUEST_CHANGES' | 'COMMENT';
13
- export type FindingSeverity = 'critical' | 'important' | 'medium' | 'minor';
13
+ export type FindingSeverity = 'critical' | 'high' | 'medium' | 'low';
14
14
  /** Minimal finding shape the builder needs — only severity is required. */
15
15
  export interface BuilderFinding {
16
16
  severity: FindingSeverity;
@@ -73,7 +73,7 @@ interface GateBuildInput {
73
73
  export type BuildInput = BuildInputBase & (LifecycleBuildInput | GateBuildInput);
74
74
  /**
75
75
  * Map a finding set to the GitHub review verdict: `REQUEST_CHANGES` when any
76
- * critical or important finding is present, otherwise `COMMENT` (including the
76
+ * critical or high finding is present, otherwise `COMMENT` (including the
77
77
  * zero-findings clean-review case). Never auto-`APPROVE`.
78
78
  */
79
79
  export declare function mapVerdict(findings: BuilderFinding[]): ReviewVerdict;
@@ -1 +1 @@
1
- {"version":3,"file":"body-builder.d.ts","sourceRoot":"","sources":["../../src/review-remote/body-builder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAE3E,MAAM,MAAM,aAAa,GAAG,iBAAiB,GAAG,SAAS,CAAC;AAE1D,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,WAAW,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE5E,2EAA2E;AAC3E,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,eAAe,CAAC;CAC3B;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,gBAAgB;IAC/B,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,eAAe,CAAC;IAC1B,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,cAAc;IACtB,oDAAoD;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,gEAAgE;IAChE,KAAK,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B;;;;;;;;OAQG;IACH,iBAAiB,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACvC,4EAA4E;IAC5E,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;CACjC;AAED,UAAU,mBAAmB;IAC3B,UAAU,EAAE,OAAO,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC;IAC9C,UAAU,CAAC,EAAE,KAAK,CAAC;CACpB;AAED,UAAU,cAAc;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,iEAAiE;IACjE,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,UAAU,GAAG,cAAc,GACrC,CAAC,mBAAmB,GAAG,cAAc,CAAC,CAAC;AAEzC;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,aAAa,CAKpE;AAgFD;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,aAAa,CAAC;CACxB,CAkCA"}
1
+ {"version":3,"file":"body-builder.d.ts","sourceRoot":"","sources":["../../src/review-remote/body-builder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAE3E,MAAM,MAAM,aAAa,GAAG,iBAAiB,GAAG,SAAS,CAAC;AAE1D,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;AAErE,2EAA2E;AAC3E,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,eAAe,CAAC;CAC3B;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,gBAAgB;IAC/B,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,eAAe,CAAC;IAC1B,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,cAAc;IACtB,oDAAoD;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,gEAAgE;IAChE,KAAK,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B;;;;;;;;OAQG;IACH,iBAAiB,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACvC,4EAA4E;IAC5E,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;CACjC;AAED,UAAU,mBAAmB;IAC3B,UAAU,EAAE,OAAO,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC;IAC9C,UAAU,CAAC,EAAE,KAAK,CAAC;CACpB;AAED,UAAU,cAAc;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,iEAAiE;IACjE,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,UAAU,GAAG,cAAc,GACrC,CAAC,mBAAmB,GAAG,cAAc,CAAC,CAAC;AAEzC;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,aAAa,CAKpE;AAgFD;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,aAAa,CAAC;CACxB,CAkCA"}
@@ -5,25 +5,25 @@
5
5
  *
6
6
  * The body is the durable handoff to `*-receive-remote`: a leading
7
7
  * HTML-comment marker block (parsed back by {@link parseMarkerBlock}) followed
8
- * by human-readable prose (summary, severity counts, optional minor-fix nudge,
8
+ * by human-readable prose (summary, severity counts, optional low-fix nudge,
9
9
  * optional verification commands).
10
10
  */
11
11
  import { MARKER_BLOCK_OPEN } from './marker-parser.js';
12
12
  /**
13
13
  * Map a finding set to the GitHub review verdict: `REQUEST_CHANGES` when any
14
- * critical or important finding is present, otherwise `COMMENT` (including the
14
+ * critical or high finding is present, otherwise `COMMENT` (including the
15
15
  * zero-findings clean-review case). Never auto-`APPROVE`.
16
16
  */
17
17
  export function mapVerdict(findings) {
18
- const hasBlocking = findings.some((f) => f.severity === 'critical' || f.severity === 'important');
18
+ const hasBlocking = findings.some((f) => f.severity === 'critical' || f.severity === 'high');
19
19
  return hasBlocking ? 'REQUEST_CHANGES' : 'COMMENT';
20
20
  }
21
21
  function countSeverities(findings) {
22
22
  const counts = {
23
23
  critical: 0,
24
- important: 0,
24
+ high: 0,
25
25
  medium: 0,
26
- minor: 0,
26
+ low: 0,
27
27
  };
28
28
  for (const f of findings) {
29
29
  counts[f.severity] += 1;
@@ -76,9 +76,9 @@ function buildOutOfDiffSection(findings) {
76
76
  });
77
77
  return ['## Findings outside the PR diff', '', ...entries].join('\n');
78
78
  }
79
- const MINOR_FIX_NUDGE = 'Minor findings are included inline. We recommend fixing minors during ' +
80
- 'this cycle rather than tracking them as backlog items — they are usually ' +
81
- 'faster to just resolve than to manage.';
79
+ const LOW_FIX_NUDGE = 'Low findings are included inline. We recommend fixing low-severity ' +
80
+ 'findings during this cycle rather than tracking them as backlog items — ' +
81
+ 'they are usually faster to just resolve than to manage.';
82
82
  /**
83
83
  * Build the posted-review body and compute its verdict.
84
84
  */
@@ -92,17 +92,17 @@ export function buildReviewBody(input) {
92
92
  '## Severity Counts',
93
93
  '',
94
94
  `- Critical: ${counts.critical}`,
95
- `- Important: ${counts.important}`,
95
+ `- High: ${counts.high}`,
96
96
  `- Medium: ${counts.medium}`,
97
- `- Minor: ${counts.minor}`,
97
+ `- Low: ${counts.low}`,
98
98
  ].join('\n'),
99
99
  ];
100
100
  const outOfDiffSection = buildOutOfDiffSection(input.outOfDiffFindings);
101
101
  if (outOfDiffSection !== null) {
102
102
  sections.push(outOfDiffSection);
103
103
  }
104
- if (counts.minor > 0) {
105
- sections.push(`## Notes\n\n${MINOR_FIX_NUDGE}`);
104
+ if (counts.low > 0) {
105
+ sections.push(`## Notes\n\n${LOW_FIX_NUDGE}`);
106
106
  }
107
107
  if (input.verificationCommands && input.verificationCommands.length > 0) {
108
108
  const commands = input.verificationCommands
@@ -33,10 +33,10 @@ import type { NarrowingResult } from './narrowing.js';
33
33
  export declare const STRUCTURED_OUTPUT_MODE_FLAG: 'oat_output_mode';
34
34
  /** The single accepted value of {@link STRUCTURED_OUTPUT_MODE_FLAG}. */
35
35
  export declare const STRUCTURED_OUTPUT_MODE_VALUE: 'structured';
36
- export type FindingSeverity = 'critical' | 'important' | 'medium' | 'minor';
36
+ export type FindingSeverity = 'critical' | 'high' | 'medium' | 'low';
37
37
  /** A single structured finding (see design.md → Data Models). */
38
38
  export interface StructuredFinding {
39
- /** Stable per-dispatch ID with a C/I/M/m prefix. */
39
+ /** Stable per-dispatch ID with a C/H/M/L prefix. */
40
40
  id: string;
41
41
  severity: FindingSeverity;
42
42
  title: string;
@@ -1 +1 @@
1
- {"version":3,"file":"reviewer-dispatch.d.ts","sourceRoot":"","sources":["../../src/review-remote/reviewer-dispatch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B,EAAG,iBAA0B,CAAC;AAEtE,wEAAwE;AACxE,eAAO,MAAM,4BAA4B,EAAG,YAAqB,CAAC;AAElE,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,WAAW,GAAG,QAAQ,GAAG,OAAO,CAAC;AAS5E,iEAAiE;AACjE,MAAM,WAAW,iBAAiB;IAChC,oDAAoD;IACpD,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,eAAe,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,0DAA0D;IAC1D,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,wEAAwE;AACxE,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,qBAAqB,EAAE,MAAM,EAAE,CAAC;CACjC;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;CACpE;AAED,iFAAiF;AACjF,MAAM,WAAW,qBAAqB;IACpC,2CAA2C;IAC3C,WAAW,EAAE,MAAM,CAAC;IACpB,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,2EAA2E;IAC3E,mBAAmB,EAAE,MAAM,CAAC;IAC5B,6EAA6E;IAC7E,mBAAmB,EAAE,MAAM,CAAC;IAC5B,8EAA8E;IAC9E,SAAS,EAAE,eAAe,CAAC;CAC5B;AAED,iFAAiF;AACjF,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,YAAY,OAAO,EAAE,MAAM,EAG1B;CACF;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,qBAAqB,GAC7B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAezB;AAgED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,kBAAkB,CA2B7E;AAED;;;;;GAKG;AACH,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,qBAAqB,EAC9B,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,kBAAkB,CAAC,CAK7B"}
1
+ {"version":3,"file":"reviewer-dispatch.d.ts","sourceRoot":"","sources":["../../src/review-remote/reviewer-dispatch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B,EAAG,iBAA0B,CAAC;AAEtE,wEAAwE;AACxE,eAAO,MAAM,4BAA4B,EAAG,YAAqB,CAAC;AAElE,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;AAgBrE,iEAAiE;AACjE,MAAM,WAAW,iBAAiB;IAChC,oDAAoD;IACpD,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,eAAe,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,0DAA0D;IAC1D,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,wEAAwE;AACxE,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,qBAAqB,EAAE,MAAM,EAAE,CAAC;CACjC;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;CACpE;AAED,iFAAiF;AACjF,MAAM,WAAW,qBAAqB;IACpC,2CAA2C;IAC3C,WAAW,EAAE,MAAM,CAAC;IACpB,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,2EAA2E;IAC3E,mBAAmB,EAAE,MAAM,CAAC;IAC5B,6EAA6E;IAC7E,mBAAmB,EAAE,MAAM,CAAC;IAC5B,8EAA8E;IAC9E,SAAS,EAAE,eAAe,CAAC;CAC5B;AAED,iFAAiF;AACjF,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,YAAY,OAAO,EAAE,MAAM,EAG1B;CACF;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,qBAAqB,GAC7B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAezB;AAsED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,kBAAkB,CA2B7E;AAED;;;;;GAKG;AACH,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,qBAAqB,EAC9B,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,kBAAkB,CAAC,CAK7B"}
@@ -34,10 +34,16 @@ export const STRUCTURED_OUTPUT_MODE_FLAG = 'oat_output_mode';
34
34
  export const STRUCTURED_OUTPUT_MODE_VALUE = 'structured';
35
35
  const SEVERITIES = new Set([
36
36
  'critical',
37
- 'important',
37
+ 'high',
38
38
  'medium',
39
- 'minor',
39
+ 'low',
40
40
  ]);
41
+ const SEVERITY_ID_PREFIX = {
42
+ critical: 'C',
43
+ high: 'H',
44
+ medium: 'M',
45
+ low: 'L',
46
+ };
41
47
  /** Typed error raised when the reviewer returns a malformed structured shape. */
42
48
  export class StructuredFindingsError extends Error {
43
49
  constructor(message) {
@@ -72,12 +78,14 @@ function validateFinding(value, index) {
72
78
  if (!isObject(value)) {
73
79
  throw new StructuredFindingsError(`${at} must be an object.`);
74
80
  }
75
- if (typeof value['id'] !== 'string' || value['id'] === '') {
76
- throw new StructuredFindingsError(`${at}.id must be a non-empty string.`);
77
- }
78
81
  if (typeof value['severity'] !== 'string' ||
79
82
  !SEVERITIES.has(value['severity'])) {
80
- throw new StructuredFindingsError(`${at}.severity must be one of critical|important|medium|minor.`);
83
+ throw new StructuredFindingsError(`${at}.severity must be one of critical|high|medium|low.`);
84
+ }
85
+ const severity = value['severity'];
86
+ const expectedIdPattern = new RegExp(`^${SEVERITY_ID_PREFIX[severity]}[1-9]\\d*$`);
87
+ if (typeof value['id'] !== 'string' || !expectedIdPattern.test(value['id'])) {
88
+ throw new StructuredFindingsError(`${at}.id must use the ${SEVERITY_ID_PREFIX[severity]} prefix for ${severity} severity followed by a positive integer.`);
81
89
  }
82
90
  if (typeof value['title'] !== 'string') {
83
91
  throw new StructuredFindingsError(`${at}.title must be a string.`);
@@ -104,7 +112,7 @@ function validateFinding(value, index) {
104
112
  }
105
113
  return {
106
114
  id: value['id'],
107
- severity: value['severity'],
115
+ severity,
108
116
  title: value['title'],
109
117
  file: fileSet ? file : null,
110
118
  line: lineSet ? line : null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-agent-toolkit/cli",
3
- "version": "0.2.77",
3
+ "version": "0.3.0",
4
4
  "private": false,
5
5
  "description": "Open Agent Toolkit CLI",
6
6
  "homepage": "https://github.com/voxmedia/open-agent-toolkit/tree/main/packages/cli",
@@ -34,7 +34,7 @@
34
34
  "ora": "^9.0.0",
35
35
  "yaml": "2.8.2",
36
36
  "zod": "^3.25.76",
37
- "@open-agent-toolkit/control-plane": "0.2.77"
37
+ "@open-agent-toolkit/control-plane": "0.3.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/node": "^22.10.0",