agentweaver 0.1.15 → 0.1.17
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/README.md +76 -19
- package/dist/artifact-manifest.js +219 -0
- package/dist/artifacts.js +88 -3
- package/dist/doctor/checks/env-diagnostics.js +25 -0
- package/dist/doctor/checks/executors.js +2 -2
- package/dist/doctor/checks/flow-readiness.js +15 -18
- package/dist/flow-state.js +212 -15
- package/dist/index.js +539 -209
- package/dist/interactive/blessed-session.js +361 -0
- package/dist/interactive/controller.js +1326 -0
- package/dist/interactive/create-interactive-session.js +5 -0
- package/dist/interactive/ink/index.js +597 -0
- package/dist/interactive/progress.js +245 -0
- package/dist/interactive/selectors.js +14 -0
- package/dist/interactive/session.js +1 -0
- package/dist/interactive/state.js +34 -0
- package/dist/interactive/tree.js +155 -0
- package/dist/interactive/types.js +1 -0
- package/dist/interactive/view-model.js +1 -0
- package/dist/interactive-ui.js +159 -194
- package/dist/pipeline/auto-flow.js +9 -6
- package/dist/pipeline/context.js +7 -5
- package/dist/pipeline/declarative-flow-runner.js +212 -6
- package/dist/pipeline/declarative-flows.js +63 -17
- package/dist/pipeline/execution-routing-config.js +15 -0
- package/dist/pipeline/flow-catalog.js +50 -12
- package/dist/pipeline/flow-run-resume.js +29 -0
- package/dist/pipeline/flow-specs/auto-common.json +90 -360
- package/dist/pipeline/flow-specs/auto-golang.json +81 -360
- package/dist/pipeline/flow-specs/auto-simple.json +141 -0
- package/dist/pipeline/flow-specs/bugz/bug-analyze.json +2 -0
- package/dist/pipeline/flow-specs/bugz/bug-fix.json +1 -0
- package/dist/pipeline/flow-specs/design-review/design-review-loop.json +316 -0
- package/dist/pipeline/flow-specs/design-review.json +10 -0
- package/dist/pipeline/flow-specs/gitlab/gitlab-diff-review.json +11 -0
- package/dist/pipeline/flow-specs/gitlab/gitlab-review.json +2 -0
- package/dist/pipeline/flow-specs/gitlab/mr-description.json +1 -0
- package/dist/pipeline/flow-specs/go/run-go-linter-loop.json +2 -0
- package/dist/pipeline/flow-specs/go/run-go-tests-loop.json +2 -0
- package/dist/pipeline/flow-specs/implement.json +13 -6
- package/dist/pipeline/flow-specs/instant-task.json +177 -0
- package/dist/pipeline/flow-specs/normalize-task-source.json +311 -0
- package/dist/pipeline/flow-specs/plan-revise.json +7 -1
- package/dist/pipeline/flow-specs/plan.json +51 -71
- package/dist/pipeline/flow-specs/review/review-fix.json +24 -4
- package/dist/pipeline/flow-specs/review/review-loop.json +351 -45
- package/dist/pipeline/flow-specs/review/review-project-loop.json +590 -0
- package/dist/pipeline/flow-specs/review/review-project.json +12 -0
- package/dist/pipeline/flow-specs/review/review.json +37 -31
- package/dist/pipeline/flow-specs/task-describe.json +2 -0
- package/dist/pipeline/flow-specs/task-source/jira-fetch.json +70 -0
- package/dist/pipeline/flow-specs/task-source/manual-input.json +216 -0
- package/dist/pipeline/launch-profile-config.js +30 -18
- package/dist/pipeline/node-contract.js +1 -0
- package/dist/pipeline/node-registry.js +115 -6
- package/dist/pipeline/node-runner.js +3 -2
- package/dist/pipeline/nodes/build-review-fix-prompt-node.js +5 -1
- package/dist/pipeline/nodes/clear-ready-to-merge-node.js +11 -0
- package/dist/pipeline/nodes/commit-message-form-node.js +8 -0
- package/dist/pipeline/nodes/design-review-verdict-node.js +36 -0
- package/dist/pipeline/nodes/ensure-summary-json-node.js +13 -2
- package/dist/pipeline/nodes/fetch-gitlab-diff-node.js +19 -2
- package/dist/pipeline/nodes/fetch-gitlab-review-node.js +19 -2
- package/dist/pipeline/nodes/flow-run-node.js +242 -8
- package/dist/pipeline/nodes/git-commit-form-node.js +8 -0
- package/dist/pipeline/nodes/gitlab-review-artifacts-node.js +19 -2
- package/dist/pipeline/nodes/jira-fetch-node.js +50 -4
- package/dist/pipeline/nodes/llm-prompt-node.js +38 -36
- package/dist/pipeline/nodes/planning-bundle-node.js +10 -0
- package/dist/pipeline/nodes/review-verdict-node.js +86 -0
- package/dist/pipeline/nodes/select-files-form-node.js +8 -0
- package/dist/pipeline/nodes/structured-summary-node.js +24 -0
- package/dist/pipeline/nodes/user-input-node.js +38 -3
- package/dist/pipeline/nodes/write-selection-file-node.js +20 -4
- package/dist/pipeline/plugin-loader.js +389 -0
- package/dist/pipeline/plugin-types.js +1 -0
- package/dist/pipeline/prompt-registry.js +3 -1
- package/dist/pipeline/prompt-runtime.js +4 -1
- package/dist/pipeline/registry.js +71 -4
- package/dist/pipeline/review-iteration.js +26 -0
- package/dist/pipeline/spec-compiler.js +3 -0
- package/dist/pipeline/spec-loader.js +14 -0
- package/dist/pipeline/spec-types.js +3 -0
- package/dist/pipeline/spec-validator.js +20 -0
- package/dist/pipeline/value-resolver.js +76 -2
- package/dist/plugin-sdk.js +1 -0
- package/dist/prompts.js +36 -14
- package/dist/review-severity.js +45 -0
- package/dist/runtime/artifact-registry.js +405 -0
- package/dist/runtime/design-review-input-contract.js +17 -16
- package/dist/runtime/env-loader.js +3 -0
- package/dist/runtime/execution-routing-store.js +134 -0
- package/dist/runtime/execution-routing.js +233 -0
- package/dist/runtime/interactive-execution-routing.js +471 -0
- package/dist/runtime/plan-revise-input-contract.js +35 -32
- package/dist/runtime/planning-bundle.js +123 -0
- package/dist/runtime/ready-to-merge.js +22 -1
- package/dist/runtime/review-input-contract.js +100 -0
- package/dist/structured-artifact-schema-registry.js +9 -0
- package/dist/structured-artifact-schemas.json +140 -1
- package/dist/structured-artifacts.js +77 -6
- package/dist/user-input.js +70 -3
- package/docs/example/.flows/examples/claude-example.json +50 -0
- package/docs/example/.plugins/claude-example-plugin/index.js +149 -0
- package/docs/example/.plugins/claude-example-plugin/plugin.json +8 -0
- package/docs/examples/.flows/claude-example.json +50 -0
- package/docs/examples/.plugins/claude-example-plugin/index.js +149 -0
- package/docs/examples/.plugins/claude-example-plugin/plugin.json +8 -0
- package/docs/plugin-sdk.md +731 -0
- package/package.json +11 -4
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { buildArtifactId, buildLogicalKeyForPayload, buildPublicationKey, createProducerSummary, diagnosticsForManifest, inferPayloadContract, parseArtifactReference, validateArtifactManifest, } from "../artifact-manifest.js";
|
|
5
|
+
import { artifactIndexFile, artifactManifestSidecarPath, ensureScopeWorkspaceDir, scopeArtifactsDir, scopeWorkspaceDir, } from "../artifacts.js";
|
|
6
|
+
import { TaskRunnerError } from "../errors.js";
|
|
7
|
+
import { isArtifactPayloadSchemaId, validateArtifactPayload, } from "../structured-artifacts.js";
|
|
8
|
+
function nowIso8601() {
|
|
9
|
+
return new Date().toISOString();
|
|
10
|
+
}
|
|
11
|
+
function historyDir(scopeKey) {
|
|
12
|
+
return path.join(scopeArtifactsDir(scopeKey), "manifest-history");
|
|
13
|
+
}
|
|
14
|
+
function historyManifestPath(scopeKey, artifactId) {
|
|
15
|
+
return path.join(historyDir(scopeKey), `${encodeURIComponent(artifactId)}.manifest.json`);
|
|
16
|
+
}
|
|
17
|
+
function scopeKeyFromPayloadPath(payloadPath) {
|
|
18
|
+
const scopeMarker = `${path.sep}.agentweaver${path.sep}scopes${path.sep}`;
|
|
19
|
+
const markerIndex = payloadPath.lastIndexOf(scopeMarker);
|
|
20
|
+
if (markerIndex < 0) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const scopePart = payloadPath.slice(markerIndex + scopeMarker.length);
|
|
24
|
+
return scopePart.split(path.sep)[0] || null;
|
|
25
|
+
}
|
|
26
|
+
function writeJsonAtomic(filePath, value) {
|
|
27
|
+
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
28
|
+
const tempPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
29
|
+
writeFileSync(tempPath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
30
|
+
renameSync(tempPath, filePath);
|
|
31
|
+
}
|
|
32
|
+
function computeContentHash(payloadPath) {
|
|
33
|
+
const hash = createHash("sha256");
|
|
34
|
+
hash.update(readFileSync(payloadPath));
|
|
35
|
+
return `sha256:${hash.digest("hex")}`;
|
|
36
|
+
}
|
|
37
|
+
function toIndexRecord(manifest) {
|
|
38
|
+
const producerSummary = manifest.producer.summary ?? createProducerSummary(manifest.producer);
|
|
39
|
+
return {
|
|
40
|
+
artifact_id: manifest.artifact_id,
|
|
41
|
+
logical_key: manifest.logical_key,
|
|
42
|
+
payload_path: manifest.payload_path,
|
|
43
|
+
manifest_path: manifest.manifest_path,
|
|
44
|
+
version: manifest.version,
|
|
45
|
+
status: manifest.status,
|
|
46
|
+
schema_id: manifest.schema_id,
|
|
47
|
+
schema_version: manifest.schema_version,
|
|
48
|
+
created_at: manifest.created_at,
|
|
49
|
+
content_hash: manifest.content_hash,
|
|
50
|
+
producer_summary: producerSummary,
|
|
51
|
+
...(manifest.supersedes ? { supersedes: manifest.supersedes } : {}),
|
|
52
|
+
is_latest: manifest.status === "ready",
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function tryLoadManifest(filePath) {
|
|
56
|
+
if (!existsSync(filePath)) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const parsed = JSON.parse(readFileSync(filePath, "utf8"));
|
|
61
|
+
validateArtifactManifest(parsed, filePath);
|
|
62
|
+
return parsed;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function collectManifestFiles(rootDir) {
|
|
69
|
+
if (!existsSync(rootDir)) {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
const queue = [rootDir];
|
|
73
|
+
const files = [];
|
|
74
|
+
while (queue.length > 0) {
|
|
75
|
+
const current = queue.shift();
|
|
76
|
+
if (!current) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const entries = readdirSync(current, { withFileTypes: true })
|
|
80
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
81
|
+
for (const entry of entries) {
|
|
82
|
+
const fullPath = path.join(current, entry.name);
|
|
83
|
+
if (entry.isDirectory()) {
|
|
84
|
+
if (entry.name === "restart-archives") {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
queue.push(fullPath);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (entry.isFile() && fullPath.endsWith(".manifest.json")) {
|
|
91
|
+
files.push(fullPath);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return files;
|
|
96
|
+
}
|
|
97
|
+
function collectScopeManifests(scopeKey) {
|
|
98
|
+
const allPaths = [
|
|
99
|
+
...collectManifestFiles(scopeWorkspaceDir(scopeKey)),
|
|
100
|
+
...collectManifestFiles(historyDir(scopeKey)),
|
|
101
|
+
];
|
|
102
|
+
const manifests = new Map();
|
|
103
|
+
for (const filePath of allPaths) {
|
|
104
|
+
const manifest = tryLoadManifest(filePath);
|
|
105
|
+
if (!manifest) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
manifests.set(manifest.artifact_id, manifest);
|
|
109
|
+
}
|
|
110
|
+
return Array.from(manifests.values()).sort((left, right) => {
|
|
111
|
+
if (left.logical_key !== right.logical_key) {
|
|
112
|
+
return left.logical_key.localeCompare(right.logical_key);
|
|
113
|
+
}
|
|
114
|
+
return left.version - right.version;
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
function writeManifestSidecar(manifest) {
|
|
118
|
+
writeJsonAtomic(manifest.manifest_path, manifest);
|
|
119
|
+
}
|
|
120
|
+
function writeManifestHistory(scopeKey, manifest) {
|
|
121
|
+
writeJsonAtomic(historyManifestPath(scopeKey, manifest.artifact_id), manifest);
|
|
122
|
+
}
|
|
123
|
+
function isRegistryTempFile(scopeKey, filePath) {
|
|
124
|
+
const relativePath = path.relative(scopeWorkspaceDir(scopeKey), filePath);
|
|
125
|
+
if (relativePath.startsWith("..")) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
if (/\.manifest\.json\.tmp-[^/\\]+$/.test(relativePath)) {
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
return relativePath.startsWith(`${path.join(".artifacts", path.basename(artifactIndexFile(scopeKey)))}.tmp-`);
|
|
132
|
+
}
|
|
133
|
+
function removeStaleTempFiles(scopeKey) {
|
|
134
|
+
const workspaceDir = scopeWorkspaceDir(scopeKey);
|
|
135
|
+
if (!existsSync(workspaceDir)) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const queue = [workspaceDir];
|
|
139
|
+
while (queue.length > 0) {
|
|
140
|
+
const current = queue.shift();
|
|
141
|
+
if (!current) {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
const entries = readdirSync(current, { withFileTypes: true })
|
|
145
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
146
|
+
for (const entry of entries) {
|
|
147
|
+
const fullPath = path.join(current, entry.name);
|
|
148
|
+
if (entry.isDirectory()) {
|
|
149
|
+
queue.push(fullPath);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (entry.isFile() && isRegistryTempFile(scopeKey, fullPath)) {
|
|
153
|
+
rmSync(fullPath, { force: true });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function indexRecordEquals(left, right) {
|
|
159
|
+
return left.artifact_id === right.artifact_id
|
|
160
|
+
&& left.logical_key === right.logical_key
|
|
161
|
+
&& left.payload_path === right.payload_path
|
|
162
|
+
&& left.manifest_path === right.manifest_path
|
|
163
|
+
&& left.version === right.version
|
|
164
|
+
&& left.status === right.status
|
|
165
|
+
&& left.schema_id === right.schema_id
|
|
166
|
+
&& left.schema_version === right.schema_version
|
|
167
|
+
&& left.created_at === right.created_at
|
|
168
|
+
&& left.content_hash === right.content_hash
|
|
169
|
+
&& left.producer_summary === right.producer_summary
|
|
170
|
+
&& left.supersedes === right.supersedes
|
|
171
|
+
&& left.is_latest === right.is_latest;
|
|
172
|
+
}
|
|
173
|
+
function selectLatestReadyManifest(manifests) {
|
|
174
|
+
return manifests
|
|
175
|
+
.filter((manifest) => manifest.status === "ready")
|
|
176
|
+
.sort((left, right) => right.version - left.version)[0] ?? null;
|
|
177
|
+
}
|
|
178
|
+
function selectNewestManifest(manifests) {
|
|
179
|
+
return manifests
|
|
180
|
+
.slice()
|
|
181
|
+
.sort((left, right) => right.version - left.version)[0] ?? null;
|
|
182
|
+
}
|
|
183
|
+
function buildScopeRecords(scopeKey, computeDiagnostics) {
|
|
184
|
+
const manifests = collectScopeManifests(scopeKey).map((manifest) => ({
|
|
185
|
+
...manifest,
|
|
186
|
+
diagnostics: computeDiagnostics(manifest),
|
|
187
|
+
}));
|
|
188
|
+
const latestByLogicalKey = new Map();
|
|
189
|
+
for (const manifest of manifests) {
|
|
190
|
+
if (manifest.status === "ready") {
|
|
191
|
+
latestByLogicalKey.set(manifest.logical_key, manifest.artifact_id);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return manifests.map((manifest) => ({
|
|
195
|
+
...toIndexRecord(manifest),
|
|
196
|
+
is_latest: latestByLogicalKey.get(manifest.logical_key) === manifest.artifact_id,
|
|
197
|
+
manifest,
|
|
198
|
+
}));
|
|
199
|
+
}
|
|
200
|
+
function readIndexProjection(scopeKey) {
|
|
201
|
+
const indexPath = artifactIndexFile(scopeKey);
|
|
202
|
+
if (!existsSync(indexPath)) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
try {
|
|
206
|
+
const parsed = JSON.parse(readFileSync(indexPath, "utf8"));
|
|
207
|
+
return Array.isArray(parsed.records) ? parsed.records : null;
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function syncIndexProjection(scopeKey, records) {
|
|
214
|
+
const projected = records.map(({ manifest: _manifest, ...record }) => record);
|
|
215
|
+
const current = readIndexProjection(scopeKey);
|
|
216
|
+
if (!current && projected.length === 0) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const needsRewrite = !current
|
|
220
|
+
|| current.length !== projected.length
|
|
221
|
+
|| current.some((record, index) => !indexRecordEquals(record, projected[index]));
|
|
222
|
+
if (!needsRewrite) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
writeJsonAtomic(artifactIndexFile(scopeKey), {
|
|
226
|
+
scope: scopeKey,
|
|
227
|
+
generated_at: nowIso8601(),
|
|
228
|
+
records: projected,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
export function createArtifactRegistry() {
|
|
232
|
+
return {
|
|
233
|
+
publish(input) {
|
|
234
|
+
ensureScopeWorkspaceDir(input.scopeKey);
|
|
235
|
+
removeStaleTempFiles(input.scopeKey);
|
|
236
|
+
const sidecarPath = artifactManifestSidecarPath(input.payloadPath);
|
|
237
|
+
const manifests = collectScopeManifests(input.scopeKey);
|
|
238
|
+
const logicalKey = input.logicalKey ?? buildLogicalKeyForPayload(input.scopeKey, input.payloadPath);
|
|
239
|
+
const publicationKey = buildPublicationKey({
|
|
240
|
+
runId: input.runId,
|
|
241
|
+
...(input.publicationRunId ? { publicationRunId: input.publicationRunId } : {}),
|
|
242
|
+
flowId: input.flowId,
|
|
243
|
+
phaseId: input.phaseId,
|
|
244
|
+
stepId: input.stepId,
|
|
245
|
+
logicalKey,
|
|
246
|
+
});
|
|
247
|
+
const existing = manifests.find((candidate) => candidate.publication_key === publicationKey);
|
|
248
|
+
if (existing) {
|
|
249
|
+
if (existing.payload_path !== input.payloadPath) {
|
|
250
|
+
throw new TaskRunnerError(`Manifest publication key collision for ${publicationKey}: ${existing.payload_path} and ${input.payloadPath} resolved to the same logical_key within one step.`);
|
|
251
|
+
}
|
|
252
|
+
return {
|
|
253
|
+
...toIndexRecord(existing),
|
|
254
|
+
manifest: existing,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
const contract = inferPayloadContract(input.scopeKey, input.payloadPath, {
|
|
258
|
+
...(input.payloadFamily ? { payloadFamily: input.payloadFamily } : {}),
|
|
259
|
+
...(input.schemaId ? { schemaId: input.schemaId } : {}),
|
|
260
|
+
...(input.schemaVersion ? { schemaVersion: input.schemaVersion } : {}),
|
|
261
|
+
});
|
|
262
|
+
const versions = manifests
|
|
263
|
+
.filter((candidate) => candidate.logical_key === logicalKey)
|
|
264
|
+
.sort((left, right) => left.version - right.version);
|
|
265
|
+
const previousLatest = [...versions].reverse().find((candidate) => candidate.status === "ready") ?? versions.at(-1) ?? null;
|
|
266
|
+
const version = (versions.at(-1)?.version ?? 0) + 1;
|
|
267
|
+
const artifactId = buildArtifactId(input.scopeKey, logicalKey, version);
|
|
268
|
+
const manifest = {
|
|
269
|
+
artifact_id: artifactId,
|
|
270
|
+
logical_key: logicalKey,
|
|
271
|
+
scope: input.scopeKey,
|
|
272
|
+
run_id: input.runId,
|
|
273
|
+
flow_id: input.flowId,
|
|
274
|
+
phase_id: input.phaseId,
|
|
275
|
+
step_id: input.stepId,
|
|
276
|
+
kind: input.kind,
|
|
277
|
+
version,
|
|
278
|
+
payload_family: contract.payloadFamily,
|
|
279
|
+
schema_id: contract.schemaId,
|
|
280
|
+
schema_version: contract.schemaVersion,
|
|
281
|
+
created_at: nowIso8601(),
|
|
282
|
+
producer: {
|
|
283
|
+
node: input.nodeKind,
|
|
284
|
+
summary: createProducerSummary({
|
|
285
|
+
node: input.nodeKind,
|
|
286
|
+
...(input.executor ? { executor: input.executor } : {}),
|
|
287
|
+
...(input.model ? { model: input.model } : {}),
|
|
288
|
+
}),
|
|
289
|
+
...(input.executor ? { executor: input.executor } : {}),
|
|
290
|
+
...(input.model ? { model: input.model } : {}),
|
|
291
|
+
},
|
|
292
|
+
inputs: input.inputs,
|
|
293
|
+
content_hash: computeContentHash(input.payloadPath),
|
|
294
|
+
status: "ready",
|
|
295
|
+
payload_path: input.payloadPath,
|
|
296
|
+
manifest_path: sidecarPath,
|
|
297
|
+
publication_key: publicationKey,
|
|
298
|
+
...(previousLatest ? { supersedes: previousLatest.artifact_id } : {}),
|
|
299
|
+
};
|
|
300
|
+
validateArtifactManifest(manifest, sidecarPath);
|
|
301
|
+
writeManifestHistory(input.scopeKey, manifest);
|
|
302
|
+
writeManifestSidecar(manifest);
|
|
303
|
+
if (previousLatest) {
|
|
304
|
+
const superseded = {
|
|
305
|
+
...previousLatest,
|
|
306
|
+
status: "superseded",
|
|
307
|
+
status_reason: `Superseded by ${artifactId}`,
|
|
308
|
+
};
|
|
309
|
+
writeManifestHistory(input.scopeKey, superseded);
|
|
310
|
+
if (previousLatest.manifest_path !== manifest.manifest_path) {
|
|
311
|
+
writeManifestSidecar(superseded);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
this.rebuildIndex(input.scopeKey);
|
|
315
|
+
return {
|
|
316
|
+
...toIndexRecord(manifest),
|
|
317
|
+
manifest,
|
|
318
|
+
};
|
|
319
|
+
},
|
|
320
|
+
resolveArtifact(scopeKey, reference) {
|
|
321
|
+
removeStaleTempFiles(scopeKey);
|
|
322
|
+
const parsedReference = parseArtifactReference(reference);
|
|
323
|
+
if (!parsedReference) {
|
|
324
|
+
throw new TaskRunnerError(`Artifact reference '${reference}' is invalid. Expected an artifact_id or a logical reference in the form <logical_key>@latest or <logical_key>@vN.`);
|
|
325
|
+
}
|
|
326
|
+
const records = buildScopeRecords(scopeKey, this.computeDiagnostics);
|
|
327
|
+
syncIndexProjection(scopeKey, records);
|
|
328
|
+
const manifests = records.map((record) => record.manifest);
|
|
329
|
+
if (parsedReference.kind === "artifact-id") {
|
|
330
|
+
if (parsedReference.parsedId.scopeKey !== scopeKey) {
|
|
331
|
+
throw new TaskRunnerError(`Artifact id '${reference}' belongs to scope '${parsedReference.parsedId.scopeKey}', expected '${scopeKey}'.`);
|
|
332
|
+
}
|
|
333
|
+
const manifest = manifests.find((candidate) => candidate.artifact_id === parsedReference.artifactId);
|
|
334
|
+
if (!manifest) {
|
|
335
|
+
throw new TaskRunnerError(`Artifact '${reference}' was not found in scope '${scopeKey}'.`);
|
|
336
|
+
}
|
|
337
|
+
return manifest;
|
|
338
|
+
}
|
|
339
|
+
const candidates = manifests.filter((candidate) => candidate.logical_key === parsedReference.logicalKey);
|
|
340
|
+
if (parsedReference.version === "latest") {
|
|
341
|
+
const manifest = selectLatestReadyManifest(candidates);
|
|
342
|
+
if (!manifest) {
|
|
343
|
+
throw new TaskRunnerError(`No ready artifact found for logical reference '${reference}' in scope '${scopeKey}'.`);
|
|
344
|
+
}
|
|
345
|
+
return manifest;
|
|
346
|
+
}
|
|
347
|
+
const manifest = candidates.find((candidate) => candidate.version === parsedReference.version);
|
|
348
|
+
if (!manifest) {
|
|
349
|
+
throw new TaskRunnerError(`Artifact reference '${reference}' was not found in scope '${scopeKey}'.`);
|
|
350
|
+
}
|
|
351
|
+
return manifest;
|
|
352
|
+
},
|
|
353
|
+
loadManifestByPayloadPath(payloadPath) {
|
|
354
|
+
const manifest = tryLoadManifest(artifactManifestSidecarPath(payloadPath));
|
|
355
|
+
const scopeKey = scopeKeyFromPayloadPath(payloadPath);
|
|
356
|
+
if (!scopeKey) {
|
|
357
|
+
return manifest;
|
|
358
|
+
}
|
|
359
|
+
removeStaleTempFiles(scopeKey);
|
|
360
|
+
const records = buildScopeRecords(scopeKey, this.computeDiagnostics);
|
|
361
|
+
syncIndexProjection(scopeKey, records);
|
|
362
|
+
const candidates = records
|
|
363
|
+
.map((record) => record.manifest)
|
|
364
|
+
.filter((candidate) => candidate.payload_path === payloadPath);
|
|
365
|
+
return selectLatestReadyManifest(candidates) ?? selectNewestManifest(candidates) ?? manifest;
|
|
366
|
+
},
|
|
367
|
+
listScopeArtifacts(scopeKey) {
|
|
368
|
+
removeStaleTempFiles(scopeKey);
|
|
369
|
+
const records = buildScopeRecords(scopeKey, this.computeDiagnostics);
|
|
370
|
+
syncIndexProjection(scopeKey, records);
|
|
371
|
+
return records;
|
|
372
|
+
},
|
|
373
|
+
rebuildIndex(scopeKey) {
|
|
374
|
+
removeStaleTempFiles(scopeKey);
|
|
375
|
+
const records = buildScopeRecords(scopeKey, this.computeDiagnostics);
|
|
376
|
+
syncIndexProjection(scopeKey, records);
|
|
377
|
+
return records;
|
|
378
|
+
},
|
|
379
|
+
resolveLineageInputFromPath(scopeKey, payloadPath) {
|
|
380
|
+
const manifest = this.loadManifestByPayloadPath(payloadPath);
|
|
381
|
+
if (!manifest) {
|
|
382
|
+
return {
|
|
383
|
+
source: "external-path",
|
|
384
|
+
path: payloadPath,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
return {
|
|
388
|
+
source: "manifest",
|
|
389
|
+
path: payloadPath,
|
|
390
|
+
artifact_id: manifest.artifact_id,
|
|
391
|
+
logical_key: manifest.logical_key,
|
|
392
|
+
schema_id: manifest.schema_id,
|
|
393
|
+
schema_version: manifest.schema_version,
|
|
394
|
+
};
|
|
395
|
+
},
|
|
396
|
+
computeDiagnostics(manifest) {
|
|
397
|
+
return diagnosticsForManifest(manifest, (schemaId, payloadPath) => {
|
|
398
|
+
if (!isArtifactPayloadSchemaId(schemaId)) {
|
|
399
|
+
throw new Error(`Structured artifact schema is not registered: ${schemaId}`);
|
|
400
|
+
}
|
|
401
|
+
validateArtifactPayload(payloadPath, schemaId);
|
|
402
|
+
});
|
|
403
|
+
},
|
|
404
|
+
};
|
|
405
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
-
import { designFile, designJsonFile, jiraAttachmentsContextFile, jiraAttachmentsManifestFile, jiraTaskFile, latestArtifactIteration, planFile, planJsonFile, planningAnswersJsonFile, qaFile, qaJsonFile, requireArtifacts, } from "../artifacts.js";
|
|
3
|
-
import { TaskRunnerError } from "../errors.js";
|
|
2
|
+
import { designFile, designJsonFile, instantTaskInputJsonFile, jiraAttachmentsContextFile, jiraAttachmentsManifestFile, jiraTaskFile, latestArtifactIteration, planFile, planJsonFile, planningAnswersJsonFile, qaFile, qaJsonFile, taskContextJsonFile, requireArtifacts, } from "../artifacts.js";
|
|
4
3
|
import { validateStructuredArtifacts } from "../structured-artifacts.js";
|
|
4
|
+
import { resolveLatestCompletedPlanningIteration } from "./planning-bundle.js";
|
|
5
5
|
const OPTIONAL_INPUT_NOT_PROVIDED = "not provided";
|
|
6
6
|
function requiredPlanningArtifactPaths(taskKey, iteration) {
|
|
7
7
|
return {
|
|
@@ -11,19 +11,6 @@ function requiredPlanningArtifactPaths(taskKey, iteration) {
|
|
|
11
11
|
planJsonFile: planJsonFile(taskKey, iteration),
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
|
-
function resolveLatestCompletedPlanningIteration(taskKey) {
|
|
15
|
-
const latestKnownIteration = Math.max(latestArtifactIteration(taskKey, "design", "md") ?? 0, latestArtifactIteration(taskKey, "design", "json") ?? 0, latestArtifactIteration(taskKey, "plan", "md") ?? 0, latestArtifactIteration(taskKey, "plan", "json") ?? 0);
|
|
16
|
-
for (let iteration = latestKnownIteration; iteration >= 1; iteration -= 1) {
|
|
17
|
-
const requiredPaths = Object.values(requiredPlanningArtifactPaths(taskKey, iteration));
|
|
18
|
-
if (requiredPaths.every((candidate) => existsSync(candidate))) {
|
|
19
|
-
return iteration;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
const fallbackIteration = latestKnownIteration || 1;
|
|
23
|
-
const fallbackPaths = Object.values(requiredPlanningArtifactPaths(taskKey, fallbackIteration));
|
|
24
|
-
requireArtifacts(fallbackPaths, "Design-review requires design and plan markdown/JSON artifacts from the latest completed planning run.");
|
|
25
|
-
throw new TaskRunnerError("Unreachable design-review planning artifact resolution state.");
|
|
26
|
-
}
|
|
27
14
|
function resolveOptionalPromptFile(filePath) {
|
|
28
15
|
if (!existsSync(filePath)) {
|
|
29
16
|
return {
|
|
@@ -79,7 +66,10 @@ function resolveOptionalQaPair(taskKey, iteration) {
|
|
|
79
66
|
* deterministic when some context is absent.
|
|
80
67
|
*/
|
|
81
68
|
export function resolveDesignReviewInputContract(taskKey) {
|
|
82
|
-
const planningIteration = resolveLatestCompletedPlanningIteration(taskKey
|
|
69
|
+
const planningIteration = resolveLatestCompletedPlanningIteration(taskKey, {
|
|
70
|
+
requireQa: false,
|
|
71
|
+
missingMessage: "Design-review requires design and plan markdown/JSON artifacts from the latest completed planning run.",
|
|
72
|
+
});
|
|
83
73
|
const requiredArtifacts = requiredPlanningArtifactPaths(taskKey, planningIteration);
|
|
84
74
|
requireArtifacts(Object.values(requiredArtifacts), "Design-review requires design and plan markdown/JSON artifacts from the latest completed planning run.");
|
|
85
75
|
validateStructuredArtifacts([
|
|
@@ -91,6 +81,11 @@ export function resolveDesignReviewInputContract(taskKey) {
|
|
|
91
81
|
const jiraAttachmentsManifest = resolveOptionalPromptFile(jiraAttachmentsManifestFile(taskKey));
|
|
92
82
|
const jiraAttachmentsContext = resolveOptionalPromptFile(jiraAttachmentsContextFile(taskKey));
|
|
93
83
|
const planningAnswers = resolveOptionalValidatedStructuredFile(planningAnswersJsonFile(taskKey), "user-input/v1", "Design-review planning answers structured artifact is invalid.");
|
|
84
|
+
const taskContextIteration = latestArtifactIteration(taskKey, "task-context", "json");
|
|
85
|
+
const taskContext = taskContextIteration === null
|
|
86
|
+
? { present: false, path: null, promptValue: OPTIONAL_INPUT_NOT_PROVIDED }
|
|
87
|
+
: resolveOptionalValidatedStructuredFile(taskContextJsonFile(taskKey, taskContextIteration), "task-context/v1", "Design-review task-context structured artifact is invalid.");
|
|
88
|
+
const taskInput = resolveOptionalValidatedStructuredFile(instantTaskInputJsonFile(taskKey), "user-input/v1", "Design-review instant-task input structured artifact is invalid.");
|
|
94
89
|
return {
|
|
95
90
|
planningIteration,
|
|
96
91
|
...requiredArtifacts,
|
|
@@ -107,6 +102,12 @@ export function resolveDesignReviewInputContract(taskKey) {
|
|
|
107
102
|
hasPlanningAnswersJsonFile: planningAnswers.present,
|
|
108
103
|
planningAnswersJsonFilePath: planningAnswers.path,
|
|
109
104
|
planningAnswersJsonFile: planningAnswers.promptValue,
|
|
105
|
+
hasTaskContextJsonFile: taskContext.present,
|
|
106
|
+
taskContextJsonFilePath: taskContext.path,
|
|
107
|
+
taskContextJsonFile: taskContext.promptValue,
|
|
108
|
+
hasTaskInputJsonFile: taskInput.present,
|
|
109
|
+
taskInputJsonFilePath: taskInput.path,
|
|
110
|
+
taskInputJsonFile: taskInput.promptValue,
|
|
110
111
|
};
|
|
111
112
|
}
|
|
112
113
|
export { OPTIONAL_INPUT_NOT_PROVIDED };
|
|
@@ -35,6 +35,9 @@ function globalConfigDir() {
|
|
|
35
35
|
function ensureGlobalConfigDir() {
|
|
36
36
|
mkdirSync(globalConfigDir(), { recursive: true });
|
|
37
37
|
}
|
|
38
|
+
export function agentweaverConfigDir() {
|
|
39
|
+
return globalConfigDir();
|
|
40
|
+
}
|
|
38
41
|
export function loadTieredEnv(projectDir) {
|
|
39
42
|
ensureGlobalConfigDir();
|
|
40
43
|
const shellEnvKeys = new Set(Object.keys(process.env));
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { TaskRunnerError } from "../errors.js";
|
|
4
|
+
import { resolveStoredExecutionRoutingSnapshot } from "./execution-routing.js";
|
|
5
|
+
import { agentweaverConfigDir } from "./env-loader.js";
|
|
6
|
+
const EXECUTION_ROUTING_STORE_VERSION = 1;
|
|
7
|
+
function storePath() {
|
|
8
|
+
return path.join(agentweaverConfigDir(), "execution-routing.json");
|
|
9
|
+
}
|
|
10
|
+
function nowIso8601() {
|
|
11
|
+
return new Date().toISOString();
|
|
12
|
+
}
|
|
13
|
+
function emptyStore() {
|
|
14
|
+
return {
|
|
15
|
+
version: EXECUTION_ROUTING_STORE_VERSION,
|
|
16
|
+
namedPresets: {},
|
|
17
|
+
flowDefaults: {},
|
|
18
|
+
lastUsedByFlow: {},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function validateSelectedPreset(value, pathLabel) {
|
|
22
|
+
if (!value || typeof value !== "object") {
|
|
23
|
+
throw new TaskRunnerError(`Invalid execution routing store entry at ${pathLabel}.selectedPreset.`);
|
|
24
|
+
}
|
|
25
|
+
const candidate = value;
|
|
26
|
+
if (typeof candidate.kind !== "string" || typeof candidate.label !== "string") {
|
|
27
|
+
throw new TaskRunnerError(`Invalid execution routing store entry at ${pathLabel}.selectedPreset.`);
|
|
28
|
+
}
|
|
29
|
+
if (candidate.kind === "built-in" || candidate.kind === "named") {
|
|
30
|
+
if (typeof candidate.presetId !== "string" || candidate.presetId.trim().length === 0) {
|
|
31
|
+
throw new TaskRunnerError(`Invalid execution routing store entry at ${pathLabel}.selectedPreset.presetId.`);
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
kind: candidate.kind,
|
|
35
|
+
presetId: candidate.presetId,
|
|
36
|
+
label: candidate.label,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (candidate.kind === "flow-default" || candidate.kind === "last-used" || candidate.kind === "custom") {
|
|
40
|
+
return {
|
|
41
|
+
kind: candidate.kind,
|
|
42
|
+
label: candidate.label,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
throw new TaskRunnerError(`Invalid execution routing store entry at ${pathLabel}.selectedPreset.kind.`);
|
|
46
|
+
}
|
|
47
|
+
function validateRoutingEntry(value, pathLabel) {
|
|
48
|
+
if (!value || typeof value !== "object") {
|
|
49
|
+
throw new TaskRunnerError(`Invalid execution routing store entry at ${pathLabel}.`);
|
|
50
|
+
}
|
|
51
|
+
const candidate = value;
|
|
52
|
+
if (typeof candidate.updatedAt !== "string") {
|
|
53
|
+
throw new TaskRunnerError(`Invalid execution routing store entry at ${pathLabel}.updatedAt.`);
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
routing: resolveStoredExecutionRoutingSnapshot(candidate.routing),
|
|
57
|
+
selectedPreset: validateSelectedPreset(candidate.selectedPreset, pathLabel),
|
|
58
|
+
updatedAt: candidate.updatedAt,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function validateEntryMap(value, pathLabel) {
|
|
62
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
63
|
+
throw new TaskRunnerError(`Invalid execution routing store section '${pathLabel}'.`);
|
|
64
|
+
}
|
|
65
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, validateRoutingEntry(entry, `${pathLabel}.${key}`)]));
|
|
66
|
+
}
|
|
67
|
+
function validateStore(raw) {
|
|
68
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
69
|
+
throw new TaskRunnerError("Execution routing store must be a JSON object.");
|
|
70
|
+
}
|
|
71
|
+
const candidate = raw;
|
|
72
|
+
if (candidate.version !== EXECUTION_ROUTING_STORE_VERSION) {
|
|
73
|
+
throw new TaskRunnerError(`Unsupported execution routing store version: ${String(candidate.version ?? "unknown")}.`);
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
version: EXECUTION_ROUTING_STORE_VERSION,
|
|
77
|
+
namedPresets: validateEntryMap(candidate.namedPresets ?? {}, "namedPresets"),
|
|
78
|
+
flowDefaults: validateEntryMap(candidate.flowDefaults ?? {}, "flowDefaults"),
|
|
79
|
+
lastUsedByFlow: validateEntryMap(candidate.lastUsedByFlow ?? {}, "lastUsedByFlow"),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
export function loadExecutionRoutingStore() {
|
|
83
|
+
const filePath = storePath();
|
|
84
|
+
if (!existsSync(filePath)) {
|
|
85
|
+
return emptyStore();
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
return validateStore(JSON.parse(readFileSync(filePath, "utf8")));
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
92
|
+
throw new TaskRunnerError(`Failed to load execution routing store ${filePath}: ${message}. Delete or repair the file and try again.`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export function saveExecutionRoutingStore(store) {
|
|
96
|
+
const filePath = storePath();
|
|
97
|
+
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
98
|
+
const tempFilePath = `${filePath}.tmp`;
|
|
99
|
+
writeFileSync(`${tempFilePath}`, `${JSON.stringify(store, null, 2)}\n`, "utf8");
|
|
100
|
+
renameSync(tempFilePath, filePath);
|
|
101
|
+
}
|
|
102
|
+
function withUpdatedAt(entry) {
|
|
103
|
+
return {
|
|
104
|
+
...entry,
|
|
105
|
+
updatedAt: nowIso8601(),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
export function saveNamedExecutionPreset(name, routing, selectedPreset) {
|
|
109
|
+
const store = loadExecutionRoutingStore();
|
|
110
|
+
store.namedPresets[name] = withUpdatedAt({ routing, selectedPreset });
|
|
111
|
+
saveExecutionRoutingStore(store);
|
|
112
|
+
}
|
|
113
|
+
export function saveFlowDefaultExecutionRouting(flowKey, routing, selectedPreset) {
|
|
114
|
+
const store = loadExecutionRoutingStore();
|
|
115
|
+
store.flowDefaults[flowKey] = withUpdatedAt({ routing, selectedPreset });
|
|
116
|
+
saveExecutionRoutingStore(store);
|
|
117
|
+
}
|
|
118
|
+
export function saveLastUsedExecutionRouting(flowKey, routing, selectedPreset) {
|
|
119
|
+
const store = loadExecutionRoutingStore();
|
|
120
|
+
store.lastUsedByFlow[flowKey] = withUpdatedAt({ routing, selectedPreset });
|
|
121
|
+
saveExecutionRoutingStore(store);
|
|
122
|
+
}
|
|
123
|
+
export function getNamedExecutionPresets() {
|
|
124
|
+
return loadExecutionRoutingStore().namedPresets;
|
|
125
|
+
}
|
|
126
|
+
export function getFlowDefaultExecutionRouting(flowKey) {
|
|
127
|
+
return loadExecutionRoutingStore().flowDefaults[flowKey] ?? null;
|
|
128
|
+
}
|
|
129
|
+
export function getLastUsedExecutionRouting(flowKey) {
|
|
130
|
+
return loadExecutionRoutingStore().lastUsedByFlow[flowKey] ?? null;
|
|
131
|
+
}
|
|
132
|
+
export function executionRoutingStoreFile() {
|
|
133
|
+
return storePath();
|
|
134
|
+
}
|