@fitlab-ai/agent-infra 0.11.2 → 0.11.4
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 +7 -2
- package/dist/lib/agent-clients/adapter.js +3 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +234 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +8 -35
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +1 -34
- package/dist/lib/agent-clients/adapters/codex.js +10 -0
- package/dist/lib/internal/cli-route-inventory.js +1 -1
- package/dist/lib/internal/manual-validation.js +22 -27
- package/dist/lib/internal/sandbox-control.js +1 -43
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-finalization.js +41 -1
- package/dist/lib/internal/task-ledger.js +13 -5
- package/dist/lib/internal/task-lifecycle.js +1 -1
- package/dist/lib/internal/task-operation-registry.js +7 -2
- package/dist/lib/platform/completion-backfill.js +63 -17
- package/dist/lib/platform/issue-comments.js +2 -31
- package/dist/lib/platform/pr-summary.js +7 -2
- package/dist/lib/sandbox/control/client.js +3 -44
- package/dist/lib/sandbox/control/executor.js +14 -79
- package/dist/lib/sandbox/control/protocol.js +5 -64
- package/dist/lib/sandbox/control/server.js +49 -343
- package/dist/lib/sandbox/control/state.js +1 -7
- package/dist/lib/sandbox/control/task-view.js +1 -2
- package/dist/lib/sandbox/task-cleanup.js +13 -20
- 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 +1 -26
- package/dist/lib/task/artifact-receipts.js +4 -1
- package/dist/lib/task/artifact-schema.js +1 -0
- 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 +37 -38
- package/dist/lib/task/control-recovery.js +3 -42
- package/dist/lib/task/decision-intents.js +12 -0
- package/dist/lib/task/delegation-receipts.js +2 -2
- package/dist/lib/task/delivery.js +1 -43
- package/dist/lib/task/events.js +80 -64
- package/dist/lib/task/finalization-artifacts.js +1 -3
- package/dist/lib/task/finalization-state.js +1 -1
- package/dist/lib/task/finalization.js +319 -234
- package/dist/lib/task/ledger-intents.js +157 -15
- package/dist/lib/task/lifecycle-path.js +75 -0
- package/dist/lib/task/lifecycle.js +11 -1
- package/dist/lib/task/local-artifact-finalization.js +1 -20
- package/dist/lib/task/manual-validation-receipt.js +8 -3
- package/dist/lib/task/orchestration.js +29 -13
- package/dist/lib/task/review-finalization.js +3 -16
- package/dist/lib/task/rework-intent.js +70 -4
- package/dist/lib/task/verification-engine.js +20 -30
- package/dist/lib/task/verification.js +1 -0
- package/dist/lib/task/workflow-warning-intents.js +21 -6
- package/dist/lib/task/workspace-snapshot.js +7 -2
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapter.ts +46 -0
- package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +297 -0
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +13 -33
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +1 -44
- package/lib/agent-clients/adapters/codex.ts +10 -0
- package/lib/internal/cli-route-inventory.ts +1 -1
- package/lib/internal/manual-validation.ts +22 -26
- package/lib/internal/sandbox-control.ts +0 -42
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-finalization.ts +40 -1
- package/lib/internal/task-ledger.ts +12 -5
- package/lib/internal/task-lifecycle.ts +2 -2
- package/lib/internal/task-operation-registry.ts +8 -4
- package/lib/platform/completion-backfill.ts +78 -16
- package/lib/platform/issue-comments.ts +1 -35
- package/lib/platform/pr-summary.ts +10 -2
- package/lib/sandbox/control/client.ts +5 -68
- package/lib/sandbox/control/executor.ts +14 -81
- package/lib/sandbox/control/protocol.ts +7 -83
- package/lib/sandbox/control/server.ts +47 -362
- package/lib/sandbox/control/state.ts +1 -9
- package/lib/sandbox/control/task-view.ts +1 -2
- package/lib/sandbox/task-cleanup.ts +12 -20
- package/lib/task/artifact-command.ts +14 -4
- package/lib/task/artifact-lifecycle.ts +236 -85
- package/lib/task/artifact-operations.ts +1 -26
- package/lib/task/artifact-receipts.ts +4 -1
- package/lib/task/artifact-schema.ts +1 -0
- 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 +41 -48
- package/lib/task/control-recovery.ts +3 -43
- package/lib/task/decision-intents.ts +9 -0
- package/lib/task/delegation-receipts.ts +2 -2
- package/lib/task/delivery.ts +1 -40
- package/lib/task/events.ts +82 -64
- package/lib/task/finalization-artifacts.ts +1 -3
- package/lib/task/finalization-state.ts +1 -1
- package/lib/task/finalization.ts +310 -216
- package/lib/task/ledger-intents.ts +155 -15
- package/lib/task/lifecycle-path.ts +98 -0
- package/lib/task/lifecycle.ts +15 -2
- package/lib/task/local-artifact-finalization.ts +2 -25
- package/lib/task/manual-validation-receipt.ts +8 -2
- package/lib/task/orchestration.ts +56 -27
- package/lib/task/review-finalization.ts +3 -16
- package/lib/task/rework-intent.ts +75 -5
- package/lib/task/verification-engine.ts +19 -30
- package/lib/task/verification.ts +2 -1
- package/lib/task/workflow-warning-intents.ts +18 -6
- package/lib/task/workspace-snapshot.ts +3 -2
- package/package.json +1 -1
- package/templates/.agents/rules/lifecycle-orchestration.en.md +19 -5
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +19 -5
- 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 +11 -4
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +11 -4
- 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 +1 -1
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +2 -2
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/complete-task/SKILL.en.md +7 -8
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +7 -8
- 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-code/SKILL.en.md +5 -1
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -1
- 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/run-manual-validation/SKILL.en.md +2 -2
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/run-task/SKILL.en.md +9 -8
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -8
- 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/sandbox/control/finalization-response.js +0 -35
- package/dist/lib/sandbox/control/task-workflow.js +0 -74
- package/dist/lib/sandbox/control/workflow-executor.js +0 -142
- package/dist/lib/task/lifecycle-recovery.js +0 -549
- package/dist/lib/task/recovery-warning.js +0 -19
- package/dist/lib/task/workflow-command.js +0 -21
- package/dist/lib/task/workflow-dispatch.js +0 -213
- package/lib/sandbox/control/finalization-response.ts +0 -37
- package/lib/sandbox/control/task-workflow.ts +0 -95
- package/lib/sandbox/control/workflow-executor.ts +0 -156
- package/lib/task/lifecycle-recovery.ts +0 -691
- package/lib/task/recovery-warning.ts +0 -21
- package/lib/task/workflow-command.ts +0 -22
- package/lib/task/workflow-dispatch.ts +0 -214
|
@@ -13,11 +13,10 @@ function receiptIdentity(value, generation, requestId) {
|
|
|
13
13
|
if (!binding || typeof binding !== 'object' || Array.isArray(binding))
|
|
14
14
|
return null;
|
|
15
15
|
const controlBinding = binding;
|
|
16
|
-
if (receipt.version !==
|
|
16
|
+
if (receipt.version !== 4 || typeof receipt.receiptId !== 'string' || receipt.receiptId.length === 0
|
|
17
17
|
|| !Number.isSafeInteger(receipt.revision) || receipt.revision < 0
|
|
18
18
|
|| receipt.lifecycle !== 'done'
|
|
19
19
|
|| !['pending', 'done', 'skipped'].includes(String(receipt.summary))
|
|
20
|
-
|| !['pending', 'done', 'skipped'].includes(String(receipt.postSummaryVerification))
|
|
21
20
|
|| controlBinding.generation !== generation
|
|
22
21
|
|| typeof controlBinding.requestId !== 'string'
|
|
23
22
|
|| !/^[a-f0-9-]{16,64}$/u.test(controlBinding.requestId)
|
|
@@ -8,7 +8,6 @@ import { enumerateAllTaskDirs } from '../task/resolve-ref.js';
|
|
|
8
8
|
import { resolveSandboxCleanupTarget } from './workspace-identity.js';
|
|
9
9
|
import { readSandboxControlManifest } from './control/lifecycle.js';
|
|
10
10
|
import { readJsonFile, readSandboxControlStatus } from './control/state.js';
|
|
11
|
-
import { finalizationTerminalResponse } from './control/finalization-response.js';
|
|
12
11
|
const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
|
|
13
12
|
function matchingControlManifest(repoRoot, controlRoot, taskId, binding) {
|
|
14
13
|
if (!controlRootIdentity(controlRoot))
|
|
@@ -77,11 +76,9 @@ function canonicalPath(input) {
|
|
|
77
76
|
return path.resolve(input);
|
|
78
77
|
}
|
|
79
78
|
}
|
|
80
|
-
function taskFinalizationReceiptComplete(receipt, taskId
|
|
79
|
+
function taskFinalizationReceiptComplete(receipt, taskId) {
|
|
81
80
|
return receipt.taskId === taskId
|
|
82
|
-
&& taskFinalizationReceiptState(receipt) === 'complete'
|
|
83
|
-
&& receipt.controlBinding?.generation === binding.generation
|
|
84
|
-
&& receipt.controlBinding.requestId === binding.requestId;
|
|
81
|
+
&& taskFinalizationReceiptState(receipt) === 'complete';
|
|
85
82
|
}
|
|
86
83
|
function terminalControlBindingEvidence(repoRootInput, controlRootInput, taskId, binding) {
|
|
87
84
|
const repoRoot = canonicalPath(repoRootInput);
|
|
@@ -105,26 +102,22 @@ function terminalControlBindingEvidence(repoRootInput, controlRootInput, taskId,
|
|
|
105
102
|
return false;
|
|
106
103
|
}
|
|
107
104
|
const receipt = readTaskFinalizationReceipt(repoRoot, taskId);
|
|
108
|
-
if (!receipt || !taskFinalizationReceiptComplete(receipt, taskId
|
|
105
|
+
if (!receipt || !taskFinalizationReceiptComplete(receipt, taskId)) {
|
|
109
106
|
return false;
|
|
110
107
|
}
|
|
111
|
-
const status = readSandboxControlStatus(manifest.publicStatusDir);
|
|
112
|
-
const viewReceipt = status.taskView.receipt;
|
|
113
|
-
const terminalTaskView = (status.taskView.state === 'current' && status.taskView.observedSource === 'completed')
|
|
114
|
-
|| (status.taskView.state === 'finalized-stale' && status.taskView.observedSource === 'active');
|
|
115
|
-
if (status.generation !== binding.generation || status.state !== 'healthy'
|
|
116
|
-
|| status.activeRequestId !== null || !terminalTaskView || status.taskView.taskId !== taskId
|
|
117
|
-
|| !viewReceipt || viewReceipt.generation !== binding.generation
|
|
118
|
-
|| viewReceipt.requestId !== binding.requestId
|
|
119
|
-
|| viewReceipt.receiptId !== receipt.receiptId
|
|
120
|
-
|| viewReceipt.revision !== receipt.revision)
|
|
121
|
-
return false;
|
|
122
108
|
const responsePath = path.join(manifest.channelDir, 'responses', `${binding.requestId}.json`);
|
|
123
109
|
if (!inspectOwnedPath(path.join(controlRoot, 'channel'), responsePath, 'file'))
|
|
124
110
|
return false;
|
|
125
111
|
const actual = readJsonFile(responsePath);
|
|
126
|
-
const
|
|
127
|
-
|
|
112
|
+
const response = actual;
|
|
113
|
+
if (response.version !== 2 || response.id !== binding.requestId || response.phase !== 'completed'
|
|
114
|
+
|| response.exitCode !== 0 || typeof response.stdout !== 'string')
|
|
115
|
+
return false;
|
|
116
|
+
const envelope = JSON.parse(response.stdout);
|
|
117
|
+
const result = envelope.result;
|
|
118
|
+
return envelope.status === 'completed' && envelope.accepted === true
|
|
119
|
+
&& result !== null && typeof result === 'object'
|
|
120
|
+
&& result.status === 'completed' && result.taskId === taskId;
|
|
128
121
|
}
|
|
129
122
|
catch {
|
|
130
123
|
return false;
|
|
@@ -153,7 +146,7 @@ function removedControlBindingEvidence(repoRootInput, journal, taskId, binding)
|
|
|
153
146
|
return false;
|
|
154
147
|
try {
|
|
155
148
|
const receipt = readTaskFinalizationReceipt(repoRoot, taskId);
|
|
156
|
-
return receipt !== null && taskFinalizationReceiptComplete(receipt, taskId
|
|
149
|
+
return receipt !== null && taskFinalizationReceiptComplete(receipt, taskId);
|
|
157
150
|
}
|
|
158
151
|
catch {
|
|
159
152
|
return false;
|
|
@@ -13,7 +13,7 @@ export function parseArtifactCommand(args) {
|
|
|
13
13
|
}
|
|
14
14
|
const fields = {};
|
|
15
15
|
const allowed = {
|
|
16
|
-
inspect: ['--family'],
|
|
16
|
+
inspect: ['--family', '--source-finding', '--source-artifact', '--source-sha256'],
|
|
17
17
|
init: ['--family', '--artifact', '--locale'],
|
|
18
18
|
'preflight': ['--family', '--artifact'],
|
|
19
19
|
'finalize-local': ['--family', '--artifact']
|
|
@@ -37,8 +37,10 @@ export function parseArtifactCommand(args) {
|
|
|
37
37
|
if (locale !== undefined && locale !== 'zh-CN' && locale !== 'en')
|
|
38
38
|
throw new Error("option '--locale' must be 'zh-CN' or 'en'");
|
|
39
39
|
return {
|
|
40
|
-
taskRef, operation: operation, family: fields['--family'],
|
|
41
|
-
artifact: fields['--artifact'] ?? '', locale
|
|
40
|
+
taskRef, operation: operation, family: fields['--family'] ?? '',
|
|
41
|
+
artifact: fields['--artifact'] ?? '', locale,
|
|
42
|
+
sourceFinding: fields['--source-finding'], sourceArtifact: fields['--source-artifact'],
|
|
43
|
+
sourceSha256: fields['--source-sha256']
|
|
42
44
|
};
|
|
43
45
|
}
|
|
44
46
|
/** Resolve authoritative task metadata separately from a trusted candidate directory. */
|
|
@@ -46,7 +48,12 @@ export function executeArtifactCommand(command, options = {}) {
|
|
|
46
48
|
const { taskRef, operation, family, artifact, locale } = command;
|
|
47
49
|
const fail = (code, message) => ({ status: 'failed', changed: false, error: { code, message } });
|
|
48
50
|
if (operation === 'inspect') {
|
|
49
|
-
const result = resolveArtifactContext(taskRef, family, {
|
|
51
|
+
const result = resolveArtifactContext(taskRef, family, {
|
|
52
|
+
repoRoot: options.repoRoot,
|
|
53
|
+
sourceFinding: command.sourceFinding,
|
|
54
|
+
sourceArtifact: command.sourceArtifact,
|
|
55
|
+
sourceSha256: command.sourceSha256
|
|
56
|
+
});
|
|
50
57
|
return family === 'code' && result.codeMode ? {
|
|
51
58
|
...result, mode: result.codeMode.mode, code_max: result.codeMode.codeMax, rev_max: result.codeMode.reviewMax,
|
|
52
59
|
verdict: result.codeMode.verdict, next_round: result.next?.round ?? null, next_artifact: result.next?.name ?? null,
|
|
@@ -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
|
}
|
|
@@ -194,27 +193,6 @@ function artifactRound(family, artifact) {
|
|
|
194
193
|
const identity = parseArtifactName(artifact);
|
|
195
194
|
return identity?.family === family ? identity.round : null;
|
|
196
195
|
}
|
|
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
196
|
function initializeArtifactSkeleton(request) {
|
|
219
197
|
const schema = getArtifactSchema(request.family);
|
|
220
198
|
if (!schema)
|
|
@@ -244,10 +222,7 @@ function initializeArtifactSkeleton(request) {
|
|
|
244
222
|
artifact: request.artifact,
|
|
245
223
|
...(request.locale ? { locale: request.locale } : {})
|
|
246
224
|
});
|
|
247
|
-
const
|
|
248
|
-
if (!audit.ok)
|
|
249
|
-
return resultFailure(audit.code, audit.message);
|
|
250
|
-
const content = `${skeleton.trimEnd()}${audit.content}`;
|
|
225
|
+
const content = skeleton.trimEnd();
|
|
251
226
|
const tempPath = path.join(request.taskDir, `.${request.artifact}.init-${process.pid}-${Date.now()}.tmp`);
|
|
252
227
|
try {
|
|
253
228
|
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: [
|
|
@@ -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
|