@open-agent-toolkit/cli 0.1.35 → 0.1.37

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.
@@ -0,0 +1,180 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { getFrontmatterBlock } from '../shared/frontmatter.js';
3
+ import YAML from 'yaml';
4
+ const SEVERITIES = [
5
+ 'critical',
6
+ 'important',
7
+ 'medium',
8
+ 'minor',
9
+ ];
10
+ const FRONTMATTER_COUNT_KEYS = {
11
+ critical: ['oat_review_critical_count', 'critical'],
12
+ important: ['oat_review_important_count', 'important'],
13
+ medium: ['oat_review_medium_count', 'medium'],
14
+ minor: ['oat_review_minor_count', 'minor'],
15
+ };
16
+ function normalizeReviewType(value) {
17
+ return value === 'code' || value === 'artifact' ? value : 'unknown';
18
+ }
19
+ function stringOrNull(value) {
20
+ return typeof value === 'string' && value.trim() ? value.trim() : null;
21
+ }
22
+ function parseCountValue(value) {
23
+ const numberValue = typeof value === 'number'
24
+ ? value
25
+ : typeof value === 'string'
26
+ ? Number(value.trim())
27
+ : Number.NaN;
28
+ if (!Number.isInteger(numberValue) || numberValue < 0) {
29
+ return null;
30
+ }
31
+ return numberValue;
32
+ }
33
+ function readFrontmatterCounts(frontmatter) {
34
+ const nestedCounts = typeof frontmatter['oat_review_counts'] === 'object' &&
35
+ frontmatter['oat_review_counts'] !== null &&
36
+ !Array.isArray(frontmatter['oat_review_counts'])
37
+ ? frontmatter['oat_review_counts']
38
+ : null;
39
+ const counts = {
40
+ critical: 0,
41
+ important: 0,
42
+ medium: 0,
43
+ minor: 0,
44
+ };
45
+ for (const severity of SEVERITIES) {
46
+ const candidateValues = [
47
+ ...FRONTMATTER_COUNT_KEYS[severity].map((key) => frontmatter[key]),
48
+ ...(nestedCounts
49
+ ? FRONTMATTER_COUNT_KEYS[severity].map((key) => nestedCounts[key])
50
+ : []),
51
+ ].filter((value) => value !== undefined && value !== null);
52
+ if (candidateValues.length === 0) {
53
+ return null;
54
+ }
55
+ const parsedCount = candidateValues.reduce((parsed, value) => {
56
+ if (parsed !== null) {
57
+ return parsed;
58
+ }
59
+ return parseCountValue(value);
60
+ }, null);
61
+ if (parsedCount === null) {
62
+ return null;
63
+ }
64
+ counts[severity] = parsedCount;
65
+ }
66
+ return counts;
67
+ }
68
+ function sectionContentIsEmpty(content) {
69
+ const cleaned = content
70
+ .split('\n')
71
+ .map((line) => line.trim())
72
+ .filter((line) => line.length > 0 && !/^<!--.*-->$/.test(line))
73
+ .join('\n')
74
+ .trim();
75
+ return cleaned.length === 0 || /^none\.?$/i.test(cleaned);
76
+ }
77
+ function countFindingsInSection(content) {
78
+ if (sectionContentIsEmpty(content)) {
79
+ return 0;
80
+ }
81
+ return content
82
+ .split('\n')
83
+ .filter((line) => /^([-*+]\s+\S|\d+\.\s+\S)/.test(line)).length;
84
+ }
85
+ function normalizeHeading(value) {
86
+ const normalized = value.trim().toLowerCase();
87
+ if (SEVERITIES.includes(normalized)) {
88
+ return normalized;
89
+ }
90
+ return null;
91
+ }
92
+ function parseFindingsSummaryCounts(content) {
93
+ const match = content.match(/^Findings:\s*(\d+)\s+critical,\s*(\d+)\s+important,\s*(\d+)\s+medium,\s*(\d+)\s+minor\s*$/im);
94
+ if (!match) {
95
+ return null;
96
+ }
97
+ return {
98
+ critical: Number.parseInt(match[1] ?? '0', 10),
99
+ important: Number.parseInt(match[2] ?? '0', 10),
100
+ medium: Number.parseInt(match[3] ?? '0', 10),
101
+ minor: Number.parseInt(match[4] ?? '0', 10),
102
+ };
103
+ }
104
+ function parseFindingsSectionCounts(content, artifactPath) {
105
+ const headingMatches = [...content.matchAll(/^#{3,6}\s+(.+?)\s*#*\s*$/gm)];
106
+ const severityHeadings = headingMatches
107
+ .map((match) => ({
108
+ severity: normalizeHeading(match[1] ?? ''),
109
+ index: match.index ?? 0,
110
+ headingLength: match[0].length,
111
+ }))
112
+ .filter((heading) => heading.severity !== null);
113
+ if (severityHeadings.length === 0) {
114
+ return null;
115
+ }
116
+ const seenSeverities = new Set(severityHeadings.map((heading) => heading.severity));
117
+ const missingSeverities = SEVERITIES.filter((severity) => !seenSeverities.has(severity));
118
+ if (missingSeverities.length > 0) {
119
+ throw new Error(`Review artifact at ${artifactPath} has an incomplete Findings section; expected headings for Critical, Important, Medium, and Minor. Missing: ${missingSeverities.join(', ')}.`);
120
+ }
121
+ const counts = {
122
+ critical: 0,
123
+ important: 0,
124
+ medium: 0,
125
+ minor: 0,
126
+ };
127
+ for (const [offset, heading] of severityHeadings.entries()) {
128
+ const nextHeading = severityHeadings[offset + 1];
129
+ const sectionStart = heading.index + heading.headingLength;
130
+ const sectionEnd = nextHeading?.index ?? content.length;
131
+ counts[heading.severity] = countFindingsInSection(content.slice(sectionStart, sectionEnd));
132
+ }
133
+ return counts;
134
+ }
135
+ function parseFrontmatterObject(frontmatter, artifactPath) {
136
+ try {
137
+ const parsed = YAML.parse(frontmatter);
138
+ if (typeof parsed === 'object' &&
139
+ parsed !== null &&
140
+ !Array.isArray(parsed)) {
141
+ return parsed;
142
+ }
143
+ throw new Error('frontmatter must be a YAML object');
144
+ }
145
+ catch (error) {
146
+ const detail = error instanceof Error ? error.message : String(error);
147
+ throw new Error(`Unable to parse review artifact frontmatter at ${artifactPath}: ${detail}`, { cause: error });
148
+ }
149
+ }
150
+ function hasBlockingFindings(counts) {
151
+ return counts.critical > 0 || counts.important > 0;
152
+ }
153
+ export async function parseReviewGateVerdict(artifactPath) {
154
+ let content;
155
+ try {
156
+ content = await readFile(artifactPath, 'utf8');
157
+ }
158
+ catch (error) {
159
+ const detail = error instanceof Error ? error.message : String(error);
160
+ throw new Error(`Unable to read review artifact at ${artifactPath}: ${detail}`, { cause: error });
161
+ }
162
+ const frontmatterBlock = getFrontmatterBlock(content);
163
+ const frontmatter = frontmatterBlock
164
+ ? parseFrontmatterObject(frontmatterBlock, artifactPath)
165
+ : {};
166
+ const counts = readFrontmatterCounts(frontmatter) ??
167
+ parseFindingsSummaryCounts(content) ??
168
+ parseFindingsSectionCounts(content, artifactPath);
169
+ if (!counts) {
170
+ throw new Error(`Review artifact at ${artifactPath} does not contain recognizable review findings or explicit verdict counts.`);
171
+ }
172
+ return {
173
+ artifactPath,
174
+ reviewType: normalizeReviewType(frontmatter['oat_review_type']),
175
+ scope: stringOrNull(frontmatter['oat_review_scope']),
176
+ invocation: stringOrNull(frontmatter['oat_review_invocation']),
177
+ counts,
178
+ blocking: hasBlockingFindings(counts),
179
+ };
180
+ }
@@ -45,7 +45,7 @@ export const BUILTIN_EXEC_TARGETS = {
45
45
  },
46
46
  'cursor-default': {
47
47
  runtime: 'cursor',
48
- baseCommand: ['cursor-agent', '-p', '--force'],
48
+ baseCommand: ['cursor-agent', '-p'],
49
49
  hostDetectionCommand: ['sh', '-c', 'test -n "$CURSOR_AGENT"'],
50
50
  availabilityCommand: ['cursor-agent', '--version'],
51
51
  priority: 70,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-agent-toolkit/cli",
3
- "version": "0.1.35",
3
+ "version": "0.1.37",
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.1.35"
37
+ "@open-agent-toolkit/control-plane": "0.1.37"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/node": "^22.10.0",