@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.
Files changed (172) hide show
  1. package/README.md +17 -8
  2. package/README.zh-CN.md +17 -8
  3. package/dist/bin/internal-cli.js +1 -6
  4. package/dist/lib/decide.js +4 -41
  5. package/dist/lib/git/branch-worktree.js +45 -0
  6. package/dist/lib/internal/cli-route-inventory.js +1 -4
  7. package/dist/lib/internal/task-activity.js +7 -40
  8. package/dist/lib/internal/task-artifact.js +1 -1
  9. package/dist/lib/internal/task-event.js +4 -37
  10. package/dist/lib/internal/task-finalization.js +48 -1
  11. package/dist/lib/internal/task-ledger.js +14 -44
  12. package/dist/lib/internal/task-lifecycle.js +1 -1
  13. package/dist/lib/internal/task-operation-registry.js +2 -4
  14. package/dist/lib/internal/task-review.js +5 -37
  15. package/dist/lib/internal/task-verify.js +3 -47
  16. package/dist/lib/internal/task-warning.js +4 -40
  17. package/dist/lib/platform/completion-backfill.js +63 -17
  18. package/dist/lib/platform/pr-summary.js +4 -23
  19. package/dist/lib/platform/verification-sync.js +12 -49
  20. package/dist/lib/sandbox/control/client.js +4 -1
  21. package/dist/lib/sandbox/control/executor.js +12 -4
  22. package/dist/lib/sandbox/control/protocol.js +2 -1
  23. package/dist/lib/sandbox/control/server.js +26 -143
  24. package/dist/lib/sandbox/removal.js +15 -8
  25. package/dist/lib/sandbox/task-cleanup.js +13 -20
  26. package/dist/lib/task/activity-intent.js +1 -2
  27. package/dist/lib/task/artifact-command.js +11 -4
  28. package/dist/lib/task/artifact-lifecycle.js +217 -85
  29. package/dist/lib/task/artifact-operations.js +37 -28
  30. package/dist/lib/task/artifact-receipts.js +4 -1
  31. package/dist/lib/task/artifact-schema.js +8 -7
  32. package/dist/lib/task/artifact-selection.js +25 -0
  33. package/dist/lib/task/capabilities.js +158 -39
  34. package/dist/lib/task/commands/status.js +9 -2
  35. package/dist/lib/task/control-authority.js +12 -168
  36. package/dist/lib/task/decision-intents.js +14 -4
  37. package/dist/lib/task/delivery.js +1 -43
  38. package/dist/lib/task/events.js +68 -73
  39. package/dist/lib/task/finalization-handoff.js +92 -0
  40. package/dist/lib/task/finalization.js +423 -154
  41. package/dist/lib/task/intermediate-cleanup.js +9 -148
  42. package/dist/lib/task/ledger-intents.js +159 -20
  43. package/dist/lib/task/lifecycle-path.js +75 -0
  44. package/dist/lib/task/lifecycle.js +60 -11
  45. package/dist/lib/task/local-artifact-finalization.js +13 -47
  46. package/dist/lib/task/orchestration.js +17 -7
  47. package/dist/lib/task/review-command.js +1 -2
  48. package/dist/lib/task/review-finalization.js +4 -18
  49. package/dist/lib/task/rework-intent.js +70 -4
  50. package/dist/lib/task/snapshot.js +6 -6
  51. package/dist/lib/task/verification-engine.js +25 -30
  52. package/dist/lib/task/verification.js +2 -13
  53. package/dist/lib/task/workflow-warning-intents.js +23 -10
  54. package/dist/lib/task/workspace-snapshot.js +7 -2
  55. package/dist/lib/task/write.js +1 -2
  56. package/dist/lifecycle-build-manifest.json +3 -3
  57. package/lib/decide.ts +5 -35
  58. package/lib/git/branch-worktree.ts +41 -0
  59. package/lib/internal/cli-route-inventory.ts +1 -3
  60. package/lib/internal/task-activity.ts +6 -34
  61. package/lib/internal/task-artifact.ts +1 -1
  62. package/lib/internal/task-event.ts +4 -37
  63. package/lib/internal/task-finalization.ts +47 -1
  64. package/lib/internal/task-ledger.ts +15 -41
  65. package/lib/internal/task-lifecycle.ts +2 -2
  66. package/lib/internal/task-operation-registry.ts +2 -4
  67. package/lib/internal/task-review.ts +5 -31
  68. package/lib/internal/task-verify.ts +3 -39
  69. package/lib/internal/task-warning.ts +3 -32
  70. package/lib/platform/completion-backfill.ts +78 -16
  71. package/lib/platform/pr-summary.ts +4 -25
  72. package/lib/platform/verification-sync.ts +13 -53
  73. package/lib/sandbox/control/client.ts +5 -1
  74. package/lib/sandbox/control/executor.ts +13 -4
  75. package/lib/sandbox/control/protocol.ts +3 -2
  76. package/lib/sandbox/control/server.ts +21 -146
  77. package/lib/sandbox/removal.ts +23 -7
  78. package/lib/sandbox/task-cleanup.ts +12 -20
  79. package/lib/task/activity-intent.ts +1 -2
  80. package/lib/task/artifact-command.ts +14 -4
  81. package/lib/task/artifact-lifecycle.ts +236 -85
  82. package/lib/task/artifact-operations.ts +59 -28
  83. package/lib/task/artifact-receipts.ts +4 -1
  84. package/lib/task/artifact-schema.ts +10 -7
  85. package/lib/task/artifact-selection.ts +53 -0
  86. package/lib/task/capabilities.ts +154 -40
  87. package/lib/task/commands/status.ts +11 -2
  88. package/lib/task/control-authority.ts +17 -189
  89. package/lib/task/decision-intents.ts +11 -4
  90. package/lib/task/delivery.ts +1 -40
  91. package/lib/task/events.ts +73 -74
  92. package/lib/task/finalization-handoff.ts +104 -0
  93. package/lib/task/finalization.ts +388 -122
  94. package/lib/task/intermediate-cleanup.ts +9 -125
  95. package/lib/task/ledger-intents.ts +157 -20
  96. package/lib/task/lifecycle-path.ts +98 -0
  97. package/lib/task/lifecycle.ts +58 -13
  98. package/lib/task/local-artifact-finalization.ts +14 -56
  99. package/lib/task/orchestration.ts +26 -8
  100. package/lib/task/review-command.ts +2 -5
  101. package/lib/task/review-finalization.ts +4 -20
  102. package/lib/task/rework-intent.ts +75 -5
  103. package/lib/task/snapshot.ts +6 -6
  104. package/lib/task/verification-engine.ts +29 -30
  105. package/lib/task/verification.ts +3 -16
  106. package/lib/task/workflow-warning-intents.ts +20 -10
  107. package/lib/task/workspace-snapshot.ts +3 -2
  108. package/lib/task/write.ts +1 -4
  109. package/package.json +1 -1
  110. package/templates/.agents/rules/review-handshake.en.md +4 -4
  111. package/templates/.agents/rules/review-handshake.zh-CN.md +4 -4
  112. package/templates/.agents/skills/analyze-task/SKILL.en.md +21 -5
  113. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +21 -5
  114. package/templates/.agents/skills/code-task/SKILL.en.md +14 -8
  115. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +18 -13
  116. package/templates/.agents/skills/code-task/reference/code-rules.en.md +2 -2
  117. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +2 -2
  118. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +4 -4
  119. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +4 -4
  120. package/templates/.agents/skills/code-task/reference/report-template.en.md +7 -7
  121. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +7 -7
  122. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +2 -2
  123. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +2 -2
  124. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +1 -3
  125. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +1 -3
  126. package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
  127. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
  128. package/templates/.agents/skills/complete-task/config/verify.en.json +2 -2
  129. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
  130. package/templates/.agents/skills/plan-task/SKILL.en.md +13 -10
  131. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +13 -10
  132. package/templates/.agents/skills/review-analysis/SKILL.en.md +5 -1
  133. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +5 -1
  134. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +3 -3
  135. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +3 -3
  136. package/templates/.agents/skills/review-code/SKILL.en.md +5 -1
  137. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -1
  138. package/templates/.agents/skills/review-code/reference/report-template.en.md +3 -3
  139. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +3 -3
  140. package/templates/.agents/skills/review-plan/SKILL.en.md +5 -1
  141. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +5 -1
  142. package/templates/.agents/skills/review-plan/reference/report-template.en.md +3 -3
  143. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +3 -3
  144. package/templates/.agents/skills/review-pr/config/verify.en.json +3 -1
  145. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +3 -1
  146. package/templates/.agents/skills/review-pr/reference/report-template.en.md +3 -3
  147. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +3 -3
  148. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +2 -2
  149. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +2 -2
  150. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -2
  151. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -2
  152. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +18 -2
  153. package/templates/.agents/templates/task.en.md +2 -2
  154. package/templates/.agents/templates/task.zh-CN.md +2 -2
  155. package/templates/.agents/workflows/bug-fix.en.yaml +11 -6
  156. package/templates/.agents/workflows/bug-fix.zh-CN.yaml +11 -6
  157. package/templates/.agents/workflows/feature-development.en.yaml +11 -6
  158. package/templates/.agents/workflows/feature-development.zh-CN.yaml +11 -6
  159. package/templates/.agents/workflows/refactoring.en.yaml +11 -6
  160. package/templates/.agents/workflows/refactoring.zh-CN.yaml +11 -6
  161. package/dist/lib/internal/task-override.js +0 -138
  162. package/dist/lib/sandbox/control/finalization-response.js +0 -33
  163. package/dist/lib/task/artifact-recovery.js +0 -476
  164. package/dist/lib/task/artifact-repair-intent.js +0 -181
  165. package/dist/lib/task/guard-override.js +0 -31
  166. package/dist/lib/task/human-override.js +0 -1259
  167. package/lib/internal/task-override.ts +0 -144
  168. package/lib/sandbox/control/finalization-response.ts +0 -35
  169. package/lib/task/artifact-recovery.ts +0 -572
  170. package/lib/task/artifact-repair-intent.ts +0 -227
  171. package/lib/task/guard-override.ts +0 -50
  172. package/lib/task/human-override.ts +0 -1522
@@ -3,8 +3,7 @@ import { spawnSync } from 'node:child_process';
3
3
  import path from 'node:path';
4
4
  import { parseTaskFrontmatter } from './frontmatter.js';
5
5
  import { resolveTaskRef } from './resolve-ref.js';
6
- import { resolveDeliveryTarget, resolveDiffBase, resolveTargetHead, validateBaseRef, validateRemote } from './delivery-target.js';
7
- import { extractReviewBaseline, extractReviewDiffBase, extractReviewTargetHead, extractReviewedHead, findAuthoritativeReviewCodeArtifact } from './review-fingerprint.js';
6
+ import { resolveDeliveryTarget, validateBaseRef, validateRemote } from './delivery-target.js';
8
7
  import { captureTaskWriteMetadata, writeTask } from './write.js';
9
8
  import { withRepositoryMutationLock, withTaskExecutionLock } from './task-execution-lock.js';
10
9
  function classifyDeliveryState(localHead, remoteHead, lastDelivered) {
@@ -27,41 +26,6 @@ function gitValue(cwd, args) {
27
26
  function validateBranch(cwd, branch) {
28
27
  return git(cwd, ['check-ref-format', `refs/heads/${branch}`]).status === 0;
29
28
  }
30
- function validateReviewTargetCompatibility(repoRoot, taskDir, target, reviewedHead) {
31
- const artifact = findAuthoritativeReviewCodeArtifact(taskDir);
32
- if (!artifact.ok || !artifact.path)
33
- return { ok: false, code: 'DELIVERY_REVIEW_REQUIRED', message: 'A current review-code artifact is required before delivery' };
34
- let content;
35
- try {
36
- content = fs.readFileSync(artifact.path, 'utf8');
37
- }
38
- catch (error) {
39
- return { ok: false, code: 'DELIVERY_REVIEW_REQUIRED', message: error instanceof Error ? error.message : String(error) };
40
- }
41
- const savedReviewedHead = extractReviewedHead(content) || extractReviewBaseline(content);
42
- const savedTargetHead = extractReviewTargetHead(content);
43
- const savedDiffBase = extractReviewDiffBase(content);
44
- if (!savedReviewedHead || !savedTargetHead || !savedDiffBase) {
45
- return { ok: false, code: 'DELIVERY_REVIEW_TARGET_INVALID', message: 'review-code must save reviewed head, target head, and diff base before delivery' };
46
- }
47
- if (savedReviewedHead !== reviewedHead) {
48
- return { ok: false, code: 'DELIVERY_REVIEW_REQUIRED', message: 'review-code reviewed head does not match last_reviewed_commit' };
49
- }
50
- const saved = resolveDiffBase(repoRoot, reviewedHead, savedTargetHead);
51
- if (!saved.ok || saved.diffBase !== savedDiffBase) {
52
- return { ok: false, code: 'DELIVERY_REVIEW_TARGET_INVALID', message: saved.ok ? 'saved review diff base does not match saved target head' : saved.message };
53
- }
54
- const currentTarget = resolveTargetHead(repoRoot, target);
55
- if (!currentTarget.ok)
56
- return { ok: false, code: currentTarget.code, message: currentTarget.message };
57
- const current = resolveDiffBase(repoRoot, reviewedHead, currentTarget.head);
58
- if (!current.ok)
59
- return { ok: false, code: current.code, message: current.message };
60
- if (current.diffBase !== savedDiffBase) {
61
- return { ok: false, code: 'DELIVERY_REVIEW_TARGET_CHANGED', message: 'current delivery target changed the reviewed diff base; re-run review-code' };
62
- }
63
- return { ok: true };
64
- }
65
29
  function failure(taskId, code, message, retryable = false, status = retryable ? 'blocked' : 'failed') {
66
30
  return { status, changed: false, taskId, remoteHead: null, localHead: null, state: null, error: { code, message, retryable } };
67
31
  }
@@ -102,12 +66,6 @@ function deliverUnlocked(taskId, taskDir, repoRoot, options) {
102
66
  return failure(taskId, 'DELIVERY_GIT_INSPECT_FAILED', status.stderr.trim() || 'Unable to inspect Git state');
103
67
  if (status.stdout.trim())
104
68
  return failure(taskId, 'DELIVERY_WORKTREE_DIRTY', 'Working tree must be clean before branch delivery');
105
- const reviewed = frontmatter.last_reviewed_commit ?? '';
106
- if (reviewed !== localHead)
107
- return failure(taskId, 'DELIVERY_REVIEW_REQUIRED', 'Local HEAD must equal last_reviewed_commit before delivery');
108
- const reviewTarget = validateReviewTargetCompatibility(repoRoot, taskDir, target.value, reviewed);
109
- if (!reviewTarget.ok)
110
- return failure(taskId, reviewTarget.code, reviewTarget.message, reviewTarget.code.includes('UNAVAILABLE'), reviewTarget.code.includes('UNAVAILABLE') ? 'blocked' : 'failed');
111
69
  const ref = `refs/heads/${branch}`;
112
70
  const remoteOutput = git(repoRoot, ['ls-remote', '--refs', target.value.remote, ref]);
113
71
  if (remoteOutput.status !== 0)
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { execFileSync } from 'node:child_process';
4
+ import { resolveBranchWorktree } from '../git/branch-worktree.js';
4
5
  import { appendActivityEntry, locateActivityLog, pairEntries, startedBackedRows } from './activity-log.js';
5
6
  import { buildArtifactLinkSection, inspectArtifactDirectory, resolveArtifactContext, validateCompletedArtifact } from './artifact-lifecycle.js';
6
7
  import { artifactName, parseArtifactName } from './artifact-name.js';
@@ -16,17 +17,17 @@ import { validateLifecycleExecution } from './lifecycle-execution.js';
16
17
  import { commitOrchestrationStageCompletion } from './orchestration.js';
17
18
  import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
18
19
  import { captureTaskWriteMetadata, writeTask } from './write.js';
19
- import { allowsManualOverride } from './guard-override.js';
20
20
  import { validateLocalArtifact } from './local-artifact-finalization.js';
21
- import { buildLifecycleFacts, canStart } from './capabilities.js';
21
+ import { buildLifecycleFacts, canStart, effectiveReworkTarget } from './capabilities.js';
22
22
  import { createInvalidationOperation, invalidationMutation, parseInvalidationDocument, targetIdFor, upsertInvalidation } from './invalidation.js';
23
23
  import { reconcileTaskInvalidation } from './invalidation-command.js';
24
24
  import { consumeReworkIntents, parseReworkIntentDocument, reworkIntentMutation, supersedeReworkIntents } from './rework-intent.js';
25
- import { ARTIFACT_FAMILIES, expectedQualificationRelations, parseQualificationAudit, parseTaskQualification, upstreamArtifactDigest, validateQualificationAudit } from './qualification-audit.js';
25
+ import { ARTIFACT_FAMILIES, parseQualificationAudit, parseTaskQualification, upstreamArtifactDigest } from './qualification-audit.js';
26
26
  import { getArtifactSchema } from './artifact-schema.js';
27
27
  import { canonicalSemanticDigest, inspectArtifactContract } from './artifact-operations.js';
28
28
  import { consumeLifecycleRecoveryAttestation, lifecycleRecoveryAttestationDigest, validateLifecycleRecoveryAttestation } from './control-authority.js';
29
29
  import { readManualValidationCompletion } from './manual-validation-completion.js';
30
+ import { parseLifecyclePathDecision } from './lifecycle-path.js';
30
31
  const eventCatalog = [
31
32
  'analyze.started', 'analyze.awaiting-input', 'analyze.completed',
32
33
  'review-analysis.started', 'review-analysis.completed',
@@ -37,28 +38,36 @@ const eventCatalog = [
37
38
  'manual-validation.started', 'manual-validation.completed',
38
39
  'validation-run.started', 'validation-run.completed'
39
40
  ];
40
- function approvedCleanReviewedCommit(reviewContent, verdict, repoRoot) {
41
+ function branchWorktree(repoRoot, branch) {
42
+ if (!branch)
43
+ return repoRoot;
44
+ return resolveBranchWorktree(repoRoot, branch);
45
+ }
46
+ function approvedCleanReviewedCommit(reviewContent, verdict, repoRoot, branch) {
41
47
  if (verdict !== 'approved')
42
48
  return null;
43
49
  const reviewedHead = extractReviewedHead(reviewContent) || extractReviewBaseline(reviewContent);
44
50
  const reviewedTree = extractReviewedSnapshotTree(reviewContent);
45
51
  if (!/^[a-f0-9]{40}$/.test(reviewedHead) || !/^[a-f0-9]{40}$/.test(reviewedTree))
46
52
  return null;
53
+ const worktree = branchWorktree(repoRoot, branch);
54
+ if (!worktree)
55
+ return null;
47
56
  try {
48
- const currentHead = execFileSync('git', ['-C', repoRoot, 'rev-parse', 'HEAD'], { encoding: 'utf8' }).trim();
57
+ const currentHead = execFileSync('git', ['-C', worktree, 'rev-parse', 'HEAD'], { encoding: 'utf8' }).trim();
49
58
  if (currentHead !== reviewedHead)
50
59
  return null;
51
- const currentTree = execFileSync('git', ['-C', repoRoot, 'rev-parse', 'HEAD^{tree}'], { encoding: 'utf8' }).trim();
60
+ const currentTree = execFileSync('git', ['-C', worktree, 'rev-parse', 'HEAD^{tree}'], { encoding: 'utf8' }).trim();
52
61
  if (currentTree !== reviewedTree)
53
62
  return null;
54
- execFileSync('git', ['-C', repoRoot, 'diff-index', '--quiet', 'HEAD', '--']);
63
+ execFileSync('git', ['-C', worktree, 'diff-index', '--quiet', 'HEAD', '--']);
55
64
  return reviewedHead;
56
65
  }
57
66
  catch {
58
67
  return null;
59
68
  }
60
69
  }
61
- const BASE_FIELDS = new Set(['taskRef', 'event', 'agent', 'dryRun', 'overrideTicket', 'overrideTarget', 'overrideScope', 'initiator', 'requestId', 'reasonCode', 'sourceFinding', 'sourceArtifact', 'sourceSha256']);
70
+ const BASE_FIELDS = new Set(['taskRef', 'event', 'agent', 'dryRun', 'initiator', 'requestId', 'reasonCode', 'sourceFinding', 'sourceArtifact', 'sourceSha256']);
62
71
  const SCHEMAS = {
63
72
  'analyze.started': { optional: ['round'] },
64
73
  'analyze.awaiting-input': { required: ['question'] },
@@ -283,7 +292,13 @@ function failed(request, error, extra = {}) {
283
292
  }
284
293
  function normalizeStarted(request, repoRoot) {
285
294
  const family = eventParts(request.event).family;
286
- const context = resolveArtifactContext(request.taskRef, FAMILY[family].artifact, { repoRoot });
295
+ const context = resolveArtifactContext(request.taskRef, FAMILY[family].artifact, {
296
+ repoRoot,
297
+ reasonCode: request.reasonCode,
298
+ sourceFinding: request.sourceFinding,
299
+ sourceArtifact: request.sourceArtifact,
300
+ sourceSha256: request.sourceSha256
301
+ });
287
302
  if (context.status !== 'ready' || !context.next) {
288
303
  return { error: { code: context.error?.code ?? 'EVENT_ARTIFACT_CONFLICT', message: context.error?.message ?? context.codeMode?.message ?? 'artifact context is not writable' }, context };
289
304
  }
@@ -337,24 +352,6 @@ function openStartedIdentity(rows, family) {
337
352
  function reviewInputFamily(family) {
338
353
  return family === 'review-analysis' ? 'analysis' : family === 'review-plan' ? 'plan' : 'code';
339
354
  }
340
- function relationForStartedInput(family, input) {
341
- if (family.startsWith('review-')) {
342
- return input.family === reviewInputFamily(family) ? 'reviewed-input' : 'approval-context';
343
- }
344
- const consumer = family === 'analyze' ? 'analysis' : family;
345
- if ((consumer === 'plan' && input.family === 'analysis') || (consumer === 'code' && input.family === 'plan'))
346
- return 'required-input';
347
- return 'review-context';
348
- }
349
- function qualificationRelationsForStarted(family, inputs) {
350
- return inputs.map((input) => ({
351
- upstreamFamily: input.family,
352
- upstreamArtifact: input.name,
353
- upstreamRound: input.round,
354
- upstreamSha256: sha256File(input.path),
355
- relation: relationForStartedInput(family, input)
356
- }));
357
- }
358
355
  function lifecycleAction(family) {
359
356
  return family === 'analyze' ? 'analysis' : family;
360
357
  }
@@ -555,7 +552,21 @@ function reworkIntentMutationForCompletion(content, taskDir, family, artifact, t
555
552
  return [];
556
553
  }
557
554
  }));
558
- next = consumeReworkIntents(next, family === 'analyze' ? 'analysis' : family, hashes, timestamp).intents;
555
+ const action = family === 'analyze' ? 'analysis' : family;
556
+ let target = action;
557
+ if (family === 'analyze') {
558
+ const previousAnalysis = fs.readdirSync(taskDir)
559
+ .map((name) => parseArtifactName(name))
560
+ .filter((identity) => identity?.family === 'analysis' && identity.name !== artifact.name)
561
+ .sort((left, right) => right.round - left.round || left.name.localeCompare(right.name))[0];
562
+ const pathState = previousAnalysis
563
+ ? parseLifecyclePathDecision(fs.readFileSync(path.join(taskDir, previousAnalysis.name), 'utf8'))
564
+ : undefined;
565
+ const pending = next.find((intent) => intent.status === 'pending');
566
+ if (pending && effectiveReworkTarget(pending.target, pathState) === action)
567
+ target = pending.target;
568
+ }
569
+ next = consumeReworkIntents(next, target, hashes, timestamp).intents;
559
570
  }
560
571
  if (family === 'review-analysis' || family === 'review-plan' || family === 'review-code') {
561
572
  next = supersedeReworkIntents(next, artifact.name, hash, timestamp).intents;
@@ -577,13 +588,16 @@ function buildCompletionReceipt(content, taskDir, family, artifact, completedAt,
577
588
  const startedSha256 = typeof frontmatter.code_input_sha256 === 'string'
578
589
  ? frontmatter.code_input_sha256 : existing?.inputSha256 ?? '';
579
590
  if (!startedInput || !startedSha256)
580
- return { ok: false, message: 'code.started plan input context is missing' };
581
- const plan = inspectArtifactDirectory(taskDir, 'plan');
582
- if (plan.status !== 'ready' || !plan.latest || plan.latest.name !== startedInput) {
583
- return { ok: false, message: `code input '${startedInput}' is not the latest plan artifact` };
591
+ return { ok: false, message: 'code.started lifecycle input context is missing' };
592
+ const inputIdentity = parseArtifactName(startedInput);
593
+ if (!inputIdentity || !['analysis', 'plan'].includes(inputIdentity.family))
594
+ return { ok: false, message: `code input '${startedInput}' has an invalid family` };
595
+ const lifecycleInput = inspectArtifactDirectory(taskDir, inputIdentity.family);
596
+ if (lifecycleInput.status !== 'ready' || !lifecycleInput.latest || lifecycleInput.latest.name !== startedInput) {
597
+ return { ok: false, message: `code input '${startedInput}' is not the latest ${inputIdentity.family} artifact` };
584
598
  }
585
599
  try {
586
- const inputSha256 = sha256File(plan.latest.path);
600
+ const inputSha256 = sha256File(lifecycleInput.latest.path);
587
601
  if (inputSha256 !== startedSha256)
588
602
  return { ok: false, message: `code input ${startedInput} changed after code.started` };
589
603
  return {
@@ -595,7 +609,7 @@ function buildCompletionReceipt(content, taskDir, family, artifact, completedAt,
595
609
  };
596
610
  }
597
611
  catch (error) {
598
- return { ok: false, message: `cannot hash code input plan: ${error instanceof Error ? error.message : String(error)}` };
612
+ return { ok: false, message: `cannot hash code lifecycle input: ${error instanceof Error ? error.message : String(error)}` };
599
613
  }
600
614
  }
601
615
  if (!family.startsWith('review-'))
@@ -691,8 +705,7 @@ function applyTaskEventUnlocked(request, options = {}) {
691
705
  const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
692
706
  if (!resolved.ok)
693
707
  return failed(request, { code: resolved.code, message: resolved.message }, { taskId: resolved.taskId });
694
- const stateOverride = allowsManualOverride(options.manualOverride, 'task-event', 'TASK_STATE_MISMATCH');
695
- if (resolved.state !== 'active' && !stateOverride)
708
+ if (resolved.state !== 'active')
696
709
  return failed(request, { code: 'TASK_STATE_MISMATCH', message: `task ${resolved.taskId} is ${resolved.state}, expected active` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
697
710
  let content;
698
711
  try {
@@ -753,6 +766,12 @@ function applyTaskEventUnlocked(request, options = {}) {
753
766
  normalized = { ...request, round: openIdentity.round, artifact: artifactName(FAMILY[initialParts.family].artifact, openIdentity.round), fixFor: openIdentity.fixFor, implementationInput: openIdentity.implementationInput };
754
767
  return successNoOp(normalized, resolved.taskId, resolved.taskMdPath, typeof frontmatter.current_step === 'string' ? frontmatter.current_step : '', identity(normalized), openIdentity.row.started, frontmatter, null);
755
768
  }
769
+ if (rows.some((item) => item.started && !item.done)) {
770
+ return failed(request, {
771
+ code: 'EVENT_TRANSITION_INVALID',
772
+ message: 'EXECUTION_BUSY: another lifecycle execution is open'
773
+ }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
774
+ }
756
775
  const result = normalizeStarted(request, resolved.repoRoot);
757
776
  artifactContext = result.context;
758
777
  if ('error' in result)
@@ -805,7 +824,7 @@ function applyTaskEventUnlocked(request, options = {}) {
805
824
  }
806
825
  return successNoOp(normalized, resolved.taskId, resolved.taskMdPath, currentStep, eventIdentity, row.started, frontmatter, artifactContext);
807
826
  }
808
- if (eventIdentity.phase === 'completed' && !row?.started && completedRows.length === 0 && !allowsManualOverride(options.manualOverride, 'task-event', 'EVENT_START_MISSING'))
827
+ if (eventIdentity.phase === 'completed' && !row?.started && completedRows.length === 0)
809
828
  return failed(normalized, { code: 'EVENT_START_MISSING', message: 'completion requires a started event or an earlier completed attempt' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
810
829
  if (eventIdentity.phase === 'completed') {
811
830
  const validated = validateCompletedArtifact(resolved.taskDir, FAMILY[eventIdentity.family].artifact, normalized.artifact, normalized.round);
@@ -822,25 +841,13 @@ function applyTaskEventUnlocked(request, options = {}) {
822
841
  reviewContent = fs.readFileSync(completedArtifact.path, 'utf8');
823
842
  }
824
843
  catch (error) {
825
- return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: `cannot read qualification audit from ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
844
+ return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: `cannot read ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
826
845
  }
827
846
  const schema = getArtifactSchema(eventIdentity.family);
828
847
  const structure = schema ? inspectArtifactContract(reviewContent, schema) : null;
829
848
  if (structure && !structure.ok) {
830
849
  return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: `shared artifact structure invalid: ${structure.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; ')}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
831
850
  }
832
- const expected = expectedQualificationRelations(content, eventIdentity.family);
833
- if (!expected.ok) {
834
- return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: `${expected.code}: ${expected.message}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
835
- }
836
- const qualification = validateQualificationAudit(content, reviewContent, {
837
- family: eventIdentity.family,
838
- artifact: completedArtifact.name,
839
- expectedUpstreamRelations: expected.relations
840
- });
841
- if (!qualification.ok) {
842
- return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: `${qualification.code}: ${qualification.message}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
843
- }
844
851
  }
845
852
  if (eventIdentity.family === 'analyze' || eventIdentity.family === 'plan' || eventIdentity.family === 'code') {
846
853
  const localFamily = eventIdentity.family === 'analyze'
@@ -854,9 +861,7 @@ function applyTaskEventUnlocked(request, options = {}) {
854
861
  return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: `cannot read ${completedArtifact.name}: ${String(error)}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
855
862
  }
856
863
  const local = validateLocalArtifact(artifactContent, {
857
- family: localFamily,
858
- taskContent: content,
859
- artifact: completedArtifact.name
864
+ family: localFamily
860
865
  });
861
866
  if (!local.ok) {
862
867
  return failed(normalized, {
@@ -889,10 +894,9 @@ function applyTaskEventUnlocked(request, options = {}) {
889
894
  if (!facts.ok)
890
895
  return failed(normalized, { code: 'EVENT_TRANSITION_INVALID', message: `${facts.code}: ${facts.message}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
891
896
  const capability = canStart(lifecycleAction(eventIdentity.family), facts.facts, trigger);
892
- const capabilityOverride = capability.reasonCode === 'TASK_NOT_ACTIVE'
893
- ? stateOverride
894
- : allowsManualOverride(options.manualOverride, 'task-event', 'EVENT_TRANSITION_INVALID');
895
- if (!capability.allowed && !capabilityOverride) {
897
+ const safetyFailure = capability.reasonCode === 'TASK_NOT_ACTIVE'
898
+ || capability.reasonCode === 'LIFECYCLE_EXECUTION_OPEN';
899
+ if (!capability.allowed && (normalized.initiator === 'orchestrator' || safetyFailure)) {
896
900
  const code = capability.reasonCode === 'INVALIDATION_INCOMPLETE' ? 'TASK_INVALIDATION_BLOCKED' : 'EVENT_TRANSITION_INVALID';
897
901
  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 });
898
902
  }
@@ -901,10 +905,11 @@ function applyTaskEventUnlocked(request, options = {}) {
901
905
  if (findingCountError)
902
906
  return failed(normalized, findingCountError, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
903
907
  let orchestrationCompletion = null;
904
- if (eventIdentity.phase === 'completed' && eventIdentity.family !== 'manual-validation' && eventIdentity.family !== 'validation-run') {
908
+ if (eventIdentity.phase === 'completed' && normalized.orchestrated
909
+ && eventIdentity.family !== 'manual-validation' && eventIdentity.family !== 'validation-run') {
905
910
  const orchestrationStage = eventIdentity.family === 'analyze' ? 'analysis' : eventIdentity.family;
906
911
  const execution = validateLifecycleExecution(normalized.taskRef, {
907
- mode: normalized.orchestrated ? 'orchestrated' : 'standalone',
912
+ mode: 'orchestrated',
908
913
  identity: {
909
914
  stage: orchestrationStage,
910
915
  round: normalized.round,
@@ -969,18 +974,10 @@ function applyTaskEventUnlocked(request, options = {}) {
969
974
  if (currentFact)
970
975
  frontmatterSet.completion_facts = JSON.stringify(replaceCompletionFact(completionFacts(frontmatter), currentFact));
971
976
  let frontmatterRemove;
972
- if (eventIdentity.phase === 'started' && artifactContext) {
973
- try {
974
- frontmatterSet.qualification_input_relations = JSON.stringify(qualificationRelationsForStarted(eventIdentity.family, artifactContext.inputs));
975
- }
976
- catch (error) {
977
- return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: `cannot capture qualification input relations: ${error instanceof Error ? error.message : String(error)}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: step, action: eventIdentity.action, phase: eventIdentity.phase, artifactContext });
978
- }
979
- }
980
977
  if (eventIdentity.phase === 'started' && eventIdentity.family === 'code') {
981
- const planInput = artifactContext?.inputs.find((input) => input.family === 'plan');
978
+ const planInput = artifactContext?.inputs.find((input) => input.family === 'plan' || input.family === 'analysis');
982
979
  if (!planInput)
983
- return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: 'code.started plan input context is unavailable' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: step, action: eventIdentity.action, phase: eventIdentity.phase, artifactContext });
980
+ return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: 'code.started lifecycle input context is unavailable' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: step, action: eventIdentity.action, phase: eventIdentity.phase, artifactContext });
984
981
  try {
985
982
  frontmatterSet.code_input_artifact = planInput.name;
986
983
  frontmatterSet.code_input_sha256 = sha256File(planInput.path);
@@ -1009,14 +1006,12 @@ function applyTaskEventUnlocked(request, options = {}) {
1009
1006
  frontmatterRemove = ['review_input_artifact', 'review_input_sha256'];
1010
1007
  }
1011
1008
  if (eventIdentity.phase === 'completed' && eventIdentity.family === 'review-code' && reviewContent !== null) {
1012
- const reviewedCommit = approvedCleanReviewedCommit(reviewContent, normalized.verdict, resolved.repoRoot);
1009
+ const reviewedCommit = approvedCleanReviewedCommit(reviewContent, normalized.verdict, resolved.repoRoot, typeof frontmatter.branch === 'string' ? frontmatter.branch : '');
1013
1010
  if (reviewedCommit)
1014
1011
  frontmatterSet.last_reviewed_commit = reviewedCommit;
1015
1012
  else if (normalized.verdict === 'approved')
1016
1013
  frontmatterSet.last_reviewed_commit = '';
1017
1014
  }
1018
- if (eventIdentity.phase === 'completed')
1019
- frontmatterRemove = [...(frontmatterRemove ?? []), 'qualification_input_relations'];
1020
1015
  if (eventIdentity.phase === 'started' && normalized.implementationInput)
1021
1016
  frontmatterSet.last_reviewed_commit = '';
1022
1017
  const mutations = [
@@ -1057,7 +1052,7 @@ function applyTaskEventUnlocked(request, options = {}) {
1057
1052
  mutations.push({ kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: section.heading, body });
1058
1053
  const sourceCompletion = eventIdentity.phase === 'completed'
1059
1054
  && ['analyze', 'plan', 'code'].includes(eventIdentity.family);
1060
- const result = writeTask({ taskRef: normalized.taskRef, expectedState: stateOverride ? resolved.state : 'active', dryRun: normalized.dryRun, mutations }, { ...options, invalidationContext: sourceCompletion ? 'source-completion' : 'standard', metadataProvider: () => metadata });
1055
+ const result = writeTask({ taskRef: normalized.taskRef, expectedState: 'active', dryRun: normalized.dryRun, mutations }, { ...options, invalidationContext: sourceCompletion ? 'source-completion' : 'standard', metadataProvider: () => metadata });
1061
1056
  if (result.status === 'failed')
1062
1057
  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 });
1063
1058
  if (!normalized.dryRun && orchestrationCompletion) {
@@ -0,0 +1,92 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ const HANDOFF_VERSION = 1;
5
+ const MAX_HANDOFF_BYTES = 1024 * 1024;
6
+ function digest(value) {
7
+ return createHash('sha256').update(JSON.stringify(value)).digest('hex');
8
+ }
9
+ export function finalizationHandoffPath(directory, taskId) {
10
+ return path.join(directory, '.agent-infra', 'task-finalization', `${taskId}.json`);
11
+ }
12
+ export function publishTaskFinalizationHandoff(directory, receipt, binding) {
13
+ if (receipt.taskId === '' || receipt.controlBinding?.generation !== binding.generation
14
+ || receipt.controlBinding.requestId !== binding.requestId) {
15
+ throw new Error('TASK_FINALIZATION_HANDOFF_BINDING_INVALID');
16
+ }
17
+ const envelope = {
18
+ version: HANDOFF_VERSION, taskId: receipt.taskId, binding, receipt, receiptSha256: digest(receipt)
19
+ };
20
+ const target = finalizationHandoffPath(directory, receipt.taskId);
21
+ fs.mkdirSync(path.dirname(target), { recursive: true, mode: 0o700 });
22
+ const temporary = `${target}.${process.pid}.${randomUUID()}.tmp`;
23
+ fs.writeFileSync(temporary, `${JSON.stringify(envelope)}\n`, { mode: 0o600, flag: 'wx' });
24
+ try {
25
+ fs.renameSync(temporary, target);
26
+ }
27
+ catch (error) {
28
+ try {
29
+ fs.unlinkSync(temporary);
30
+ }
31
+ catch { /* preserve the primary error */ }
32
+ throw error;
33
+ }
34
+ return digest(envelope);
35
+ }
36
+ export function readTaskFinalizationHandoff(directory, taskId, binding, handoffSha256) {
37
+ const file = finalizationHandoffPath(directory, taskId);
38
+ const initial = fs.lstatSync(file);
39
+ if (!initial.isFile() || initial.isSymbolicLink() || initial.size > MAX_HANDOFF_BYTES) {
40
+ throw new Error('TASK_FINALIZATION_HANDOFF_INVALID');
41
+ }
42
+ const descriptor = fs.openSync(file, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0));
43
+ let content;
44
+ try {
45
+ const opened = fs.fstatSync(descriptor);
46
+ if (!opened.isFile() || opened.dev !== initial.dev || opened.ino !== initial.ino
47
+ || opened.size > MAX_HANDOFF_BYTES)
48
+ throw new Error('TASK_FINALIZATION_HANDOFF_INVALID');
49
+ const bytes = Buffer.alloc(MAX_HANDOFF_BYTES + 1);
50
+ const length = fs.readSync(descriptor, bytes, 0, bytes.length, 0);
51
+ if (length !== opened.size || length > MAX_HANDOFF_BYTES)
52
+ throw new Error('TASK_FINALIZATION_HANDOFF_INVALID');
53
+ content = bytes.toString('utf8', 0, length);
54
+ const after = fs.fstatSync(descriptor);
55
+ if (after.dev !== opened.dev || after.ino !== opened.ino || after.size !== opened.size) {
56
+ throw new Error('TASK_FINALIZATION_HANDOFF_INVALID');
57
+ }
58
+ }
59
+ finally {
60
+ fs.closeSync(descriptor);
61
+ }
62
+ const envelope = JSON.parse(content);
63
+ if (!envelope || typeof envelope !== 'object' || Array.isArray(envelope)
64
+ || Object.keys(envelope).sort().join('\0') !== ['binding', 'receipt', 'receiptSha256', 'taskId', 'version'].join('\0')
65
+ || envelope.version !== HANDOFF_VERSION || envelope.taskId !== taskId
66
+ || !envelope.binding || typeof envelope.binding !== 'object' || Array.isArray(envelope.binding)
67
+ || Object.keys(envelope.binding).sort().join('\0') !== ['generation', 'requestId'].join('\0')
68
+ || envelope.binding.generation !== binding.generation
69
+ || envelope.binding.requestId !== binding.requestId || !envelope.receipt
70
+ || envelope.receipt.controlBinding?.generation !== binding.generation
71
+ || envelope.receipt.controlBinding.requestId !== binding.requestId
72
+ || typeof envelope.receiptSha256 !== 'string' || envelope.receiptSha256 !== digest(envelope.receipt)
73
+ || digest(envelope) !== handoffSha256) {
74
+ throw new Error('TASK_FINALIZATION_HANDOFF_INVALID');
75
+ }
76
+ return {
77
+ receipt: envelope.receipt,
78
+ cleanup: () => {
79
+ try {
80
+ const current = fs.lstatSync(file);
81
+ if (current.isFile() && !current.isSymbolicLink()
82
+ && current.dev === initial.dev && current.ino === initial.ino)
83
+ fs.unlinkSync(file);
84
+ }
85
+ catch (error) {
86
+ if (error.code !== 'ENOENT')
87
+ throw error;
88
+ }
89
+ }
90
+ };
91
+ }
92
+ //# sourceMappingURL=finalization-handoff.js.map