@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
|
@@ -7,17 +7,10 @@ import { checkpointCommitMatches } from './commit-identity.js';
|
|
|
7
7
|
import { readCheckpointIntent } from './commit-intent.js';
|
|
8
8
|
import { parseTaskFrontmatter } from './frontmatter.js';
|
|
9
9
|
import { readTaskFinalizationReceipt } from './finalization.js';
|
|
10
|
-
import { semanticDigest, sha256Content } from './local-artifact-finalization.js';
|
|
11
|
-
import { artifactRecoveryRoot, readArtifactRecoveryIntent } from './artifact-repair-intent.js';
|
|
12
|
-
import { parseArtifactName } from './artifact-name.js';
|
|
13
10
|
import { enumerateAllTaskDirs } from './resolve-ref.js';
|
|
14
11
|
const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
|
|
15
|
-
const LOCAL_INTENT_RE = /^(TASK-\d{8}-\d{6})-(analysis|review-analysis|plan|review-plan|code|review-code)-(.+\.md)\.json$/;
|
|
16
12
|
const COMMIT_INTENT_RE = /^(TASK-\d{8}-\d{6})\.json$/;
|
|
17
|
-
const AUXILIARY_ROOTS = [
|
|
18
|
-
'.local-artifact-finalization-intents',
|
|
19
|
-
'.task-commit-intents'
|
|
20
|
-
];
|
|
13
|
+
const AUXILIARY_ROOTS = ['.task-commit-intents'];
|
|
21
14
|
function auxiliaryRoot(repoRoot, name) {
|
|
22
15
|
return path.join(repoRoot, '.agents', 'workspace', name);
|
|
23
16
|
}
|
|
@@ -98,46 +91,6 @@ function receiptGate(repoRoot, task, options) {
|
|
|
98
91
|
}
|
|
99
92
|
return null;
|
|
100
93
|
}
|
|
101
|
-
function canonicalLocalIntentPath(taskId, family, artifact) {
|
|
102
|
-
return `${taskId}-${family}-${artifact}.json`;
|
|
103
|
-
}
|
|
104
|
-
function localIntentCandidate(repoRoot, task, filePath, taskId, family, artifact, identity, options) {
|
|
105
|
-
const protectedReason = taskGate(task);
|
|
106
|
-
if (protectedReason)
|
|
107
|
-
return item('LFAI-CONSUMED', taskId, filePath, 'protected', protectedReason, identity);
|
|
108
|
-
if (path.basename(filePath) !== canonicalLocalIntentPath(taskId, family, artifact)
|
|
109
|
-
|| !parseArtifactName(artifact)?.family || parseArtifactName(artifact).family !== family) {
|
|
110
|
-
return item('LFAI-CONSUMED', taskId, filePath, 'protected', 'PATH_IDENTITY_MISMATCH', identity);
|
|
111
|
-
}
|
|
112
|
-
let intent;
|
|
113
|
-
try {
|
|
114
|
-
intent = readArtifactRecoveryIntent(repoRoot, taskId, family, artifact);
|
|
115
|
-
}
|
|
116
|
-
catch {
|
|
117
|
-
return item('LFAI-CONSUMED', taskId, filePath, 'protected', 'LFAI_SCHEMA_INVALID', identity);
|
|
118
|
-
}
|
|
119
|
-
if (!intent || intent.state !== 'consumed') {
|
|
120
|
-
return item('LFAI-CONSUMED', taskId, filePath, 'protected', 'LFAI_STATE_PROTECTED', identity);
|
|
121
|
-
}
|
|
122
|
-
const artifactPath = path.join(task.taskDir, artifact);
|
|
123
|
-
if (safeLstat(task.taskDir, artifactPath, 'file') === null) {
|
|
124
|
-
return item('LFAI-CONSUMED', taskId, filePath, 'protected', 'ARTIFACT_IDENTITY_MISMATCH', identity);
|
|
125
|
-
}
|
|
126
|
-
let content;
|
|
127
|
-
try {
|
|
128
|
-
content = fs.readFileSync(artifactPath, 'utf8');
|
|
129
|
-
}
|
|
130
|
-
catch {
|
|
131
|
-
return item('LFAI-CONSUMED', taskId, filePath, 'protected', 'ARTIFACT_UNREADABLE', identity);
|
|
132
|
-
}
|
|
133
|
-
if (intent.finalArtifactSha256 !== sha256Content(content) || intent.finalSemanticDigest !== semanticDigest(content)) {
|
|
134
|
-
return item('LFAI-CONSUMED', taskId, filePath, 'protected', 'ARTIFACT_DIGEST_MISMATCH', identity);
|
|
135
|
-
}
|
|
136
|
-
const receiptReason = receiptGate(repoRoot, task, options);
|
|
137
|
-
if (receiptReason)
|
|
138
|
-
return item('LFAI-CONSUMED', taskId, filePath, 'protected', receiptReason, identity);
|
|
139
|
-
return item('LFAI-CONSUMED', taskId, filePath, 'planned', 'LFAI_CONSUMED_AND_VERIFIED', identity);
|
|
140
|
-
}
|
|
141
94
|
function commitIntentCandidate(repoRoot, task, filePath, taskId, identity, options) {
|
|
142
95
|
const protectedReason = taskGate(task);
|
|
143
96
|
if (protectedReason)
|
|
@@ -209,24 +162,13 @@ function readAuxiliaryCandidates(repoRoot, tasks, options) {
|
|
|
209
162
|
candidates.push(item('AUXILIARY-UNKNOWN', null, filePath, 'protected', 'PATH_IDENTITY_MISMATCH'));
|
|
210
163
|
continue;
|
|
211
164
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
if (!
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
continue;
|
|
218
|
-
}
|
|
219
|
-
candidates.push(localIntentCandidate(repoRoot, tasks.get(match[1]), filePath, match[1], match[2], match[3], identity, options));
|
|
220
|
-
}
|
|
221
|
-
else {
|
|
222
|
-
const match = COMMIT_INTENT_RE.exec(entry.name);
|
|
223
|
-
if (!match || (selected && !selected.has(match[1]))) {
|
|
224
|
-
if (!selected || !match)
|
|
225
|
-
candidates.push(item('AUXILIARY-UNKNOWN', null, filePath, 'protected', 'PATH_IDENTITY_MISMATCH', identity));
|
|
226
|
-
continue;
|
|
227
|
-
}
|
|
228
|
-
candidates.push(commitIntentCandidate(repoRoot, tasks.get(match[1]), filePath, match[1], identity, options));
|
|
165
|
+
const match = COMMIT_INTENT_RE.exec(entry.name);
|
|
166
|
+
if (!match || (selected && !selected.has(match[1]))) {
|
|
167
|
+
if (!selected || !match)
|
|
168
|
+
candidates.push(item('AUXILIARY-UNKNOWN', null, filePath, 'protected', 'PATH_IDENTITY_MISMATCH', identity));
|
|
169
|
+
continue;
|
|
229
170
|
}
|
|
171
|
+
candidates.push(commitIntentCandidate(repoRoot, tasks.get(match[1]), filePath, match[1], identity, options));
|
|
230
172
|
}
|
|
231
173
|
if (entries.length === 0) {
|
|
232
174
|
candidates.push(item('EMPTY-AUX-PARENT', null, root, 'planned', 'EMPTY_CANONICAL_AUXILIARY_ROOT', rootIdentity));
|
|
@@ -255,85 +197,7 @@ function scanIntermediateCleanup(repoRoot, options = {}) {
|
|
|
255
197
|
const candidates = scanInternal(repoRoot, options);
|
|
256
198
|
return buildIntermediateCleanupReport(candidates, true);
|
|
257
199
|
}
|
|
258
|
-
function
|
|
259
|
-
if (candidate.kind !== 'LFAI-CONSUMED' || !candidate.taskId)
|
|
260
|
-
return true;
|
|
261
|
-
if (!task)
|
|
262
|
-
return false;
|
|
263
|
-
const match = LOCAL_INTENT_RE.exec(path.basename(candidate.path));
|
|
264
|
-
if (!match)
|
|
265
|
-
return false;
|
|
266
|
-
let intent;
|
|
267
|
-
try {
|
|
268
|
-
intent = readArtifactRecoveryIntent(repoRoot, candidate.taskId, match[2], match[3]);
|
|
269
|
-
}
|
|
270
|
-
catch {
|
|
271
|
-
return false;
|
|
272
|
-
}
|
|
273
|
-
if (!intent || intent.state !== 'consumed')
|
|
274
|
-
return false;
|
|
275
|
-
const recovery = artifactRecoveryRoot(task.taskDir, intent.recoveryOperationId);
|
|
276
|
-
let stat = null;
|
|
277
|
-
try {
|
|
278
|
-
stat = fs.lstatSync(recovery);
|
|
279
|
-
}
|
|
280
|
-
catch (error) {
|
|
281
|
-
if (errorCode(error) === 'ENOENT')
|
|
282
|
-
return true;
|
|
283
|
-
return false;
|
|
284
|
-
}
|
|
285
|
-
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
286
|
-
return false;
|
|
287
|
-
let entries;
|
|
288
|
-
try {
|
|
289
|
-
entries = fs.readdirSync(recovery, { withFileTypes: true });
|
|
290
|
-
}
|
|
291
|
-
catch {
|
|
292
|
-
return false;
|
|
293
|
-
}
|
|
294
|
-
for (const entry of entries) {
|
|
295
|
-
if (entry.isSymbolicLink())
|
|
296
|
-
return false;
|
|
297
|
-
if (entry.isFile()) {
|
|
298
|
-
if (!['baseline.md', 'candidate.md', 'final.md', 'publish.md'].includes(entry.name))
|
|
299
|
-
return false;
|
|
300
|
-
continue;
|
|
301
|
-
}
|
|
302
|
-
if (!entry.isDirectory() || entry.name !== 'generations')
|
|
303
|
-
return false;
|
|
304
|
-
let generations;
|
|
305
|
-
try {
|
|
306
|
-
generations = fs.readdirSync(path.join(recovery, entry.name), { withFileTypes: true });
|
|
307
|
-
}
|
|
308
|
-
catch {
|
|
309
|
-
return false;
|
|
310
|
-
}
|
|
311
|
-
if (generations.some((generation) => generation.isSymbolicLink()
|
|
312
|
-
|| !generation.isFile() || !/^[a-f0-9]{64}\.md$/u.test(generation.name)))
|
|
313
|
-
return false;
|
|
314
|
-
}
|
|
315
|
-
try {
|
|
316
|
-
for (const entry of entries) {
|
|
317
|
-
const target = path.join(recovery, entry.name);
|
|
318
|
-
if (entry.isFile())
|
|
319
|
-
fs.unlinkSync(target);
|
|
320
|
-
else {
|
|
321
|
-
for (const generation of fs.readdirSync(target, { withFileTypes: true }))
|
|
322
|
-
fs.unlinkSync(path.join(target, generation.name));
|
|
323
|
-
fs.rmdirSync(target);
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
fs.rmdirSync(recovery);
|
|
327
|
-
const recoveryRoot = path.dirname(recovery);
|
|
328
|
-
if (fs.readdirSync(recoveryRoot).length === 0)
|
|
329
|
-
fs.rmdirSync(recoveryRoot);
|
|
330
|
-
return true;
|
|
331
|
-
}
|
|
332
|
-
catch (error) {
|
|
333
|
-
return errorCode(error) === 'ENOENT';
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
function removeCandidate(repoRoot, candidate, task) {
|
|
200
|
+
function removeCandidate(repoRoot, candidate) {
|
|
337
201
|
if (candidate.disposition !== 'planned')
|
|
338
202
|
return candidate;
|
|
339
203
|
const root = candidate.kind === 'EMPTY-AUX-PARENT'
|
|
@@ -358,9 +222,6 @@ function removeCandidate(repoRoot, candidate, task) {
|
|
|
358
222
|
return { ...candidate, disposition: 'protected', reason: 'PATH_IDENTITY_CHANGED' };
|
|
359
223
|
if (!sameFileIdentity(expected, actual))
|
|
360
224
|
return { ...candidate, disposition: 'protected', reason: 'PATH_IDENTITY_CHANGED' };
|
|
361
|
-
if (!cleanupConsumedRecoveryFiles(repoRoot, task, candidate)) {
|
|
362
|
-
return { ...candidate, disposition: 'protected', reason: 'RECOVERY_STAGING_NOT_SAFE_TO_REMOVE' };
|
|
363
|
-
}
|
|
364
225
|
try {
|
|
365
226
|
if (candidate.kind === 'EMPTY-AUX-PARENT') {
|
|
366
227
|
if (fs.readdirSync(candidate.path).length !== 0)
|
|
@@ -413,7 +274,7 @@ function cleanupIntermediateUnderRemovalCoordinator(repoRoot, options = {}) {
|
|
|
413
274
|
continue;
|
|
414
275
|
const current = refreshedByKey.get(intermediateCleanupKey(initialCandidate));
|
|
415
276
|
items[index] = current
|
|
416
|
-
? removeCandidate(repoRoot, current
|
|
277
|
+
? removeCandidate(repoRoot, current)
|
|
417
278
|
: { ...initialCandidate, disposition: 'protected', reason: 'CANDIDATE_NO_LONGER_ELIGIBLE' };
|
|
418
279
|
}
|
|
419
280
|
};
|
|
@@ -6,8 +6,10 @@ import { parseArtifactName } from './artifact-name.js';
|
|
|
6
6
|
import { LEDGER_COLUMNS, LEDGER_HEADINGS, LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, nextHdId, parseLedgerDocument, validateLedgerRows } from './ledger.js';
|
|
7
7
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
8
8
|
import { writeTask } from './write.js';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { parseLegacyReworkIntentDocument, parseReworkIntentDocument, reworkIntentMutation, upsertReworkIntent } from './rework-intent.js';
|
|
10
|
+
import { extractSection, extractSubSection } from './sections.js';
|
|
11
|
+
import { parseLifecyclePathDecision } from './lifecycle-path.js';
|
|
12
|
+
import { scanVisibleMarkdown } from './markdown.js';
|
|
11
13
|
const PREFIX = { analysis: 'AN', plan: 'PL', code: 'CD' };
|
|
12
14
|
const RESPONSE = new Set(['accepted', 'adjusted', 'refuted', 'cannot-judge']);
|
|
13
15
|
const DISPOSITION = new Set(['confirmed', 'closed', 'open', 'needs-human-decision']);
|
|
@@ -23,15 +25,50 @@ function ledgerReadErrorCode(error) {
|
|
|
23
25
|
return code;
|
|
24
26
|
return code === 'TABLE_DUPLICATE_KEY' ? 'LEDGER_DUPLICATE_ID' : 'LEDGER_DOCUMENT_INVALID';
|
|
25
27
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
const CLASSIFICATION_TARGET = {
|
|
29
|
+
'scope-or-requirement': 'analysis', design: 'plan', implementation: 'code',
|
|
30
|
+
'human-decision': 'pause', 'insufficient-evidence': 'pause'
|
|
31
|
+
};
|
|
32
|
+
function semanticDigest(value) {
|
|
33
|
+
return createHash('sha256').update(value.normalize('NFKC').replace(/\s+/g, ' ').trim()).digest('hex');
|
|
34
|
+
}
|
|
35
|
+
function normalizedTaskFact(value) {
|
|
36
|
+
return value.normalize('NFKC').replace(/\s+/g, ' ').trim();
|
|
37
|
+
}
|
|
38
|
+
function semanticFindingEvidence(reviewContent, heading, end) {
|
|
39
|
+
const title = heading.text.replace(/^\d+[.、::]\s*/, '').trim();
|
|
40
|
+
return `${title}\n${reviewContent.slice(heading.end, end)}`;
|
|
41
|
+
}
|
|
42
|
+
function findingEvidence(reviewContent, evidence) {
|
|
43
|
+
const anchor = evidence.split('#')[1];
|
|
44
|
+
if (!anchor)
|
|
45
|
+
return null;
|
|
46
|
+
const markdown = scanVisibleMarkdown(reviewContent);
|
|
47
|
+
const explicit = markdown.anchors.find((candidate) => candidate.id === anchor);
|
|
48
|
+
if (explicit) {
|
|
49
|
+
const heading = markdown.headings.find((candidate) => candidate.start > explicit.start);
|
|
50
|
+
const next = heading && markdown.headings.find((candidate) => candidate.start > heading.start && candidate.level <= heading.level);
|
|
51
|
+
const end = next?.start ?? reviewContent.length;
|
|
52
|
+
return heading ? semanticFindingEvidence(reviewContent, heading, end) : reviewContent.slice(explicit.end, end);
|
|
34
53
|
}
|
|
54
|
+
const heading = markdown.headings.find((candidate) => {
|
|
55
|
+
const escapedAnchor = anchor.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
56
|
+
return candidate.text === anchor || new RegExp(`^${escapedAnchor}(?:\\s|[.、::])`).test(candidate.text);
|
|
57
|
+
});
|
|
58
|
+
if (!heading)
|
|
59
|
+
return null;
|
|
60
|
+
const next = markdown.headings.find((candidate) => candidate.start > heading.start && candidate.level <= heading.level);
|
|
61
|
+
return semanticFindingEvidence(reviewContent, heading, next?.start ?? reviewContent.length);
|
|
62
|
+
}
|
|
63
|
+
function taskFactDigest(taskDir, content) {
|
|
64
|
+
const analysisNames = fs.readdirSync(taskDir).filter((name) => /^analysis(?:-r\d+)?\.md$/.test(name));
|
|
65
|
+
const latest = analysisNames.map((name) => ({ name, round: parseArtifactName(name)?.round ?? 0 })).sort((a, b) => b.round - a.round)[0];
|
|
66
|
+
const flow = latest ? parseLifecyclePathDecision(fs.readFileSync(path.join(taskDir, latest.name), 'utf8')) : null;
|
|
67
|
+
return semanticDigest(JSON.stringify({
|
|
68
|
+
taskInput: normalizedTaskFact(extractSection(content, ['任务输入', 'Task Input'])),
|
|
69
|
+
requirements: normalizedTaskFact(extractSection(content, ['需求', 'Requirements'])),
|
|
70
|
+
flow: flow?.status === 'valid' ? flow.decision.semanticDigest : flow?.status ?? 'missing'
|
|
71
|
+
}));
|
|
35
72
|
}
|
|
36
73
|
function nextFindingId(rows, stage) {
|
|
37
74
|
const prefix = PREFIX[stage];
|
|
@@ -67,8 +104,7 @@ function applyLedgerIntent(intent, options = {}) {
|
|
|
67
104
|
const resolved = resolveTaskRef(intent.taskRef, { repoRoot: options.repoRoot });
|
|
68
105
|
if (!resolved.ok)
|
|
69
106
|
return failed(intent, resolved.code, resolved.message, resolved.taskId);
|
|
70
|
-
|
|
71
|
-
if (resolved.state !== 'active' && !stateOverride)
|
|
107
|
+
if (resolved.state !== 'active')
|
|
72
108
|
return failed(intent, 'TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
|
|
73
109
|
let content;
|
|
74
110
|
let rows;
|
|
@@ -89,12 +125,15 @@ function applyLedgerIntent(intent, options = {}) {
|
|
|
89
125
|
return { status: 'no-op', changed: false, intent: intent.kind, taskId: resolved.taskId, entityId: nextHdId(rows), before: null, after: null, operations: [], error: null };
|
|
90
126
|
}
|
|
91
127
|
if (intent.kind === 'rework-intent-upsert') {
|
|
92
|
-
if (!/^RI-[1-9]\d*$/.test(intent.intentId) || !/^(AN|PL|CD)-[1-9]\d*$/.test(intent.findingId) || !
|
|
93
|
-
return failed(intent, 'LEDGER_PAYLOAD_INVALID', 'rework intent identity,
|
|
128
|
+
if (!/^RI-[1-9]\d*$/.test(intent.intentId) || !/^(AN|PL|CD)-[1-9]\d*$/.test(intent.findingId) || !(intent.classification in CLASSIFICATION_TARGET) || !/^[a-f0-9]{64}$/.test(intent.sourceSha256)) {
|
|
129
|
+
return failed(intent, 'LEDGER_PAYLOAD_INVALID', 'rework intent identity, classification, or source hash is invalid', resolved.taskId, intent.intentId);
|
|
94
130
|
}
|
|
95
131
|
const finding = rows.find((row) => row.id === intent.findingId);
|
|
96
132
|
if (!finding || finding.severity === 'decision')
|
|
97
133
|
return failed(intent, 'LEDGER_NOT_FOUND', `finding '${intent.findingId}' was not found`, resolved.taskId, intent.findingId);
|
|
134
|
+
if (intent.classification === 'human-decision' && finding.status !== 'needs-human-decision') {
|
|
135
|
+
return failed(intent, 'LEDGER_TRANSITION_INVALID', 'human-decision classification requires a needs-human-decision finding', resolved.taskId, intent.findingId);
|
|
136
|
+
}
|
|
98
137
|
const sourceFromEvidence = finding.evidence.split('#')[0];
|
|
99
138
|
if (sourceFromEvidence !== intent.sourceArtifact || parseArtifactName(intent.sourceArtifact)?.family !== `review-${finding.stage}`) {
|
|
100
139
|
return failed(intent, 'LEDGER_IDENTITY_CONFLICT', 'rework intent source artifact does not match finding evidence', resolved.taskId, intent.intentId);
|
|
@@ -106,12 +145,35 @@ function applyLedgerIntent(intent, options = {}) {
|
|
|
106
145
|
const parsed = parseReworkIntentDocument(content);
|
|
107
146
|
if (!parsed.ok)
|
|
108
147
|
return failed(intent, parsed.code, parsed.message, resolved.taskId, intent.intentId);
|
|
109
|
-
const
|
|
148
|
+
const reviewContent = fs.readFileSync(path.join(resolved.taskDir, intent.sourceArtifact), 'utf8');
|
|
149
|
+
const evidenceBlock = findingEvidence(reviewContent, finding.evidence);
|
|
150
|
+
if (!evidenceBlock)
|
|
151
|
+
return failed(intent, 'LEDGER_EVIDENCE_INVALID', `finding evidence anchor '${finding.evidence}' was not found`, resolved.taskId, intent.intentId);
|
|
152
|
+
const evidenceDigest = semanticDigest(evidenceBlock);
|
|
153
|
+
const factsDigest = taskFactDigest(resolved.taskDir, content);
|
|
154
|
+
const requestedIntent = {
|
|
110
155
|
intentId: intent.intentId, findingId: intent.findingId, sourceArtifact: intent.sourceArtifact,
|
|
111
|
-
sourceSha256: intent.sourceSha256, target: intent.
|
|
156
|
+
sourceSha256: intent.sourceSha256, target: CLASSIFICATION_TARGET[intent.classification], classification: intent.classification,
|
|
157
|
+
evidenceDigest, taskFactDigest: factsDigest, status: 'pending',
|
|
112
158
|
declaredAt: new Date().toISOString(), consumedAt: ''
|
|
113
159
|
};
|
|
114
|
-
|
|
160
|
+
if (parsed.intents.some((candidate) => candidate.intentId === intent.intentId)) {
|
|
161
|
+
const replay = upsertReworkIntent(parsed.intents, requestedIntent);
|
|
162
|
+
if (!replay.changed) {
|
|
163
|
+
return { status: 'no-op', changed: false, intent: intent.kind, taskId: resolved.taskId, entityId: intent.intentId, before: null, after: null, operations: [], error: null };
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const same = parsed.intents.find((candidate) => candidate.findingId === intent.findingId
|
|
167
|
+
&& candidate.evidenceDigest === evidenceDigest && candidate.taskFactDigest === factsDigest
|
|
168
|
+
&& candidate.classification === intent.classification && candidate.target === CLASSIFICATION_TARGET[intent.classification]);
|
|
169
|
+
const classification = same ? 'insufficient-evidence' : intent.classification;
|
|
170
|
+
const nextIntent = {
|
|
171
|
+
...requestedIntent, target: CLASSIFICATION_TARGET[classification], classification
|
|
172
|
+
};
|
|
173
|
+
const now = nextIntent.declaredAt;
|
|
174
|
+
const superseded = parsed.intents.map((candidate) => candidate.status === 'pending'
|
|
175
|
+
? { ...candidate, status: 'superseded', consumedAt: now } : candidate);
|
|
176
|
+
const next = upsertReworkIntent(superseded, nextIntent);
|
|
115
177
|
if (!next.changed)
|
|
116
178
|
return { status: 'no-op', changed: false, intent: intent.kind, taskId: resolved.taskId, entityId: intent.intentId, before: null, after: null, operations: [], error: null };
|
|
117
179
|
const result = writeTask({ taskRef: intent.taskRef, expectedState: 'active', dryRun: intent.dryRun, mutations: [reworkIntentMutation(content, next.intents)] }, { ...options, taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state } });
|
|
@@ -123,6 +185,84 @@ function applyLedgerIntent(intent, options = {}) {
|
|
|
123
185
|
return failed(intent, 'LEDGER_IDENTITY_CONFLICT', error instanceof Error ? error.message : String(error), resolved.taskId, intent.intentId);
|
|
124
186
|
}
|
|
125
187
|
}
|
|
188
|
+
if (intent.kind === 'rework-intent-rebuild') {
|
|
189
|
+
const beforeSha256 = createHash('sha256').update(content).digest('hex');
|
|
190
|
+
const supplied = [intent.findingId, intent.sourceArtifact, intent.sourceSha256, intent.classification].filter((value) => value !== undefined).length;
|
|
191
|
+
if (supplied !== 0 && supplied !== 4)
|
|
192
|
+
return failed(intent, 'LEDGER_PAYLOAD_INVALID', 'rebuild binding options must be provided together', resolved.taskId);
|
|
193
|
+
const current = parseReworkIntentDocument(content);
|
|
194
|
+
if (current.ok && current.present) {
|
|
195
|
+
if (supplied !== 0)
|
|
196
|
+
return failed(intent, 'LEDGER_PAYLOAD_INVALID', 'current rework intent format does not accept rebuild binding options', resolved.taskId);
|
|
197
|
+
return {
|
|
198
|
+
status: 'no-op', changed: false, intent: intent.kind, taskId: resolved.taskId, entityId: null,
|
|
199
|
+
before: null, after: null, operations: [], error: null,
|
|
200
|
+
rebuild: { mode: 'format-only', beforeSha256, afterSha256: beforeSha256, converted: current.intents.length, newIntentId: null }
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
const legacy = parseLegacyReworkIntentDocument(content);
|
|
204
|
+
if (!legacy.ok)
|
|
205
|
+
return failed(intent, legacy.code, legacy.message, resolved.taskId);
|
|
206
|
+
const pending = legacy.intents.filter((row) => row.status === 'pending');
|
|
207
|
+
if (pending.length === 0 && supplied !== 0)
|
|
208
|
+
return failed(intent, 'LEDGER_PAYLOAD_INVALID', 'format-only rebuild does not accept finding or classification options', resolved.taskId);
|
|
209
|
+
if (pending.length > 0 && supplied !== 4)
|
|
210
|
+
return failed(intent, 'LEDGER_PAYLOAD_INVALID', 'legacy pending intents require finding, review, hash, and classification', resolved.taskId);
|
|
211
|
+
const now = new Date().toISOString();
|
|
212
|
+
let next = legacy.intents.map((row) => row.status === 'pending' ? { ...row, status: 'superseded', consumedAt: now } : row);
|
|
213
|
+
let entityId = null;
|
|
214
|
+
if (pending.length > 0) {
|
|
215
|
+
const finding = rows.find((row) => row.id === intent.findingId);
|
|
216
|
+
if (!finding || finding.severity === 'decision' || finding.evidence.split('#')[0] !== intent.sourceArtifact)
|
|
217
|
+
return failed(intent, 'LEDGER_IDENTITY_CONFLICT', 'rebuild binding does not match a review finding', resolved.taskId);
|
|
218
|
+
if (intent.classification === 'human-decision' && finding.status !== 'needs-human-decision') {
|
|
219
|
+
return failed(intent, 'LEDGER_TRANSITION_INVALID', 'human-decision classification requires a needs-human-decision finding', resolved.taskId);
|
|
220
|
+
}
|
|
221
|
+
const sourceIdentity = parseArtifactName(intent.sourceArtifact);
|
|
222
|
+
const expectedFamily = `review-${finding.stage}`;
|
|
223
|
+
const latestSource = fs.readdirSync(resolved.taskDir)
|
|
224
|
+
.map((name) => parseArtifactName(name))
|
|
225
|
+
.filter((identity) => identity?.family === expectedFamily)
|
|
226
|
+
.sort((left, right) => right.round - left.round || left.name.localeCompare(right.name))[0];
|
|
227
|
+
if (!sourceIdentity || sourceIdentity.family !== expectedFamily || latestSource?.name !== sourceIdentity.name) {
|
|
228
|
+
return failed(intent, 'LEDGER_IDENTITY_CONFLICT', 'rebuild source artifact is not the latest review for the finding stage', resolved.taskId);
|
|
229
|
+
}
|
|
230
|
+
const sourcePath = path.join(resolved.taskDir, intent.sourceArtifact);
|
|
231
|
+
const actualHash = createHash('sha256').update(fs.readFileSync(sourcePath)).digest('hex');
|
|
232
|
+
if (actualHash !== intent.sourceSha256)
|
|
233
|
+
return failed(intent, 'LEDGER_IDENTITY_CONFLICT', 'rebuild source artifact hash does not match', resolved.taskId);
|
|
234
|
+
const max = next.reduce((value, row) => Math.max(value, Number(row.intentId.slice(3))), 0);
|
|
235
|
+
entityId = `RI-${max + 1}`;
|
|
236
|
+
const reviewContent = fs.readFileSync(sourcePath, 'utf8');
|
|
237
|
+
const classification = intent.classification;
|
|
238
|
+
if (!(classification in CLASSIFICATION_TARGET))
|
|
239
|
+
return failed(intent, 'LEDGER_PAYLOAD_INVALID', 'rebuild classification is invalid', resolved.taskId);
|
|
240
|
+
const evidenceBlock = findingEvidence(reviewContent, finding.evidence);
|
|
241
|
+
if (!evidenceBlock)
|
|
242
|
+
return failed(intent, 'LEDGER_EVIDENCE_INVALID', `finding evidence anchor '${finding.evidence}' was not found`, resolved.taskId);
|
|
243
|
+
next = [...next, {
|
|
244
|
+
intentId: entityId, findingId: intent.findingId, sourceArtifact: intent.sourceArtifact, sourceSha256: intent.sourceSha256,
|
|
245
|
+
target: CLASSIFICATION_TARGET[classification], classification,
|
|
246
|
+
evidenceDigest: semanticDigest(evidenceBlock),
|
|
247
|
+
taskFactDigest: taskFactDigest(resolved.taskDir, content), status: 'pending', declaredAt: now, consumedAt: ''
|
|
248
|
+
}];
|
|
249
|
+
}
|
|
250
|
+
const result = writeTask({ taskRef: intent.taskRef, expectedState: 'active', dryRun: intent.dryRun, mutations: [reworkIntentMutation(content, next)] }, { ...options, taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state } });
|
|
251
|
+
if (result.status === 'failed')
|
|
252
|
+
return failed(intent, result.error.code, result.error.message, result.taskId, entityId);
|
|
253
|
+
const afterContent = result.status === 'applied' || result.status === 'no-op' ? fs.readFileSync(resolved.taskMdPath, 'utf8') : content;
|
|
254
|
+
return {
|
|
255
|
+
status: result.status, changed: result.changed, intent: intent.kind, taskId: result.taskId, entityId,
|
|
256
|
+
before: null, after: null, operations: result.operations, error: null,
|
|
257
|
+
rebuild: {
|
|
258
|
+
mode: pending.length > 0 ? 'pending-reclassification' : 'format-only',
|
|
259
|
+
beforeSha256,
|
|
260
|
+
afterSha256: createHash('sha256').update(afterContent).digest('hex'),
|
|
261
|
+
converted: legacy.intents.length,
|
|
262
|
+
newIntentId: entityId
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
}
|
|
126
266
|
let before = null;
|
|
127
267
|
let after;
|
|
128
268
|
if (intent.kind === 'finding-upsert') {
|
|
@@ -199,8 +339,7 @@ function applyLedgerIntent(intent, options = {}) {
|
|
|
199
339
|
? new Set(['confirmed', 'open', 'needs-human-decision'])
|
|
200
340
|
: before.status === 'cannot-judge' ? new Set(['open', 'needs-human-decision']) : new Set();
|
|
201
341
|
const sameRoundMinorClose = before.status === 'open' && before.severity === 'minor' && intent.status === 'closed';
|
|
202
|
-
if (
|
|
203
|
-
&& !allowsManualOverride(options.manualOverride, 'ledger-intent', 'LEDGER_TRANSITION_INVALID')) {
|
|
342
|
+
if (!sameRoundMinorClose && !allowed.has(intent.status)) {
|
|
204
343
|
return failed(intent, 'LEDGER_TRANSITION_INVALID', `finding '${intent.id}' cannot transition from ${before.status} to ${intent.status}`, resolved.taskId, intent.id);
|
|
205
344
|
}
|
|
206
345
|
after = { ...before, status: intent.status, evidence };
|
|
@@ -233,7 +372,7 @@ function applyLedgerIntent(intent, options = {}) {
|
|
|
233
372
|
}
|
|
234
373
|
}
|
|
235
374
|
const result = writeTask({
|
|
236
|
-
taskRef: intent.taskRef, expectedState:
|
|
375
|
+
taskRef: intent.taskRef, expectedState: 'active',
|
|
237
376
|
mutations: [rowMutation(after), ...(implementationMutation ? [implementationMutation] : [])],
|
|
238
377
|
dryRun: 'dryRun' in intent ? intent.dryRun : false
|
|
239
378
|
}, { ...options, taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state } });
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { extractSection } from './sections.js';
|
|
3
|
+
const PATH_STAGES = {
|
|
4
|
+
streamlined: ['analysis', 'code', 'review-code'],
|
|
5
|
+
standard: ['analysis', 'plan', 'code', 'review-code'],
|
|
6
|
+
full: ['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code']
|
|
7
|
+
};
|
|
8
|
+
const FIELD_ALIASES = {
|
|
9
|
+
path: ['本任务路径', 'Path'],
|
|
10
|
+
basis: ['判定依据', 'Basis'],
|
|
11
|
+
unmetHigherConditions: ['未满足的更高路径条件', '未满足的较低路径条件', 'Unmet Higher-path Conditions'],
|
|
12
|
+
upgradeTriggers: ['升级触发条件', 'Upgrade Triggers']
|
|
13
|
+
};
|
|
14
|
+
function normalizeText(value) {
|
|
15
|
+
return value.normalize('NFKC').replace(/\s+/g, ' ').trim().replace(/[。.]$/, '');
|
|
16
|
+
}
|
|
17
|
+
function digest(value) {
|
|
18
|
+
return createHash('sha256').update(JSON.stringify(value)).digest('hex');
|
|
19
|
+
}
|
|
20
|
+
function pathToken(value) {
|
|
21
|
+
const normalized = normalizeText(value).toLowerCase().replace(/[`*_]/g, '');
|
|
22
|
+
if (/^(?:streamlined|精简路径|精简)$/.test(normalized))
|
|
23
|
+
return 'streamlined';
|
|
24
|
+
if (/^(?:standard|标准路径|标准)$/.test(normalized))
|
|
25
|
+
return 'standard';
|
|
26
|
+
if (/^(?:full|完整路径|完整)$/.test(normalized))
|
|
27
|
+
return 'full';
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
function fieldValues(body, aliases) {
|
|
31
|
+
const wanted = new Set(aliases);
|
|
32
|
+
const values = [];
|
|
33
|
+
for (const line of body.split(/\r?\n/)) {
|
|
34
|
+
const match = /^\s*[-*]\s+\*\*([^*]+)\*\*[::]\s*(.*?)\s*$/.exec(line);
|
|
35
|
+
if (match && wanted.has(match[1].trim()))
|
|
36
|
+
values.push(match[2].trim());
|
|
37
|
+
}
|
|
38
|
+
return values;
|
|
39
|
+
}
|
|
40
|
+
function parseLifecyclePathDecision(analysisContent, sourceArtifact = '', sourceSha256 = '') {
|
|
41
|
+
const body = extractSection(analysisContent, ['流程裁定', 'Flow Decision']);
|
|
42
|
+
if (!body)
|
|
43
|
+
return { status: 'missing', decision: null, message: 'analysis flow decision section is missing' };
|
|
44
|
+
const fields = Object.fromEntries(Object.entries(FIELD_ALIASES).map(([key, aliases]) => [key, fieldValues(body, aliases)]));
|
|
45
|
+
const invalid = Object.entries(fields).find(([, values]) => values.length !== 1);
|
|
46
|
+
if (invalid)
|
|
47
|
+
return { status: 'invalid', decision: null, message: `flow decision field '${invalid[0]}' must appear exactly once` };
|
|
48
|
+
const path = pathToken(fields.path[0]);
|
|
49
|
+
if (!path)
|
|
50
|
+
return { status: 'invalid', decision: null, message: `unknown lifecycle path '${fields.path[0]}'` };
|
|
51
|
+
const basis = normalizeText(fields.basis[0]);
|
|
52
|
+
const unmetHigherConditions = normalizeText(fields.unmetHigherConditions[0]);
|
|
53
|
+
const upgradeTriggers = normalizeText(fields.upgradeTriggers[0]);
|
|
54
|
+
if (!basis || !unmetHigherConditions || !upgradeTriggers)
|
|
55
|
+
return { status: 'invalid', decision: null, message: 'flow decision fields must be non-empty' };
|
|
56
|
+
return {
|
|
57
|
+
status: 'valid',
|
|
58
|
+
message: null,
|
|
59
|
+
decision: {
|
|
60
|
+
path,
|
|
61
|
+
stages: PATH_STAGES[path],
|
|
62
|
+
basis,
|
|
63
|
+
unmetHigherConditions,
|
|
64
|
+
upgradeTriggers,
|
|
65
|
+
semanticDigest: digest({ path, basis, unmetHigherConditions, upgradeTriggers }),
|
|
66
|
+
sourceArtifact,
|
|
67
|
+
sourceSha256
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function pathIncludes(state, stage) {
|
|
72
|
+
return state?.status === 'valid' && state.decision.stages.includes(stage);
|
|
73
|
+
}
|
|
74
|
+
export { PATH_STAGES, parseLifecyclePathDecision, pathIncludes };
|
|
75
|
+
//# sourceMappingURL=lifecycle-path.js.map
|