@fitlab-ai/agent-infra 0.11.3 → 0.11.5
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 +17 -8
- package/README.zh-CN.md +17 -8
- package/dist/bin/internal-cli.js +1 -6
- package/dist/lib/decide.js +4 -41
- package/dist/lib/git/branch-worktree.js +45 -0
- package/dist/lib/internal/cli-route-inventory.js +1 -4
- package/dist/lib/internal/task-activity.js +7 -40
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-event.js +4 -37
- package/dist/lib/internal/task-finalization.js +48 -1
- package/dist/lib/internal/task-ledger.js +14 -44
- package/dist/lib/internal/task-lifecycle.js +1 -1
- package/dist/lib/internal/task-operation-registry.js +2 -4
- package/dist/lib/internal/task-review.js +5 -37
- package/dist/lib/internal/task-verify.js +3 -47
- package/dist/lib/internal/task-warning.js +4 -40
- package/dist/lib/platform/completion-backfill.js +63 -17
- package/dist/lib/platform/pr-summary.js +4 -23
- package/dist/lib/platform/verification-sync.js +12 -49
- package/dist/lib/sandbox/control/client.js +4 -1
- package/dist/lib/sandbox/control/executor.js +12 -4
- package/dist/lib/sandbox/control/protocol.js +2 -1
- package/dist/lib/sandbox/control/server.js +26 -143
- package/dist/lib/sandbox/removal.js +15 -8
- package/dist/lib/sandbox/task-cleanup.js +13 -20
- package/dist/lib/task/activity-intent.js +1 -2
- package/dist/lib/task/artifact-command.js +11 -4
- package/dist/lib/task/artifact-lifecycle.js +217 -85
- package/dist/lib/task/artifact-operations.js +37 -28
- package/dist/lib/task/artifact-receipts.js +4 -1
- package/dist/lib/task/artifact-schema.js +8 -7
- package/dist/lib/task/artifact-selection.js +25 -0
- package/dist/lib/task/capabilities.js +158 -39
- package/dist/lib/task/commands/status.js +9 -2
- package/dist/lib/task/control-authority.js +12 -168
- package/dist/lib/task/decision-intents.js +14 -4
- package/dist/lib/task/delivery.js +1 -43
- package/dist/lib/task/events.js +68 -73
- package/dist/lib/task/finalization-handoff.js +92 -0
- package/dist/lib/task/finalization.js +423 -154
- package/dist/lib/task/intermediate-cleanup.js +9 -148
- package/dist/lib/task/ledger-intents.js +159 -20
- package/dist/lib/task/lifecycle-path.js +75 -0
- package/dist/lib/task/lifecycle.js +60 -11
- package/dist/lib/task/local-artifact-finalization.js +13 -47
- package/dist/lib/task/orchestration.js +17 -7
- package/dist/lib/task/review-command.js +1 -2
- package/dist/lib/task/review-finalization.js +4 -18
- package/dist/lib/task/rework-intent.js +70 -4
- package/dist/lib/task/snapshot.js +6 -6
- package/dist/lib/task/verification-engine.js +25 -30
- package/dist/lib/task/verification.js +2 -13
- package/dist/lib/task/workflow-warning-intents.js +23 -10
- package/dist/lib/task/workspace-snapshot.js +7 -2
- package/dist/lib/task/write.js +1 -2
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/decide.ts +5 -35
- package/lib/git/branch-worktree.ts +41 -0
- package/lib/internal/cli-route-inventory.ts +1 -3
- package/lib/internal/task-activity.ts +6 -34
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-event.ts +4 -37
- package/lib/internal/task-finalization.ts +47 -1
- package/lib/internal/task-ledger.ts +15 -41
- package/lib/internal/task-lifecycle.ts +2 -2
- package/lib/internal/task-operation-registry.ts +2 -4
- package/lib/internal/task-review.ts +5 -31
- package/lib/internal/task-verify.ts +3 -39
- package/lib/internal/task-warning.ts +3 -32
- package/lib/platform/completion-backfill.ts +78 -16
- package/lib/platform/pr-summary.ts +4 -25
- package/lib/platform/verification-sync.ts +13 -53
- package/lib/sandbox/control/client.ts +5 -1
- package/lib/sandbox/control/executor.ts +13 -4
- package/lib/sandbox/control/protocol.ts +3 -2
- package/lib/sandbox/control/server.ts +21 -146
- package/lib/sandbox/removal.ts +23 -7
- package/lib/sandbox/task-cleanup.ts +12 -20
- package/lib/task/activity-intent.ts +1 -2
- package/lib/task/artifact-command.ts +14 -4
- package/lib/task/artifact-lifecycle.ts +236 -85
- package/lib/task/artifact-operations.ts +59 -28
- package/lib/task/artifact-receipts.ts +4 -1
- package/lib/task/artifact-schema.ts +10 -7
- package/lib/task/artifact-selection.ts +53 -0
- package/lib/task/capabilities.ts +154 -40
- package/lib/task/commands/status.ts +11 -2
- package/lib/task/control-authority.ts +17 -189
- package/lib/task/decision-intents.ts +11 -4
- package/lib/task/delivery.ts +1 -40
- package/lib/task/events.ts +73 -74
- package/lib/task/finalization-handoff.ts +104 -0
- package/lib/task/finalization.ts +388 -122
- package/lib/task/intermediate-cleanup.ts +9 -125
- package/lib/task/ledger-intents.ts +157 -20
- package/lib/task/lifecycle-path.ts +98 -0
- package/lib/task/lifecycle.ts +58 -13
- package/lib/task/local-artifact-finalization.ts +14 -56
- package/lib/task/orchestration.ts +26 -8
- package/lib/task/review-command.ts +2 -5
- package/lib/task/review-finalization.ts +4 -20
- package/lib/task/rework-intent.ts +75 -5
- package/lib/task/snapshot.ts +6 -6
- package/lib/task/verification-engine.ts +29 -30
- package/lib/task/verification.ts +3 -16
- package/lib/task/workflow-warning-intents.ts +20 -10
- package/lib/task/workspace-snapshot.ts +3 -2
- package/lib/task/write.ts +1 -4
- package/package.json +1 -1
- package/templates/.agents/rules/review-handshake.en.md +4 -4
- package/templates/.agents/rules/review-handshake.zh-CN.md +4 -4
- package/templates/.agents/skills/analyze-task/SKILL.en.md +21 -5
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +21 -5
- package/templates/.agents/skills/code-task/SKILL.en.md +14 -8
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +18 -13
- package/templates/.agents/skills/code-task/reference/code-rules.en.md +2 -2
- package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +4 -4
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +4 -4
- package/templates/.agents/skills/code-task/reference/report-template.en.md +7 -7
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +7 -7
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +2 -2
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +2 -2
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +1 -3
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +1 -3
- package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/complete-task/config/verify.en.json +2 -2
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
- package/templates/.agents/skills/plan-task/SKILL.en.md +13 -10
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +13 -10
- package/templates/.agents/skills/review-analysis/SKILL.en.md +5 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +3 -3
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +3 -3
- package/templates/.agents/skills/review-code/SKILL.en.md +5 -1
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/review-code/reference/report-template.en.md +3 -3
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +3 -3
- package/templates/.agents/skills/review-plan/SKILL.en.md +5 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +3 -3
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +3 -3
- package/templates/.agents/skills/review-pr/config/verify.en.json +3 -1
- package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +3 -1
- package/templates/.agents/skills/review-pr/reference/report-template.en.md +3 -3
- package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +3 -3
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +2 -2
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +2 -2
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -2
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -2
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +18 -2
- package/templates/.agents/templates/task.en.md +2 -2
- package/templates/.agents/templates/task.zh-CN.md +2 -2
- package/templates/.agents/workflows/bug-fix.en.yaml +11 -6
- package/templates/.agents/workflows/bug-fix.zh-CN.yaml +11 -6
- package/templates/.agents/workflows/feature-development.en.yaml +11 -6
- package/templates/.agents/workflows/feature-development.zh-CN.yaml +11 -6
- package/templates/.agents/workflows/refactoring.en.yaml +11 -6
- package/templates/.agents/workflows/refactoring.zh-CN.yaml +11 -6
- package/dist/lib/internal/task-override.js +0 -138
- package/dist/lib/sandbox/control/finalization-response.js +0 -33
- package/dist/lib/task/artifact-recovery.js +0 -476
- package/dist/lib/task/artifact-repair-intent.js +0 -181
- package/dist/lib/task/guard-override.js +0 -31
- package/dist/lib/task/human-override.js +0 -1259
- package/lib/internal/task-override.ts +0 -144
- package/lib/sandbox/control/finalization-response.ts +0 -35
- package/lib/task/artifact-recovery.ts +0 -572
- package/lib/task/artifact-repair-intent.ts +0 -227
- package/lib/task/guard-override.ts +0 -50
- package/lib/task/human-override.ts +0 -1522
|
@@ -8,7 +8,9 @@ import { parseVerdict } from './review-artifacts.js';
|
|
|
8
8
|
import { extractSection, findSectionHeading } from './sections.js';
|
|
9
9
|
import { receiptForOutput, sha256File } from './artifact-receipts.js';
|
|
10
10
|
import { isArtifactInvalidated, parseInvalidationDocument } from './invalidation.js';
|
|
11
|
-
import {
|
|
11
|
+
import { parseLifecyclePathDecision } from './lifecycle-path.js';
|
|
12
|
+
import { parseReworkIntentDocument } from './rework-intent.js';
|
|
13
|
+
import { selectArtifactDisposition } from './artifact-selection.js';
|
|
12
14
|
const artifactFamilyCatalog = ARTIFACT_FAMILY_CATALOG;
|
|
13
15
|
const ARTIFACT_STEPS = {
|
|
14
16
|
analysis: 'Analyze Task', 'review-analysis': 'Review Analysis',
|
|
@@ -27,6 +29,14 @@ export function hasOpenArtifactRound(content, family, round) {
|
|
|
27
29
|
const expected = new RegExp(`^${step} \\(Round ${round}${qualifier}\\)$`);
|
|
28
30
|
return startedBackedRows(pairEntries(activity.entries)).filter((row) => expected.test(row.step) && !row.done).length === 1;
|
|
29
31
|
}
|
|
32
|
+
function hasCompletedArtifactRound(content, family, round, name) {
|
|
33
|
+
const activity = locateActivityLog(content);
|
|
34
|
+
const step = ARTIFACT_STEPS[family];
|
|
35
|
+
if (!activity || !step)
|
|
36
|
+
return false;
|
|
37
|
+
const expected = new RegExp(`^${escapeRegExp(step)} \\(Round ${round}(?:, [^)]+)?\\)$`);
|
|
38
|
+
return startedBackedRows(pairEntries(activity.entries)).some((row) => expected.test(row.step) && Boolean(row.done) && row.note.includes(`→ ${name}`));
|
|
39
|
+
}
|
|
30
40
|
const BLOCKING_DIAGNOSTICS = new Set([
|
|
31
41
|
'NONCANONICAL_NAME', 'ROUND_OUT_OF_RANGE', 'MISSING_BASE', 'ROUND_GAP',
|
|
32
42
|
'DUPLICATE_LOGICAL_ROUND', 'NON_REGULAR_FILE', 'SYMBOLIC_LINK',
|
|
@@ -205,15 +215,29 @@ function resolveCodeInputReceipt(taskDir, code) {
|
|
|
205
215
|
}
|
|
206
216
|
if (!receipt || receipt.event !== 'code.completed')
|
|
207
217
|
return null;
|
|
208
|
-
const
|
|
209
|
-
if (
|
|
218
|
+
const identity = parseArtifactName(receipt.input);
|
|
219
|
+
if (!identity || !['analysis', 'plan'].includes(identity.family))
|
|
220
|
+
return null;
|
|
221
|
+
const source = inspectArtifactDirectory(taskDir, identity.family);
|
|
222
|
+
if (source.status !== 'ready')
|
|
210
223
|
return null;
|
|
211
|
-
const input =
|
|
224
|
+
const input = source.artifacts.find((artifact) => artifact.name === receipt.input);
|
|
212
225
|
return input ? { input, inputSha256: receipt.inputSha256 } : null;
|
|
213
226
|
}
|
|
214
227
|
function resolveCodePlanInput(taskDir, code) {
|
|
215
228
|
return resolveCodeInputReceipt(taskDir, code);
|
|
216
229
|
}
|
|
230
|
+
function lifecyclePathState(taskDir) {
|
|
231
|
+
const analysis = inspectArtifactDirectory(taskDir, 'analysis');
|
|
232
|
+
if (analysis.status !== 'ready' || !analysis.latest)
|
|
233
|
+
return { status: 'missing', decision: null, message: 'analysis artifact is missing' };
|
|
234
|
+
try {
|
|
235
|
+
return parseLifecyclePathDecision(fs.readFileSync(analysis.latest.path, 'utf8'), analysis.latest.name, sha256File(analysis.latest.path));
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
return { status: 'invalid', decision: null, message: error instanceof Error ? error.message : String(error) };
|
|
239
|
+
}
|
|
240
|
+
}
|
|
217
241
|
function assertWritableInventory(inventory) {
|
|
218
242
|
if (inventory.status === 'failed')
|
|
219
243
|
return inventory.error;
|
|
@@ -227,13 +251,87 @@ const REQUIRED_INPUT = {
|
|
|
227
251
|
'review-analysis': 'analysis', plan: 'analysis', 'review-plan': 'plan', 'review-code': 'code'
|
|
228
252
|
};
|
|
229
253
|
const OPTIONAL_CONTEXT = {
|
|
230
|
-
analysis: { family: 'review-analysis'
|
|
231
|
-
plan: { family: 'review-plan'
|
|
254
|
+
analysis: { family: 'review-analysis' },
|
|
255
|
+
plan: { family: 'review-plan' },
|
|
232
256
|
'review-code': { family: 'review-plan' },
|
|
233
257
|
'manual-validation': { family: 'review-code' },
|
|
234
258
|
'validation-run': { family: 'review-code' }
|
|
235
259
|
};
|
|
236
|
-
|
|
260
|
+
function hasSelectionEvidence(content, family, options, context) {
|
|
261
|
+
if (options.sourceFinding && options.sourceArtifact && options.sourceSha256)
|
|
262
|
+
return true;
|
|
263
|
+
if (options.reasonCode === 'new-requirement' || options.reasonCode === 'upstream-fact-doubt')
|
|
264
|
+
return true;
|
|
265
|
+
if (family.startsWith('review-')) {
|
|
266
|
+
const reviewedFamily = family === 'review-analysis' ? 'analysis' : family === 'review-plan' ? 'plan' : 'code';
|
|
267
|
+
const currentInput = context.inputs.find((input) => input.family === reviewedFamily);
|
|
268
|
+
if (currentInput && context.reviewedInput?.name !== currentInput.name)
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
const target = family === 'analysis' ? 'analysis' : family === 'plan' ? 'plan' : family === 'code' ? 'code' : null;
|
|
272
|
+
if (target) {
|
|
273
|
+
const parsed = parseReworkIntentDocument(content);
|
|
274
|
+
if (!parsed.ok)
|
|
275
|
+
throw new Error(`${parsed.code}: ${parsed.message}`);
|
|
276
|
+
if (parsed.intents.some((intent) => intent.status === 'pending'
|
|
277
|
+
&& (intent.target === target || family === 'analysis' && intent.target === 'plan')))
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
if (family === 'code') {
|
|
281
|
+
if (context.latest && context.codeMode?.mode === 'init')
|
|
282
|
+
return true;
|
|
283
|
+
if (parseImplementationInputs(content).rows.some((input) => input.needsImplementation && input.status === 'pending'))
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
return context.inputs.filter((candidate) => candidate.family.startsWith('review-')).some((input) => {
|
|
287
|
+
const verdict = parseVerdict(input.path);
|
|
288
|
+
if (!verdict.ok || verdict.verdict !== 'Changes Requested' || !context.latest)
|
|
289
|
+
return false;
|
|
290
|
+
const receipt = receiptForOutput(content, input.name);
|
|
291
|
+
return receipt?.event === reviewEventName(input.family)
|
|
292
|
+
&& receipt.input === context.latest.name
|
|
293
|
+
&& receipt.inputSha256 === sha256File(context.latest.path);
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
function attachArtifactSelection(context, pathState, options) {
|
|
297
|
+
if (context.status !== 'ready' || !context.taskDir || !context.taskId
|
|
298
|
+
|| (context.family !== 'analysis' && pathState.status !== 'valid'))
|
|
299
|
+
return context;
|
|
300
|
+
const family = context.family;
|
|
301
|
+
const fail = (code, message) => ({
|
|
302
|
+
...context, status: 'failed', selection: null, error: { code, message }
|
|
303
|
+
});
|
|
304
|
+
if (!context.next)
|
|
305
|
+
return fail('ARTIFACT_SELECTION_INDETERMINATE', 'artifact inventory did not provide a writable identity');
|
|
306
|
+
let taskContent;
|
|
307
|
+
try {
|
|
308
|
+
taskContent = fs.readFileSync(path.join(context.taskDir, 'task.md'), 'utf8');
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
return fail('ARTIFACT_SELECTION_INDETERMINATE', error instanceof Error ? error.message : String(error));
|
|
312
|
+
}
|
|
313
|
+
const openLatest = context.latest ? hasOpenArtifactRound(taskContent, family, context.latest.round) : false;
|
|
314
|
+
const openNext = hasOpenArtifactRound(taskContent, family, context.next.round);
|
|
315
|
+
const open = openLatest || openNext;
|
|
316
|
+
const selectedIdentity = openLatest ? context.latest : context.next;
|
|
317
|
+
try {
|
|
318
|
+
const hasChangeEvidence = hasSelectionEvidence(taskContent, family, options, context);
|
|
319
|
+
const selection = selectArtifactDisposition({
|
|
320
|
+
next: { family, round: selectedIdentity.round, name: selectedIdentity.name },
|
|
321
|
+
latest: context.latest,
|
|
322
|
+
open,
|
|
323
|
+
hasChangeEvidence
|
|
324
|
+
});
|
|
325
|
+
return {
|
|
326
|
+
...context,
|
|
327
|
+
selection,
|
|
328
|
+
next: selection.writeRequired ? { round: selection.artifact.round, name: selection.artifact.name } : null
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
catch (error) {
|
|
332
|
+
return fail('ARTIFACT_SELECTION_INDETERMINATE', error instanceof Error ? error.message : String(error));
|
|
333
|
+
}
|
|
334
|
+
}
|
|
237
335
|
function resolveArtifactContext(taskRef, family, options = {}) {
|
|
238
336
|
const inventory = inspectTaskArtifacts(taskRef, family, options);
|
|
239
337
|
if (inventory.status === 'failed')
|
|
@@ -241,8 +339,43 @@ function resolveArtifactContext(taskRef, family, options = {}) {
|
|
|
241
339
|
const writableError = assertWritableInventory(inventory);
|
|
242
340
|
if (writableError)
|
|
243
341
|
return { ...inventory, status: 'failed', inputs: [], codeMode: null, error: writableError };
|
|
244
|
-
|
|
245
|
-
|
|
342
|
+
const pathState = lifecyclePathState(inventory.taskDir);
|
|
343
|
+
if (inventory.family !== 'analysis' && pathState.status !== 'valid') {
|
|
344
|
+
return contextFailure(inventory, 'LIFECYCLE_PATH_INVALID', pathState.message);
|
|
345
|
+
}
|
|
346
|
+
if (inventory.family === 'code') {
|
|
347
|
+
const code = resolveCodeContext(inventory, options);
|
|
348
|
+
const selected = attachArtifactSelection(code, pathState, options);
|
|
349
|
+
if (selected.status !== 'ready' || !selected.selection)
|
|
350
|
+
return selected;
|
|
351
|
+
if (selected.selection.disposition === 'reuse')
|
|
352
|
+
return selected;
|
|
353
|
+
if (selected.codeMode?.mode === 'error') {
|
|
354
|
+
return {
|
|
355
|
+
...selected,
|
|
356
|
+
status: 'ready',
|
|
357
|
+
codeMode: {
|
|
358
|
+
...selected.codeMode,
|
|
359
|
+
mode: 'init',
|
|
360
|
+
message: 'A new implementation round was requested.'
|
|
361
|
+
},
|
|
362
|
+
error: null
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
if (selected.codeMode?.mode === 'refused') {
|
|
366
|
+
return {
|
|
367
|
+
...selected,
|
|
368
|
+
status: 'ready',
|
|
369
|
+
codeMode: {
|
|
370
|
+
...selected.codeMode,
|
|
371
|
+
mode: 'init',
|
|
372
|
+
message: 'A new implementation round was requested.'
|
|
373
|
+
},
|
|
374
|
+
error: null
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
return selected;
|
|
378
|
+
}
|
|
246
379
|
const required = REQUIRED_INPUT[inventory.family];
|
|
247
380
|
const inputs = [];
|
|
248
381
|
if (required) {
|
|
@@ -250,98 +383,71 @@ function resolveArtifactContext(taskRef, family, options = {}) {
|
|
|
250
383
|
if (input.status === 'failed' || !input.latest) {
|
|
251
384
|
return { ...inventory, status: 'failed', inputs, codeMode: null, error: { code: 'ARTIFACT_INPUT_MISSING', message: `latest ${required} artifact is required` } };
|
|
252
385
|
}
|
|
253
|
-
const qualificationError = qualificationErrorForArtifact(input.latest);
|
|
254
|
-
if (qualificationError)
|
|
255
|
-
return { ...inventory, status: 'failed', inputs, codeMode: null, error: { code: 'ARTIFACT_REFERENCE_INVALID', message: qualificationError } };
|
|
256
386
|
inputs.push(input.latest);
|
|
257
387
|
}
|
|
258
388
|
const optional = OPTIONAL_CONTEXT[inventory.family];
|
|
259
389
|
if (optional) {
|
|
260
390
|
const context = inspectTaskArtifacts(taskRef, optional.family, options);
|
|
261
391
|
if (context.status === 'ready' && context.latest) {
|
|
262
|
-
if (optional.requireReference && !context.reviewedInput) {
|
|
263
|
-
return { ...inventory, status: 'failed', inputs, codeMode: null, error: { code: 'ARTIFACT_REFERENCE_INVALID', message: `${context.latest.name} has no valid reviewed input` } };
|
|
264
|
-
}
|
|
265
|
-
const qualificationError = qualificationErrorForArtifact(context.latest);
|
|
266
|
-
if (qualificationError && !QUALIFICATION_RECOVERY_OPTIONAL_CONTEXT_CONSUMERS.has(inventory.family)) {
|
|
267
|
-
return { ...inventory, status: 'failed', inputs, codeMode: null, error: { code: 'ARTIFACT_REFERENCE_INVALID', message: qualificationError } };
|
|
268
|
-
}
|
|
269
392
|
inputs.push(context.latest);
|
|
270
393
|
}
|
|
271
394
|
}
|
|
272
|
-
|
|
273
|
-
}
|
|
274
|
-
function qualificationErrorForArtifact(artifact) {
|
|
275
|
-
try {
|
|
276
|
-
const taskContent = fs.readFileSync(path.join(path.dirname(artifact.path), 'task.md'), 'utf8');
|
|
277
|
-
const artifactContent = fs.readFileSync(artifact.path, 'utf8');
|
|
278
|
-
const result = validateQualificationAudit(taskContent, artifactContent);
|
|
279
|
-
return result.ok ? null : `${result.code}: ${result.message}`;
|
|
280
|
-
}
|
|
281
|
-
catch (error) {
|
|
282
|
-
return error instanceof Error ? error.message : String(error);
|
|
395
|
+
if (inventory.family === 'manual-validation' || inventory.family === 'validation-run') {
|
|
396
|
+
return { ...inventory, inputs, codeMode: null };
|
|
283
397
|
}
|
|
398
|
+
return attachArtifactSelection({ ...inventory, inputs, codeMode: null }, pathState, options);
|
|
284
399
|
}
|
|
285
400
|
function resolveCodeContext(inventory, options) {
|
|
286
401
|
const taskRef = inventory.requestRef;
|
|
287
|
-
const
|
|
288
|
-
if (
|
|
289
|
-
return contextFailure(inventory, '
|
|
290
|
-
const
|
|
402
|
+
const pathState = lifecyclePathState(inventory.taskDir);
|
|
403
|
+
if (pathState.status !== 'valid')
|
|
404
|
+
return contextFailure(inventory, 'LIFECYCLE_PATH_INVALID', pathState.message);
|
|
405
|
+
const inputFamily = pathState.decision.path === 'streamlined' ? 'analysis' : 'plan';
|
|
406
|
+
const source = inspectTaskArtifacts(taskRef, inputFamily, options);
|
|
407
|
+
if (source.status === 'failed' || !source.latest)
|
|
408
|
+
return contextFailure(inventory, 'ARTIFACT_INPUT_MISSING', `latest ${inputFamily} artifact is required`);
|
|
291
409
|
const reviewCode = inspectTaskArtifacts(taskRef, 'review-code', options);
|
|
292
410
|
const latestCode = inventory.latest;
|
|
293
411
|
const codeMax = latestCode?.round ?? 0;
|
|
294
412
|
const reviewMax = reviewCode.latest?.round ?? 0;
|
|
295
|
-
const inputs = [
|
|
296
|
-
const planQualificationError = qualificationErrorForArtifact(plan.latest);
|
|
297
|
-
if (planQualificationError)
|
|
298
|
-
return contextFailure(inventory, 'ARTIFACT_REFERENCE_INVALID', planQualificationError);
|
|
413
|
+
const inputs = [source.latest];
|
|
299
414
|
if (latestCode) {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
415
|
+
try {
|
|
416
|
+
const taskContent = fs.readFileSync(path.join(inventory.taskDir, 'task.md'), 'utf8');
|
|
417
|
+
if (hasOpenArtifactRound(taskContent, 'code', latestCode.round)) {
|
|
418
|
+
const review = reviewCode.latest;
|
|
419
|
+
const openInputs = review ? [...inputs, review] : inputs;
|
|
420
|
+
const openVerdict = review ? parseVerdict(review.path) : null;
|
|
421
|
+
const openRow = locateActivityLog(taskContent)
|
|
422
|
+
? startedBackedRows(pairEntries(locateActivityLog(taskContent).entries))
|
|
423
|
+
.find((row) => row.step.startsWith(`Code Task (Round ${latestCode.round}`) && !row.done)
|
|
424
|
+
: null;
|
|
425
|
+
const decisionInput = /, decision (II-[1-9]\d*)\)$/u.exec(openRow?.step ?? '')?.[1] ?? null;
|
|
426
|
+
const fixInput = /, fix for (review-code(?:-r\d+)?\.md)\)$/u.exec(openRow?.step ?? '')?.[1] ?? null;
|
|
427
|
+
const mode = decisionInput ? 'decision'
|
|
428
|
+
: fixInput || (openVerdict?.ok && openVerdict.verdict === 'Changes Requested') ? 'fix'
|
|
429
|
+
: 'init';
|
|
430
|
+
return withCodeMode(inventory, openInputs, 'ready', mode, codeMax, reviewMax, openVerdict?.ok ? openVerdict.verdict : null, fixInput ?? review?.name ?? null, `Resuming open code round ${latestCode.round}.`, decisionInput);
|
|
431
|
+
}
|
|
312
432
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
return contextFailure(inventory, 'ARTIFACT_VERDICT_INVALID', `${verdict.code}: ${verdict.message}`, reviewPlan.latest.name);
|
|
316
|
-
if (verdict.verdict !== 'Approved') {
|
|
317
|
-
return contextFailure(inventory, 'ARTIFACT_REFERENCE_INVALID', `latest ${reviewPlan.latest.name} is not approved`, reviewPlan.latest.name);
|
|
433
|
+
catch (error) {
|
|
434
|
+
return contextFailure(inventory, 'ARTIFACT_REFERENCE_INVALID', error instanceof Error ? error.message : String(error));
|
|
318
435
|
}
|
|
436
|
+
}
|
|
437
|
+
if (!latestCode) {
|
|
319
438
|
return withCodeMode(inventory, inputs, 'ready', 'init', codeMax, reviewMax, null, null, 'No prior code artifact. Starting initial implementation (round 1 -> code.md).');
|
|
320
439
|
}
|
|
321
440
|
const codePlanInput = resolveCodePlanInput(inventory.taskDir, latestCode);
|
|
322
441
|
if (!codePlanInput) {
|
|
323
|
-
return
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
if (reviewPlan.latest) {
|
|
329
|
-
const qualificationError = qualificationErrorForArtifact(reviewPlan.latest);
|
|
330
|
-
if (qualificationError)
|
|
331
|
-
return contextFailure(inventory, 'ARTIFACT_REFERENCE_INVALID', qualificationError, reviewPlan.latest.name);
|
|
332
|
-
}
|
|
333
|
-
if (reviewPlan.latest && reviewPlan.reviewedInput?.name === plan.latest.name) {
|
|
334
|
-
const verdict = parseVerdict(reviewPlan.latest.path);
|
|
335
|
-
if (!verdict.ok)
|
|
336
|
-
return contextFailure(inventory, 'ARTIFACT_VERDICT_INVALID', `${verdict.code}: ${verdict.message}`, reviewPlan.latest.name);
|
|
337
|
-
const reviewedPlanSha256 = sha256File(reviewPlan.reviewedInput.path);
|
|
338
|
-
if (verdict.ok && verdict.verdict === 'Approved' && (codePlanInput.input.name !== plan.latest.name || codePlanInput.inputSha256 !== reviewedPlanSha256)) {
|
|
339
|
-
return withCodeMode(inventory, inputs, 'ready', 'init', codeMax, reviewMax, verdict.verdict, reviewPlan.latest.name, `Latest ${reviewPlan.latest.name} approves plan content not captured by the latest code input receipt. Entering replan-driven init.`);
|
|
340
|
-
}
|
|
442
|
+
return withCodeMode(inventory, inputs, 'ready', 'error', codeMax, reviewMax, null, null, `Code input receipt for ${latestCode.name} is unavailable.`);
|
|
443
|
+
}
|
|
444
|
+
const currentInputSha256 = sha256File(source.latest.path);
|
|
445
|
+
if (codePlanInput.input.name !== source.latest.name || codePlanInput.inputSha256 !== currentInputSha256) {
|
|
446
|
+
return withCodeMode(inventory, inputs, 'ready', 'init', codeMax, reviewMax, null, null, `Latest ${source.latest.name} is not captured by the latest code input receipt. Entering replan-driven init.`);
|
|
341
447
|
}
|
|
342
448
|
if (!reviewCode.latest || reviewCode.reviewedInput?.name !== latestCode.name) {
|
|
343
449
|
const expected = reviewCode.next?.name ?? artifactName('review-code', reviewMax + 1);
|
|
344
|
-
return
|
|
450
|
+
return withCodeMode(inventory, inputs, 'ready', 'error', codeMax, reviewMax, null, expected, `${expected} is required before another code round`);
|
|
345
451
|
}
|
|
346
452
|
const review = reviewCode.latest;
|
|
347
453
|
if (!review)
|
|
@@ -349,18 +455,31 @@ function resolveCodeContext(inventory, options) {
|
|
|
349
455
|
const verdict = parseVerdict(review.path);
|
|
350
456
|
if (!verdict.ok)
|
|
351
457
|
return contextFailure(inventory, 'ARTIFACT_VERDICT_INVALID', `${verdict.code}: ${verdict.message}`, review.name);
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
458
|
+
const decision = resolveDecisionImplementationInput(inventory, review);
|
|
459
|
+
if ('error' in decision)
|
|
460
|
+
return contextFailure(inventory, 'ARTIFACT_REFERENCE_INVALID', decision.error, review.name);
|
|
461
|
+
if (decision.input) {
|
|
462
|
+
return withCodeMode(inventory, [...inputs, review], 'ready', 'decision', codeMax, reviewMax, verdict.verdict, review.name, `Implementation input ${decision.input.id} requires a new code round.`, decision.input.id, decision.input.ledgerId, decision.input.decisionEvidence);
|
|
463
|
+
}
|
|
464
|
+
try {
|
|
465
|
+
const taskContent = fs.readFileSync(path.join(inventory.taskDir, 'task.md'), 'utf8');
|
|
466
|
+
const rework = parseReworkIntentDocument(taskContent);
|
|
467
|
+
const reviewSha256 = sha256File(review.path);
|
|
468
|
+
if (rework.ok && rework.intents.some((intent) => intent.status === 'pending'
|
|
469
|
+
&& intent.target === 'code'
|
|
470
|
+
&& intent.sourceArtifact === review.name
|
|
471
|
+
&& intent.sourceSha256 === reviewSha256)) {
|
|
472
|
+
return withCodeMode(inventory, [...inputs, review], 'ready', 'fix', codeMax, reviewMax, verdict.verdict, review.name, `Pending rework evidence requires a new code round after ${review.name}.`);
|
|
358
473
|
}
|
|
359
474
|
}
|
|
360
|
-
|
|
361
|
-
return
|
|
362
|
-
|
|
363
|
-
|
|
475
|
+
catch (error) {
|
|
476
|
+
return contextFailure(inventory, 'ARTIFACT_REFERENCE_INVALID', error instanceof Error ? error.message : String(error), review.name);
|
|
477
|
+
}
|
|
478
|
+
if (verdict.verdict === 'Approved') {
|
|
479
|
+
return withCodeMode(inventory, [...inputs, review], 'ready', 'refused', codeMax, reviewMax, verdict.verdict, review.name, `Latest ${review.name} verdict is Approved with no findings. Nothing to fix. Run /commit to proceed.`);
|
|
480
|
+
}
|
|
481
|
+
if (verdict.verdict === 'Rejected') {
|
|
482
|
+
return withCodeMode(inventory, [...inputs, review], 'ready', 'refused', codeMax, reviewMax, verdict.verdict, review.name, `Latest ${review.name} verdict is Rejected. Re-plan before re-running code-task.`);
|
|
364
483
|
}
|
|
365
484
|
return withCodeMode(inventory, [...inputs, review], 'ready', 'fix', codeMax, reviewMax, verdict.verdict, review.name, `Latest ${review.name} requires or permits fixes. Entering fix mode.`);
|
|
366
485
|
}
|
|
@@ -439,8 +558,21 @@ export function validateArtifactPublication(taskDir, family, name) {
|
|
|
439
558
|
const error = assertWritableInventory(inventory);
|
|
440
559
|
if (error)
|
|
441
560
|
return error;
|
|
442
|
-
if (inventory.next?.name === name
|
|
561
|
+
if (inventory.next?.name === name)
|
|
443
562
|
return null;
|
|
563
|
+
const existing = inventory.artifacts.find((artifact) => artifact.name === name);
|
|
564
|
+
if (existing) {
|
|
565
|
+
try {
|
|
566
|
+
const task = fs.readFileSync(path.join(taskDir, 'task.md'), 'utf8');
|
|
567
|
+
if (hasCompletedArtifactRound(task, family, existing.round, name)) {
|
|
568
|
+
return { code: 'ARTIFACT_IDENTITY_INVALID', message: `completed artifact '${name}' is immutable` };
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
catch (error) {
|
|
572
|
+
return { code: 'ARTIFACT_SELECTION_INDETERMINATE', message: error instanceof Error ? error.message : String(error) };
|
|
573
|
+
}
|
|
574
|
+
return null;
|
|
575
|
+
}
|
|
444
576
|
return { code: 'ARTIFACT_IDENTITY_INVALID', message: `artifact '${name}' is not a current or next ${family} slot` };
|
|
445
577
|
}
|
|
446
578
|
function buildArtifactLinkSection(content, artifact) {
|
|
@@ -6,7 +6,6 @@ import { parseArtifactName } from './artifact-name.js';
|
|
|
6
6
|
import { scanVisibleMarkdown } from './markdown.js';
|
|
7
7
|
import { getArtifactSchema, renderArtifactSkeleton } from './artifact-schema.js';
|
|
8
8
|
import { withTaskExecutionLock } from './task-execution-lock.js';
|
|
9
|
-
import { buildQualificationAudit, expectedQualificationRelations, parseTaskQualification, renderQualificationAudit } from './qualification-audit.js';
|
|
10
9
|
function sha256Content(content) {
|
|
11
10
|
return createHash('sha256').update(content, 'utf8').digest('hex');
|
|
12
11
|
}
|
|
@@ -167,10 +166,44 @@ function inspectArtifactPatterns(content, schema) {
|
|
|
167
166
|
diagnostics
|
|
168
167
|
};
|
|
169
168
|
}
|
|
170
|
-
function
|
|
169
|
+
function inspectArtifactStateCheck(content, schema, scopeToStateCheckSection) {
|
|
170
|
+
const stateCheck = schema.sections.find((section) => section.id === 'state-check');
|
|
171
|
+
if (!stateCheck || schema.stateCheckPatterns.length === 0) {
|
|
172
|
+
return { ok: true, family: schema.family, semanticDigest: canonicalSemanticDigest(content), diagnostics: [] };
|
|
173
|
+
}
|
|
174
|
+
if (!scopeToStateCheckSection) {
|
|
175
|
+
const diagnostics = schema.stateCheckPatterns
|
|
176
|
+
.filter((pattern) => !new RegExp(pattern, 'm').test(content))
|
|
177
|
+
.map((pattern) => diagnostic('ARTIFACT_STATE_CHECK_PATTERN_MISSING', `artifact is missing required state-check command '${pattern}'`, stateCheck.id, null));
|
|
178
|
+
return {
|
|
179
|
+
ok: diagnostics.length === 0,
|
|
180
|
+
family: schema.family,
|
|
181
|
+
semanticDigest: canonicalSemanticDigest(content),
|
|
182
|
+
diagnostics
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
const scanned = scanVisibleMarkdown(content);
|
|
186
|
+
const headings = scanned.headings.filter((heading) => (heading.level === 2 && sectionHeadings(schema, stateCheck).includes(heading.text)));
|
|
187
|
+
if (headings.length !== 1) {
|
|
188
|
+
return { ok: true, family: schema.family, semanticDigest: canonicalSemanticDigest(content), diagnostics: [] };
|
|
189
|
+
}
|
|
190
|
+
const bodyBounds = sectionBodyBounds(content, headings[0], scanned);
|
|
191
|
+
const body = content.slice(bodyBounds.start, bodyBounds.end);
|
|
192
|
+
const diagnostics = schema.stateCheckPatterns
|
|
193
|
+
.filter((pattern) => !new RegExp(pattern, 'm').test(body))
|
|
194
|
+
.map((pattern) => diagnostic('ARTIFACT_STATE_CHECK_PATTERN_MISSING', `state-check section is missing required command '${pattern}'`, stateCheck.id, lineNumber(content, headings[0].start)));
|
|
195
|
+
return {
|
|
196
|
+
ok: diagnostics.length === 0,
|
|
197
|
+
family: schema.family,
|
|
198
|
+
semanticDigest: canonicalSemanticDigest(content),
|
|
199
|
+
diagnostics
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function inspectArtifactContract(content, schema, options = {}) {
|
|
171
203
|
const structure = inspectArtifactStructure(content, schema);
|
|
204
|
+
const stateCheck = inspectArtifactStateCheck(content, schema, options.scopeStateCheckSection === true);
|
|
172
205
|
const patterns = inspectArtifactPatterns(content, schema);
|
|
173
|
-
const diagnostics = [...structure.diagnostics, ...patterns.diagnostics];
|
|
206
|
+
const diagnostics = [...structure.diagnostics, ...stateCheck.diagnostics, ...patterns.diagnostics];
|
|
174
207
|
return {
|
|
175
208
|
ok: diagnostics.length === 0,
|
|
176
209
|
family: schema.family,
|
|
@@ -194,27 +227,6 @@ function artifactRound(family, artifact) {
|
|
|
194
227
|
const identity = parseArtifactName(artifact);
|
|
195
228
|
return identity?.family === family ? identity.round : null;
|
|
196
229
|
}
|
|
197
|
-
function qualificationAuditForInit(request) {
|
|
198
|
-
let taskContent;
|
|
199
|
-
try {
|
|
200
|
-
taskContent = readStableFileSync(path.join(request.taskDir, 'task.md'), { maxBytes: 1024 * 1024 }).bytes.toString('utf8');
|
|
201
|
-
}
|
|
202
|
-
catch (error) {
|
|
203
|
-
return { ok: false, code: 'ARTIFACT_INIT_TASK_READ_FAILED', message: error instanceof Error ? error.message : String(error) };
|
|
204
|
-
}
|
|
205
|
-
const qualification = parseTaskQualification(taskContent);
|
|
206
|
-
if (!qualification.ok)
|
|
207
|
-
return qualification;
|
|
208
|
-
if (!qualification.qualification.present)
|
|
209
|
-
return { ok: true, content: '' };
|
|
210
|
-
const relations = expectedQualificationRelations(taskContent, request.family);
|
|
211
|
-
if (!relations.ok)
|
|
212
|
-
return relations;
|
|
213
|
-
const audit = buildQualificationAudit(taskContent, { upstreamRelations: relations.relations ?? [] });
|
|
214
|
-
if (!audit.ok)
|
|
215
|
-
return audit;
|
|
216
|
-
return { ok: true, content: `\n## 资格审计\n\n${renderQualificationAudit(audit.audit)}\n` };
|
|
217
|
-
}
|
|
218
230
|
function initializeArtifactSkeleton(request) {
|
|
219
231
|
const schema = getArtifactSchema(request.family);
|
|
220
232
|
if (!schema)
|
|
@@ -244,10 +256,7 @@ function initializeArtifactSkeleton(request) {
|
|
|
244
256
|
artifact: request.artifact,
|
|
245
257
|
...(request.locale ? { locale: request.locale } : {})
|
|
246
258
|
});
|
|
247
|
-
const
|
|
248
|
-
if (!audit.ok)
|
|
249
|
-
return resultFailure(audit.code, audit.message);
|
|
250
|
-
const content = `${skeleton.trimEnd()}${audit.content}`;
|
|
259
|
+
const content = skeleton.trimEnd();
|
|
251
260
|
const tempPath = path.join(request.taskDir, `.${request.artifact}.init-${process.pid}-${Date.now()}.tmp`);
|
|
252
261
|
try {
|
|
253
262
|
fs.writeFileSync(tempPath, content, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
|
|
@@ -36,7 +36,10 @@ function validateReceiptShape(event, output, input) {
|
|
|
36
36
|
if (!outputIdentity || !inputIdentity)
|
|
37
37
|
throw new ArtifactReceiptError(`receipt artifact identity is invalid: ${output} -> ${input}`);
|
|
38
38
|
const shape = RECEIPT_SHAPES[event];
|
|
39
|
-
|
|
39
|
+
const inputMatches = event === 'code.completed'
|
|
40
|
+
? inputIdentity.family === 'analysis' || inputIdentity.family === 'plan'
|
|
41
|
+
: inputIdentity.family === shape.input;
|
|
42
|
+
if (outputIdentity.family !== shape.output || !inputMatches) {
|
|
40
43
|
throw new ArtifactReceiptError(`receipt event '${event}' does not match ${output} -> ${input}`);
|
|
41
44
|
}
|
|
42
45
|
}
|
|
@@ -7,6 +7,7 @@ const LOCAL_SECTIONS = {
|
|
|
7
7
|
['impact', '影响评估', 'Impact Assessment'],
|
|
8
8
|
['risks', '技术风险', 'Technical Risks'],
|
|
9
9
|
['effort', '工作量和复杂度评估', 'Effort and Complexity Assessment'],
|
|
10
|
+
['flow-decision', '流程裁定', 'Flow Decision'],
|
|
10
11
|
['state-check', '状态核对', 'State Check']
|
|
11
12
|
],
|
|
12
13
|
plan: [
|
|
@@ -80,21 +81,21 @@ function buildSections(family) {
|
|
|
80
81
|
marker: `artifact-section:${family}:${id}`
|
|
81
82
|
}));
|
|
82
83
|
}
|
|
84
|
+
const TASK_SNAPSHOT_PATTERN = 'agent-infra-internal[ \\t]+task-snapshot\\b';
|
|
83
85
|
const ARTIFACT_SCHEMAS = [
|
|
84
|
-
{ family: 'analysis', title: { zh: '需求分析报告', en: 'Requirements Analysis' }, sections: buildSections('analysis'), requiredPatterns: [
|
|
85
|
-
{ family: 'review-analysis', title: { zh: '需求分析审查报告', en: 'Analysis Review Report' }, sections: buildSections('review-analysis'), requiredPatterns: ['^### (?:审查决定|Approval Decision)$'
|
|
86
|
-
{ family: 'plan', title: { zh: '技术方案', en: 'Technical Plan' }, sections: buildSections('plan'), requiredPatterns: [
|
|
87
|
-
{ family: 'review-plan', title: { zh: '技术方案审查报告', en: 'Plan Review Report' }, sections: buildSections('review-plan'), requiredPatterns: ['^### (?:审查决定|Approval Decision)$'
|
|
88
|
-
{ family: 'code', title: { zh: '实现报告', en: 'Implementation Report' }, sections: buildSections('code'), requiredPatterns: [
|
|
86
|
+
{ family: 'analysis', title: { zh: '需求分析报告', en: 'Requirements Analysis' }, sections: buildSections('analysis'), stateCheckPatterns: [TASK_SNAPSHOT_PATTERN], requiredPatterns: [] },
|
|
87
|
+
{ family: 'review-analysis', title: { zh: '需求分析审查报告', en: 'Analysis Review Report' }, sections: buildSections('review-analysis'), stateCheckPatterns: [TASK_SNAPSHOT_PATTERN], requiredPatterns: ['^### (?:审查决定|Approval Decision)$'] },
|
|
88
|
+
{ family: 'plan', title: { zh: '技术方案', en: 'Technical Plan' }, sections: buildSections('plan'), stateCheckPatterns: [TASK_SNAPSHOT_PATTERN], requiredPatterns: [] },
|
|
89
|
+
{ family: 'review-plan', title: { zh: '技术方案审查报告', en: 'Plan Review Report' }, sections: buildSections('review-plan'), stateCheckPatterns: [TASK_SNAPSHOT_PATTERN], requiredPatterns: ['^### (?:审查决定|Approval Decision)$'] },
|
|
90
|
+
{ family: 'code', title: { zh: '实现报告', en: 'Implementation Report' }, sections: buildSections('code'), stateCheckPatterns: [TASK_SNAPSHOT_PATTERN], requiredPatterns: [] },
|
|
89
91
|
{ family: 'review-code', title: { zh: '代码审查报告', en: 'Code Review Report' }, sections: buildSections('review-code'), requiredPatterns: [
|
|
90
92
|
'^### (?:审查决定|Approval Decision)$',
|
|
91
|
-
'^\\$ ',
|
|
92
93
|
'^- \\*\\*(?:总体结论|Overall Verdict)\\*\\*[::]\\s*(?:通过|需要修改|拒绝|Approved|Changes Requested|Rejected)\\s*$',
|
|
93
94
|
'^- \\*\\*(?:审查基线提交|Review Baseline Commit)\\*\\*[::]\\s*\\S',
|
|
94
95
|
'^- \\*\\*(?:审查差异基线|Reviewed Diff Base)\\*\\*[::]\\s*[0-9a-f]{40,64}\\s*$',
|
|
95
96
|
'^- \\*\\*(?:审查差异指纹|Reviewed Diff Fingerprint)\\*\\*[::]\\s*sha256:[0-9a-f]{64}\\s*$',
|
|
96
97
|
'^- \\*\\*(?:审查快照树|Reviewed Snapshot Tree)\\*\\*[::]\\s*[0-9a-f]{40,64}\\s*$'
|
|
97
|
-
] }
|
|
98
|
+
], stateCheckPatterns: [TASK_SNAPSHOT_PATTERN] }
|
|
98
99
|
];
|
|
99
100
|
function getArtifactSchema(family) {
|
|
100
101
|
return ARTIFACT_SCHEMAS.find((schema) => schema.family === family) ?? null;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
function selectArtifactDisposition(request) {
|
|
2
|
+
if (request.open) {
|
|
3
|
+
return {
|
|
4
|
+
disposition: 'resume', reasonCode: 'open-round', artifact: request.next,
|
|
5
|
+
writeRequired: true
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
if (!request.latest) {
|
|
9
|
+
return {
|
|
10
|
+
disposition: 'create',
|
|
11
|
+
reasonCode: request.next.round === 1 ? 'no-history' : 'invalidated-history',
|
|
12
|
+
artifact: request.next, writeRequired: true
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
const reasonCode = request.hasChangeEvidence ? 'change-evidence' : 'history-matched';
|
|
16
|
+
const reuse = reasonCode === 'history-matched';
|
|
17
|
+
return {
|
|
18
|
+
disposition: reuse ? 'reuse' : 'create',
|
|
19
|
+
reasonCode,
|
|
20
|
+
artifact: reuse ? request.latest : request.next,
|
|
21
|
+
writeRequired: !reuse
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export { selectArtifactDisposition };
|
|
25
|
+
//# sourceMappingURL=artifact-selection.js.map
|