@fitlab-ai/agent-infra 0.11.9 → 0.11.10
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/dist/bin/internal-cli.js +0 -6
- package/dist/lib/git/workflow.js +1 -1
- package/dist/lib/internal/cli-route-inventory.js +0 -3
- package/dist/lib/internal/sandbox-control.js +30 -5
- package/dist/lib/internal/task-finalization.js +14 -23
- package/dist/lib/internal/task-operation-registry.js +2 -3
- package/dist/lib/internal/task-platform-recovery.js +9 -4
- package/dist/lib/platform/completion-backfill.js +2 -1
- package/dist/lib/platform/context.js +4 -2
- package/dist/lib/platform/github-client.js +20 -17
- package/dist/lib/platform/github-provider.js +42 -22
- package/dist/lib/platform/github-release-notes.js +4 -2
- package/dist/lib/platform/in-label-sync.js +2 -1
- package/dist/lib/platform/issue-comments.js +13 -9
- package/dist/lib/platform/operation-coordinator.js +1 -1
- package/dist/lib/platform/pr-review.js +2 -1
- package/dist/lib/platform/provider-bridge.js +4 -5
- package/dist/lib/platform/provider-contract.d.ts +3 -6
- package/dist/lib/platform/provider-contract.js +3 -15
- package/dist/lib/platform/provider-validation.js +9 -4
- package/dist/lib/platform/pull-requests.js +4 -2
- package/dist/lib/sandbox/control/client.js +54 -6
- package/dist/lib/sandbox/control/executor.js +8 -3
- package/dist/lib/sandbox/removal.js +0 -1
- package/dist/lib/task/artifact-command.js +11 -1
- package/dist/lib/task/artifact-lifecycle.js +119 -62
- package/dist/lib/task/artifact-selection.js +1 -1
- package/dist/lib/task/capabilities.js +55 -21
- package/dist/lib/task/commands/status.js +0 -27
- package/dist/lib/task/commit-identity.js +22 -7
- package/dist/lib/task/commit-intent.js +67 -26
- package/dist/lib/task/commit-operation.js +19 -8
- package/dist/lib/task/completion-facts.js +32 -10
- package/dist/lib/task/control-authority.js +2 -4
- package/dist/lib/task/current-contract.js +1 -5
- package/dist/lib/task/events.js +8 -274
- package/dist/lib/task/execution-inputs.js +14 -0
- package/dist/lib/task/finalization-envelope.js +4 -0
- package/dist/lib/task/finalization.js +10 -36
- package/dist/lib/task/frontmatter.js +1 -1
- package/dist/lib/task/ledger-intents.js +4 -7
- package/dist/lib/task/orchestration.js +11 -10
- package/dist/lib/task/platform-operation-journal.js +17 -10
- package/dist/lib/task/platform-operation-recovery.js +95 -28
- package/dist/lib/task/review-fingerprint.js +4 -26
- package/dist/lib/task/verification-engine.js +95 -6
- package/dist/lib/task/write.js +0 -11
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/git/workflow.ts +1 -1
- package/lib/internal/cli-route-inventory.ts +0 -2
- package/lib/internal/sandbox-control.ts +39 -6
- package/lib/internal/task-finalization.ts +16 -24
- package/lib/internal/task-operation-registry.ts +2 -3
- package/lib/internal/task-platform-recovery.ts +9 -4
- package/lib/platform/completion-backfill.ts +2 -1
- package/lib/platform/context.ts +8 -4
- package/lib/platform/github-client.ts +24 -20
- package/lib/platform/github-provider.ts +47 -26
- package/lib/platform/github-release-notes.ts +5 -3
- package/lib/platform/in-label-sync.ts +2 -1
- package/lib/platform/issue-comments.ts +14 -9
- package/lib/platform/operation-coordinator.ts +1 -1
- package/lib/platform/pr-review.ts +2 -1
- package/lib/platform/provider-bridge.ts +10 -9
- package/lib/platform/provider-contract.ts +6 -21
- package/lib/platform/provider-loader.ts +7 -6
- package/lib/platform/provider-validation.ts +30 -8
- package/lib/platform/pull-requests.ts +4 -2
- package/lib/platform/repository-metadata.ts +3 -2
- package/lib/platform/security-alerts.ts +1 -1
- package/lib/platform/types.ts +2 -0
- package/lib/sandbox/control/client.ts +57 -6
- package/lib/sandbox/control/executor.ts +8 -3
- package/lib/sandbox/removal.ts +2 -10
- package/lib/task/artifact-command.ts +10 -1
- package/lib/task/artifact-lifecycle.ts +100 -52
- package/lib/task/artifact-selection.ts +1 -1
- package/lib/task/capabilities.ts +47 -24
- package/lib/task/commands/status.ts +0 -31
- package/lib/task/commit-identity.ts +24 -7
- package/lib/task/commit-intent.ts +58 -24
- package/lib/task/commit-operation.ts +17 -8
- package/lib/task/completion-facts.ts +31 -9
- package/lib/task/control-authority.ts +1 -4
- package/lib/task/current-contract.ts +1 -6
- package/lib/task/events.ts +8 -247
- package/lib/task/execution-inputs.ts +25 -0
- package/lib/task/finalization-envelope.ts +21 -0
- package/lib/task/finalization.ts +10 -35
- package/lib/task/frontmatter.ts +1 -1
- package/lib/task/ledger-intents.ts +4 -7
- package/lib/task/orchestration.ts +11 -17
- package/lib/task/platform-operation-journal.ts +18 -11
- package/lib/task/platform-operation-recovery.ts +90 -31
- package/lib/task/review-fingerprint.ts +4 -26
- package/lib/task/verification-engine.ts +103 -9
- package/lib/task/write.ts +0 -19
- package/package.json +1 -1
- package/templates/.agents/rules/commit-and-pr.en.md +5 -5
- package/templates/.agents/rules/commit-and-pr.zh-CN.md +5 -5
- package/templates/.agents/rules/decision-qualification.en.md +1 -1
- package/templates/.agents/rules/decision-qualification.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +25 -9
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +26 -9
- package/templates/.agents/skills/code-task/reference/code-rules.en.md +1 -1
- package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/reference/fix-mode.en.md +5 -16
- package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +5 -16
- 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-task/SKILL.en.md +15 -12
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +11 -10
- package/templates/.agents/skills/update-agent-infra/SKILL.en.md +1 -1
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +1 -1
- package/templates/.agents/templates/task.zh-CN.md +0 -12
- package/templates/.github/workflows/pr-label.yml +1 -2
- package/dist/lib/internal/task-invalidation.js +0 -63
- package/dist/lib/task/intermediate-cleanup.js +0 -327
- package/dist/lib/task/invalidation-command.js +0 -88
- package/dist/lib/task/invalidation.js +0 -242
- package/lib/internal/task-invalidation.ts +0 -41
- package/lib/task/intermediate-cleanup.ts +0 -393
- package/lib/task/invalidation-command.ts +0 -88
- package/lib/task/invalidation.ts +0 -313
package/dist/lib/task/events.js
CHANGED
|
@@ -19,10 +19,8 @@ import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-
|
|
|
19
19
|
import { captureTaskWriteMetadata, writeTask } from './write.js';
|
|
20
20
|
import { validateLocalArtifact } from './local-artifact-finalization.js';
|
|
21
21
|
import { buildLifecycleFacts, canStart, effectiveReworkTarget } from './capabilities.js';
|
|
22
|
-
import { createInvalidationOperation, invalidationMutation, parseInvalidationDocument, targetIdFor, upsertInvalidation } from './invalidation.js';
|
|
23
|
-
import { reconcileTaskInvalidation } from './invalidation-command.js';
|
|
24
22
|
import { consumeReworkIntents, parseReworkIntentDocument, reworkIntentMutation, supersedeReworkIntents } from './rework-intent.js';
|
|
25
|
-
import {
|
|
23
|
+
import { parseTaskQualification } from './qualification-audit.js';
|
|
26
24
|
import { getArtifactSchema } from './artifact-schema.js';
|
|
27
25
|
import { canonicalSemanticDigest, inspectArtifactContract } from './artifact-operations.js';
|
|
28
26
|
import { inspectReviewIdentity } from './review-identity.js';
|
|
@@ -370,235 +368,6 @@ function eventTrigger(request, family) {
|
|
|
370
368
|
explicitRequest: request.requestId !== undefined || request.reasonCode !== undefined || request.initiator !== undefined || request.orchestrated === true
|
|
371
369
|
};
|
|
372
370
|
}
|
|
373
|
-
function receiptGraphHasCycle(receipts) {
|
|
374
|
-
const edges = new Map();
|
|
375
|
-
for (const receipt of receipts) {
|
|
376
|
-
const outputs = edges.get(receipt.input) ?? [];
|
|
377
|
-
outputs.push(receipt.output);
|
|
378
|
-
edges.set(receipt.input, outputs);
|
|
379
|
-
}
|
|
380
|
-
const visiting = new Set();
|
|
381
|
-
const visited = new Set();
|
|
382
|
-
const visit = (node) => {
|
|
383
|
-
if (visiting.has(node))
|
|
384
|
-
return true;
|
|
385
|
-
if (visited.has(node))
|
|
386
|
-
return false;
|
|
387
|
-
visiting.add(node);
|
|
388
|
-
for (const output of edges.get(node) ?? [])
|
|
389
|
-
if (visit(output))
|
|
390
|
-
return true;
|
|
391
|
-
visiting.delete(node);
|
|
392
|
-
visited.add(node);
|
|
393
|
-
return false;
|
|
394
|
-
};
|
|
395
|
-
return [...edges.keys()].some(visit);
|
|
396
|
-
}
|
|
397
|
-
function qualificationInvalidationSeeds(content, taskDir, inventory, baselineDigest) {
|
|
398
|
-
const task = parseTaskQualification(content);
|
|
399
|
-
if (!task.ok)
|
|
400
|
-
return { changed: true, safe: false, seeds: [] };
|
|
401
|
-
if (baselineDigest !== undefined) {
|
|
402
|
-
if (!/^[a-f0-9]{64}$/u.test(baselineDigest))
|
|
403
|
-
return { changed: true, safe: false, seeds: [] };
|
|
404
|
-
if (baselineDigest === task.qualification.taskInputDigest)
|
|
405
|
-
return { changed: false, safe: true, seeds: [] };
|
|
406
|
-
}
|
|
407
|
-
const audits = [];
|
|
408
|
-
let observedQualification = false;
|
|
409
|
-
for (const artifact of inventory) {
|
|
410
|
-
let artifactContent;
|
|
411
|
-
try {
|
|
412
|
-
artifactContent = fs.readFileSync(path.join(taskDir, artifact.name), 'utf8');
|
|
413
|
-
}
|
|
414
|
-
catch {
|
|
415
|
-
return { changed: true, safe: false, seeds: [] };
|
|
416
|
-
}
|
|
417
|
-
const parsed = parseQualificationAudit(artifactContent);
|
|
418
|
-
if (!parsed.ok)
|
|
419
|
-
return { changed: true, safe: false, seeds: [] };
|
|
420
|
-
if (!parsed.audit.present)
|
|
421
|
-
continue;
|
|
422
|
-
observedQualification = true;
|
|
423
|
-
const snapshot = parsed.audit.snapshot;
|
|
424
|
-
if (!snapshot)
|
|
425
|
-
return { changed: true, safe: false, seeds: [] };
|
|
426
|
-
audits.push({
|
|
427
|
-
name: artifact.name,
|
|
428
|
-
snapshotTaskDigest: snapshot.taskInputDigest,
|
|
429
|
-
snapshotNonConstraintDigest: snapshot.nonConstraintInputDigest,
|
|
430
|
-
dependencies: parsed.audit.constraintDependencies,
|
|
431
|
-
candidates: parsed.audit.candidateQualifications
|
|
432
|
-
});
|
|
433
|
-
}
|
|
434
|
-
if (!task.qualification.present)
|
|
435
|
-
return { changed: false, safe: true, seeds: [] };
|
|
436
|
-
const allAuditsPresent = observedQualification && audits.length === inventory.length;
|
|
437
|
-
if (!allAuditsPresent)
|
|
438
|
-
return { changed: true, safe: false, seeds: [] };
|
|
439
|
-
if (baselineDigest === undefined && audits.every((audit) => audit.snapshotTaskDigest === task.qualification.taskInputDigest)) {
|
|
440
|
-
return { changed: false, safe: true, seeds: [] };
|
|
441
|
-
}
|
|
442
|
-
const candidateMap = new Map(task.qualification.candidates.map((candidate) => [candidate.candidateId, candidate]));
|
|
443
|
-
const currentConstraints = new Map(task.qualification.constraints.map((constraint) => [constraint.constraintId, constraint.digest]));
|
|
444
|
-
const snapshotsMatch = audits.every((audit) => audit.snapshotNonConstraintDigest === task.qualification.nonConstraintInputDigest);
|
|
445
|
-
const candidatesMatch = audits.every((audit) => audit.candidates.length === candidateMap.size
|
|
446
|
-
&& audit.candidates.every((row) => {
|
|
447
|
-
const current = candidateMap.get(row.candidateId);
|
|
448
|
-
return Boolean(current && row.status === current.status && row.impact === current.impact
|
|
449
|
-
&& row.evidence === current.evidence && row.constraintIds.join(',') === current.constraintIds.join(','));
|
|
450
|
-
}));
|
|
451
|
-
const changedConstraints = new Set();
|
|
452
|
-
let referencesValid = true;
|
|
453
|
-
for (const audit of audits) {
|
|
454
|
-
for (const dependency of audit.dependencies) {
|
|
455
|
-
const currentDigest = currentConstraints.get(dependency.constraintId);
|
|
456
|
-
if (!currentDigest)
|
|
457
|
-
referencesValid = false;
|
|
458
|
-
else if (currentDigest !== dependency.constraintDigest)
|
|
459
|
-
changedConstraints.add(dependency.constraintId);
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
const safe = allAuditsPresent && snapshotsMatch && candidatesMatch && referencesValid && changedConstraints.size > 0;
|
|
463
|
-
if (!safe)
|
|
464
|
-
return { changed: true, safe: false, seeds: [] };
|
|
465
|
-
const seeds = new Set();
|
|
466
|
-
for (const audit of audits) {
|
|
467
|
-
if (audit.dependencies.some((dependency) => changedConstraints.has(dependency.constraintId)))
|
|
468
|
-
seeds.add(audit.name);
|
|
469
|
-
}
|
|
470
|
-
return { changed: true, safe: true, seeds: [...seeds] };
|
|
471
|
-
}
|
|
472
|
-
function invalidationMutationForCompletion(content, taskDir, family, artifact, timestamp, frontmatter) {
|
|
473
|
-
if (!['analyze', 'plan', 'code'].includes(family))
|
|
474
|
-
return { mutation: null };
|
|
475
|
-
const parsed = parseInvalidationDocument(content);
|
|
476
|
-
if (!parsed.ok)
|
|
477
|
-
return { error: parsed.message };
|
|
478
|
-
const downstream = {
|
|
479
|
-
analyze: ['review-analysis', 'plan', 'review-plan', 'code', 'review-code'],
|
|
480
|
-
plan: ['review-plan', 'code', 'review-code'],
|
|
481
|
-
code: ['review-code']
|
|
482
|
-
};
|
|
483
|
-
const qualificationFallback = ['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code'];
|
|
484
|
-
const sourceFamily = family;
|
|
485
|
-
const sourceArtifactFamily = sourceFamily === 'analyze' ? 'analysis' : sourceFamily;
|
|
486
|
-
let receipts = [];
|
|
487
|
-
try {
|
|
488
|
-
receipts = parseArtifactReceipts(content).rows;
|
|
489
|
-
}
|
|
490
|
-
catch (error) {
|
|
491
|
-
return { error: error instanceof Error ? error.message : String(error) };
|
|
492
|
-
}
|
|
493
|
-
const inventory = fs.readdirSync(taskDir).flatMap((name) => {
|
|
494
|
-
const identity = parseArtifactName(name);
|
|
495
|
-
if (!identity || !ARTIFACT_FAMILIES.includes(identity.family))
|
|
496
|
-
return [];
|
|
497
|
-
try {
|
|
498
|
-
const stat = fs.lstatSync(path.join(taskDir, name));
|
|
499
|
-
if (!stat.isFile() || stat.isSymbolicLink())
|
|
500
|
-
return [];
|
|
501
|
-
return [{ family: identity.family, name, round: identity.round, sha256: sha256File(path.join(taskDir, name)) }];
|
|
502
|
-
}
|
|
503
|
-
catch (error) {
|
|
504
|
-
throw new Error(`cannot inspect invalidation artifact '${name}': ${error instanceof Error ? error.message : String(error)}`);
|
|
505
|
-
}
|
|
506
|
-
});
|
|
507
|
-
const nodeMap = new Map(inventory.map((node) => [`${node.family}/${node.name}`, node]));
|
|
508
|
-
let graphUsable = receipts.length > 0;
|
|
509
|
-
for (const receipt of receipts) {
|
|
510
|
-
const output = parseArtifactName(receipt.output);
|
|
511
|
-
const input = nodeMap.get(`${parseArtifactName(receipt.input)?.family}/${receipt.input}`);
|
|
512
|
-
if (!output || !nodeMap.has(`${output.family}/${receipt.output}`) || !input || input.sha256 !== receipt.inputSha256) {
|
|
513
|
-
graphUsable = false;
|
|
514
|
-
break;
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
if (graphUsable && receiptGraphHasCycle(receipts))
|
|
518
|
-
graphUsable = false;
|
|
519
|
-
if (inventory.some((node) => node.family !== 'analysis' && node.name !== artifact.name
|
|
520
|
-
&& !receipts.some((receipt) => receipt.output === node.name)))
|
|
521
|
-
graphUsable = false;
|
|
522
|
-
const selected = new Set();
|
|
523
|
-
if (graphUsable) {
|
|
524
|
-
const currentIdentity = parseArtifactName(artifact.name);
|
|
525
|
-
const previousName = currentIdentity && currentIdentity.round > 1 ? artifactName(sourceArtifactFamily, currentIdentity.round - 1) : null;
|
|
526
|
-
const previous = previousName ? inventory.find((node) => node.family === sourceArtifactFamily && node.name === previousName) : null;
|
|
527
|
-
const previousFact = previousName ? parseCompletionFacts(frontmatter.completion_facts).find((fact) => fact.output === previousName && fact.event === `${sourceFamily === 'analyze' ? 'analysis' : sourceFamily}.completed`) : null;
|
|
528
|
-
if (!previous || !previousFact || previousFact.outputSha256 !== previous.sha256)
|
|
529
|
-
graphUsable = false;
|
|
530
|
-
if (graphUsable && previous) {
|
|
531
|
-
selected.add(`${previous.family}/${previous.name}`);
|
|
532
|
-
let changed = true;
|
|
533
|
-
while (changed) {
|
|
534
|
-
changed = false;
|
|
535
|
-
for (const receipt of receipts) {
|
|
536
|
-
const consumer = `${parseArtifactName(receipt.output).family}/${receipt.output}`;
|
|
537
|
-
if (selected.has(`${parseArtifactName(receipt.input).family}/${receipt.input}`) && !selected.has(consumer)) {
|
|
538
|
-
selected.add(consumer);
|
|
539
|
-
changed = true;
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
selected.delete(`${previous.family}/${previous.name}`);
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
const qualification = qualificationInvalidationSeeds(content, taskDir, inventory, typeof frontmatter.qualification_input_digest === 'string' ? frontmatter.qualification_input_digest : undefined);
|
|
547
|
-
let qualificationChange = qualification.changed;
|
|
548
|
-
if (qualificationChange && (!graphUsable || !qualification.safe))
|
|
549
|
-
graphUsable = false;
|
|
550
|
-
if (qualificationChange && graphUsable) {
|
|
551
|
-
for (const seed of qualification.seeds)
|
|
552
|
-
selected.add(`${parseArtifactName(seed)?.family}/${seed}`);
|
|
553
|
-
let changed = true;
|
|
554
|
-
while (changed) {
|
|
555
|
-
changed = false;
|
|
556
|
-
for (const receipt of receipts) {
|
|
557
|
-
const consumer = `${parseArtifactName(receipt.output).family}/${receipt.output}`;
|
|
558
|
-
if (selected.has(`${parseArtifactName(receipt.input).family}/${receipt.input}`) && !selected.has(consumer)) {
|
|
559
|
-
selected.add(consumer);
|
|
560
|
-
changed = true;
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
const reasonCode = qualificationChange ? 'qualification-changed' : 'upstream-replaced';
|
|
566
|
-
const fallbackFamilies = qualificationChange ? qualificationFallback : downstream[sourceFamily];
|
|
567
|
-
const targetNodes = (graphUsable
|
|
568
|
-
? inventory.filter((node) => selected.has(`${node.family}/${node.name}`))
|
|
569
|
-
: inventory.filter((node) => fallbackFamilies.includes(node.family)))
|
|
570
|
-
.filter((node) => !(node.family === FAMILY[family].artifact && node.name === artifact.name));
|
|
571
|
-
const targets = targetNodes.flatMap((node) => {
|
|
572
|
-
const shapes = [
|
|
573
|
-
{ targetKind: 'artifact', targetFamily: node.family, targetArtifact: node.name, targetRound: node.round, targetSha256: node.sha256 }
|
|
574
|
-
];
|
|
575
|
-
for (const receipt of receipts.filter((candidate) => candidate.output === node.name)) {
|
|
576
|
-
shapes.push({ targetKind: 'receipt', targetFamily: node.family, targetArtifact: node.name, targetInput: receipt.input, targetRound: node.round, targetSha256: receipt.inputSha256 });
|
|
577
|
-
}
|
|
578
|
-
if (node.family.startsWith('review-'))
|
|
579
|
-
shapes.push({ targetKind: 'approval', targetFamily: node.family, targetArtifact: node.name, targetRound: node.round, targetSha256: node.sha256 });
|
|
580
|
-
if (node.family === 'review-code')
|
|
581
|
-
shapes.push({ targetKind: 'reviewed-snapshot', targetFamily: node.family, targetArtifact: node.name, targetRound: node.round, targetSha256: node.sha256 });
|
|
582
|
-
return shapes.map((targetShape) => ({ ...targetShape, targetId: targetIdFor('pending', targetShape), operationId: 'pending', status: 'pending', reasonCode, updatedAt: timestamp }));
|
|
583
|
-
});
|
|
584
|
-
if (targets.length === 0)
|
|
585
|
-
return { mutation: null };
|
|
586
|
-
const source = {
|
|
587
|
-
sourceFamily: lifecycleAction(family), sourceArtifact: artifact.name,
|
|
588
|
-
sourceRound: parseArtifactName(artifact.name)?.round ?? 1, sourceSha256: sha256File(artifact.path),
|
|
589
|
-
createdAt: timestamp, updatedAt: timestamp
|
|
590
|
-
};
|
|
591
|
-
const operation = createInvalidationOperation(source);
|
|
592
|
-
const normalizedTargets = targets.map((target) => ({
|
|
593
|
-
...target, operationId: operation.operationId,
|
|
594
|
-
targetId: targetIdFor(operation.operationId, target)
|
|
595
|
-
}));
|
|
596
|
-
const operationWithTotal = createInvalidationOperation(source, normalizedTargets);
|
|
597
|
-
const next = upsertInvalidation(parsed.document, operationWithTotal, normalizedTargets);
|
|
598
|
-
if (!next.ok)
|
|
599
|
-
return { error: next.message };
|
|
600
|
-
return { mutation: next.changed ? invalidationMutation(content, next.document) : null };
|
|
601
|
-
}
|
|
602
371
|
function reworkIntentMutationForCompletion(content, taskDir, family, artifact, timestamp) {
|
|
603
372
|
const parsed = parseReworkIntentDocument(content);
|
|
604
373
|
if (!parsed.ok)
|
|
@@ -606,23 +375,15 @@ function reworkIntentMutationForCompletion(content, taskDir, family, artifact, t
|
|
|
606
375
|
const hash = sha256File(artifact.path);
|
|
607
376
|
let next = parsed.intents;
|
|
608
377
|
if (family === 'analyze' || family === 'plan' || family === 'code') {
|
|
609
|
-
const hashes = Object.fromEntries(
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
try {
|
|
613
|
-
return [[name, sha256File(path.join(taskDir, name))]];
|
|
614
|
-
}
|
|
615
|
-
catch {
|
|
616
|
-
return [];
|
|
617
|
-
}
|
|
378
|
+
const hashes = Object.fromEntries(['review-analysis', 'review-plan', 'review-code'].flatMap((reviewFamily) => {
|
|
379
|
+
const inventory = inspectArtifactDirectory(taskDir, reviewFamily);
|
|
380
|
+
return inventory.status === 'ready' ? inventory.completed.map((review) => [review.name, sha256File(review.path)]) : [];
|
|
618
381
|
}));
|
|
619
382
|
const action = family === 'analyze' ? 'analysis' : family;
|
|
620
383
|
let target = action;
|
|
621
384
|
if (family === 'analyze') {
|
|
622
|
-
const previousAnalysis =
|
|
623
|
-
.
|
|
624
|
-
.filter((identity) => identity?.family === 'analysis' && identity.name !== artifact.name)
|
|
625
|
-
.sort((left, right) => right.round - left.round || left.name.localeCompare(right.name))[0];
|
|
385
|
+
const previousAnalysis = inspectArtifactDirectory(taskDir, 'analysis').completed
|
|
386
|
+
.filter((identity) => identity.name !== artifact.name).at(-1);
|
|
626
387
|
const pathState = previousAnalysis
|
|
627
388
|
? parseLifecyclePathDecision(fs.readFileSync(path.join(taskDir, previousAnalysis.name), 'utf8'))
|
|
628
389
|
: undefined;
|
|
@@ -764,23 +525,6 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
764
525
|
return failed(request, { code: 'TASK_READ_FAILED', message: String(error) }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
765
526
|
}
|
|
766
527
|
const initialParts = eventParts(request.event);
|
|
767
|
-
if (initialParts.phase === 'started' && !request.dryRun) {
|
|
768
|
-
const reconciled = reconcileTaskInvalidation(request.taskRef, { repoRoot: resolved.repoRoot });
|
|
769
|
-
if (reconciled.status === 'failed') {
|
|
770
|
-
return failed(request, {
|
|
771
|
-
code: 'EVENT_TRANSITION_INVALID',
|
|
772
|
-
message: `INVALIDATION_RECONCILE_FAILED: ${reconciled.error?.code ?? 'UNKNOWN'}: ${reconciled.error?.message ?? ''}`
|
|
773
|
-
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
774
|
-
}
|
|
775
|
-
if (reconciled.changed) {
|
|
776
|
-
try {
|
|
777
|
-
content = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
778
|
-
}
|
|
779
|
-
catch (error) {
|
|
780
|
-
return failed(request, { code: 'TASK_READ_FAILED', message: String(error) }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
528
|
let frontmatter;
|
|
785
529
|
try {
|
|
786
530
|
frontmatter = parseTypedTaskFrontmatter(content);
|
|
@@ -946,8 +690,7 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
946
690
|
const safetyFailure = capability.reasonCode === 'TASK_NOT_ACTIVE'
|
|
947
691
|
|| capability.reasonCode === 'LIFECYCLE_EXECUTION_OPEN';
|
|
948
692
|
if (!capability.allowed && (normalized.initiator === 'orchestrator' || safetyFailure)) {
|
|
949
|
-
|
|
950
|
-
return failed(normalized, { code, message: `${capability.reasonCode}: ${capability.evidence.join(', ')}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
693
|
+
return failed(normalized, { code: 'EVENT_TRANSITION_INVALID', message: `${capability.reasonCode}: ${capability.evidence.join(', ')}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
951
694
|
}
|
|
952
695
|
}
|
|
953
696
|
const findingCountError = validateReviewFindingCounts(normalized, content, eventIdentity.family, completedArtifact?.path ?? null);
|
|
@@ -985,7 +728,6 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
985
728
|
}
|
|
986
729
|
let completionReceipts = [];
|
|
987
730
|
let currentFact = null;
|
|
988
|
-
let completionInvalidation = null;
|
|
989
731
|
let completionRework = null;
|
|
990
732
|
if (eventIdentity.phase === 'completed' && completedArtifact) {
|
|
991
733
|
const receipt = buildCompletionReceipt(content, resolved.taskDir, eventIdentity.family, completedArtifact, metadata.timestamp, frontmatter, normalized);
|
|
@@ -1006,10 +748,6 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
1006
748
|
return successNoOp(normalized, resolved.taskId, resolved.taskMdPath, currentStep, eventIdentity, completionReceipts[0]?.completedAt ?? metadata.timestamp, frontmatter, artifactContext);
|
|
1007
749
|
}
|
|
1008
750
|
try {
|
|
1009
|
-
const invalidation = invalidationMutationForCompletion(content, resolved.taskDir, eventIdentity.family, completedArtifact, metadata.timestamp, frontmatter);
|
|
1010
|
-
if ('error' in invalidation)
|
|
1011
|
-
return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: invalidation.error }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
1012
|
-
completionInvalidation = invalidation.mutation;
|
|
1013
751
|
const rework = reworkIntentMutationForCompletion(content, resolved.taskDir, eventIdentity.family, completedArtifact, metadata.timestamp);
|
|
1014
752
|
if ('error' in rework)
|
|
1015
753
|
return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: rework.error }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
@@ -1104,8 +842,6 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
1104
842
|
return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
1105
843
|
}
|
|
1106
844
|
}
|
|
1107
|
-
if (completionInvalidation)
|
|
1108
|
-
mutations.push(completionInvalidation);
|
|
1109
845
|
if (completionRework)
|
|
1110
846
|
mutations.push(completionRework);
|
|
1111
847
|
if (eventIdentity.phase === 'completed' && normalized.implementationInput) {
|
|
@@ -1123,9 +859,7 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
1123
859
|
});
|
|
1124
860
|
}
|
|
1125
861
|
mutations.push({ kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: section.heading, body });
|
|
1126
|
-
const
|
|
1127
|
-
&& ['analyze', 'plan', 'code'].includes(eventIdentity.family);
|
|
1128
|
-
const result = writeTask({ taskRef: normalized.taskRef, expectedState: 'active', dryRun: normalized.dryRun, mutations }, { ...options, invalidationContext: sourceCompletion ? 'source-completion' : 'standard', metadataProvider: () => metadata });
|
|
862
|
+
const result = writeTask({ taskRef: normalized.taskRef, expectedState: 'active', dryRun: normalized.dryRun, mutations }, { ...options, metadataProvider: () => metadata });
|
|
1129
863
|
if (result.status === 'failed')
|
|
1130
864
|
return failed(normalized, result.error, { taskId: result.taskId, taskMdPath: result.taskMdPath, fromStep: currentStep, toStep: step, action: eventIdentity.action, phase: eventIdentity.phase, timestamp: result.timestamp, agentInfraVersion: result.agentInfraVersion, operations: result.operations, artifactContext });
|
|
1131
865
|
if (!normalized.dryRun && orchestrationCompletion) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { pathIncludes } from './lifecycle-path.js';
|
|
2
|
+
function expectedExecutionInputFamilies(stage, pathState) {
|
|
3
|
+
if (pathState.status !== 'valid')
|
|
4
|
+
return [];
|
|
5
|
+
const input = stage === 'plan' ? 'analysis' : pathIncludes(pathState, 'plan') ? 'plan' : 'analysis';
|
|
6
|
+
const review = input === 'plan' ? 'review-plan' : 'review-analysis';
|
|
7
|
+
return [input, ...(pathIncludes(pathState, review) ? [review] : [])];
|
|
8
|
+
}
|
|
9
|
+
function executionInputsMatchLatest(stage, pathState, latest, captured) {
|
|
10
|
+
const expected = expectedExecutionInputFamilies(stage, pathState);
|
|
11
|
+
return expected.every((family) => Boolean(latest[family] && captured.includes(latest[family])));
|
|
12
|
+
}
|
|
13
|
+
export { expectedExecutionInputFamilies, executionInputsMatchLatest };
|
|
14
|
+
//# sourceMappingURL=execution-inputs.js.map
|
|
@@ -678,16 +678,6 @@ async function prepareUnderLock(request, taskId, options) {
|
|
|
678
678
|
catch (error) {
|
|
679
679
|
return failed(taskId, errorOf(error, 'TASK_FINALIZATION_RECEIPT_INVALID'));
|
|
680
680
|
}
|
|
681
|
-
if (options.controlBinding && (receipt.controlBinding?.generation !== options.controlBinding.generation
|
|
682
|
-
|| receipt.controlBinding.requestId !== options.controlBinding.requestId)) {
|
|
683
|
-
const state = resolveTaskRef(taskId, { repoRoot });
|
|
684
|
-
if (!(receipt.lifecycle === 'done' && state.ok && state.state === 'completed')) {
|
|
685
|
-
return failed(taskId, {
|
|
686
|
-
code: 'TASK_FINALIZATION_RECOVERY_PROOF_UNAVAILABLE',
|
|
687
|
-
message: 'canonical receipt is not bound to the current sandbox request', retryable: false
|
|
688
|
-
});
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
681
|
const preflightState = resolveTaskRef(taskId, { repoRoot });
|
|
692
682
|
if (options.preflight && preflightState.ok && preflightState.state === 'active') {
|
|
693
683
|
try {
|
|
@@ -710,7 +700,7 @@ async function prepareUnderLock(request, taskId, options) {
|
|
|
710
700
|
if (preflightState.ok && preflightState.state === 'active') {
|
|
711
701
|
const recovery = await recoverPlatformOperations(taskId, 'all', { agent: request.agent, cwd: repoRoot });
|
|
712
702
|
if (recovery.status !== 'applied' && recovery.status !== 'no-op') {
|
|
713
|
-
return failed(taskId, recovery.error
|
|
703
|
+
return failed(taskId, recovery.error ? { ...recovery.error, retryable: true } : {
|
|
714
704
|
code: 'PLATFORM_OPERATION_RECOVERY_PENDING',
|
|
715
705
|
message: 'Pending platform operations remain unresolved',
|
|
716
706
|
retryable: true
|
|
@@ -720,12 +710,10 @@ async function prepareUnderLock(request, taskId, options) {
|
|
|
720
710
|
const taskFrontmatter = parseTaskFrontmatter(taskContent);
|
|
721
711
|
const issueIdentity = taskIssueIdentity(taskFrontmatter);
|
|
722
712
|
if (issueIdentity) {
|
|
723
|
-
const base = typeof taskFrontmatter.delivery_base_ref === 'string' ? taskFrontmatter.delivery_base_ref.trim() : '';
|
|
724
713
|
let synced;
|
|
725
714
|
try {
|
|
726
715
|
synced = await issueSync(taskId, {
|
|
727
|
-
agent: request.agent, cwd: repoRoot, requirements: true, issueType: true, fields: true,
|
|
728
|
-
...(base ? { inLabels: 'from-diff', base } : {}), dependency: 'required'
|
|
716
|
+
agent: request.agent, cwd: repoRoot, requirements: true, issueType: true, fields: true, dependency: 'required'
|
|
729
717
|
});
|
|
730
718
|
}
|
|
731
719
|
catch (error) {
|
|
@@ -733,7 +721,7 @@ async function prepareUnderLock(request, taskId, options) {
|
|
|
733
721
|
return failed(taskId, detail);
|
|
734
722
|
}
|
|
735
723
|
const issueOperations = synced.operations;
|
|
736
|
-
const badOperation = issueOperations?.find((operation) => ['requirements', 'issue-type', 'fields'
|
|
724
|
+
const badOperation = issueOperations?.find((operation) => ['requirements', 'issue-type', 'fields'].includes(operation.name)
|
|
737
725
|
&& operation.status !== 'applied' && operation.status !== 'no-op'
|
|
738
726
|
&& !(operation.name === 'requirements' && operation.status === 'skipped' && operation.reasonCode === 'NO_REQUIREMENTS_ANCHOR'));
|
|
739
727
|
if ((synced.status !== 'applied' && synced.status !== 'no-op') || synced.error || badOperation) {
|
|
@@ -759,7 +747,7 @@ async function prepareUnderLock(request, taskId, options) {
|
|
|
759
747
|
retryable: true
|
|
760
748
|
});
|
|
761
749
|
}
|
|
762
|
-
for (const name of ['requirements', 'issue-type', 'fields'
|
|
750
|
+
for (const name of ['requirements', 'issue-type', 'fields']) {
|
|
763
751
|
const operation = issueOperations?.find((candidate) => candidate.name === name);
|
|
764
752
|
if (!operation)
|
|
765
753
|
return failed(taskId, {
|
|
@@ -782,14 +770,6 @@ async function prepareUnderLock(request, taskId, options) {
|
|
|
782
770
|
code: 'FINALIZATION_ISSUE_TYPE_UNCONFIRMED', message: 'Issue Type did not match the verified target', retryable: true
|
|
783
771
|
});
|
|
784
772
|
}
|
|
785
|
-
else if (name === 'labels:in') {
|
|
786
|
-
const expected = Array.isArray(operation.value) ? operation.value : before?.labels;
|
|
787
|
-
if (!expected || expected.filter((label) => label.startsWith('in:')).sort().join('\0')
|
|
788
|
-
!== remote.labels.filter((label) => label.startsWith('in:')).sort().join('\0'))
|
|
789
|
-
return failed(taskId, {
|
|
790
|
-
code: 'FINALIZATION_IN_LABELS_UNCONFIRMED', message: 'Issue in-labels did not match the verified target', retryable: true
|
|
791
|
-
});
|
|
792
|
-
}
|
|
793
773
|
else {
|
|
794
774
|
const expected = operation.value && typeof operation.value === 'object'
|
|
795
775
|
? operation.value
|
|
@@ -989,6 +969,7 @@ async function commitPreparedTaskFinalization(request, options) {
|
|
|
989
969
|
}
|
|
990
970
|
}
|
|
991
971
|
let receipt = readReceipt(repoRoot, resolved.taskId);
|
|
972
|
+
let importedHandoff = false;
|
|
992
973
|
if (!receipt && options.controlBinding && options.handoffDirectory && request.handoffSha256) {
|
|
993
974
|
try {
|
|
994
975
|
const handoff = readTaskFinalizationHandoff(options.handoffDirectory, resolved.taskId, options.controlBinding, request.handoffSha256);
|
|
@@ -999,6 +980,7 @@ async function commitPreparedTaskFinalization(request, options) {
|
|
|
999
980
|
throw new Error('TASK_FINALIZATION_RECOVERY_PROOF_UNAVAILABLE');
|
|
1000
981
|
}
|
|
1001
982
|
writeReceipt(repoRoot, receipt);
|
|
983
|
+
importedHandoff = true;
|
|
1002
984
|
try {
|
|
1003
985
|
handoff.cleanup();
|
|
1004
986
|
}
|
|
@@ -1020,27 +1002,19 @@ async function commitPreparedTaskFinalization(request, options) {
|
|
|
1020
1002
|
}
|
|
1021
1003
|
if (options.controlBinding) {
|
|
1022
1004
|
const proof = inspectTaskLifecycleProgress(repoRoot, resolved.taskId, request.agent);
|
|
1023
|
-
|
|
1024
|
-
if (!oldBinding || oldBinding.generation !== options.controlBinding.generation || proof === 'unknown') {
|
|
1005
|
+
if (proof === 'unknown') {
|
|
1025
1006
|
return failed(resolved.taskId, {
|
|
1026
1007
|
code: 'TASK_FINALIZATION_RECOVERY_PROOF_UNAVAILABLE',
|
|
1027
|
-
message: '
|
|
1008
|
+
message: 'task lifecycle progress cannot be verified', retryable: false
|
|
1028
1009
|
});
|
|
1029
1010
|
}
|
|
1030
|
-
if (
|
|
1031
|
-
if (receipt.lifecycle !== 'pending' || !options.handoffDirectory || !request.handoffSha256) {
|
|
1032
|
-
return failed(resolved.taskId, {
|
|
1033
|
-
code: 'TASK_FINALIZATION_RECOVERY_PROOF_UNAVAILABLE',
|
|
1034
|
-
message: 'pending receipt cannot be rebound without a current handoff', retryable: false
|
|
1035
|
-
});
|
|
1036
|
-
}
|
|
1011
|
+
if (!importedHandoff && options.handoffDirectory && request.handoffSha256) {
|
|
1037
1012
|
try {
|
|
1038
1013
|
const handoff = readTaskFinalizationHandoff(options.handoffDirectory, resolved.taskId, options.controlBinding, request.handoffSha256);
|
|
1039
1014
|
const nextReceipt = validateReceipt(handoff.receipt, resolved.taskId);
|
|
1040
1015
|
if (nextReceipt.receiptId !== receipt.receiptId || nextReceipt.intent !== receipt.intent
|
|
1041
|
-
|| nextReceipt.lifecycle !==
|
|
1016
|
+
|| nextReceipt.lifecycle !== receipt.lifecycle)
|
|
1042
1017
|
throw new Error('handoff is not continuous with canonical receipt');
|
|
1043
|
-
receipt = updateReceipt(repoRoot, receipt, { controlBinding: options.controlBinding });
|
|
1044
1018
|
try {
|
|
1045
1019
|
handoff.cleanup();
|
|
1046
1020
|
}
|
|
@@ -47,7 +47,7 @@ function serializeScalar(value) {
|
|
|
47
47
|
}
|
|
48
48
|
if (value === '')
|
|
49
49
|
return '';
|
|
50
|
-
const serialized = stringify(value).trimEnd();
|
|
50
|
+
const serialized = stringify(value, { lineWidth: 0 }).trimEnd();
|
|
51
51
|
if (serialized.includes('\n') || serialized.includes('\r')) {
|
|
52
52
|
throw new FrontmatterError('MUTATION_INVALID', 'frontmatter values must be scalar');
|
|
53
53
|
}
|
|
@@ -10,6 +10,7 @@ import { parseLegacyReworkIntentDocument, parseReworkIntentDocument, reworkInten
|
|
|
10
10
|
import { extractSection, extractSubSection } from './sections.js';
|
|
11
11
|
import { parseLifecyclePathDecision } from './lifecycle-path.js';
|
|
12
12
|
import { scanVisibleMarkdown } from './markdown.js';
|
|
13
|
+
import { inspectArtifactDirectory } from './artifact-lifecycle.js';
|
|
13
14
|
const PREFIX = { analysis: 'AN', plan: 'PL', code: 'CD' };
|
|
14
15
|
const RESPONSE = new Set(['accepted', 'adjusted', 'refuted', 'cannot-judge']);
|
|
15
16
|
const DISPOSITION = new Set(['confirmed', 'closed', 'open', 'needs-human-decision']);
|
|
@@ -61,9 +62,8 @@ function findingEvidence(reviewContent, evidence) {
|
|
|
61
62
|
return semanticFindingEvidence(reviewContent, heading, next?.start ?? reviewContent.length);
|
|
62
63
|
}
|
|
63
64
|
function taskFactDigest(taskDir, content) {
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
const flow = latest ? parseLifecyclePathDecision(fs.readFileSync(path.join(taskDir, latest.name), 'utf8')) : null;
|
|
65
|
+
const latest = inspectArtifactDirectory(taskDir, 'analysis').latest;
|
|
66
|
+
const flow = latest ? parseLifecyclePathDecision(fs.readFileSync(latest.path, 'utf8'), latest.name) : null;
|
|
67
67
|
return semanticDigest(JSON.stringify({
|
|
68
68
|
taskInput: normalizedTaskFact(extractSection(content, ['任务输入', 'Task Input'])),
|
|
69
69
|
requirements: normalizedTaskFact(extractSection(content, ['需求', 'Requirements'])),
|
|
@@ -220,10 +220,7 @@ function applyLedgerIntent(intent, options = {}) {
|
|
|
220
220
|
}
|
|
221
221
|
const sourceIdentity = parseArtifactName(intent.sourceArtifact);
|
|
222
222
|
const expectedFamily = `review-${finding.stage}`;
|
|
223
|
-
const latestSource =
|
|
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];
|
|
223
|
+
const latestSource = inspectArtifactDirectory(resolved.taskDir, expectedFamily).latest;
|
|
227
224
|
if (!sourceIdentity || sourceIdentity.family !== expectedFamily || latestSource?.name !== sourceIdentity.name) {
|
|
228
225
|
return failed(intent, 'LEDGER_IDENTITY_CONFLICT', 'rebuild source artifact is not the latest review for the finding stage', resolved.taskId);
|
|
229
226
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { createHash, randomUUID } from 'node:crypto';
|
|
4
|
-
import {
|
|
4
|
+
import { inspectArtifactDirectory } from './artifact-lifecycle.js';
|
|
5
5
|
import { parseTypedTaskFrontmatter } from './frontmatter.js';
|
|
6
6
|
import { validateCurrentTaskContract } from './current-contract.js';
|
|
7
7
|
import { parseReviewSummary } from './review-artifacts.js';
|
|
@@ -20,7 +20,6 @@ import { extractReviewBaseline, extractReviewDiffBase, extractReviewTargetHead,
|
|
|
20
20
|
import { resolveDeliveryTarget, resolveDiffBase, resolveTargetHead } from './delivery-target.js';
|
|
21
21
|
import { buildLifecycleFacts, canStart, recommendNext } from './capabilities.js';
|
|
22
22
|
import { hasOpenLifecycleExecution } from './activity-log.js';
|
|
23
|
-
import { reconcileTaskInvalidation } from './invalidation-command.js';
|
|
24
23
|
import { normalizeAgentToken } from '../agent-clients/tokens.js';
|
|
25
24
|
import { resolveArtifactContext } from './artifact-lifecycle.js';
|
|
26
25
|
function supportsLifecycleDelegation(client) {
|
|
@@ -441,8 +440,11 @@ function beginOrResumeOrchestration(taskRef, options = {}) {
|
|
|
441
440
|
return { status: 'running', changed: true, taskId: resolved.taskId, run, next: null, error: null };
|
|
442
441
|
}
|
|
443
442
|
function validateSavedCurrentDiffBase(repoRoot, metadata, taskDir, reviewedHead) {
|
|
444
|
-
const
|
|
445
|
-
|
|
443
|
+
const reviewInventory = inspectArtifactDirectory(taskDir, 'review-code');
|
|
444
|
+
if (reviewInventory.status !== 'ready' || !reviewInventory.latest) {
|
|
445
|
+
return { code: 'ORCHESTRATION_REVIEW_INVALID', message: 'latest completed review-code artifact is unavailable' };
|
|
446
|
+
}
|
|
447
|
+
const reviewContent = fs.readFileSync(reviewInventory.latest.path, 'utf8');
|
|
446
448
|
const savedTargetHead = extractReviewTargetHead(reviewContent);
|
|
447
449
|
const savedDiffBase = extractReviewDiffBase(reviewContent);
|
|
448
450
|
const savedReviewedHead = extractReviewedHead(reviewContent) || extractReviewBaseline(reviewContent);
|
|
@@ -540,8 +542,11 @@ function routeOrchestration(taskRef, options = {}) {
|
|
|
540
542
|
if (facts.facts.executionBusy && !run?.pendingDelegation) {
|
|
541
543
|
return failed('ORCHESTRATION_EXECUTION_BUSY', 'clean completion requires no open lifecycle execution', resolved.taskId);
|
|
542
544
|
}
|
|
543
|
-
const
|
|
544
|
-
|
|
545
|
+
const reviewInventory = inspectArtifactDirectory(resolved.taskDir, 'review-code');
|
|
546
|
+
if (reviewInventory.status !== 'ready' || !reviewInventory.latest) {
|
|
547
|
+
return failed('ORCHESTRATION_REVIEW_INVALID', 'latest completed review-code artifact is unavailable', resolved.taskId);
|
|
548
|
+
}
|
|
549
|
+
const review = parseReviewSummary(fs.readFileSync(reviewInventory.latest.path, 'utf8'));
|
|
545
550
|
if (!review.ok || review.summary.manualValidation === null) {
|
|
546
551
|
return failed('ORCHESTRATION_REVIEW_INVALID', 'latest code review has no numeric manual-validation count', resolved.taskId);
|
|
547
552
|
}
|
|
@@ -678,10 +683,6 @@ function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
|
|
|
678
683
|
}
|
|
679
684
|
if (run.stepCount >= run.maxSteps)
|
|
680
685
|
return pauseOrchestration(taskRef, 'ORCHESTRATION_MAX_STEPS', 'maximum orchestration steps reached', true, options);
|
|
681
|
-
const invalidation = reconcileTaskInvalidation(taskRef, { repoRoot: resolved.repoRoot });
|
|
682
|
-
if (invalidation.status === 'failed') {
|
|
683
|
-
return failed('ORCHESTRATION_INVALIDATION_RECONCILE_FAILED', `${invalidation.error?.code ?? 'UNKNOWN'}: ${invalidation.error?.message ?? ''}`, resolved.taskId);
|
|
684
|
-
}
|
|
685
686
|
const routed = routeOrchestration(taskRef, options);
|
|
686
687
|
if (!routed.next)
|
|
687
688
|
return routed;
|