@fitlab-ai/agent-infra 0.11.3 → 0.11.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/README.md +17 -8
  2. package/README.zh-CN.md +17 -8
  3. package/dist/bin/internal-cli.js +1 -1
  4. package/dist/lib/internal/cli-route-inventory.js +1 -1
  5. package/dist/lib/internal/task-artifact.js +1 -1
  6. package/dist/lib/internal/task-finalization.js +41 -1
  7. package/dist/lib/internal/task-ledger.js +13 -5
  8. package/dist/lib/internal/task-operation-registry.js +1 -0
  9. package/dist/lib/platform/completion-backfill.js +63 -17
  10. package/dist/lib/sandbox/control/server.js +26 -143
  11. package/dist/lib/sandbox/task-cleanup.js +13 -20
  12. package/dist/lib/task/artifact-command.js +11 -4
  13. package/dist/lib/task/artifact-lifecycle.js +217 -85
  14. package/dist/lib/task/artifact-operations.js +1 -26
  15. package/dist/lib/task/artifact-receipts.js +4 -1
  16. package/dist/lib/task/artifact-schema.js +1 -0
  17. package/dist/lib/task/artifact-selection.js +25 -0
  18. package/dist/lib/task/capabilities.js +158 -39
  19. package/dist/lib/task/commands/status.js +9 -2
  20. package/dist/lib/task/control-authority.js +3 -2
  21. package/dist/lib/task/decision-intents.js +12 -0
  22. package/dist/lib/task/delivery.js +1 -43
  23. package/dist/lib/task/events.js +80 -64
  24. package/dist/lib/task/finalization.js +301 -143
  25. package/dist/lib/task/ledger-intents.js +157 -15
  26. package/dist/lib/task/lifecycle-path.js +75 -0
  27. package/dist/lib/task/lifecycle.js +10 -0
  28. package/dist/lib/task/local-artifact-finalization.js +1 -20
  29. package/dist/lib/task/orchestration.js +17 -7
  30. package/dist/lib/task/review-finalization.js +3 -16
  31. package/dist/lib/task/rework-intent.js +70 -4
  32. package/dist/lib/task/verification-engine.js +20 -30
  33. package/dist/lib/task/verification.js +1 -0
  34. package/dist/lib/task/workflow-warning-intents.js +21 -6
  35. package/dist/lib/task/workspace-snapshot.js +7 -2
  36. package/dist/lifecycle-build-manifest.json +3 -3
  37. package/lib/internal/cli-route-inventory.ts +1 -1
  38. package/lib/internal/task-artifact.ts +1 -1
  39. package/lib/internal/task-finalization.ts +40 -1
  40. package/lib/internal/task-ledger.ts +12 -5
  41. package/lib/internal/task-operation-registry.ts +1 -0
  42. package/lib/platform/completion-backfill.ts +78 -16
  43. package/lib/sandbox/control/server.ts +21 -146
  44. package/lib/sandbox/task-cleanup.ts +12 -20
  45. package/lib/task/artifact-command.ts +14 -4
  46. package/lib/task/artifact-lifecycle.ts +236 -85
  47. package/lib/task/artifact-operations.ts +1 -26
  48. package/lib/task/artifact-receipts.ts +4 -1
  49. package/lib/task/artifact-schema.ts +1 -0
  50. package/lib/task/artifact-selection.ts +53 -0
  51. package/lib/task/capabilities.ts +154 -40
  52. package/lib/task/commands/status.ts +11 -2
  53. package/lib/task/control-authority.ts +3 -1
  54. package/lib/task/decision-intents.ts +9 -0
  55. package/lib/task/delivery.ts +1 -40
  56. package/lib/task/events.ts +82 -64
  57. package/lib/task/finalization.ts +269 -111
  58. package/lib/task/ledger-intents.ts +155 -15
  59. package/lib/task/lifecycle-path.ts +98 -0
  60. package/lib/task/lifecycle.ts +13 -0
  61. package/lib/task/local-artifact-finalization.ts +2 -25
  62. package/lib/task/orchestration.ts +26 -8
  63. package/lib/task/review-finalization.ts +3 -16
  64. package/lib/task/rework-intent.ts +75 -5
  65. package/lib/task/verification-engine.ts +19 -30
  66. package/lib/task/verification.ts +2 -1
  67. package/lib/task/workflow-warning-intents.ts +18 -6
  68. package/lib/task/workspace-snapshot.ts +3 -2
  69. package/package.json +1 -1
  70. package/templates/.agents/rules/review-handshake.en.md +4 -4
  71. package/templates/.agents/rules/review-handshake.zh-CN.md +4 -4
  72. package/templates/.agents/skills/analyze-task/SKILL.en.md +11 -4
  73. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +11 -4
  74. package/templates/.agents/skills/code-task/SKILL.en.md +14 -8
  75. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +18 -13
  76. package/templates/.agents/skills/code-task/reference/code-rules.en.md +2 -2
  77. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +2 -2
  78. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +4 -4
  79. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +4 -4
  80. package/templates/.agents/skills/code-task/reference/report-template.en.md +1 -1
  81. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +1 -1
  82. package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
  83. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
  84. package/templates/.agents/skills/plan-task/SKILL.en.md +13 -10
  85. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +13 -10
  86. package/templates/.agents/skills/review-analysis/SKILL.en.md +5 -1
  87. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +5 -1
  88. package/templates/.agents/skills/review-code/SKILL.en.md +5 -1
  89. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -1
  90. package/templates/.agents/skills/review-plan/SKILL.en.md +5 -1
  91. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +5 -1
  92. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +18 -2
  93. package/templates/.agents/templates/task.en.md +2 -2
  94. package/templates/.agents/templates/task.zh-CN.md +2 -2
  95. package/templates/.agents/workflows/bug-fix.en.yaml +11 -6
  96. package/templates/.agents/workflows/bug-fix.zh-CN.yaml +11 -6
  97. package/templates/.agents/workflows/feature-development.en.yaml +11 -6
  98. package/templates/.agents/workflows/feature-development.zh-CN.yaml +11 -6
  99. package/templates/.agents/workflows/refactoring.en.yaml +11 -6
  100. package/templates/.agents/workflows/refactoring.zh-CN.yaml +11 -6
  101. package/dist/lib/sandbox/control/finalization-response.js +0 -33
  102. package/lib/sandbox/control/finalization-response.ts +0 -35
@@ -18,15 +18,16 @@ import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-
18
18
  import { captureTaskWriteMetadata, writeTask } from './write.js';
19
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,21 +38,46 @@ 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
+ try {
45
+ const records = execFileSync('git', ['worktree', 'list', '--porcelain'], {
46
+ cwd: repoRoot,
47
+ encoding: 'utf8',
48
+ stdio: ['ignore', 'pipe', 'pipe']
49
+ }).trim().split(/\r?\n\r?\n/);
50
+ const expectedRef = `refs/heads/${branch}`;
51
+ for (const record of records) {
52
+ const worktree = /^worktree (.+)$/m.exec(record)?.[1];
53
+ const branchRef = /^branch (.+)$/m.exec(record)?.[1];
54
+ if (worktree && branchRef === expectedRef)
55
+ return worktree;
56
+ }
57
+ }
58
+ catch {
59
+ // A missing worktree must not anchor a review to an unrelated repository.
60
+ }
61
+ return null;
62
+ }
63
+ function approvedCleanReviewedCommit(reviewContent, verdict, repoRoot, branch) {
41
64
  if (verdict !== 'approved')
42
65
  return null;
43
66
  const reviewedHead = extractReviewedHead(reviewContent) || extractReviewBaseline(reviewContent);
44
67
  const reviewedTree = extractReviewedSnapshotTree(reviewContent);
45
68
  if (!/^[a-f0-9]{40}$/.test(reviewedHead) || !/^[a-f0-9]{40}$/.test(reviewedTree))
46
69
  return null;
70
+ const worktree = branchWorktree(repoRoot, branch);
71
+ if (!worktree)
72
+ return null;
47
73
  try {
48
- const currentHead = execFileSync('git', ['-C', repoRoot, 'rev-parse', 'HEAD'], { encoding: 'utf8' }).trim();
74
+ const currentHead = execFileSync('git', ['-C', worktree, 'rev-parse', 'HEAD'], { encoding: 'utf8' }).trim();
49
75
  if (currentHead !== reviewedHead)
50
76
  return null;
51
- const currentTree = execFileSync('git', ['-C', repoRoot, 'rev-parse', 'HEAD^{tree}'], { encoding: 'utf8' }).trim();
77
+ const currentTree = execFileSync('git', ['-C', worktree, 'rev-parse', 'HEAD^{tree}'], { encoding: 'utf8' }).trim();
52
78
  if (currentTree !== reviewedTree)
53
79
  return null;
54
- execFileSync('git', ['-C', repoRoot, 'diff-index', '--quiet', 'HEAD', '--']);
80
+ execFileSync('git', ['-C', worktree, 'diff-index', '--quiet', 'HEAD', '--']);
55
81
  return reviewedHead;
56
82
  }
57
83
  catch {
@@ -283,7 +309,13 @@ function failed(request, error, extra = {}) {
283
309
  }
284
310
  function normalizeStarted(request, repoRoot) {
285
311
  const family = eventParts(request.event).family;
286
- const context = resolveArtifactContext(request.taskRef, FAMILY[family].artifact, { repoRoot });
312
+ const context = resolveArtifactContext(request.taskRef, FAMILY[family].artifact, {
313
+ repoRoot,
314
+ reasonCode: request.reasonCode,
315
+ sourceFinding: request.sourceFinding,
316
+ sourceArtifact: request.sourceArtifact,
317
+ sourceSha256: request.sourceSha256
318
+ });
287
319
  if (context.status !== 'ready' || !context.next) {
288
320
  return { error: { code: context.error?.code ?? 'EVENT_ARTIFACT_CONFLICT', message: context.error?.message ?? context.codeMode?.message ?? 'artifact context is not writable' }, context };
289
321
  }
@@ -337,24 +369,6 @@ function openStartedIdentity(rows, family) {
337
369
  function reviewInputFamily(family) {
338
370
  return family === 'review-analysis' ? 'analysis' : family === 'review-plan' ? 'plan' : 'code';
339
371
  }
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
372
  function lifecycleAction(family) {
359
373
  return family === 'analyze' ? 'analysis' : family;
360
374
  }
@@ -555,7 +569,21 @@ function reworkIntentMutationForCompletion(content, taskDir, family, artifact, t
555
569
  return [];
556
570
  }
557
571
  }));
558
- next = consumeReworkIntents(next, family === 'analyze' ? 'analysis' : family, hashes, timestamp).intents;
572
+ const action = family === 'analyze' ? 'analysis' : family;
573
+ let target = action;
574
+ if (family === 'analyze') {
575
+ const previousAnalysis = fs.readdirSync(taskDir)
576
+ .map((name) => parseArtifactName(name))
577
+ .filter((identity) => identity?.family === 'analysis' && identity.name !== artifact.name)
578
+ .sort((left, right) => right.round - left.round || left.name.localeCompare(right.name))[0];
579
+ const pathState = previousAnalysis
580
+ ? parseLifecyclePathDecision(fs.readFileSync(path.join(taskDir, previousAnalysis.name), 'utf8'))
581
+ : undefined;
582
+ const pending = next.find((intent) => intent.status === 'pending');
583
+ if (pending && effectiveReworkTarget(pending.target, pathState) === action)
584
+ target = pending.target;
585
+ }
586
+ next = consumeReworkIntents(next, target, hashes, timestamp).intents;
559
587
  }
560
588
  if (family === 'review-analysis' || family === 'review-plan' || family === 'review-code') {
561
589
  next = supersedeReworkIntents(next, artifact.name, hash, timestamp).intents;
@@ -577,13 +605,16 @@ function buildCompletionReceipt(content, taskDir, family, artifact, completedAt,
577
605
  const startedSha256 = typeof frontmatter.code_input_sha256 === 'string'
578
606
  ? frontmatter.code_input_sha256 : existing?.inputSha256 ?? '';
579
607
  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` };
608
+ return { ok: false, message: 'code.started lifecycle input context is missing' };
609
+ const inputIdentity = parseArtifactName(startedInput);
610
+ if (!inputIdentity || !['analysis', 'plan'].includes(inputIdentity.family))
611
+ return { ok: false, message: `code input '${startedInput}' has an invalid family` };
612
+ const lifecycleInput = inspectArtifactDirectory(taskDir, inputIdentity.family);
613
+ if (lifecycleInput.status !== 'ready' || !lifecycleInput.latest || lifecycleInput.latest.name !== startedInput) {
614
+ return { ok: false, message: `code input '${startedInput}' is not the latest ${inputIdentity.family} artifact` };
584
615
  }
585
616
  try {
586
- const inputSha256 = sha256File(plan.latest.path);
617
+ const inputSha256 = sha256File(lifecycleInput.latest.path);
587
618
  if (inputSha256 !== startedSha256)
588
619
  return { ok: false, message: `code input ${startedInput} changed after code.started` };
589
620
  return {
@@ -595,7 +626,7 @@ function buildCompletionReceipt(content, taskDir, family, artifact, completedAt,
595
626
  };
596
627
  }
597
628
  catch (error) {
598
- return { ok: false, message: `cannot hash code input plan: ${error instanceof Error ? error.message : String(error)}` };
629
+ return { ok: false, message: `cannot hash code lifecycle input: ${error instanceof Error ? error.message : String(error)}` };
599
630
  }
600
631
  }
601
632
  if (!family.startsWith('review-'))
@@ -753,6 +784,12 @@ function applyTaskEventUnlocked(request, options = {}) {
753
784
  normalized = { ...request, round: openIdentity.round, artifact: artifactName(FAMILY[initialParts.family].artifact, openIdentity.round), fixFor: openIdentity.fixFor, implementationInput: openIdentity.implementationInput };
754
785
  return successNoOp(normalized, resolved.taskId, resolved.taskMdPath, typeof frontmatter.current_step === 'string' ? frontmatter.current_step : '', identity(normalized), openIdentity.row.started, frontmatter, null);
755
786
  }
787
+ if (rows.some((item) => item.started && !item.done)) {
788
+ return failed(request, {
789
+ code: 'EVENT_TRANSITION_INVALID',
790
+ message: 'EXECUTION_BUSY: another lifecycle execution is open'
791
+ }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
792
+ }
756
793
  const result = normalizeStarted(request, resolved.repoRoot);
757
794
  artifactContext = result.context;
758
795
  if ('error' in result)
@@ -822,25 +859,13 @@ function applyTaskEventUnlocked(request, options = {}) {
822
859
  reviewContent = fs.readFileSync(completedArtifact.path, 'utf8');
823
860
  }
824
861
  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 });
862
+ 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
863
  }
827
864
  const schema = getArtifactSchema(eventIdentity.family);
828
865
  const structure = schema ? inspectArtifactContract(reviewContent, schema) : null;
829
866
  if (structure && !structure.ok) {
830
867
  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
868
  }
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
869
  }
845
870
  if (eventIdentity.family === 'analyze' || eventIdentity.family === 'plan' || eventIdentity.family === 'code') {
846
871
  const localFamily = eventIdentity.family === 'analyze'
@@ -854,9 +879,7 @@ function applyTaskEventUnlocked(request, options = {}) {
854
879
  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
880
  }
856
881
  const local = validateLocalArtifact(artifactContent, {
857
- family: localFamily,
858
- taskContent: content,
859
- artifact: completedArtifact.name
882
+ family: localFamily
860
883
  });
861
884
  if (!local.ok) {
862
885
  return failed(normalized, {
@@ -892,7 +915,9 @@ function applyTaskEventUnlocked(request, options = {}) {
892
915
  const capabilityOverride = capability.reasonCode === 'TASK_NOT_ACTIVE'
893
916
  ? stateOverride
894
917
  : allowsManualOverride(options.manualOverride, 'task-event', 'EVENT_TRANSITION_INVALID');
895
- if (!capability.allowed && !capabilityOverride) {
918
+ const safetyFailure = capability.reasonCode === 'TASK_NOT_ACTIVE'
919
+ || capability.reasonCode === 'LIFECYCLE_EXECUTION_OPEN';
920
+ if (!capability.allowed && (normalized.initiator === 'orchestrator' || safetyFailure) && !capabilityOverride) {
896
921
  const code = capability.reasonCode === 'INVALIDATION_INCOMPLETE' ? 'TASK_INVALIDATION_BLOCKED' : 'EVENT_TRANSITION_INVALID';
897
922
  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
923
  }
@@ -901,10 +926,11 @@ function applyTaskEventUnlocked(request, options = {}) {
901
926
  if (findingCountError)
902
927
  return failed(normalized, findingCountError, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
903
928
  let orchestrationCompletion = null;
904
- if (eventIdentity.phase === 'completed' && eventIdentity.family !== 'manual-validation' && eventIdentity.family !== 'validation-run') {
929
+ if (eventIdentity.phase === 'completed' && normalized.orchestrated
930
+ && eventIdentity.family !== 'manual-validation' && eventIdentity.family !== 'validation-run') {
905
931
  const orchestrationStage = eventIdentity.family === 'analyze' ? 'analysis' : eventIdentity.family;
906
932
  const execution = validateLifecycleExecution(normalized.taskRef, {
907
- mode: normalized.orchestrated ? 'orchestrated' : 'standalone',
933
+ mode: 'orchestrated',
908
934
  identity: {
909
935
  stage: orchestrationStage,
910
936
  round: normalized.round,
@@ -969,18 +995,10 @@ function applyTaskEventUnlocked(request, options = {}) {
969
995
  if (currentFact)
970
996
  frontmatterSet.completion_facts = JSON.stringify(replaceCompletionFact(completionFacts(frontmatter), currentFact));
971
997
  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
998
  if (eventIdentity.phase === 'started' && eventIdentity.family === 'code') {
981
- const planInput = artifactContext?.inputs.find((input) => input.family === 'plan');
999
+ const planInput = artifactContext?.inputs.find((input) => input.family === 'plan' || input.family === 'analysis');
982
1000
  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 });
1001
+ 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
1002
  try {
985
1003
  frontmatterSet.code_input_artifact = planInput.name;
986
1004
  frontmatterSet.code_input_sha256 = sha256File(planInput.path);
@@ -1009,14 +1027,12 @@ function applyTaskEventUnlocked(request, options = {}) {
1009
1027
  frontmatterRemove = ['review_input_artifact', 'review_input_sha256'];
1010
1028
  }
1011
1029
  if (eventIdentity.phase === 'completed' && eventIdentity.family === 'review-code' && reviewContent !== null) {
1012
- const reviewedCommit = approvedCleanReviewedCommit(reviewContent, normalized.verdict, resolved.repoRoot);
1030
+ const reviewedCommit = approvedCleanReviewedCommit(reviewContent, normalized.verdict, resolved.repoRoot, typeof frontmatter.branch === 'string' ? frontmatter.branch : '');
1013
1031
  if (reviewedCommit)
1014
1032
  frontmatterSet.last_reviewed_commit = reviewedCommit;
1015
1033
  else if (normalized.verdict === 'approved')
1016
1034
  frontmatterSet.last_reviewed_commit = '';
1017
1035
  }
1018
- if (eventIdentity.phase === 'completed')
1019
- frontmatterRemove = [...(frontmatterRemove ?? []), 'qualification_input_relations'];
1020
1036
  if (eventIdentity.phase === 'started' && normalized.implementationInput)
1021
1037
  frontmatterSet.last_reviewed_commit = '';
1022
1038
  const mutations = [