@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.
Files changed (124) hide show
  1. package/dist/bin/internal-cli.js +0 -6
  2. package/dist/lib/git/workflow.js +1 -1
  3. package/dist/lib/internal/cli-route-inventory.js +0 -3
  4. package/dist/lib/internal/sandbox-control.js +30 -5
  5. package/dist/lib/internal/task-finalization.js +14 -23
  6. package/dist/lib/internal/task-operation-registry.js +2 -3
  7. package/dist/lib/internal/task-platform-recovery.js +9 -4
  8. package/dist/lib/platform/completion-backfill.js +2 -1
  9. package/dist/lib/platform/context.js +4 -2
  10. package/dist/lib/platform/github-client.js +20 -17
  11. package/dist/lib/platform/github-provider.js +42 -22
  12. package/dist/lib/platform/github-release-notes.js +4 -2
  13. package/dist/lib/platform/in-label-sync.js +2 -1
  14. package/dist/lib/platform/issue-comments.js +13 -9
  15. package/dist/lib/platform/operation-coordinator.js +1 -1
  16. package/dist/lib/platform/pr-review.js +2 -1
  17. package/dist/lib/platform/provider-bridge.js +4 -5
  18. package/dist/lib/platform/provider-contract.d.ts +3 -6
  19. package/dist/lib/platform/provider-contract.js +3 -15
  20. package/dist/lib/platform/provider-validation.js +9 -4
  21. package/dist/lib/platform/pull-requests.js +4 -2
  22. package/dist/lib/sandbox/control/client.js +54 -6
  23. package/dist/lib/sandbox/control/executor.js +8 -3
  24. package/dist/lib/sandbox/removal.js +0 -1
  25. package/dist/lib/task/artifact-command.js +11 -1
  26. package/dist/lib/task/artifact-lifecycle.js +119 -62
  27. package/dist/lib/task/artifact-selection.js +1 -1
  28. package/dist/lib/task/capabilities.js +55 -21
  29. package/dist/lib/task/commands/status.js +0 -27
  30. package/dist/lib/task/commit-identity.js +22 -7
  31. package/dist/lib/task/commit-intent.js +67 -26
  32. package/dist/lib/task/commit-operation.js +19 -8
  33. package/dist/lib/task/completion-facts.js +32 -10
  34. package/dist/lib/task/control-authority.js +2 -4
  35. package/dist/lib/task/current-contract.js +1 -5
  36. package/dist/lib/task/events.js +8 -274
  37. package/dist/lib/task/execution-inputs.js +14 -0
  38. package/dist/lib/task/finalization-envelope.js +4 -0
  39. package/dist/lib/task/finalization.js +10 -36
  40. package/dist/lib/task/frontmatter.js +1 -1
  41. package/dist/lib/task/ledger-intents.js +4 -7
  42. package/dist/lib/task/orchestration.js +11 -10
  43. package/dist/lib/task/platform-operation-journal.js +17 -10
  44. package/dist/lib/task/platform-operation-recovery.js +95 -28
  45. package/dist/lib/task/review-fingerprint.js +4 -26
  46. package/dist/lib/task/verification-engine.js +95 -6
  47. package/dist/lib/task/write.js +0 -11
  48. package/dist/lifecycle-build-manifest.json +3 -3
  49. package/lib/git/workflow.ts +1 -1
  50. package/lib/internal/cli-route-inventory.ts +0 -2
  51. package/lib/internal/sandbox-control.ts +39 -6
  52. package/lib/internal/task-finalization.ts +16 -24
  53. package/lib/internal/task-operation-registry.ts +2 -3
  54. package/lib/internal/task-platform-recovery.ts +9 -4
  55. package/lib/platform/completion-backfill.ts +2 -1
  56. package/lib/platform/context.ts +8 -4
  57. package/lib/platform/github-client.ts +24 -20
  58. package/lib/platform/github-provider.ts +47 -26
  59. package/lib/platform/github-release-notes.ts +5 -3
  60. package/lib/platform/in-label-sync.ts +2 -1
  61. package/lib/platform/issue-comments.ts +14 -9
  62. package/lib/platform/operation-coordinator.ts +1 -1
  63. package/lib/platform/pr-review.ts +2 -1
  64. package/lib/platform/provider-bridge.ts +10 -9
  65. package/lib/platform/provider-contract.ts +6 -21
  66. package/lib/platform/provider-loader.ts +7 -6
  67. package/lib/platform/provider-validation.ts +30 -8
  68. package/lib/platform/pull-requests.ts +4 -2
  69. package/lib/platform/repository-metadata.ts +3 -2
  70. package/lib/platform/security-alerts.ts +1 -1
  71. package/lib/platform/types.ts +2 -0
  72. package/lib/sandbox/control/client.ts +57 -6
  73. package/lib/sandbox/control/executor.ts +8 -3
  74. package/lib/sandbox/removal.ts +2 -10
  75. package/lib/task/artifact-command.ts +10 -1
  76. package/lib/task/artifact-lifecycle.ts +100 -52
  77. package/lib/task/artifact-selection.ts +1 -1
  78. package/lib/task/capabilities.ts +47 -24
  79. package/lib/task/commands/status.ts +0 -31
  80. package/lib/task/commit-identity.ts +24 -7
  81. package/lib/task/commit-intent.ts +58 -24
  82. package/lib/task/commit-operation.ts +17 -8
  83. package/lib/task/completion-facts.ts +31 -9
  84. package/lib/task/control-authority.ts +1 -4
  85. package/lib/task/current-contract.ts +1 -6
  86. package/lib/task/events.ts +8 -247
  87. package/lib/task/execution-inputs.ts +25 -0
  88. package/lib/task/finalization-envelope.ts +21 -0
  89. package/lib/task/finalization.ts +10 -35
  90. package/lib/task/frontmatter.ts +1 -1
  91. package/lib/task/ledger-intents.ts +4 -7
  92. package/lib/task/orchestration.ts +11 -17
  93. package/lib/task/platform-operation-journal.ts +18 -11
  94. package/lib/task/platform-operation-recovery.ts +90 -31
  95. package/lib/task/review-fingerprint.ts +4 -26
  96. package/lib/task/verification-engine.ts +103 -9
  97. package/lib/task/write.ts +0 -19
  98. package/package.json +1 -1
  99. package/templates/.agents/rules/commit-and-pr.en.md +5 -5
  100. package/templates/.agents/rules/commit-and-pr.zh-CN.md +5 -5
  101. package/templates/.agents/rules/decision-qualification.en.md +1 -1
  102. package/templates/.agents/rules/decision-qualification.zh-CN.md +1 -1
  103. package/templates/.agents/skills/code-task/SKILL.en.md +25 -9
  104. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +26 -9
  105. package/templates/.agents/skills/code-task/reference/code-rules.en.md +1 -1
  106. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +1 -1
  107. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +5 -16
  108. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +5 -16
  109. package/templates/.agents/skills/code-task/reference/report-template.en.md +1 -1
  110. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +1 -1
  111. package/templates/.agents/skills/complete-task/SKILL.en.md +15 -12
  112. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +11 -10
  113. package/templates/.agents/skills/update-agent-infra/SKILL.en.md +1 -1
  114. package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +1 -1
  115. package/templates/.agents/templates/task.zh-CN.md +0 -12
  116. package/templates/.github/workflows/pr-label.yml +1 -2
  117. package/dist/lib/internal/task-invalidation.js +0 -63
  118. package/dist/lib/task/intermediate-cleanup.js +0 -327
  119. package/dist/lib/task/invalidation-command.js +0 -88
  120. package/dist/lib/task/invalidation.js +0 -242
  121. package/lib/internal/task-invalidation.ts +0 -41
  122. package/lib/task/intermediate-cleanup.ts +0 -393
  123. package/lib/task/invalidation-command.ts +0 -88
  124. package/lib/task/invalidation.ts +0 -313
@@ -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 { ARTIFACT_FAMILIES, parseQualificationAudit, parseTaskQualification } from './qualification-audit.js';
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(fs.readdirSync(taskDir).flatMap((name) => {
610
- if (!/^review-(?:analysis|plan|code)(?:-r\d+)?\.md$/.test(name))
611
- return [];
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 = fs.readdirSync(taskDir)
623
- .map((name) => parseArtifactName(name))
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
- const code = capability.reasonCode === 'INVALIDATION_INCOMPLETE' ? 'TASK_INVALIDATION_BLOCKED' : 'EVENT_TRANSITION_INVALID';
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 sourceCompletion = eventIdentity.phase === 'completed'
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
@@ -0,0 +1,4 @@
1
+ export function serializeTaskFinalizationEnvelope(envelope) {
2
+ return `${JSON.stringify({ version: 2, ...envelope })}\n`;
3
+ }
4
+ //# sourceMappingURL=finalization-envelope.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', ...(base ? ['labels:in'] : [])].includes(operation.name)
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', ...(base ? ['labels:in'] : [])]) {
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
- const oldBinding = receipt.controlBinding;
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: 'canonical receipt has no valid host recovery proof', retryable: false
1008
+ message: 'task lifecycle progress cannot be verified', retryable: false
1028
1009
  });
1029
1010
  }
1030
- if (oldBinding.requestId !== options.controlBinding.requestId && proof === 'not-started') {
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 !== 'pending')
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 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;
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 = 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];
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 { artifactName, maxArtifactRound } from './artifact-name.js';
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 reviewRound = maxArtifactRound(fs.readdirSync(taskDir), 'review-code');
445
- const reviewContent = fs.readFileSync(path.join(taskDir, artifactName('review-code', reviewRound)), 'utf8');
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 reviewRound = maxArtifactRound(fs.readdirSync(resolved.taskDir), 'review-code');
544
- const review = parseReviewSummary(fs.readFileSync(path.join(resolved.taskDir, artifactName('review-code', reviewRound)), 'utf8'));
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;