agentweaver 0.1.7 → 0.1.9
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/Dockerfile.codex +3 -3
- package/README.md +24 -10
- package/dist/artifacts.js +30 -0
- package/dist/executors/configs/fetch-gitlab-diff-config.js +3 -0
- package/dist/executors/configs/opencode-config.js +6 -0
- package/dist/executors/fetch-gitlab-diff-executor.js +26 -0
- package/dist/executors/jira-fetch-executor.js +8 -2
- package/dist/executors/opencode-executor.js +35 -0
- package/dist/gitlab.js +199 -5
- package/dist/index.js +160 -121
- package/dist/interactive-ui.js +45 -10
- package/dist/jira.js +116 -14
- package/dist/pipeline/auto-flow.js +1 -1
- package/dist/pipeline/declarative-flows.js +41 -6
- package/dist/pipeline/flow-catalog.js +66 -0
- package/dist/pipeline/flow-specs/auto.json +183 -1
- package/dist/pipeline/flow-specs/bug-analyze.json +1 -1
- package/dist/pipeline/flow-specs/gitlab-diff-review.json +226 -0
- package/dist/pipeline/flow-specs/gitlab-review.json +1 -31
- package/dist/pipeline/flow-specs/plan-opencode.json +603 -0
- package/dist/pipeline/flow-specs/plan.json +183 -1
- package/dist/pipeline/flow-specs/run-go-linter-loop.json +83 -7
- package/dist/pipeline/flow-specs/run-go-tests-loop.json +83 -7
- package/dist/pipeline/flow-specs/task-describe.json +1 -1
- package/dist/pipeline/node-registry.js +80 -8
- package/dist/pipeline/nodes/fetch-gitlab-diff-node.js +34 -0
- package/dist/pipeline/nodes/flow-run-node.js +2 -2
- package/dist/pipeline/nodes/jira-fetch-node.js +26 -2
- package/dist/pipeline/nodes/local-script-check-node.js +50 -8
- package/dist/pipeline/nodes/opencode-prompt-node.js +32 -0
- package/dist/pipeline/nodes/planning-questions-form-node.js +69 -0
- package/dist/pipeline/nodes/user-input-node.js +9 -1
- package/dist/pipeline/prompt-registry.js +4 -1
- package/dist/pipeline/registry.js +10 -0
- package/dist/pipeline/spec-loader.js +37 -3
- package/dist/pipeline/spec-types.js +43 -1
- package/dist/pipeline/spec-validator.js +53 -7
- package/dist/pipeline/value-resolver.js +25 -1
- package/dist/prompts.js +54 -14
- package/dist/scope.js +25 -16
- package/dist/structured-artifact-schemas.json +560 -0
- package/dist/structured-artifacts.js +103 -262
- package/dist/user-input.js +7 -0
- package/docker-compose.yml +2 -2
- package/package.json +3 -3
- package/run_go_linter.py +128 -0
- package/run_go_tests.py +120 -0
- package/verify_build.sh +3 -3
- package/run_go_linter.sh +0 -89
- package/run_go_tests.sh +0 -83
|
@@ -1,284 +1,121 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
2
4
|
import { TaskRunnerError } from "./errors.js";
|
|
5
|
+
export const STRUCTURED_ARTIFACT_SCHEMA_IDS = [
|
|
6
|
+
"bug-analysis/v1",
|
|
7
|
+
"bug-fix-design/v1",
|
|
8
|
+
"bug-fix-plan/v1",
|
|
9
|
+
"gitlab-mr-diff/v1",
|
|
10
|
+
"gitlab-review/v1",
|
|
11
|
+
"implementation-design/v1",
|
|
12
|
+
"implementation-plan/v1",
|
|
13
|
+
"jira-description/v1",
|
|
14
|
+
"mr-description/v1",
|
|
15
|
+
"planning-questions/v1",
|
|
16
|
+
"qa-plan/v1",
|
|
17
|
+
"review-findings/v1",
|
|
18
|
+
"review-fix-report/v1",
|
|
19
|
+
"review-reply/v1",
|
|
20
|
+
"task-summary/v1",
|
|
21
|
+
"user-input/v1",
|
|
22
|
+
];
|
|
23
|
+
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
24
|
+
const SCHEMA_REGISTRY_PATH = path.join(MODULE_DIR, "structured-artifact-schemas.json");
|
|
3
25
|
function isRecord(value) {
|
|
4
26
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5
27
|
}
|
|
6
|
-
function
|
|
7
|
-
if (
|
|
8
|
-
|
|
28
|
+
function schemaLabel(node) {
|
|
29
|
+
if ("anyOf" in node) {
|
|
30
|
+
return "a valid value";
|
|
9
31
|
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
function expectStringArray(value, path, issues, allowEmpty = false) {
|
|
24
|
-
if (!Array.isArray(value)) {
|
|
25
|
-
issues.push(`${path} must be an array`);
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
if (!allowEmpty && value.length === 0) {
|
|
29
|
-
issues.push(`${path} must not be empty`);
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
value.forEach((item, index) => expectNonEmptyString(item, `${path}[${index}]`, issues));
|
|
33
|
-
}
|
|
34
|
-
function expectObjectArray(value, path, issues, validateItem, allowEmpty = false) {
|
|
35
|
-
if (!Array.isArray(value)) {
|
|
36
|
-
issues.push(`${path} must be an array`);
|
|
37
|
-
return;
|
|
32
|
+
switch (node.type) {
|
|
33
|
+
case "string":
|
|
34
|
+
return node.nonEmpty ? "a non-empty string" : "a string";
|
|
35
|
+
case "boolean":
|
|
36
|
+
return "a boolean";
|
|
37
|
+
case "number":
|
|
38
|
+
return "a number";
|
|
39
|
+
case "null":
|
|
40
|
+
return "null";
|
|
41
|
+
case "array":
|
|
42
|
+
return "an array";
|
|
43
|
+
case "object":
|
|
44
|
+
return "an object";
|
|
38
45
|
}
|
|
39
|
-
if (!allowEmpty && value.length === 0) {
|
|
40
|
-
issues.push(`${path} must not be empty`);
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
value.forEach((item, index) => {
|
|
44
|
-
const itemPath = `${path}[${index}]`;
|
|
45
|
-
if (!expectObject(item, itemPath, issues)) {
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
validateItem(item, itemPath, issues);
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
function validateBriefText(value, path, issues) {
|
|
52
|
-
if (!expectObject(value, path, issues)) {
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
expectNonEmptyString(value.summary, `${path}.summary`, issues);
|
|
56
46
|
}
|
|
57
|
-
function
|
|
58
|
-
if (
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
id: "implementation-design/v1",
|
|
65
|
-
validate({ path, value }) {
|
|
66
|
-
const issues = [];
|
|
67
|
-
if (!expectObject(value, path, issues)) {
|
|
68
|
-
return issues;
|
|
47
|
+
function validateNode(value, schema, currentPath) {
|
|
48
|
+
if ("anyOf" in schema) {
|
|
49
|
+
let bestIssues = null;
|
|
50
|
+
for (const option of schema.anyOf) {
|
|
51
|
+
const issues = validateNode(value, option, currentPath);
|
|
52
|
+
if (issues.length === 0) {
|
|
53
|
+
return [];
|
|
69
54
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
expectStringArray(value.non_goals, `${path}.non_goals`, issues, true);
|
|
73
|
-
expectStringArray(value.components, `${path}.components`, issues);
|
|
74
|
-
expectObjectArray(value.decisions, `${path}.decisions`, issues, (item, itemPath, currentIssues) => {
|
|
75
|
-
expectNonEmptyString(item.component, `${itemPath}.component`, currentIssues);
|
|
76
|
-
expectNonEmptyString(item.decision, `${itemPath}.decision`, currentIssues);
|
|
77
|
-
expectNonEmptyString(item.rationale, `${itemPath}.rationale`, currentIssues);
|
|
78
|
-
});
|
|
79
|
-
expectStringArray(value.risks, `${path}.risks`, issues, true);
|
|
80
|
-
expectStringArray(value.open_questions, `${path}.open_questions`, issues, true);
|
|
81
|
-
return issues;
|
|
82
|
-
},
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
function implementationPlanSchema() {
|
|
86
|
-
return {
|
|
87
|
-
id: "implementation-plan/v1",
|
|
88
|
-
validate({ path, value }) {
|
|
89
|
-
const issues = [];
|
|
90
|
-
if (!expectObject(value, path, issues)) {
|
|
91
|
-
return issues;
|
|
92
|
-
}
|
|
93
|
-
expectNonEmptyString(value.summary, `${path}.summary`, issues);
|
|
94
|
-
expectStringArray(value.prerequisites, `${path}.prerequisites`, issues, true);
|
|
95
|
-
expectObjectArray(value.implementation_steps, `${path}.implementation_steps`, issues, (item, itemPath, currentIssues) => {
|
|
96
|
-
expectNonEmptyString(item.id, `${itemPath}.id`, currentIssues);
|
|
97
|
-
expectNonEmptyString(item.title, `${itemPath}.title`, currentIssues);
|
|
98
|
-
expectNonEmptyString(item.details, `${itemPath}.details`, currentIssues);
|
|
99
|
-
});
|
|
100
|
-
expectStringArray(value.tests, `${path}.tests`, issues);
|
|
101
|
-
expectStringArray(value.rollout_notes, `${path}.rollout_notes`, issues, true);
|
|
102
|
-
return issues;
|
|
103
|
-
},
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
function qaPlanSchema() {
|
|
107
|
-
return {
|
|
108
|
-
id: "qa-plan/v1",
|
|
109
|
-
validate({ path, value }) {
|
|
110
|
-
const issues = [];
|
|
111
|
-
if (!expectObject(value, path, issues)) {
|
|
112
|
-
return issues;
|
|
55
|
+
if (bestIssues === null || issues.length < bestIssues.length) {
|
|
56
|
+
bestIssues = issues;
|
|
113
57
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
return issues;
|
|
122
|
-
},
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
function bugAnalysisSchema() {
|
|
126
|
-
return {
|
|
127
|
-
id: "bug-analysis/v1",
|
|
128
|
-
validate({ path, value }) {
|
|
129
|
-
const issues = [];
|
|
130
|
-
if (!expectObject(value, path, issues)) {
|
|
131
|
-
return issues;
|
|
132
|
-
}
|
|
133
|
-
expectNonEmptyString(value.summary, `${path}.summary`, issues);
|
|
134
|
-
if (expectObject(value.suspected_root_cause, `${path}.suspected_root_cause`, issues)) {
|
|
135
|
-
expectNonEmptyString(value.suspected_root_cause.hypothesis, `${path}.suspected_root_cause.hypothesis`, issues);
|
|
136
|
-
expectNonEmptyString(value.suspected_root_cause.confidence, `${path}.suspected_root_cause.confidence`, issues);
|
|
58
|
+
}
|
|
59
|
+
return bestIssues ?? [`${currentPath} must be ${schemaLabel(schema)}`];
|
|
60
|
+
}
|
|
61
|
+
switch (schema.type) {
|
|
62
|
+
case "string":
|
|
63
|
+
if (typeof value !== "string" || (schema.nonEmpty && value.trim().length === 0)) {
|
|
64
|
+
return [`${currentPath} must be ${schemaLabel(schema)}`];
|
|
137
65
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
return {
|
|
149
|
-
id: "bug-fix-design/v1",
|
|
150
|
-
validate: implementationDesignSchema().validate,
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
function bugFixPlanSchema() {
|
|
154
|
-
return {
|
|
155
|
-
id: "bug-fix-plan/v1",
|
|
156
|
-
validate: implementationPlanSchema().validate,
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
function reviewFindingsSchema() {
|
|
160
|
-
return {
|
|
161
|
-
id: "review-findings/v1",
|
|
162
|
-
validate({ path, value }) {
|
|
163
|
-
const issues = [];
|
|
164
|
-
if (!expectObject(value, path, issues)) {
|
|
165
|
-
return issues;
|
|
66
|
+
return [];
|
|
67
|
+
case "boolean":
|
|
68
|
+
return typeof value === "boolean" ? [] : [`${currentPath} must be a boolean`];
|
|
69
|
+
case "number":
|
|
70
|
+
return typeof value === "number" && !Number.isNaN(value) ? [] : [`${currentPath} must be a number`];
|
|
71
|
+
case "null":
|
|
72
|
+
return value === null ? [] : [`${currentPath} must be null`];
|
|
73
|
+
case "array": {
|
|
74
|
+
if (!Array.isArray(value)) {
|
|
75
|
+
return [`${currentPath} must be an array`];
|
|
166
76
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
expectObjectArray(value.findings, `${path}.findings`, issues, (item, itemPath, currentIssues) => {
|
|
170
|
-
expectNonEmptyString(item.severity, `${itemPath}.severity`, currentIssues);
|
|
171
|
-
expectNonEmptyString(item.title, `${itemPath}.title`, currentIssues);
|
|
172
|
-
expectNonEmptyString(item.description, `${itemPath}.description`, currentIssues);
|
|
173
|
-
}, true);
|
|
174
|
-
return issues;
|
|
175
|
-
},
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
function reviewReplySchema() {
|
|
179
|
-
return {
|
|
180
|
-
id: "review-reply/v1",
|
|
181
|
-
validate({ path, value }) {
|
|
182
|
-
const issues = [];
|
|
183
|
-
if (!expectObject(value, path, issues)) {
|
|
184
|
-
return issues;
|
|
77
|
+
if (schema.minItems !== undefined && value.length < schema.minItems) {
|
|
78
|
+
return [schema.minItems === 1 ? `${currentPath} must not be empty` : `${currentPath} must contain at least ${schema.minItems} items`];
|
|
185
79
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}, true);
|
|
192
|
-
expectBoolean(value.ready_to_merge, `${path}.ready_to_merge`, issues);
|
|
193
|
-
return issues;
|
|
194
|
-
},
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
function reviewFixReportSchema() {
|
|
198
|
-
return {
|
|
199
|
-
id: "review-fix-report/v1",
|
|
200
|
-
validate({ path, value }) {
|
|
201
|
-
const issues = [];
|
|
202
|
-
if (!expectObject(value, path, issues)) {
|
|
203
|
-
return issues;
|
|
80
|
+
return value.flatMap((item, index) => validateNode(item, schema.items, `${currentPath}[${index}]`));
|
|
81
|
+
}
|
|
82
|
+
case "object": {
|
|
83
|
+
if (!isRecord(value)) {
|
|
84
|
+
return [`${currentPath} must be an object`];
|
|
204
85
|
}
|
|
205
|
-
expectNonEmptyString(value.summary, `${path}.summary`, issues);
|
|
206
|
-
expectStringArray(value.completed_actions, `${path}.completed_actions`, issues);
|
|
207
|
-
expectStringArray(value.validation_steps, `${path}.validation_steps`, issues, true);
|
|
208
|
-
return issues;
|
|
209
|
-
},
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
function gitlabReviewSchema() {
|
|
213
|
-
return {
|
|
214
|
-
id: "gitlab-review/v1",
|
|
215
|
-
validate({ path, value }) {
|
|
216
86
|
const issues = [];
|
|
217
|
-
|
|
218
|
-
|
|
87
|
+
const properties = schema.properties ?? {};
|
|
88
|
+
const required = new Set(schema.required ?? []);
|
|
89
|
+
for (const propertyName of required) {
|
|
90
|
+
issues.push(...validateNode(value[propertyName], properties[propertyName] ?? { type: "object" }, `${currentPath}.${propertyName}`));
|
|
219
91
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
expectNumber(value.merge_request_iid, `${path}.merge_request_iid`, issues);
|
|
224
|
-
expectNonEmptyString(value.fetched_at, `${path}.fetched_at`, issues);
|
|
225
|
-
expectObjectArray(value.comments, `${path}.comments`, issues, (item, itemPath, currentIssues) => {
|
|
226
|
-
expectNonEmptyString(item.id, `${itemPath}.id`, currentIssues);
|
|
227
|
-
expectNonEmptyString(item.discussion_id, `${itemPath}.discussion_id`, currentIssues);
|
|
228
|
-
expectNonEmptyString(item.body, `${itemPath}.body`, currentIssues);
|
|
229
|
-
expectNonEmptyString(item.author, `${itemPath}.author`, currentIssues);
|
|
230
|
-
expectNonEmptyString(item.created_at, `${itemPath}.created_at`, currentIssues);
|
|
231
|
-
if (item.file_path !== undefined && item.file_path !== null) {
|
|
232
|
-
expectNonEmptyString(item.file_path, `${itemPath}.file_path`, currentIssues);
|
|
92
|
+
for (const [propertyName, propertySchema] of Object.entries(properties)) {
|
|
93
|
+
if (required.has(propertyName) || !(propertyName in value)) {
|
|
94
|
+
continue;
|
|
233
95
|
}
|
|
234
|
-
|
|
235
|
-
return issues;
|
|
236
|
-
},
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
function userInputSchema() {
|
|
240
|
-
return {
|
|
241
|
-
id: "user-input/v1",
|
|
242
|
-
validate({ path, value }) {
|
|
243
|
-
const issues = [];
|
|
244
|
-
if (!expectObject(value, path, issues)) {
|
|
245
|
-
return issues;
|
|
96
|
+
issues.push(...validateNode(value[propertyName], propertySchema, `${currentPath}.${propertyName}`));
|
|
246
97
|
}
|
|
247
|
-
expectNonEmptyString(value.form_id, `${path}.form_id`, issues);
|
|
248
|
-
expectNonEmptyString(value.submitted_at, `${path}.submitted_at`, issues);
|
|
249
|
-
expectObject(value.values, `${path}.values`, issues);
|
|
250
98
|
return issues;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
253
101
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
"qa-plan/v1": qaPlanSchema(),
|
|
272
|
-
"review-findings/v1": reviewFindingsSchema(),
|
|
273
|
-
"review-fix-report/v1": reviewFixReportSchema(),
|
|
274
|
-
"review-reply/v1": reviewReplySchema(),
|
|
275
|
-
"task-summary/v1": { id: "task-summary/v1", validate: ({ path, value }) => {
|
|
276
|
-
const issues = [];
|
|
277
|
-
validateBriefText(value, path, issues);
|
|
278
|
-
return issues;
|
|
279
|
-
} },
|
|
280
|
-
"user-input/v1": userInputSchema(),
|
|
281
|
-
};
|
|
102
|
+
function loadSchemaRegistry() {
|
|
103
|
+
if (!existsSync(SCHEMA_REGISTRY_PATH)) {
|
|
104
|
+
throw new TaskRunnerError(`Structured artifact schema registry not found: ${SCHEMA_REGISTRY_PATH}`);
|
|
105
|
+
}
|
|
106
|
+
let parsed;
|
|
107
|
+
try {
|
|
108
|
+
parsed = JSON.parse(readFileSync(SCHEMA_REGISTRY_PATH, "utf8"));
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
throw new TaskRunnerError(`Failed to parse structured artifact schema registry: ${error.message}`);
|
|
112
|
+
}
|
|
113
|
+
if (!isRecord(parsed)) {
|
|
114
|
+
throw new TaskRunnerError(`Structured artifact schema registry ${SCHEMA_REGISTRY_PATH} must be a JSON object.`);
|
|
115
|
+
}
|
|
116
|
+
return parsed;
|
|
117
|
+
}
|
|
118
|
+
const schemas = loadSchemaRegistry();
|
|
282
119
|
export function validateStructuredArtifact(path, schemaId) {
|
|
283
120
|
if (!existsSync(path)) {
|
|
284
121
|
throw new TaskRunnerError(`Structured artifact file not found: ${path}`);
|
|
@@ -290,7 +127,11 @@ export function validateStructuredArtifact(path, schemaId) {
|
|
|
290
127
|
catch (error) {
|
|
291
128
|
throw new TaskRunnerError(`Structured artifact ${path} is not valid JSON: ${error.message}`);
|
|
292
129
|
}
|
|
293
|
-
const
|
|
130
|
+
const schema = schemas[schemaId];
|
|
131
|
+
if (!schema) {
|
|
132
|
+
throw new TaskRunnerError(`Structured artifact schema is not registered: ${schemaId}`);
|
|
133
|
+
}
|
|
134
|
+
const issues = validateNode(parsed, schema, path);
|
|
294
135
|
if (issues.length > 0) {
|
|
295
136
|
throw new TaskRunnerError(`Structured artifact ${path} failed schema ${schemaId} validation:\n${issues.join("\n")}`);
|
|
296
137
|
}
|
package/dist/user-input.js
CHANGED
|
@@ -83,6 +83,13 @@ function parseBoolean(value) {
|
|
|
83
83
|
return null;
|
|
84
84
|
}
|
|
85
85
|
export async function requestUserInputInTerminal(form) {
|
|
86
|
+
if (form.fields.length === 0) {
|
|
87
|
+
return {
|
|
88
|
+
formId: form.formId,
|
|
89
|
+
submittedAt: new Date().toISOString(),
|
|
90
|
+
values: {},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
86
93
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
87
94
|
throw new TaskRunnerError(`Flow requires interactive user input for form '${form.formId}', but no TTY is available.`);
|
|
88
95
|
}
|
package/docker-compose.yml
CHANGED
|
@@ -260,7 +260,7 @@ services:
|
|
|
260
260
|
read_only: true
|
|
261
261
|
bind:
|
|
262
262
|
create_host_path: false
|
|
263
|
-
entrypoint: ["/usr/local/bin/run_go_tests.
|
|
263
|
+
entrypoint: ["/usr/local/bin/run_go_tests.py"]
|
|
264
264
|
cap_drop:
|
|
265
265
|
- ALL
|
|
266
266
|
security_opt:
|
|
@@ -321,7 +321,7 @@ services:
|
|
|
321
321
|
read_only: true
|
|
322
322
|
bind:
|
|
323
323
|
create_host_path: false
|
|
324
|
-
entrypoint: ["/usr/local/bin/run_go_linter.
|
|
324
|
+
entrypoint: ["/usr/local/bin/run_go_linter.py"]
|
|
325
325
|
cap_drop:
|
|
326
326
|
- ALL
|
|
327
327
|
security_opt:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentweaver",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "CLI orchestrator for Jira/Codex/Claude engineering workflows",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"docker-compose.yml",
|
|
33
33
|
"Dockerfile.codex",
|
|
34
34
|
"verify_build.sh",
|
|
35
|
-
"run_go_tests.
|
|
36
|
-
"run_go_linter.
|
|
35
|
+
"run_go_tests.py",
|
|
36
|
+
"run_go_linter.py",
|
|
37
37
|
"run_go_coverage.sh"
|
|
38
38
|
],
|
|
39
39
|
"publishConfig": {
|
package/run_go_linter.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
ROOT_DIR = Path(os.environ.get("VERIFY_BUILD_ROOT_DIR") or os.getcwd()).resolve()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def log(message: str) -> None:
|
|
15
|
+
print(message, file=sys.stderr)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def strip_ansi(value: str) -> str:
|
|
19
|
+
import re
|
|
20
|
+
|
|
21
|
+
return re.sub(r"\x1b\[[0-9;]*m", "", value)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def emit_result(
|
|
25
|
+
ok: bool,
|
|
26
|
+
kind: str,
|
|
27
|
+
stage: str,
|
|
28
|
+
exit_code: int,
|
|
29
|
+
summary: str,
|
|
30
|
+
command: str,
|
|
31
|
+
details: dict[str, object] | None = None,
|
|
32
|
+
) -> None:
|
|
33
|
+
payload = {
|
|
34
|
+
"ok": ok,
|
|
35
|
+
"kind": kind,
|
|
36
|
+
"stage": stage,
|
|
37
|
+
"exitCode": exit_code,
|
|
38
|
+
"summary": summary,
|
|
39
|
+
"command": command,
|
|
40
|
+
"details": details or {},
|
|
41
|
+
}
|
|
42
|
+
print(json.dumps(payload, ensure_ascii=False))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def fail(exit_code: int, summary: str, command: str, details: dict[str, object] | None = None) -> "Never":
|
|
46
|
+
emit_result(False, "linter", "run_go_linter", exit_code, summary, command, details)
|
|
47
|
+
raise SystemExit(exit_code)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def require_cmd(command: str) -> None:
|
|
51
|
+
if shutil.which(command):
|
|
52
|
+
return
|
|
53
|
+
fail(
|
|
54
|
+
2,
|
|
55
|
+
f"Missing required command: {command}",
|
|
56
|
+
command,
|
|
57
|
+
{"failedStep": "require_cmd", "missingCommand": command},
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def collect_issues(output: str) -> list[str]:
|
|
62
|
+
issues: list[str] = []
|
|
63
|
+
for raw_line in output.splitlines():
|
|
64
|
+
cleaned = strip_ansi(raw_line).rstrip("\r").strip()
|
|
65
|
+
if not cleaned or cleaned.startswith("==>"):
|
|
66
|
+
continue
|
|
67
|
+
issues.append(cleaned)
|
|
68
|
+
return issues
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def run_command(argv: list[str]) -> tuple[int, str]:
|
|
72
|
+
completed = subprocess.run(
|
|
73
|
+
argv,
|
|
74
|
+
cwd=ROOT_DIR,
|
|
75
|
+
stdout=subprocess.PIPE,
|
|
76
|
+
stderr=subprocess.STDOUT,
|
|
77
|
+
text=True,
|
|
78
|
+
errors="replace",
|
|
79
|
+
check=False,
|
|
80
|
+
)
|
|
81
|
+
output = completed.stdout or ""
|
|
82
|
+
if output:
|
|
83
|
+
print(output, end="" if output.endswith("\n") else "\n", file=sys.stderr)
|
|
84
|
+
return completed.returncode, output
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def run_step(label: str, argv: list[str], failed_step: str, summary: str) -> None:
|
|
88
|
+
log(label)
|
|
89
|
+
exit_code, output = run_command(argv)
|
|
90
|
+
if exit_code == 0:
|
|
91
|
+
return
|
|
92
|
+
|
|
93
|
+
fail(
|
|
94
|
+
exit_code,
|
|
95
|
+
summary,
|
|
96
|
+
" ".join(argv),
|
|
97
|
+
{
|
|
98
|
+
"failedStep": failed_step,
|
|
99
|
+
"tool": argv[0],
|
|
100
|
+
"raw": output,
|
|
101
|
+
"issues": collect_issues(output),
|
|
102
|
+
},
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def main() -> int:
|
|
107
|
+
require_cmd("go")
|
|
108
|
+
require_cmd("golangci-lint")
|
|
109
|
+
|
|
110
|
+
os.chdir(ROOT_DIR)
|
|
111
|
+
|
|
112
|
+
run_step("==> Generating code (go generate ./...)", ["go", "generate", "./..."], "go-generate", "go generate failed")
|
|
113
|
+
run_step("==> Running linter (golangci-lint run)", ["golangci-lint", "run"], "golangci-lint", "golangci-lint failed")
|
|
114
|
+
|
|
115
|
+
emit_result(
|
|
116
|
+
True,
|
|
117
|
+
"linter",
|
|
118
|
+
"run_go_linter",
|
|
119
|
+
0,
|
|
120
|
+
"Linter checks passed",
|
|
121
|
+
"go generate ./... && golangci-lint run",
|
|
122
|
+
{"steps": ["go-generate", "golangci-lint"]},
|
|
123
|
+
)
|
|
124
|
+
return 0
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
if __name__ == "__main__":
|
|
128
|
+
raise SystemExit(main())
|