@open-agent-toolkit/cli 0.2.79 → 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 (57) hide show
  1. package/assets/agents/oat-phase-implementer.md +5 -5
  2. package/assets/agents/oat-reviewer.md +15 -15
  3. package/assets/bundle-metadata.json +1 -1
  4. package/assets/docs/cli-utilities/workflow-gates.md +1 -1
  5. package/assets/docs/workflows/projects/artifacts.md +2 -2
  6. package/assets/docs/workflows/projects/autonomy.md +1 -1
  7. package/assets/docs/workflows/projects/implementation-execution.md +4 -4
  8. package/assets/docs/workflows/projects/lifecycle.md +1 -1
  9. package/assets/docs/workflows/projects/reviews.md +11 -11
  10. package/assets/public-package-versions.json +4 -4
  11. package/assets/skills/oat-agent-instructions-analyze/SKILL.md +3 -3
  12. package/assets/skills/oat-docs-analyze/SKILL.md +3 -3
  13. package/assets/skills/oat-project-autonomous/SKILL.md +2 -2
  14. package/assets/skills/oat-project-autonomous/references/gate-inventory.md +14 -14
  15. package/assets/skills/oat-project-design/SKILL.md +2 -2
  16. package/assets/skills/oat-project-discover/SKILL.md +2 -2
  17. package/assets/skills/oat-project-document/SKILL.md +1 -1
  18. package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +14 -14
  19. package/assets/skills/oat-project-implement/SKILL.md +2 -2
  20. package/assets/skills/oat-project-implement/references/completion-and-closeout.md +1 -1
  21. package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +14 -14
  22. package/assets/skills/oat-project-implement/references/phase-execution.md +3 -3
  23. package/assets/skills/oat-project-implement/references/plan-and-resume.md +2 -2
  24. package/assets/skills/oat-project-import-plan/SKILL.md +2 -2
  25. package/assets/skills/oat-project-lite/SKILL.md +1 -1
  26. package/assets/skills/oat-project-lite/references/docs/autonomy-contract.md +14 -14
  27. package/assets/skills/oat-project-plan/SKILL.md +2 -2
  28. package/assets/skills/oat-project-plan-writing/SKILL.md +4 -4
  29. package/assets/skills/oat-project-pr-final/SKILL.md +1 -1
  30. package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +14 -14
  31. package/assets/skills/oat-project-quick-start/SKILL.md +2 -2
  32. package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +14 -14
  33. package/assets/skills/oat-project-review-provide/SKILL.md +14 -14
  34. package/assets/skills/oat-project-review-provide-remote/SKILL.md +10 -10
  35. package/assets/skills/oat-project-review-receive/SKILL.md +64 -45
  36. package/assets/skills/oat-project-review-receive-remote/SKILL.md +6 -6
  37. package/assets/skills/oat-project-revise/SKILL.md +2 -2
  38. package/assets/skills/oat-review-provide/SKILL.md +2 -2
  39. package/assets/skills/oat-review-provide/references/review-artifact-template.md +3 -3
  40. package/assets/skills/oat-review-provide-remote/SKILL.md +11 -11
  41. package/assets/skills/oat-review-receive/SKILL.md +18 -17
  42. package/assets/skills/oat-review-receive-remote/SKILL.md +13 -13
  43. package/assets/skills/oat-wave-execute/SKILL.md +2 -2
  44. package/assets/templates/plan.md +1 -1
  45. package/dist/commands/gate/__fixtures__/fake-runtime.mjs +4 -4
  46. package/dist/commands/gate/index.d.ts.map +1 -1
  47. package/dist/commands/gate/index.js +20 -11
  48. package/dist/commands/gate/review-verdict.d.ts +2 -2
  49. package/dist/commands/gate/review-verdict.d.ts.map +1 -1
  50. package/dist/commands/gate/review-verdict.js +223 -57
  51. package/dist/review-remote/body-builder.d.ts +3 -3
  52. package/dist/review-remote/body-builder.d.ts.map +1 -1
  53. package/dist/review-remote/body-builder.js +12 -12
  54. package/dist/review-remote/reviewer-dispatch.d.ts +2 -2
  55. package/dist/review-remote/reviewer-dispatch.d.ts.map +1 -1
  56. package/dist/review-remote/reviewer-dispatch.js +15 -7
  57. package/package.json +2 -2
@@ -5,7 +5,7 @@ disable-model-invocation: true
5
5
  user-invocable: true
6
6
  allowed-tools: Read, Write, Bash, Glob, Grep, AskUserQuestion
7
7
  metadata:
8
- version: 1.3.1
8
+ version: 1.3.2
9
9
  ---
10
10
 
11
11
  # Remote Review Receive (Ad-hoc GitHub PR)
@@ -71,8 +71,8 @@ Normalize every finding to this shape:
71
71
 
72
72
  ```yaml
73
73
  finding:
74
- id: "C1" | "I1" | "M1" | "m1"
75
- severity: critical | important | medium | minor
74
+ id: "C1" | "H1" | "M1" | "L1"
75
+ severity: critical | high | medium | low
76
76
  title: string
77
77
  file: string | null
78
78
  line: number | null
@@ -86,9 +86,9 @@ finding:
86
86
  Severity conventions:
87
87
 
88
88
  - `critical`: Broken behavior, security risk, or missing P0 requirement.
89
- - `important`: Missing P1 requirement, major robustness issue.
89
+ - `high`: Missing P1 requirement, major robustness issue.
90
90
  - `medium`: Meaningful but non-blocking quality/maintainability issue.
91
- - `minor`: Cosmetic/style/documentation issue.
91
+ - `low`: Cosmetic/style/documentation issue.
92
92
 
93
93
  ## Process
94
94
 
@@ -126,16 +126,16 @@ For each item in JSON:
126
126
 
127
127
  - Capture `type` (`review_comment`, `issue_comment`, `review`).
128
128
  - Map location fields (`path`, `line`) when present.
129
- - Use comment body + context to assign severity (`critical`, `important`, `medium`, `minor`).
130
- - Treat `CHANGES_REQUESTED` review state as a strong hint toward `important+`, not an automatic override.
129
+ - Use comment body + context to assign severity (`critical`, `high`, `medium`, `low`).
130
+ - Treat `CHANGES_REQUESTED` review state as a strong hint toward `high+`, not an automatic override.
131
131
  - Normalize into the shared findings model.
132
132
 
133
133
  ID assignment per severity bucket:
134
134
 
135
135
  - Critical: `C1`, `C2`, ...
136
- - Important: `I1`, `I2`, ...
136
+ - High: `H1`, `H2`, ...
137
137
  - Medium: `M1`, `M2`, ...
138
- - Minor: `m1`, `m2`, ...
138
+ - Low: `L1`, `L2`, ...
139
139
 
140
140
  ### Step 4: Present Findings Overview and Triage
141
141
 
@@ -146,21 +146,21 @@ Before triage prompts, output:
146
146
 
147
147
  Disposition options per finding:
148
148
 
149
- - `convert` (default for critical/important/medium/minor)
149
+ - `convert` (default for critical/high/medium/low)
150
150
  - `defer`
151
151
  - `dismiss`
152
152
 
153
153
  Rules:
154
154
 
155
- - Require concrete rationale for `defer`/`dismiss` at any severity, including minor. Small findings are usually cheaper to fix inline than to track as backlog items, so a minor `defer` must be justified just like any other deferral.
156
- - For any deferral (minor included), require a concrete reason (duplicate, dependency, explicit out-of-scope follow-up, risky churn).
155
+ - Require concrete rationale for `defer`/`dismiss` at any severity, including low. Small findings are usually cheaper to fix inline than to track as backlog items, so a low `defer` must be justified just like any other deferral.
156
+ - For any deferral (low included), require a concrete reason (duplicate, dependency, explicit out-of-scope follow-up, risky churn).
157
157
 
158
158
  ### Step 5: Generate Standalone Task List
159
159
 
160
160
  Task entry format:
161
161
 
162
162
  ```markdown
163
- - [ ] [important] Add null-guard in OAuth callback parser (`packages/auth/src/callback.ts:142`) - Validate provider payload before dereference.
163
+ - [ ] [high] Add null-guard in OAuth callback parser (`packages/auth/src/callback.ts:142`) - Validate provider payload before dereference.
164
164
  ```
165
165
 
166
166
  Output modes:
@@ -6,7 +6,7 @@ disable-model-invocation: false
6
6
  user-invocable: true
7
7
  allowed-tools: Read, Write, Edit, Bash, Grep, Glob, Task
8
8
  metadata:
9
- version: 1.9.4
9
+ version: 1.9.5
10
10
  ---
11
11
 
12
12
  # Execute a Wave of External Plans
@@ -181,7 +181,7 @@ excluded from groups) — never mid-run. **Reconciliation contract:** when recon
181
181
  reveals a stale plan premise, the reconciliation must be NON-NARROWING (WHERE
182
182
  the work happens may change; WHAT must be true may not), recorded exactly once
183
183
  in the Drift Refresh Record with pointer-only references elsewhere — a
184
- reconciliation that waives a source-plan requirement is a plan-gate Important
184
+ reconciliation that waives a source-plan requirement is a plan-gate High
185
185
  (wave-4 evidence).
186
186
 
187
187
  Recon is delegated; the Drift Refresh Record entries and any reconciliation
@@ -189,7 +189,7 @@ cell; never truncate a widened row back to five columns.
189
189
  - `received`: review artifact exists (not yet converted into fix tasks)
190
190
  - `fixes_added`: fix tasks were added to the plan (work queued)
191
191
  - `fixes_completed`: fix tasks implemented, awaiting re-review
192
- - `passed`: re-review run and recorded as passing (no Critical/Important)
192
+ - `passed`: re-review run and recorded as passing (no Critical/High)
193
193
 
194
194
  ---
195
195
 
@@ -55,9 +55,9 @@ async function writeArtifact() {
55
55
  `oat_invocation_reasoning_effort: ${promptValue('oat_invocation_reasoning_effort')}`,
56
56
  `oat_invocation_source: ${promptValue('oat_invocation_source')}`,
57
57
  'oat_review_critical_count: 0',
58
- 'oat_review_important_count: 0',
58
+ 'oat_review_high_count: 0',
59
59
  'oat_review_medium_count: 0',
60
- 'oat_review_minor_count: 0',
60
+ 'oat_review_low_count: 0',
61
61
  '---',
62
62
  '',
63
63
  '# Fake runtime review',
@@ -68,7 +68,7 @@ async function writeArtifact() {
68
68
  '',
69
69
  'None',
70
70
  '',
71
- '### Important',
71
+ '### High',
72
72
  '',
73
73
  'None',
74
74
  '',
@@ -76,7 +76,7 @@ async function writeArtifact() {
76
76
  '',
77
77
  'None',
78
78
  '',
79
- '### Minor',
79
+ '### Low',
80
80
  '',
81
81
  'None',
82
82
  '',
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/gate/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/gate/index.ts"],"names":[],"mappings":"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"}
@@ -65,10 +65,18 @@ const VALID_CROSS_PROVIDER_AVOIDS = [
65
65
  ];
66
66
  const VALID_REVIEW_GATE_THRESHOLDS = [
67
67
  'critical',
68
- 'important',
68
+ 'high',
69
69
  'medium',
70
- 'minor',
70
+ 'low',
71
71
  ];
72
+ /**
73
+ * Threshold aliases accepted from configuration written before the severity
74
+ * rename, so an existing `exit_nonzero_on: important` keeps its meaning.
75
+ */
76
+ const LEGACY_REVIEW_GATE_THRESHOLDS = new Map([
77
+ ['important', 'high'],
78
+ ['minor', 'low'],
79
+ ]);
72
80
  const VALID_IDENTITY_PROVENANCES = [
73
81
  'declared',
74
82
  'observed',
@@ -76,7 +84,7 @@ const VALID_IDENTITY_PROVENANCES = [
76
84
  'unknown',
77
85
  ];
78
86
  const REVIEW_GATE_CONTEXT_NOTE = [
79
- 'This review is gate-originated. If you run `oat-project-review-provide`, set `oat_review_invocation: gate` in the review artifact. Write a canonical review artifact with `### Critical`, `### Important`, `### Medium`, and `### Minor` headings in that order, using `None` for empty sections.',
87
+ 'This review is gate-originated. If you run `oat-project-review-provide`, set `oat_review_invocation: gate` in the review artifact. Write a canonical review artifact with `### Critical`, `### High`, `### Medium`, and `### Low` headings in that order, using `None` for empty sections.',
80
88
  'Complete the review, artifact write, and required bookkeeping inline or through a synchronously awaited child before this headless process exits. Do not start background tasks, monitors, or waiters that outlive this turn.',
81
89
  "Artifact hygiene contract: Before finishing or committing, format every file you created or edited. Use the concrete write/fix formatting command supplied by the governing plan, task, or brief. If none is usable, discover the repository's documented write/fix command from applicable `AGENTS.md`/`CLAUDE.md` instructions and relevant package manifests; do not infer or hardcode a formatter. Prefer a file-scoped invocation when supported, and avoid rewriting unrelated files. If no command is discoverable, warn once with `no format command discovered in repo instructions; skipping`, then continue.",
82
90
  ].join('\n\n');
@@ -349,9 +357,10 @@ function parseCrossProviderAvoid(value) {
349
357
  return avoid;
350
358
  }
351
359
  function parseReviewGateThreshold(value) {
352
- const threshold = value?.trim() || 'important';
360
+ const raw = value?.trim() || 'high';
361
+ const threshold = LEGACY_REVIEW_GATE_THRESHOLDS.get(raw) ?? raw;
353
362
  if (!VALID_REVIEW_GATE_THRESHOLDS.includes(threshold)) {
354
- throw new Error('--exit-nonzero-on must be one of critical | important | medium | minor.');
363
+ throw new Error('--exit-nonzero-on must be one of critical | high | medium | low.');
355
364
  }
356
365
  return threshold;
357
366
  }
@@ -1421,10 +1430,10 @@ function reviewBlocksAtThreshold(verdict, threshold) {
1421
1430
  if (threshold === 'critical') {
1422
1431
  return false;
1423
1432
  }
1424
- if (verdict.counts.important > 0) {
1433
+ if (verdict.counts.high > 0) {
1425
1434
  return true;
1426
1435
  }
1427
- if (threshold === 'important') {
1436
+ if (threshold === 'high') {
1428
1437
  return false;
1429
1438
  }
1430
1439
  if (verdict.counts.medium > 0) {
@@ -1433,7 +1442,7 @@ function reviewBlocksAtThreshold(verdict, threshold) {
1433
1442
  if (threshold === 'medium') {
1434
1443
  return false;
1435
1444
  }
1436
- return verdict.counts.minor > 0;
1445
+ return verdict.counts.low > 0;
1437
1446
  }
1438
1447
  function buildReviewGateHandoff(options) {
1439
1448
  const receiveInstruction = `Run oat-project-review-receive for ${options.artifactPath} before treating this gate review as consumed.`;
@@ -1481,7 +1490,7 @@ function writeReviewGateResult(context, payload) {
1481
1490
  if (payload.normalization) {
1482
1491
  context.logger.info(`${payload.normalization.persisted ? 'Artifact normalized' : 'Artifact snapshot normalized in memory; source file unchanged'}: inserted ${payload.normalization.insertedSeverities.map((severity) => severityDisplayName(severity)).join(', ')} empty Findings section(s).`);
1483
1492
  }
1484
- context.logger.info(`Verdict: ${payload.status} (critical=${payload.counts.critical}, important=${payload.counts.important}, medium=${payload.counts.medium}, minor=${payload.counts.minor})`);
1493
+ context.logger.info(`Verdict: ${payload.status} (critical=${payload.counts.critical}, high=${payload.counts.high}, medium=${payload.counts.medium}, low=${payload.counts.low})`);
1485
1494
  if (payload.diversity) {
1486
1495
  if (payload.diversity.warning) {
1487
1496
  context.logger.warn(payload.diversity.warning);
@@ -1893,7 +1902,7 @@ async function emitGateProjectLogPartialFinalization(options) {
1893
1902
  }
1894
1903
  async function finalizeReviewGateProjectLog(context, dependencies, finalization) {
1895
1904
  const findings = finalization.counts
1896
- ? ` findings=critical:${finalization.counts.critical},important:${finalization.counts.important},medium:${finalization.counts.medium},minor:${finalization.counts.minor}`
1905
+ ? ` findings=critical:${finalization.counts.critical},high:${finalization.counts.high},medium:${finalization.counts.medium},low:${finalization.counts.low}`
1897
1906
  : '';
1898
1907
  const artifact = finalization.artifactPath
1899
1908
  ? ` artifact=${finalization.artifactPath}`
@@ -3012,7 +3021,7 @@ export function createGateCommand(overrides = {}) {
3012
3021
  .option('--project <path-or-name>', 'Project path or name to review; defaults to the active project')
3013
3022
  .option('--review-scope <scope>', 'Review scope hint for the provider')
3014
3023
  .option('--review-type <type>', 'Review type hint for the provider')
3015
- .option('--exit-nonzero-on <severity>', 'Lowest severity that exits nonzero: critical, important, medium, or minor')
3024
+ .option('--exit-nonzero-on <severity>', 'Lowest severity that exits nonzero: critical, high, medium, or low')
3016
3025
  .argument('<prompt...>', 'Review prompt arguments appended to the target command')
3017
3026
  .action(async (prompt, options, command) => {
3018
3027
  const context = dependencies.buildCommandContext(readGlobalOptions(command));
@@ -7,9 +7,9 @@ export interface ReviewGateVerdict {
7
7
  gateInvocation?: ReviewArtifactGateInvocation;
8
8
  counts: {
9
9
  critical: number;
10
- important: number;
10
+ high: number;
11
11
  medium: number;
12
- minor: number;
12
+ low: number;
13
13
  };
14
14
  blocking: boolean;
15
15
  normalization?: {
@@ -1 +1 @@
1
- {"version":3,"file":"review-verdict.d.ts","sourceRoot":"","sources":["../../../src/commands/gate/review-verdict.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,iBAAiB;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAAC;IAC5C,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,cAAc,CAAC,EAAE,4BAA4B,CAAC;IAC9C,MAAM,EAAE;QACN,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IACF,QAAQ,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE;QACd,kBAAkB,EAAE,QAAQ,EAAE,CAAC;QAC/B,SAAS,EAAE,OAAO,CAAC;KACpB,CAAC;CACH;AAED,MAAM,WAAW,4BAA4B;IAC3C,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,MAAM,MAAM,QAAQ,GAAG,MAAM,iBAAiB,CAAC,QAAQ,CAAC,CAAC;AAEzD,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,6BAA6B;IAC5C,qCAAqC,CAAC,EAAE,OAAO,CAAC;IAChD,gBAAgB,CAAC,EAAE,0BAA0B,CAAC;CAC/C;AA6LD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAE9D;AAgSD,wBAAsB,sBAAsB,CAC1C,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,6BAAkC,GAC1C,OAAO,CAAC,iBAAiB,CAAC,CAiF5B"}
1
+ {"version":3,"file":"review-verdict.d.ts","sourceRoot":"","sources":["../../../src/commands/gate/review-verdict.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,iBAAiB;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAAC;IAC5C,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,cAAc,CAAC,EAAE,4BAA4B,CAAC;IAC9C,MAAM,EAAE;QACN,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,GAAG,EAAE,MAAM,CAAC;KACb,CAAC;IACF,QAAQ,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE;QACd,kBAAkB,EAAE,QAAQ,EAAE,CAAC;QAC/B,SAAS,EAAE,OAAO,CAAC;KACpB,CAAC;CACH;AAED,MAAM,WAAW,4BAA4B;IAC3C,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,MAAM,MAAM,QAAQ,GAAG,MAAM,iBAAiB,CAAC,QAAQ,CAAC,CAAC;AAEzD,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,6BAA6B;IAC5C,qCAAqC,CAAC,EAAE,OAAO,CAAC;IAChD,gBAAgB,CAAC,EAAE,0BAA0B,CAAC;CAC/C;AAmOD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAE9D;AA2eD,wBAAsB,sBAAsB,CAC1C,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,6BAAkC,GAC1C,OAAO,CAAC,iBAAiB,CAAC,CA8F5B"}
@@ -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) {