@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.
- package/assets/agents/oat-reviewer.md +22 -2
- package/assets/docs/cli-utilities/config-and-local-state.md +14 -5
- package/assets/docs/cli-utilities/configuration.md +1 -1
- package/assets/docs/cli-utilities/workflow-gates.md +123 -22
- package/assets/docs/contributing/skills.md +7 -3
- package/assets/docs/reference/cli-reference.md +4 -3
- package/assets/public-package-versions.json +4 -4
- package/assets/skills/oat-project-implement/SKILL.md +7 -3
- package/assets/skills/oat-project-import-plan/SKILL.md +53 -9
- package/assets/skills/oat-project-plan/SKILL.md +7 -3
- package/assets/skills/oat-project-quick-start/SKILL.md +37 -9
- package/assets/skills/oat-project-review-provide/SKILL.md +12 -4
- package/assets/skills/oat-project-review-receive/SKILL.md +3 -1
- package/dist/commands/gate/index.d.ts.map +1 -1
- package/dist/commands/gate/index.js +358 -16
- package/dist/commands/gate/review-verdict.d.ts +15 -0
- package/dist/commands/gate/review-verdict.d.ts.map +1 -0
- package/dist/commands/gate/review-verdict.js +180 -0
- package/dist/config/oat-config.js +1 -1
- package/package.json +2 -2
|
@@ -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'
|
|
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.
|
|
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.
|
|
37
|
+
"@open-agent-toolkit/control-plane": "0.1.37"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/node": "^22.10.0",
|