@fitlab-ai/agent-infra 0.11.5 → 0.11.7

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 (155) hide show
  1. package/dist/lib/git/primary-worktree.js +58 -0
  2. package/dist/lib/merge.js +62 -10
  3. package/dist/lib/platform/github-provider.js +9 -14
  4. package/dist/lib/platform/github-release-notes.js +80 -11
  5. package/dist/lib/platform/none-provider.js +1 -1
  6. package/dist/lib/platform/provider-contract.d.ts +39 -16
  7. package/dist/lib/platform/provider-contract.js +1 -1
  8. package/dist/lib/platform/provider-validation.js +83 -13
  9. package/dist/lib/platform/pull-requests.js +29 -3
  10. package/dist/lib/platform/release-notes.js +7 -8
  11. package/dist/lib/sandbox/capture.js +1 -1
  12. package/dist/lib/sandbox/commands/create.js +46 -1
  13. package/dist/lib/sandbox/commands/enter.js +1 -1
  14. package/dist/lib/sandbox/commands/start.js +1 -1
  15. package/dist/lib/sandbox/config.js +10 -0
  16. package/dist/lib/sandbox/control/lifecycle.js +18 -13
  17. package/dist/lib/sandbox/control/server.js +6 -2
  18. package/dist/lib/sandbox/task-resolver.js +62 -2
  19. package/dist/lib/server/config.js +7 -27
  20. package/dist/lib/server/daemon.js +4 -3
  21. package/dist/lib/server/process-control.js +4 -2
  22. package/dist/lib/server/runner.js +4 -3
  23. package/dist/lib/task/artifact-lifecycle.js +42 -10
  24. package/dist/lib/task/artifact-receipts.js +62 -22
  25. package/dist/lib/task/capabilities.js +22 -4
  26. package/dist/lib/task/completion-facts.js +56 -0
  27. package/dist/lib/task/create-service.js +6 -2
  28. package/dist/lib/task/create.js +46 -82
  29. package/dist/lib/task/events.js +268 -195
  30. package/dist/lib/task/finalization-handoff.js +2 -1
  31. package/dist/lib/task/invalidation-command.js +4 -2
  32. package/dist/lib/task/invalidation.js +38 -9
  33. package/dist/lib/task/local-artifact-finalization.js +17 -3
  34. package/dist/lib/task/qualification-audit.js +23 -105
  35. package/dist/lib/task/resolve-ref.js +3 -3
  36. package/dist/lib/task/review-finalization.js +7 -0
  37. package/dist/lib/task/review-identity.js +69 -0
  38. package/dist/lib/task/sections.js +6 -5
  39. package/dist/lib/task/task-execution-lock.js +6 -3
  40. package/dist/lib/task/verification-engine.js +14 -63
  41. package/dist/lib/task/verification.js +35 -38
  42. package/dist/lib/task/write.js +121 -1
  43. package/dist/lifecycle-build-manifest.json +3 -3
  44. package/lib/git/primary-worktree.ts +62 -0
  45. package/lib/merge.ts +59 -10
  46. package/lib/platform/github-provider.ts +11 -18
  47. package/lib/platform/github-release-notes.ts +89 -14
  48. package/lib/platform/none-provider.ts +1 -1
  49. package/lib/platform/provider-contract.ts +38 -12
  50. package/lib/platform/provider-validation.ts +83 -12
  51. package/lib/platform/pull-requests.ts +27 -3
  52. package/lib/platform/release-notes.ts +11 -12
  53. package/lib/sandbox/capture.ts +1 -1
  54. package/lib/sandbox/commands/create.ts +52 -1
  55. package/lib/sandbox/commands/enter.ts +1 -1
  56. package/lib/sandbox/commands/start.ts +1 -1
  57. package/lib/sandbox/config.ts +12 -0
  58. package/lib/sandbox/control/lifecycle.ts +22 -13
  59. package/lib/sandbox/control/server.ts +5 -2
  60. package/lib/sandbox/task-resolver.ts +51 -2
  61. package/lib/server/config.ts +7 -29
  62. package/lib/server/daemon.ts +5 -3
  63. package/lib/server/process-control.ts +4 -2
  64. package/lib/server/runner.ts +7 -4
  65. package/lib/server/server.schema.json +0 -4
  66. package/lib/task/artifact-lifecycle.ts +35 -11
  67. package/lib/task/artifact-receipts.ts +55 -15
  68. package/lib/task/capabilities.ts +17 -3
  69. package/lib/task/completion-facts.ts +66 -0
  70. package/lib/task/create-service.ts +7 -3
  71. package/lib/task/create.ts +44 -71
  72. package/lib/task/events.ts +248 -182
  73. package/lib/task/finalization-handoff.ts +2 -1
  74. package/lib/task/invalidation-command.ts +4 -2
  75. package/lib/task/invalidation.ts +35 -10
  76. package/lib/task/local-artifact-finalization.ts +13 -3
  77. package/lib/task/qualification-audit.ts +21 -114
  78. package/lib/task/resolve-ref.ts +3 -3
  79. package/lib/task/review-finalization.ts +7 -0
  80. package/lib/task/review-identity.ts +85 -0
  81. package/lib/task/sections.ts +6 -6
  82. package/lib/task/task-execution-lock.ts +7 -3
  83. package/lib/task/verification-engine.ts +13 -86
  84. package/lib/task/verification.ts +34 -38
  85. package/lib/task/write.ts +115 -1
  86. package/package.json +1 -1
  87. package/templates/.agents/rules/README.en.md +1 -0
  88. package/templates/.agents/rules/README.zh-CN.md +1 -0
  89. package/templates/.agents/rules/decision-qualification.en.md +4 -3
  90. package/templates/.agents/rules/decision-qualification.zh-CN.md +4 -3
  91. package/templates/.agents/rules/evidence-reporting.en.md +1 -2
  92. package/templates/.agents/rules/evidence-reporting.zh-CN.md +1 -2
  93. package/templates/.agents/rules/validation-output.en.md +19 -0
  94. package/templates/.agents/rules/validation-output.zh-CN.md +19 -0
  95. package/templates/.agents/skills/analyze-task/SKILL.en.md +3 -1
  96. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -3
  97. package/templates/.agents/skills/archive-tasks/SKILL.en.md +2 -2
  98. package/templates/.agents/skills/archive-tasks/SKILL.zh-CN.md +2 -2
  99. package/templates/.agents/skills/archive-tasks/scripts/archive-tasks.sh +109 -6
  100. package/templates/.agents/skills/block-task/SKILL.en.md +2 -0
  101. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +1 -1
  102. package/templates/.agents/skills/cancel-task/SKILL.en.md +2 -0
  103. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +1 -1
  104. package/templates/.agents/skills/code-task/SKILL.en.md +3 -1
  105. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -2
  106. package/templates/.agents/skills/code-task/reference/report-template.en.md +4 -8
  107. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +4 -8
  108. package/templates/.agents/skills/commit/SKILL.en.md +1 -1
  109. package/templates/.agents/skills/commit/SKILL.zh-CN.md +1 -1
  110. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +1 -1
  111. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +1 -1
  112. package/templates/.agents/skills/complete-task/SKILL.en.md +2 -0
  113. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -0
  114. package/templates/.agents/skills/create-pr/SKILL.en.md +2 -0
  115. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +1 -1
  116. package/templates/.agents/skills/create-release-note/SKILL.en.md +17 -11
  117. package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +17 -11
  118. package/templates/.agents/skills/create-task/SKILL.en.md +2 -0
  119. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +1 -1
  120. package/templates/.agents/skills/import-codescan/SKILL.en.md +2 -0
  121. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
  122. package/templates/.agents/skills/import-dependabot/SKILL.en.md +2 -0
  123. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
  124. package/templates/.agents/skills/import-issue/SKILL.en.md +2 -0
  125. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +1 -1
  126. package/templates/.agents/skills/plan-task/SKILL.en.md +3 -1
  127. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +3 -3
  128. package/templates/.agents/skills/review-analysis/SKILL.en.md +3 -1
  129. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +3 -1
  130. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +4 -8
  131. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +4 -8
  132. package/templates/.agents/skills/review-code/SKILL.en.md +13 -12
  133. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +11 -12
  134. package/templates/.agents/skills/review-code/config/verify.en.json +1 -3
  135. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -3
  136. package/templates/.agents/skills/review-code/reference/output-templates.en.md +23 -46
  137. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +27 -48
  138. package/templates/.agents/skills/review-code/reference/report-template.en.md +8 -11
  139. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +8 -11
  140. package/templates/.agents/skills/review-code/reference/review-criteria.en.md +2 -2
  141. package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +2 -2
  142. package/templates/.agents/skills/review-plan/SKILL.en.md +3 -1
  143. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +3 -1
  144. package/templates/.agents/skills/review-plan/reference/report-template.en.md +5 -9
  145. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +5 -9
  146. package/templates/.agents/skills/review-pr/SKILL.en.md +2 -0
  147. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +2 -0
  148. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +1 -1
  149. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +1 -1
  150. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  151. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  152. package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -0
  153. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +1 -1
  154. package/templates/.agents/templates/task.en.md +2 -2
  155. package/templates/.agents/templates/task.zh-CN.md +2 -2
@@ -4,14 +4,13 @@ import path from "node:path";
4
4
  import process from "node:process";
5
5
  import { execFileSync } from "node:child_process";
6
6
  import { fileURLToPath } from "node:url";
7
- import { extractReviewBaseline, extractReviewTargetHead, extractReviewedHead, extractReviewDiffBase, extractReviewDiffFingerprint, extractReviewedSnapshotTree, findAuthoritativeReviewCodeArtifact, loadPostReviewConfig, parseReviewVerdict, resolvePostReviewGlobs } from "./verification-review.js";
7
+ import { extractReviewTargetHead, extractReviewedHead, extractReviewDiffBase, findAuthoritativeReviewCodeArtifact, loadPostReviewConfig, parseReviewVerdict, resolvePostReviewGlobs } from "./verification-review.js";
8
8
  import { check as checkPlatformSync } from "../platform/verification-sync.js";
9
9
  import { check as checkRequiredChecks } from "../platform/verification-required.js";
10
10
  import { inspectPlatformPullRequest } from "../platform/pull-requests.js";
11
11
  import { resolveMaterializedReviewedHeadRelation } from "../platform/change-request-git-evidence.js";
12
12
  import { resolveReviewedHeadRelation } from "../platform/merged-pr-equivalence.js";
13
13
  import { resolveLocalReviewedCommitRelation } from "../git/reviewed-commit-equivalence.js";
14
- import { resolveBranchWorktree } from "../git/branch-worktree.js";
15
14
  import { parseTypedTaskFrontmatter } from "./frontmatter.js";
16
15
  import { LEDGER_TERMINAL, LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from "./ledger.js";
17
16
  import { isValidAgentInfraVersion } from "../version.js";
@@ -20,11 +19,9 @@ import { inspectDecisionDetailDuplicates } from "./decision-details.js";
20
19
  import { parseImplementationInputs } from "./implementation-inputs.js";
21
20
  import { scanVisibleMarkdown } from "./markdown.js";
22
21
  import { loadVerificationConfig } from "./verification-config.js";
23
- import { snapshotReview } from "../git/review-snapshot.js";
24
22
  import { inspectActivityLog } from "./activity-log.js";
25
23
  import { parseWorkflowWarnings } from "./workflow-warnings.js";
26
24
  import { OrchestrationStateError, readRun } from "./orchestration.js";
27
- import { resolveDeliveryTarget } from "./delivery-target.js";
28
25
  import { readPrDeliveryFact } from "./pr-delivery-fact.js";
29
26
  import { validateLocalArtifact } from "./local-artifact-finalization.js";
30
27
  import { getArtifactSchema } from "./artifact-schema.js";
@@ -35,6 +32,8 @@ import { readManualValidationCompletion } from "./manual-validation-completion.j
35
32
  import { sha256File } from "./artifact-receipts.js";
36
33
  import { summaryCommentState } from "../platform/pr-summary.js";
37
34
  import { taskIssueIdentity } from "../platform/task-identities.js";
35
+ import { hasArtifactCompletionFact, hasArtifactCompletionLog } from './completion-facts.js';
36
+ import { inspectReviewIdentity } from './review-identity.js';
38
37
  const TASK_ENUMS = {
39
38
  type: ["feature", "bugfix", "refactor", "docs", "chore"],
40
39
  workflow: ["feature-development", "bug-fix", "refactoring"],
@@ -1014,12 +1013,16 @@ function checkReviewFact({ taskDir, artifactFile, repositoryRoot }) {
1014
1013
  return failResult("review-fact", task.message);
1015
1014
  }
1016
1015
  const content = fs.readFileSync(resolvedArtifact.path, "utf8");
1016
+ if (!hasArtifactCompletionFact(task.content, resolvedArtifact.path, 'review-code.completed')) {
1017
+ return failResult("review-fact", `Review Code completion fact does not match ${path.basename(resolvedArtifact.path)}`);
1018
+ }
1019
+ if (!hasArtifactCompletionLog(task.content, path.basename(resolvedArtifact.path), 'review-code.completed')) {
1020
+ return failResult("review-fact", `Review Code completion entry does not reference ${path.basename(resolvedArtifact.path)}`);
1021
+ }
1017
1022
  const verdict = parseReviewVerdict(content);
1018
1023
  const reviewTargetHead = extractReviewTargetHead(content);
1019
1024
  const reviewReviewedHead = extractReviewedHead(content);
1020
1025
  const reviewDiffBase = extractReviewDiffBase(content);
1021
- const reviewedFingerprint = extractReviewDiffFingerprint(content);
1022
- const reviewedTree = extractReviewedSnapshotTree(content);
1023
1026
  if (!["通过", "需要修改", "拒绝", "Approved", "Changes Requested", "Rejected"].includes(verdict)) {
1024
1027
  return failResult("review-fact", `Unsupported review verdict '${verdict}'`);
1025
1028
  }
@@ -1031,71 +1034,19 @@ function checkReviewFact({ taskDir, artifactFile, repositoryRoot }) {
1031
1034
  if (!reviewTargetHead || !reviewReviewedHead || !reviewDiffBase) {
1032
1035
  return failResult("review-fact", "Review fact must record target head, reviewed head, and diff base");
1033
1036
  }
1034
- let gitRoot;
1035
- let head;
1036
- let baseline;
1037
- let targetHead;
1038
- let diffBase;
1039
- try {
1040
- const taskRepositoryRoot = execFileSync("git", ["-C", taskDir, "rev-parse", "--show-toplevel"], { encoding: "utf8" }).trim();
1041
- gitRoot = reviewWorktreeForTask(taskRepositoryRoot, String(task.metadata.branch || "").trim()) ?? taskRepositoryRoot;
1042
- head = execFileSync("git", ["-C", gitRoot, "rev-parse", "HEAD"], { encoding: "utf8" }).trim();
1043
- const target = resolveDeliveryTarget(gitRoot, { remote: deliveryRemote, baseRef: deliveryBaseRef });
1044
- if (!target.ok)
1045
- throw new Error(target.message);
1046
- const reviewedRef = reviewReviewedHead;
1047
- baseline = execFileSync("git", ["-C", gitRoot, "rev-parse", `${reviewedRef}^{commit}`], { encoding: "utf8" }).trim();
1048
- targetHead = execFileSync("git", ["-C", gitRoot, "rev-parse", `${reviewTargetHead}^{commit}`], { encoding: "utf8" }).trim();
1049
- const computed = execFileSync("git", ["-C", gitRoot, "merge-base", baseline, targetHead], { encoding: "utf8" }).trim();
1050
- diffBase = execFileSync("git", ["-C", gitRoot, "rev-parse", `${reviewDiffBase}^{commit}`], { encoding: "utf8" }).trim();
1051
- if (diffBase !== computed)
1052
- throw new Error('saved review diff base does not match merge-base(reviewed head, target head)');
1053
- }
1054
- catch {
1055
- return blockedResult("review-fact", `Unable to resolve saved review commits in the task repository; re-run review-code`);
1056
- }
1057
- if (baseline !== head) {
1058
- return failResult("review-fact", `Reviewed head ${baseline.slice(0, 8)} does not match current HEAD ${head.slice(0, 8)}; re-run review-code`);
1059
- }
1060
- let actualSnapshot;
1061
- try {
1062
- actualSnapshot = snapshotReview({
1063
- cwd: gitRoot,
1064
- mode: "worktree",
1065
- baseline,
1066
- diffBase,
1067
- globs: resolvePostReviewGlobs({}, loadPostReviewConfig(repositoryRoot))
1068
- });
1069
- }
1070
- catch {
1071
- return blockedResult("review-fact", `Unable to recompute reviewed diff fingerprint from baseline ${baseline.slice(0, 8)}; re-run review-code`);
1072
- }
1073
- if (actualSnapshot.fingerprint !== reviewedFingerprint) {
1074
- return failResult("review-fact", `Reviewed diff fingerprint does not match the current worktree for baseline ${baseline.slice(0, 8)}; re-run review-code`);
1075
- }
1076
- if (!reviewedTree || actualSnapshot.tree !== reviewedTree) {
1077
- return failResult("review-fact", `Reviewed snapshot tree does not match the current worktree for baseline ${baseline.slice(0, 8)}; re-run review-code`);
1037
+ const identity = inspectReviewIdentity(taskDir, content, repositoryRoot);
1038
+ if (identity.status !== 'matched' || !identity.reviewedHead) {
1039
+ return failResult('review-fact', `Review identity is ${identity.status}: ${identity.message}; re-run review-code`);
1078
1040
  }
1041
+ const baseline = identity.reviewedHead;
1079
1042
  if (["通过", "Approved"].includes(verdict)) {
1080
1043
  const lastReviewedCommit = String(task.metadata.last_reviewed_commit || "").trim();
1081
- const cleanSnapshot = actualSnapshot.tree === execFileSync("git", ["-C", gitRoot, "rev-parse", `${baseline}^{tree}`], { encoding: "utf8" }).trim();
1082
- if (cleanSnapshot && lastReviewedCommit !== baseline) {
1044
+ if (lastReviewedCommit !== baseline) {
1083
1045
  return failResult("review-fact", `Approved clean review must set task last_reviewed_commit to reviewed head ${baseline.slice(0, 8)}`);
1084
1046
  }
1085
- if (!cleanSnapshot && lastReviewedCommit) {
1086
- return failResult("review-fact", "Approved review with uncommitted changes must remain unanchored until commit");
1087
- }
1088
1047
  }
1089
1048
  return passResult("review-fact", `Review fact valid for ${path.basename(resolvedArtifact.path)} at ${baseline.slice(0, 8)}`);
1090
1049
  }
1091
- function reviewWorktreeForTask(repositoryRoot, branch) {
1092
- if (!branch)
1093
- return null;
1094
- const worktree = resolveBranchWorktree(repositoryRoot, branch);
1095
- if (worktree)
1096
- return worktree;
1097
- throw new Error(`Task branch '${branch}' is not checked out in a registered worktree`);
1098
- }
1099
1050
  function resolvePostReviewBaseline({ gitRoot, lastReviewedCommit, reviewArtifact }) {
1100
1051
  if (SHA_PATTERN.test(lastReviewedCommit) && gitCommitExists(gitRoot, lastReviewedCommit)) {
1101
1052
  return { ok: true, sha: lastReviewedCommit };
@@ -89,47 +89,44 @@ async function verifyTaskEvent(request, options = {}) {
89
89
  function statusLabel(status) {
90
90
  return status === 'fail' ? 'FAIL' : status === 'blocked' ? 'BLOCKED' : 'pass';
91
91
  }
92
- function renderPayload(payload) {
93
- const lines = [];
94
- if (Array.isArray(payload.checks)) {
95
- const gateMode = typeof payload.gate === 'string';
96
- lines.push(gateMode
97
- ? `Verification: ${payload.gate} | Skill: ${payload.skill}`
98
- : `Check: ${payload.status} | Skill: ${payload.skill} | Type: ${payload.type}`, '');
99
- for (const check of payload.checks) {
100
- const raw = statusLabel(check.status);
101
- const effective = statusLabel(check.effectiveStatus ?? check.status);
102
- const reason = check.reason ?? 'CHECK_FAILED';
103
- const message = check.message ?? reason;
104
- lines.push(` [${effective}] ${check.checkId ?? check.type} (${check.classification ?? 'hard'}; raw ${raw}; reason ${reason}) - ${message}; ${check.action ?? 'Review validation output'}`);
105
- }
106
- const summary = payload.summary ?? (() => {
107
- const checks = payload.checks;
108
- const statuses = checks.map((check) => check.effectiveStatus ?? check.status);
109
- return `${statuses.filter((status) => status === 'pass').length} passed, ${statuses.filter((status) => status === 'fail').length} failed${statuses.includes('blocked') ? `, ${statuses.filter((status) => status === 'blocked').length} blocked` : ''}`;
110
- })();
111
- lines.push('', `Result: ${summary} - ${payload.action}`);
112
- }
113
- else {
114
- lines.push(`Check: ${payload.status} | Skill: ${payload.skill} | Type: ${payload.type}`, '');
115
- const raw = statusLabel(payload.status);
116
- const effective = statusLabel(payload.effectiveStatus ?? payload.status);
117
- const reason = payload.reason ?? 'CHECK_FAILED';
118
- lines.push(` [${effective}] ${payload.checkId ?? payload.type} (${payload.classification ?? 'hard'}; raw ${raw}; reason ${reason}) - ${payload.message ?? reason}; ${payload.action ?? 'Review validation output'}`);
119
- const status = payload.effectiveStatus ?? payload.status;
120
- const summary = status === 'pass' ? '1 passed, 0 failed'
121
- : status === 'blocked' ? '0 passed, 0 failed, 1 blocked' : '0 passed, 1 failed';
122
- const action = status === 'pass' ? 'Requested check passed'
123
- : status === 'blocked' ? `Resolve blocked ${payload.type} check and re-run check`
124
- : `Fix ${payload.type} issues and re-run check`;
125
- lines.push('', `Result: ${summary} - ${action}`);
126
- }
127
- return `${lines.join('\n')}\n`;
128
- }
129
92
  function renderTaskVerification(result) {
130
93
  if (result.status === 'failed')
131
94
  return `Verification failed: ${result.error?.code ?? 'VERIFY_FAILED'} - ${result.error?.message ?? 'unknown error'}\n`;
132
- return result.invocations.map((invocation) => renderPayload(invocation.payload)).join('');
95
+ const target = `${result.event}${result.artifact ? ` (${result.artifact})` : ''}`;
96
+ const lines = [`Verification: ${result.status} | Target: ${target} | Skill: ${result.skill}`];
97
+ const checks = result.invocations.flatMap(({ payload }) => {
98
+ if (Array.isArray(payload.checks))
99
+ return payload.checks;
100
+ return [payload];
101
+ });
102
+ const counts = {
103
+ pass: checks.filter((check) => (check.effectiveStatus ?? check.status) === 'pass').length,
104
+ fail: checks.filter((check) => (check.effectiveStatus ?? check.status) === 'fail').length,
105
+ blocked: checks.filter((check) => (check.effectiveStatus ?? check.status) === 'blocked').length
106
+ };
107
+ const summary = `${counts.pass} passed, ${counts.fail} failed${counts.blocked > 0 ? `, ${counts.blocked} blocked` : ''}`;
108
+ lines.push('', `Result: ${summary}`);
109
+ for (const check of checks) {
110
+ const rawStatus = check.status;
111
+ const effectiveStatus = check.effectiveStatus ?? rawStatus;
112
+ const isSoftWarning = (rawStatus === 'fail' || rawStatus === 'blocked') && effectiveStatus === 'pass';
113
+ const reason = check.reason ?? 'CHECK_FAILED';
114
+ const message = check.message ?? reason;
115
+ const action = check.action ?? 'Review validation output';
116
+ const id = check.checkId ?? check.type;
117
+ const isHumanExemptionNotice = id === 'post-review-commit'
118
+ && typeof message === 'string'
119
+ && /\bhuman-decided (?:post-review )?exemption\b/i.test(message)
120
+ && /\bPRC-\d+\s*:/.test(message);
121
+ if (effectiveStatus === 'pass' && !isSoftWarning && !isHumanExemptionNotice)
122
+ continue;
123
+ lines.push(isSoftWarning
124
+ ? `Warning: ${id} (${rawStatus === 'blocked' ? `raw BLOCKED; ` : ''}${reason}) - ${message}; ${action}`
125
+ : isHumanExemptionNotice
126
+ ? `Notice: ${id} - ${message}; ${action}`
127
+ : ` [${statusLabel(effectiveStatus)}] ${id} (${reason}) - ${message}; ${action}`);
128
+ }
129
+ return `${lines.join('\n')}\n`;
133
130
  }
134
131
  export { VERIFICATION_CATALOG, renderTaskVerification, verifyTaskEvent };
135
132
  //# sourceMappingURL=verification.js.map
@@ -1,5 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
+ import crypto from 'node:crypto';
3
4
  import { isValidAgentInfraVersion, VERSION } from '../version.js';
4
5
  import { parseTypedTaskFrontmatter, updateTaskFrontmatter } from './frontmatter.js';
5
6
  import { resolveTaskRef } from './resolve-ref.js';
@@ -53,7 +54,7 @@ function errorDetails(error, fallback) {
53
54
  message: error instanceof Error ? error.message : String(error)
54
55
  };
55
56
  }
56
- function writeTask(request, options = {}) {
57
+ function writeTaskCore(request, options = {}) {
57
58
  const resolved = options.taskLocation
58
59
  ? {
59
60
  ok: true,
@@ -258,5 +259,124 @@ function writeTask(request, options = {}) {
258
259
  }
259
260
  return { ...successBase, status: 'applied', changed: true };
260
261
  }
262
+ function archiveContentsHash(localDir) {
263
+ const files = [];
264
+ const walk = (dir) => {
265
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
266
+ if (entry.name.includes('\n') || entry.name.includes('\r') || entry.name === 'manifest.md') {
267
+ throw new Error(`Archive source path is not valid: ${path.join(dir, entry.name)}`);
268
+ }
269
+ const target = path.join(dir, entry.name);
270
+ const stat = fs.lstatSync(target);
271
+ if (stat.isSymbolicLink())
272
+ throw new Error(`Archive source cannot contain symbolic links: ${target}`);
273
+ if (stat.isDirectory())
274
+ walk(target);
275
+ else if (stat.isFile()) {
276
+ if (entry.name !== 'contents.sha256')
277
+ files.push(target);
278
+ }
279
+ else
280
+ throw new Error(`Archive source cannot contain special files: ${target}`);
281
+ }
282
+ };
283
+ walk(localDir);
284
+ const rows = files
285
+ .map((file) => ({ file, relative: path.relative(localDir, file).split(path.sep).join('/') }))
286
+ .sort((left, right) => Buffer.compare(Buffer.from(left.relative), Buffer.from(right.relative)))
287
+ .map(({ file, relative }) => `${crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex')} ${relative}`);
288
+ return `${rows.join('\n')}${rows.length ? '\n' : ''}`;
289
+ }
290
+ function writeArchiveContentsHash(localDir) {
291
+ const contents = archiveContentsHash(localDir);
292
+ const hashPath = path.join(localDir, 'contents.sha256');
293
+ const tempPath = path.join(localDir, `.contents.sha256.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`);
294
+ try {
295
+ fs.writeFileSync(tempPath, contents, { flag: 'wx' });
296
+ fs.renameSync(tempPath, hashPath);
297
+ }
298
+ catch (error) {
299
+ try {
300
+ fs.unlinkSync(tempPath);
301
+ }
302
+ catch (cleanupError) {
303
+ if (cleanupError.code !== 'ENOENT') {
304
+ throw new Error(`${error instanceof Error ? error.message : String(error)}; failed to remove ${tempPath}: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`);
305
+ }
306
+ }
307
+ throw error;
308
+ }
309
+ }
310
+ function restoreArchivedTask(taskMdPath, contents, mode) {
311
+ const tempPath = `${taskMdPath}.${process.pid}.${Math.random().toString(36).slice(2)}.rollback`;
312
+ fs.writeFileSync(tempPath, contents, { flag: 'wx', mode });
313
+ try {
314
+ fs.renameSync(tempPath, taskMdPath);
315
+ }
316
+ catch (error) {
317
+ try {
318
+ fs.unlinkSync(tempPath);
319
+ }
320
+ catch (cleanupError) {
321
+ if (cleanupError.code !== 'ENOENT') {
322
+ throw new Error(`${error instanceof Error ? error.message : String(error)}; failed to remove ${tempPath}: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`);
323
+ }
324
+ }
325
+ throw error;
326
+ }
327
+ }
328
+ function writeTask(request, options = {}) {
329
+ const resolved = options.taskLocation
330
+ ? {
331
+ ok: true,
332
+ repoRoot: options.taskLocation.repoRoot,
333
+ taskId: options.taskLocation.taskId,
334
+ taskDir: path.dirname(options.taskLocation.taskMdPath),
335
+ taskMdPath: options.taskLocation.taskMdPath,
336
+ state: options.taskLocation.state
337
+ }
338
+ : resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
339
+ if (!resolved.ok) {
340
+ return failure(request, { taskId: resolved.taskId, taskMdPath: null, actualState: null }, resolved.code, resolved.message);
341
+ }
342
+ if (resolved.state !== 'archive' || request.expectedState !== 'archive' || request.dryRun) {
343
+ return writeTaskCore(request, { ...options, taskLocation: {
344
+ repoRoot: resolved.repoRoot,
345
+ taskId: resolved.taskId,
346
+ taskMdPath: resolved.taskMdPath,
347
+ state: resolved.state
348
+ } });
349
+ }
350
+ const identity = { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, actualState: resolved.state };
351
+ try {
352
+ const originalTask = fs.readFileSync(resolved.taskMdPath);
353
+ const originalMode = fs.statSync(resolved.taskMdPath).mode;
354
+ archiveContentsHash(resolved.taskDir);
355
+ const result = writeTaskCore(request, { ...options, taskLocation: {
356
+ repoRoot: resolved.repoRoot,
357
+ taskId: resolved.taskId,
358
+ taskMdPath: resolved.taskMdPath,
359
+ state: resolved.state
360
+ } });
361
+ if (result.status === 'applied') {
362
+ try {
363
+ writeArchiveContentsHash(resolved.taskDir);
364
+ }
365
+ catch (error) {
366
+ try {
367
+ restoreArchivedTask(resolved.taskMdPath, originalTask, originalMode);
368
+ }
369
+ catch (rollbackError) {
370
+ throw new Error(`Archive checksum update failed: ${error instanceof Error ? error.message : String(error)}; task rollback failed: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`);
371
+ }
372
+ throw error;
373
+ }
374
+ }
375
+ return result;
376
+ }
377
+ catch (error) {
378
+ return failure(request, identity, 'TASK_READ_FAILED', error instanceof Error ? error.message : String(error));
379
+ }
380
+ }
261
381
  export { writeTask, captureTaskWriteMetadata, canonicalTimestamp };
262
382
  //# sourceMappingURL=write.js.map
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "protocolVersion": 3,
3
- "packageVersion": "0.11.5",
4
- "sourceInputHash": "2f29b7f1f134733c3bc1bb7c92ea6c11e57462ed51a5a96b0198b8824a3f455d",
5
- "internalExecutableBuildHash": "cb9d00aa3fa974cf936a8aad310e1fa5f28842351f52a53fed43345fe9c46439"
3
+ "packageVersion": "0.11.7",
4
+ "sourceInputHash": "095f0cf30aa29fcd2dd45123dc755e5097d7a9f81de33420461fd943cff122b2",
5
+ "internalExecutableBuildHash": "a956f2ed366e025a111a87bc1f542b87d83d4025e068304f95b1aae704709d3f"
6
6
  }
@@ -0,0 +1,62 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+
4
+ import { sameFilesystemEntry } from './worktree-identity.ts';
5
+
6
+ function gitText(repositoryRoot: string, args: string[]): string {
7
+ return execFileSync('git', ['-C', repositoryRoot, ...args], {
8
+ encoding: 'utf8',
9
+ stdio: ['ignore', 'pipe', 'pipe']
10
+ }).replace(/\r?\n$/, '');
11
+ }
12
+
13
+ function isPrimaryWorktree(candidate: string, expectedCommonDirectory: string): boolean {
14
+ try {
15
+ const root = fs.realpathSync.native(candidate);
16
+ if (!fs.statSync(root).isDirectory()) return false;
17
+ if (gitText(root, ['rev-parse', '--show-prefix']) !== '') return false;
18
+
19
+ const commonDirectory = gitText(root, ['rev-parse', '--path-format=absolute', '--git-common-dir']);
20
+ const gitDirectory = gitText(root, ['rev-parse', '--absolute-git-dir']);
21
+ return sameFilesystemEntry(commonDirectory, expectedCommonDirectory) &&
22
+ sameFilesystemEntry(gitDirectory, expectedCommonDirectory);
23
+ } catch {
24
+ return false;
25
+ }
26
+ }
27
+
28
+ function worktreePaths(repositoryRoot: string): string[] {
29
+ const output = execFileSync('git', ['-C', repositoryRoot, 'worktree', 'list', '--porcelain', '-z'], {
30
+ encoding: 'utf8',
31
+ stdio: ['ignore', 'pipe', 'pipe']
32
+ });
33
+
34
+ return output
35
+ .split('\0\0')
36
+ .flatMap((record) => record.split('\0'))
37
+ .filter((field) => field.startsWith('worktree '))
38
+ .map((field) => field.slice('worktree '.length));
39
+ }
40
+
41
+ export function resolvePrimaryWorktreeRoot(repositoryRoot: string): string {
42
+ try {
43
+ const root = fs.realpathSync.native(repositoryRoot);
44
+ if (!fs.statSync(root).isDirectory() || gitText(root, ['rev-parse', '--show-prefix']) !== '') {
45
+ throw new Error('repository root is not a worktree root');
46
+ }
47
+
48
+ const commonDirectory = gitText(root, ['rev-parse', '--path-format=absolute', '--git-common-dir']);
49
+ const gitDirectory = gitText(root, ['rev-parse', '--absolute-git-dir']);
50
+ if (sameFilesystemEntry(gitDirectory, commonDirectory)) return root;
51
+
52
+ const matches = worktreePaths(root).filter((candidate) => isPrimaryWorktree(candidate, commonDirectory));
53
+ if (matches.length !== 1) {
54
+ throw new Error(matches.length === 0 ? 'primary worktree metadata was not found' : 'primary worktree metadata is ambiguous');
55
+ }
56
+
57
+ return fs.realpathSync.native(matches[0]!);
58
+ } catch (error) {
59
+ const detail = error instanceof Error ? error.message : String(error);
60
+ throw new Error(`server: unable to locate primary worktree: ${detail}`);
61
+ }
62
+ }
package/lib/merge.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
+ import crypto from 'node:crypto';
3
4
  import { removeDirRecursive } from './remove-dir.ts';
4
5
  import { validateCurrentTaskContract } from './task/current-contract.ts';
5
6
 
@@ -107,7 +108,7 @@ function extractTitle(content: string): string | null {
107
108
  }
108
109
 
109
110
  function normalizeTaskRecord(taskDir: string, taskFile: string, dateParts: DateParts): TaskRecord {
110
- const taskId = path.basename(taskDir);
111
+ const taskId = path.basename(taskDir) === 'local' ? path.basename(path.dirname(taskDir)) : path.basename(taskDir);
111
112
  const content = fs.readFileSync(taskFile, 'utf8');
112
113
  const completedAt = extractField(content, 'completed_at');
113
114
  const updatedAt = extractField(content, 'updated_at');
@@ -155,7 +156,7 @@ function scanSourceTasks(sourceDir: string): TaskRecord[] {
155
156
  continue;
156
157
  }
157
158
 
158
- const taskDir = path.join(dayDir, taskEntry.name);
159
+ const taskDir = path.join(dayDir, taskEntry.name, 'local');
159
160
  const taskFile = path.join(taskDir, 'task.md');
160
161
  if (!fs.existsSync(taskFile)) {
161
162
  continue;
@@ -251,7 +252,7 @@ function collectArchiveEntries(archiveDir: string): ArchiveManifestEntry[] {
251
252
  }
252
253
 
253
254
  const taskDir = path.join(dayDir, taskEntry.name);
254
- const taskFile = path.join(taskDir, 'task.md');
255
+ const taskFile = path.join(taskDir, 'local', 'task.md');
255
256
  const relativePath = `${yearEntry.name}/${monthEntry.name}/${dayEntry.name}/${taskEntry.name}/`;
256
257
  let title = taskEntry.name;
257
258
  let type = 'unknown';
@@ -624,10 +625,12 @@ function validateSourceRoot(sourceRoot: string): void {
624
625
  function validateTaskDirectory(
625
626
  sourceRoot: string,
626
627
  taskDir: string,
627
- seenTaskIds: Map<string, string>
628
+ seenTaskIds: Map<string, string>,
629
+ archived = false
628
630
  ): void {
629
631
  const taskId = path.basename(taskDir);
630
- const taskFile = path.join(taskDir, 'task.md');
632
+ const contentDir = archived ? path.join(taskDir, 'local') : taskDir;
633
+ const taskFile = path.join(contentDir, 'task.md');
631
634
  const taskDirStat = sourcePathStat(sourceRoot, taskDir);
632
635
  if (!taskDirStat || !taskDirStat.isDirectory()) {
633
636
  invalidSource(`${sourceRelativePath(sourceRoot, taskDir)} must be a directory`);
@@ -646,6 +649,33 @@ function validateTaskDirectory(
646
649
  invalidSource(`${sourceRelativePath(sourceRoot, taskFile)} id '${String(contract.metadata.id ?? 'missing')}' does not match directory '${taskId}'`);
647
650
  }
648
651
  validateTaskTree(sourceRoot, taskDir);
652
+ if (archived) {
653
+ for (const entry of fs.readdirSync(taskDir, { withFileTypes: true })) {
654
+ if (!['local', 'github', 'derived'].includes(entry.name) || !entry.isDirectory()) {
655
+ invalidSource(`${sourceRelativePath(sourceRoot, path.join(taskDir, entry.name))} is not an allowed archive source directory`);
656
+ }
657
+ }
658
+ validateArchiveSource(sourceRoot, contentDir);
659
+ const githubDir = path.join(taskDir, 'github');
660
+ if (fs.existsSync(githubDir)) {
661
+ for (const capture of fs.readdirSync(githubDir, { withFileTypes: true })) {
662
+ const captureDir = path.join(githubDir, capture.name);
663
+ if (!capture.isDirectory() || !/^capture-[A-Za-z0-9._-]+$/.test(capture.name) || !fs.existsSync(path.join(captureDir, 'capture.md'))) {
664
+ invalidSource(`${sourceRelativePath(sourceRoot, captureDir)} is not a valid GitHub capture`);
665
+ }
666
+ validateArchiveSource(sourceRoot, captureDir);
667
+ }
668
+ }
669
+ const derivedDir = path.join(taskDir, 'derived');
670
+ if (fs.existsSync(derivedDir)) {
671
+ for (const view of fs.readdirSync(derivedDir, { withFileTypes: true })) {
672
+ const viewDir = path.join(derivedDir, view.name);
673
+ if (!view.isDirectory() || !fs.existsSync(path.join(viewDir, 'inputs.md'))) {
674
+ invalidSource(`${sourceRelativePath(sourceRoot, viewDir)} requires inputs.md`);
675
+ }
676
+ }
677
+ }
678
+ }
649
679
 
650
680
  const previousPath = seenTaskIds.get(taskId);
651
681
  if (previousPath) {
@@ -654,6 +684,26 @@ function validateTaskDirectory(
654
684
  seenTaskIds.set(taskId, sourceRelativePath(sourceRoot, taskDir));
655
685
  }
656
686
 
687
+ function validateArchiveSource(sourceRoot: string, sourceDir: string): void {
688
+ const hashFile = path.join(sourceDir, 'contents.sha256');
689
+ const hashStat = sourcePathStat(sourceRoot, hashFile);
690
+ if (!hashStat?.isFile()) invalidSource(`${sourceRelativePath(sourceRoot, sourceDir)} requires contents.sha256`);
691
+ const files: string[] = [];
692
+ const walk = (dir: string) => {
693
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
694
+ const target = path.join(dir, entry.name);
695
+ if (entry.name === 'manifest.md') invalidSource(`${sourceRelativePath(sourceRoot, target)} is forbidden inside a TASK`);
696
+ if (entry.isDirectory()) walk(target);
697
+ else if (entry.isFile() && target !== hashFile) files.push(target);
698
+ }
699
+ };
700
+ walk(sourceDir);
701
+ const expected = files.sort((a, b) => Buffer.compare(Buffer.from(path.relative(sourceDir, a).split(path.sep).join('/')), Buffer.from(path.relative(sourceDir, b).split(path.sep).join('/'))))
702
+ .map((file) => `${crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex')} ${path.relative(sourceDir, file).split(path.sep).join('/')}`);
703
+ const actual = fs.readFileSync(hashFile, 'utf8').trimEnd().split(/\r?\n/).filter(Boolean);
704
+ if (JSON.stringify(expected) !== JSON.stringify(actual)) invalidSource(`${sourceRelativePath(sourceRoot, hashFile)} does not match local files`);
705
+ }
706
+
657
707
  function validateMutableSection(
658
708
  sourceRoot: string,
659
709
  section: MutableSection,
@@ -730,9 +780,6 @@ function validateArchiveSection(
730
780
  const monthDir = monthPath;
731
781
  for (const dayEntry of fs.readdirSync(monthDir, { withFileTypes: true })) {
732
782
  const dayPath = path.join(monthDir, dayEntry.name);
733
- if (allowManifest(monthDir, dayEntry)) {
734
- continue;
735
- }
736
783
  const dayStat = sourcePathStat(sourceRoot, dayPath);
737
784
  if (!dayStat?.isDirectory() || !/^\d{2}$/.test(dayEntry.name)) {
738
785
  invalidSource(`${sourceRelativePath(sourceRoot, dayPath)} is not a DD directory or manifest.md`);
@@ -745,7 +792,7 @@ function validateArchiveSection(
745
792
  if (!taskStat?.isDirectory() || !TASK_ID_RE.test(taskEntry.name)) {
746
793
  invalidSource(`${sourceRelativePath(sourceRoot, taskPath)} is not a TASK-YYYYMMDD-HHMMSS directory`);
747
794
  }
748
- validateTaskDirectory(sourceRoot, taskPath, seenTaskIds);
795
+ validateTaskDirectory(sourceRoot, taskPath, seenTaskIds, true);
749
796
  }
750
797
  }
751
798
  }
@@ -904,9 +951,10 @@ function mergeArchiveSection(sourceArchive: string, localArchive: string, report
904
951
  continue;
905
952
  }
906
953
 
954
+ const sourceTaskDir = path.dirname(task.taskDir);
907
955
  const destinationDir = path.join(localArchive, task.relativePath);
908
956
  fs.mkdirSync(path.dirname(destinationDir), { recursive: true });
909
- fs.cpSync(task.taskDir, destinationDir, { recursive: true });
957
+ fs.cpSync(sourceTaskDir, destinationDir, { recursive: true });
910
958
  recordArchive(report, 'copied', {
911
959
  action: 'copied',
912
960
  symbol: '✓',
@@ -1045,6 +1093,7 @@ async function cmdMerge(args: string[]): Promise<void> {
1045
1093
  const report = createReport(resolvedSource, backupRootRelative);
1046
1094
  detectSourceMode(resolvedSource);
1047
1095
  validateSourceWorkspace(resolvedSource);
1096
+ validateArchiveSection(workspaceDir, new Map());
1048
1097
 
1049
1098
  for (const section of ALL_SECTIONS) {
1050
1099
  fs.mkdirSync(path.join(workspaceDir, section), { recursive: true });
@@ -923,30 +923,23 @@ function createGitHubOperations(client: GitHubClient): Pick<PlatformProvider, 'i
923
923
  if (collected.status === 'failed' || collected.status === 'blocked' || !('pullRequests' in collected)) {
924
924
  return { ok: false, error: collected.error || { code: 'RELEASE_NOTES_COLLECTION_FAILED', message: 'Release notes could not be collected', retryable: false } };
925
925
  }
926
- const mergedPullRequests = collected.pullRequests.map((item: any): ChangeRequestSnapshot => ({
927
- id: String(item.number), identity: { kind: 'number', value: Number(item.number) }, number: Number(item.number),
928
- state: 'closed', title: String(item.title || ''), body: String(item.body || ''),
926
+ const mergedPullRequests = collected.pullRequests.map((item: any) => ({
927
+ id: String(item.number), identity: { kind: 'number' as const, value: Number(item.number) }, number: Number(item.number),
928
+ title: String(item.title || ''), body: String(item.body || ''),
929
929
  mergedAt: new Date(String(item.mergedAt)).toISOString(), displayUrl: String(item.url || ''),
930
930
  labels: Array.isArray(item.labels) ? item.labels.map((label: any) => String(label.name || label)) : [],
931
- assignees: [], mergeCommitSha: null
932
- }));
933
- const closingIssues: IssueSnapshot[] = collected.pullRequests.flatMap((item: any) =>
934
- Array.isArray(item.closingIssuesReferences) ? item.closingIssuesReferences.map((issue: any) => ({
931
+ author: item.author,
932
+ closingIssues: item.closingIssues.map((issue: any) => ({
935
933
  id: String(issue.number), identity: { kind: 'number', value: Number(issue.number) }, number: Number(issue.number),
936
- state: 'closed' as const, title: String(issue.title || ''), body: '', labels: Array.isArray(issue.labels) ? issue.labels.map((label: any) => String(label.name || label)) : [],
937
- assignees: [], milestone: null, fields: {}, displayUrl: String(issue.url || '')
938
- })) : []
939
- );
940
- const actors = [...collected.authors.values()].flatMap((items) => items.map((item: any) => ({
941
- ...(item.login ? { id: String(item.login) } : {}), ...(item.name ? { name: String(item.name) } : {})
942
- })));
934
+ title: String(issue.title || ''), displayUrl: String(issue.url || ''), author: issue.author
935
+ }))
936
+ }));
943
937
  return {
944
938
  ok: true,
945
939
  value: {
946
- history: commitOids.map((sha) => ({ sha, message: '', authoredAt: toTime, author: actors.find((actor) => actor.id) || null })),
940
+ history: collected.history,
941
+ commits: collected.commits,
947
942
  mergedPullRequests,
948
- closingIssues,
949
- actors
950
943
  }
951
944
  };
952
945
  }
@@ -1116,7 +1109,7 @@ function createGitHubProvider(
1116
1109
  ): PlatformProvider {
1117
1110
  return {
1118
1111
  type: input.providerType,
1119
- contractVersion: 1,
1112
+ contractVersion: 2,
1120
1113
  identity: { issue: 'number', 'pull-request': 'number', comment: 'number', release: 'key' },
1121
1114
  ...createGitHubOperations(client),
1122
1115
  context: {