@fitlab-ai/agent-infra 0.9.12 → 0.9.13

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 (239) hide show
  1. package/README.md +11 -11
  2. package/README.zh-CN.md +11 -11
  3. package/dist/bin/internal-cli.js +24 -19
  4. package/dist/lib/agent-clients/next-steps.js +6 -1
  5. package/dist/lib/decide.js +3 -3
  6. package/dist/lib/internal/platform-checks.js +3 -3
  7. package/dist/lib/internal/platform-comment.js +9 -9
  8. package/dist/lib/internal/platform-context.js +2 -2
  9. package/dist/lib/internal/platform-issue.js +8 -9
  10. package/dist/lib/internal/platform-pr-review.js +10 -10
  11. package/dist/lib/internal/platform-pr.js +63 -24
  12. package/dist/lib/internal/platform-release-notes.js +3 -3
  13. package/dist/lib/internal/pr-review-grade.js +11 -9
  14. package/dist/lib/internal/release-workflow.js +4 -4
  15. package/dist/lib/internal/sandbox-control.js +30 -4
  16. package/dist/lib/internal/task-activity.js +3 -3
  17. package/dist/lib/internal/task-artifact.js +76 -24
  18. package/dist/lib/internal/task-create.js +2 -2
  19. package/dist/lib/internal/task-event.js +6 -3
  20. package/dist/lib/internal/task-finalization.js +2 -2
  21. package/dist/lib/internal/task-invalidation.js +60 -0
  22. package/dist/lib/internal/task-ledger.js +9 -6
  23. package/dist/lib/internal/task-lifecycle.js +2 -2
  24. package/dist/lib/internal/task-override.js +4 -4
  25. package/dist/lib/internal/task-review.js +3 -3
  26. package/dist/lib/internal/task-verify.js +6 -6
  27. package/dist/lib/internal/task-warning.js +3 -3
  28. package/dist/lib/platform/adapters.js +1 -3
  29. package/dist/lib/platform/change-request-git-evidence.js +27 -7
  30. package/dist/lib/platform/completion-backfill.js +6 -5
  31. package/dist/lib/platform/context.js +70 -130
  32. package/dist/lib/platform/github-client.js +1 -1
  33. package/dist/lib/platform/github-provider.js +903 -0
  34. package/dist/lib/platform/in-label-sync.js +143 -0
  35. package/dist/lib/platform/issue-comments.js +94 -41
  36. package/dist/lib/platform/issues.js +335 -189
  37. package/dist/lib/platform/metadata-labels.js +6 -2
  38. package/dist/lib/platform/none-provider.js +28 -0
  39. package/dist/lib/platform/pr-change-report.js +551 -0
  40. package/dist/lib/platform/pr-checks.js +119 -104
  41. package/dist/lib/platform/pr-review.js +88 -72
  42. package/dist/lib/platform/pr-summary.js +520 -50
  43. package/dist/lib/platform/provider-bridge.js +45 -0
  44. package/dist/lib/platform/provider-contract.d.ts +487 -0
  45. package/dist/lib/platform/provider-contract.js +95 -0
  46. package/dist/lib/platform/provider-loader.js +251 -0
  47. package/dist/lib/platform/provider-validation.js +504 -0
  48. package/dist/lib/platform/pull-request-metadata.js +8 -6
  49. package/dist/lib/platform/pull-requests.js +783 -537
  50. package/dist/lib/platform/release-notes.js +78 -55
  51. package/dist/lib/platform/releases.js +79 -50
  52. package/dist/lib/platform/resource-identity.d.ts +26 -0
  53. package/dist/lib/platform/resource-identity.js +92 -0
  54. package/dist/lib/platform/task-identities.js +31 -0
  55. package/dist/lib/platform/verification-required.js +11 -12
  56. package/dist/lib/platform/verification-sync.js +112 -238
  57. package/dist/lib/pr-review/evidence-grading.js +19 -6
  58. package/dist/lib/run/index.js +63 -23
  59. package/dist/lib/sandbox/control/client.js +96 -15
  60. package/dist/lib/sandbox/control/executor.js +3 -3
  61. package/dist/lib/sandbox/control/protocol.js +77 -0
  62. package/dist/lib/sandbox/control/server.js +398 -25
  63. package/dist/lib/sandbox/control/state.js +204 -1
  64. package/dist/lib/server/protocol.js +38 -1
  65. package/dist/lib/task/activity-log.js +8 -1
  66. package/dist/lib/task/artifact-lifecycle.js +33 -5
  67. package/dist/lib/task/capabilities.js +239 -0
  68. package/dist/lib/task/commands/status.js +32 -0
  69. package/dist/lib/task/commit-operation.js +12 -1
  70. package/dist/lib/task/control-authority.js +8 -5
  71. package/dist/lib/task/create-service.js +8 -8
  72. package/dist/lib/task/current-contract.js +5 -1
  73. package/dist/lib/task/decision-details.js +1 -1
  74. package/dist/lib/task/events.js +297 -17
  75. package/dist/lib/task/finalization.js +37 -13
  76. package/dist/lib/task/human-override.js +58 -36
  77. package/dist/lib/task/invalidation-command.js +86 -0
  78. package/dist/lib/task/invalidation.js +260 -0
  79. package/dist/lib/task/ledger-intents.js +37 -0
  80. package/dist/lib/task/local-artifact-finalization.js +374 -0
  81. package/dist/lib/task/orchestration.js +49 -93
  82. package/dist/lib/task/pr-delivery-fact.js +102 -101
  83. package/dist/lib/task/review-fingerprint.js +24 -2
  84. package/dist/lib/task/rework-intent.js +138 -0
  85. package/dist/lib/task/skill-scope.js +23 -0
  86. package/dist/lib/task/task-execution-lock.js +27 -17
  87. package/dist/lib/task/verification-engine.js +43 -16
  88. package/dist/lib/task/verification.js +3 -3
  89. package/dist/lib/task/workflow-warning-intents.js +1 -1
  90. package/dist/lib/task/write.js +11 -0
  91. package/dist/lib/version.js +11 -1
  92. package/dist/lifecycle-build-manifest.json +3 -3
  93. package/lib/agent-clients/next-steps.ts +6 -1
  94. package/lib/decide.ts +3 -3
  95. package/lib/init.ts +1 -1
  96. package/lib/internal/platform-checks.ts +3 -3
  97. package/lib/internal/platform-comment.ts +8 -8
  98. package/lib/internal/platform-context.ts +2 -2
  99. package/lib/internal/platform-issue.ts +7 -8
  100. package/lib/internal/platform-pr-review.ts +9 -9
  101. package/lib/internal/platform-pr.ts +47 -22
  102. package/lib/internal/platform-release-notes.ts +3 -3
  103. package/lib/internal/pr-review-grade.ts +10 -8
  104. package/lib/internal/release-workflow.ts +4 -4
  105. package/lib/internal/sandbox-control.ts +27 -3
  106. package/lib/internal/task-activity.ts +3 -3
  107. package/lib/internal/task-artifact.ts +65 -22
  108. package/lib/internal/task-create.ts +2 -2
  109. package/lib/internal/task-event.ts +6 -3
  110. package/lib/internal/task-finalization.ts +3 -3
  111. package/lib/internal/task-invalidation.ts +39 -0
  112. package/lib/internal/task-ledger.ts +11 -8
  113. package/lib/internal/task-lifecycle.ts +2 -2
  114. package/lib/internal/task-override.ts +6 -6
  115. package/lib/internal/task-review.ts +3 -3
  116. package/lib/internal/task-verify.ts +6 -6
  117. package/lib/internal/task-warning.ts +3 -3
  118. package/lib/platform/adapters.ts +3 -2
  119. package/lib/platform/change-request-git-evidence.ts +36 -9
  120. package/lib/platform/completion-backfill.ts +9 -8
  121. package/lib/platform/context.ts +97 -134
  122. package/lib/platform/github-client.ts +2 -2
  123. package/lib/platform/github-provider.ts +910 -0
  124. package/lib/platform/in-label-sync.ts +208 -0
  125. package/lib/platform/issue-comments.ts +97 -47
  126. package/lib/platform/issues.ts +344 -174
  127. package/lib/platform/metadata-labels.ts +5 -2
  128. package/lib/platform/none-provider.ts +35 -0
  129. package/lib/platform/pr-change-report.ts +645 -0
  130. package/lib/platform/pr-checks.ts +126 -106
  131. package/lib/platform/pr-review.ts +88 -85
  132. package/lib/platform/pr-summary.ts +560 -53
  133. package/lib/platform/provider-bridge.ts +82 -0
  134. package/lib/platform/provider-contract.ts +536 -0
  135. package/lib/platform/provider-loader.ts +307 -0
  136. package/lib/platform/provider-validation.ts +529 -0
  137. package/lib/platform/pull-request-metadata.ts +10 -7
  138. package/lib/platform/pull-requests.ts +826 -543
  139. package/lib/platform/release-notes.ts +76 -59
  140. package/lib/platform/releases.ts +77 -43
  141. package/lib/platform/resource-identity.ts +121 -0
  142. package/lib/platform/task-identities.ts +40 -0
  143. package/lib/platform/types.ts +4 -2
  144. package/lib/platform/verification-required.ts +11 -13
  145. package/lib/platform/verification-sync.ts +112 -292
  146. package/lib/pr-review/evidence-grading.ts +25 -8
  147. package/lib/run/index.ts +57 -22
  148. package/lib/sandbox/control/client.ts +105 -15
  149. package/lib/sandbox/control/executor.ts +4 -4
  150. package/lib/sandbox/control/protocol.ts +113 -0
  151. package/lib/sandbox/control/server.ts +431 -23
  152. package/lib/sandbox/control/state.ts +251 -1
  153. package/lib/server/protocol.ts +36 -1
  154. package/lib/task/activity-log.ts +8 -0
  155. package/lib/task/artifact-lifecycle.ts +32 -6
  156. package/lib/task/capabilities.ts +262 -0
  157. package/lib/task/commands/status.ts +38 -0
  158. package/lib/task/commit-operation.ts +12 -1
  159. package/lib/task/control-authority.ts +11 -8
  160. package/lib/task/create-service.ts +8 -8
  161. package/lib/task/current-contract.ts +6 -1
  162. package/lib/task/decision-details.ts +6 -2
  163. package/lib/task/events.ts +296 -16
  164. package/lib/task/finalization.ts +50 -14
  165. package/lib/task/human-override.ts +70 -46
  166. package/lib/task/invalidation-command.ts +86 -0
  167. package/lib/task/invalidation.ts +332 -0
  168. package/lib/task/ledger-intents.ts +35 -1
  169. package/lib/task/local-artifact-finalization.ts +563 -0
  170. package/lib/task/orchestration.ts +52 -85
  171. package/lib/task/pr-delivery-fact.ts +131 -148
  172. package/lib/task/review-fingerprint.ts +23 -2
  173. package/lib/task/rework-intent.ts +165 -0
  174. package/lib/task/skill-scope.ts +24 -0
  175. package/lib/task/task-execution-lock.ts +23 -14
  176. package/lib/task/verification-engine.ts +52 -17
  177. package/lib/task/verification.ts +4 -4
  178. package/lib/task/workflow-warning-intents.ts +1 -1
  179. package/lib/task/write.ts +19 -0
  180. package/lib/update.ts +1 -1
  181. package/lib/version.ts +15 -1
  182. package/package.json +7 -1
  183. package/templates/.agents/rules/README.en.md +1 -1
  184. package/templates/.agents/rules/README.zh-CN.md +1 -1
  185. package/templates/.agents/rules/issue-pr-commands.en.md +1 -1
  186. package/templates/.agents/rules/issue-pr-commands.zh-CN.md +1 -1
  187. package/templates/.agents/rules/issue-sync.en.md +2 -0
  188. package/templates/.agents/rules/issue-sync.zh-CN.md +2 -0
  189. package/templates/.agents/rules/local-artifact-repair.en.md +9 -2
  190. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +9 -2
  191. package/templates/.agents/rules/pr-sync.en.md +10 -4
  192. package/templates/.agents/rules/pr-sync.zh-CN.md +17 -4
  193. package/templates/.agents/rules/task-short-id.en.md +1 -4
  194. package/templates/.agents/rules/task-short-id.zh-CN.md +1 -3
  195. package/templates/.agents/skills/analyze-task/SKILL.en.md +13 -3
  196. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +10 -3
  197. package/templates/.agents/skills/check-task/SKILL.en.md +16 -37
  198. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -37
  199. package/templates/.agents/skills/code-task/SKILL.en.md +27 -8
  200. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +29 -10
  201. package/templates/.agents/skills/code-task/config/verify.en.json +1 -0
  202. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -0
  203. package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +2 -0
  204. package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +2 -0
  205. package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +3 -3
  206. package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +3 -3
  207. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +5 -3
  208. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +5 -3
  209. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +4 -3
  210. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +4 -3
  211. package/templates/.agents/skills/complete-task/SKILL.en.md +21 -3
  212. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +20 -3
  213. package/templates/.agents/skills/create-pr/SKILL.en.md +7 -6
  214. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +7 -7
  215. package/templates/.agents/skills/create-pr/config/verify.json +1 -0
  216. package/templates/.agents/skills/create-pr/reference/change-report.en.md +16 -4
  217. package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +16 -4
  218. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +5 -3
  219. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +5 -3
  220. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
  221. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
  222. package/templates/.agents/skills/create-pr/scripts/change-report.mjs +3 -0
  223. package/templates/.agents/skills/plan-task/SKILL.en.md +13 -3
  224. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +10 -3
  225. package/templates/.agents/skills/review-analysis/SKILL.en.md +3 -3
  226. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +3 -3
  227. package/templates/.agents/skills/review-code/SKILL.en.md +4 -4
  228. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +4 -4
  229. package/templates/.agents/skills/review-plan/SKILL.en.md +3 -3
  230. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +3 -3
  231. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +4 -2
  232. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +4 -2
  233. package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
  234. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
  235. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +6 -0
  236. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +6 -0
  237. package/templates/.agents/templates/task.en.md +18 -1
  238. package/templates/.agents/templates/task.zh-CN.md +18 -1
  239. package/templates/.github/workflows/pr-label.yml +7 -26
@@ -0,0 +1,165 @@
1
+ const REWORK_INTENT_HEADINGS = ['返工意图', 'Rework Intent'] as const;
2
+ const REWORK_INTENT_COLUMNS = [
3
+ 'intent_id', 'finding_id', 'source_artifact', 'source_sha256', 'target',
4
+ 'status', 'declared_at', 'consumed_at'
5
+ ] as const;
6
+ type ReworkTarget = 'analysis' | 'plan' | 'code';
7
+ type ReworkIntentStatus = 'pending' | 'consumed' | 'superseded';
8
+ type ReworkIntent = {
9
+ intentId: string;
10
+ findingId: string;
11
+ sourceArtifact: string;
12
+ sourceSha256: string;
13
+ target: ReworkTarget;
14
+ status: ReworkIntentStatus;
15
+ declaredAt: string;
16
+ consumedAt: string;
17
+ };
18
+ type ReworkIntentParseResult =
19
+ | { ok: true; present: boolean; intents: readonly ReworkIntent[] }
20
+ | { ok: false; code: 'TASK_REWORK_INTENT_INVALID'; message: string };
21
+
22
+ function cells(line: string): string[] | null {
23
+ const first = line.indexOf('|');
24
+ const last = line.lastIndexOf('|');
25
+ if (first < 0 || first === last || line.slice(0, first).trim() || line.slice(last + 1).trim()) return null;
26
+ const inner = line.slice(first + 1, last);
27
+ const result: string[] = [];
28
+ let start = 0;
29
+ let escaped = false;
30
+ for (let index = 0; index < inner.length; index += 1) {
31
+ const char = inner[index]!;
32
+ if (char === '|' && !escaped) {
33
+ result.push(inner.slice(start, index));
34
+ start = index + 1;
35
+ }
36
+ escaped = char === '\\' && !escaped;
37
+ if (char !== '\\') escaped = false;
38
+ }
39
+ result.push(inner.slice(start));
40
+ return result.map((cell) => cell.replace(/\\([\\|])/g, '$1').trim());
41
+ }
42
+
43
+ function sectionBody(content: string): string | null {
44
+ const heading = REWORK_INTENT_HEADINGS.map((value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
45
+ const match = new RegExp(`^##\\s+(${heading})\\s*$`, 'm').exec(content);
46
+ if (!match) return null;
47
+ const start = (match.index ?? 0) + match[0].length;
48
+ const rest = content.slice(start);
49
+ const end = rest.search(/^##\s+/m);
50
+ return rest.slice(0, end < 0 ? rest.length : end);
51
+ }
52
+
53
+ function parseReworkIntentDocument(content: string): ReworkIntentParseResult {
54
+ const body = sectionBody(content);
55
+ if (body === null) return { ok: true, present: false, intents: [] };
56
+ const lines = body.split(/\r?\n/).filter((line) => line.trim());
57
+ const header = lines[0] ? cells(lines[0]) : null;
58
+ const separator = lines[1] ? cells(lines[1]) : null;
59
+ if (!header || !separator || header.length !== REWORK_INTENT_COLUMNS.length || !header.every((value, index) => value === REWORK_INTENT_COLUMNS[index]) || !separator.every((value) => /^:?-{3,}:?$/.test(value))) {
60
+ return { ok: false, code: 'TASK_REWORK_INTENT_INVALID', message: 'rework intent section must contain its canonical table' };
61
+ }
62
+ try {
63
+ const intents = lines.slice(2).map((line) => {
64
+ const values = cells(line);
65
+ if (!values || values.length !== REWORK_INTENT_COLUMNS.length) throw new Error('rework intent row has the wrong column count');
66
+ const row = Object.fromEntries(REWORK_INTENT_COLUMNS.map((column, index) => [column, values[index]!])) as Record<string, string>;
67
+ if (!/^RI-[1-9]\d*$/.test(row.intent_id!)) throw new Error(`invalid intent id '${row.intent_id}'`);
68
+ if (!/^(AN|PL|CD)-[1-9]\d*$/.test(row.finding_id!)) throw new Error(`invalid finding id '${row.finding_id}'`);
69
+ if (!['analysis', 'plan', 'code'].includes(row.target!)) throw new Error(`invalid rework target '${row.target}'`);
70
+ if (!['pending', 'consumed', 'superseded'].includes(row.status!)) throw new Error(`invalid rework status '${row.status}'`);
71
+ if (!/^[a-f0-9]{64}$/.test(row.source_sha256!)) throw new Error(`invalid source hash for '${row.intent_id}'`);
72
+ if (!row.source_artifact || !row.declared_at) throw new Error(`rework intent '${row.intent_id}' has invalid required fields`);
73
+ const terminal = row.status === 'consumed' || row.status === 'superseded';
74
+ if (terminal !== Boolean(row.consumed_at)) throw new Error(`rework intent '${row.intent_id}' has invalid consumed_at`);
75
+ return {
76
+ intentId: row.intent_id!, findingId: row.finding_id!, sourceArtifact: row.source_artifact!,
77
+ sourceSha256: row.source_sha256!, target: row.target as ReworkTarget,
78
+ status: row.status as ReworkIntentStatus, declaredAt: row.declared_at!, consumedAt: row.consumed_at!
79
+ } satisfies ReworkIntent;
80
+ });
81
+ const ids = new Set<string>();
82
+ for (const intent of intents) {
83
+ if (ids.has(intent.intentId)) throw new Error(`duplicate intent '${intent.intentId}'`);
84
+ ids.add(intent.intentId);
85
+ }
86
+ return { ok: true, present: true, intents };
87
+ } catch (error) {
88
+ return { ok: false, code: 'TASK_REWORK_INTENT_INVALID', message: error instanceof Error ? error.message : String(error) };
89
+ }
90
+ }
91
+
92
+ function renderReworkIntents(intents: readonly ReworkIntent[]): string {
93
+ const escape = (value: string) => value.replace(/\\/g, '\\\\').replace(/\|/g, '\\|').replace(/[\r\n]/g, ' ');
94
+ return [
95
+ `| ${REWORK_INTENT_COLUMNS.join(' | ')} |`,
96
+ `| ${REWORK_INTENT_COLUMNS.map(() => '---').join(' | ')} |`,
97
+ ...intents.map((intent) => `| ${[
98
+ intent.intentId, intent.findingId, intent.sourceArtifact, intent.sourceSha256,
99
+ intent.target, intent.status, intent.declaredAt, intent.consumedAt
100
+ ].map(escape).join(' | ')} |`)
101
+ ].join('\n');
102
+ }
103
+
104
+ function upsertReworkIntent(intents: readonly ReworkIntent[], intent: ReworkIntent): { changed: boolean; intents: readonly ReworkIntent[] } {
105
+ const existing = intents.find((candidate) => candidate.intentId === intent.intentId);
106
+ if (!existing) return { changed: true, intents: [...intents, intent] };
107
+ const identity = (value: ReworkIntent) => [value.intentId, value.findingId, value.sourceArtifact, value.sourceSha256, value.target];
108
+ if (JSON.stringify(identity(existing)) !== JSON.stringify(identity(intent))) throw new Error(`intent '${intent.intentId}' identity conflicts with existing state`);
109
+ return { changed: false, intents };
110
+ }
111
+
112
+ function consumeReworkIntents(
113
+ intents: readonly ReworkIntent[],
114
+ target: ReworkTarget,
115
+ sourceHashes: Readonly<Record<string, string>>,
116
+ consumedAt: string
117
+ ): { changed: boolean; intents: readonly ReworkIntent[] } {
118
+ let changed = false;
119
+ const next = intents.map((intent) => {
120
+ if (
121
+ intent.status !== 'pending'
122
+ || intent.target !== target
123
+ || sourceHashes[intent.sourceArtifact] !== intent.sourceSha256
124
+ ) return intent;
125
+ changed = true;
126
+ return { ...intent, status: 'consumed' as const, consumedAt };
127
+ });
128
+ return { changed, intents: next };
129
+ }
130
+
131
+ function supersedeReworkIntents(
132
+ intents: readonly ReworkIntent[],
133
+ sourceArtifact: string,
134
+ sourceSha256: string,
135
+ supersededAt: string
136
+ ): { changed: boolean; intents: readonly ReworkIntent[] } {
137
+ let changed = false;
138
+ const next = intents.map((intent) => {
139
+ if (intent.status !== 'pending' || intent.sourceArtifact !== sourceArtifact || intent.sourceSha256 === sourceSha256) return intent;
140
+ changed = true;
141
+ return { ...intent, status: 'superseded' as const, consumedAt: supersededAt };
142
+ });
143
+ return { changed, intents: next };
144
+ }
145
+
146
+ function reworkIntentMutation(content: string, intents: readonly ReworkIntent[]) {
147
+ return {
148
+ kind: 'section' as const,
149
+ aliases: REWORK_INTENT_HEADINGS,
150
+ heading: /^##\s+Rework Intent\s*$/m.test(content) ? REWORK_INTENT_HEADINGS[1] : REWORK_INTENT_HEADINGS[0],
151
+ body: renderReworkIntents(intents)
152
+ };
153
+ }
154
+
155
+ export {
156
+ REWORK_INTENT_COLUMNS,
157
+ REWORK_INTENT_HEADINGS,
158
+ parseReworkIntentDocument,
159
+ renderReworkIntents,
160
+ reworkIntentMutation,
161
+ consumeReworkIntents,
162
+ supersedeReworkIntents,
163
+ upsertReworkIntent
164
+ };
165
+ export type { ReworkIntent, ReworkIntentParseResult, ReworkIntentStatus, ReworkTarget };
@@ -0,0 +1,24 @@
1
+ const TASK_SCOPE_SKILLS: ReadonlySet<string> = new Set([
2
+ 'analyze-task',
3
+ 'block-task',
4
+ 'cancel-task',
5
+ 'check-task',
6
+ 'code-task',
7
+ 'commit',
8
+ 'complete-manual-validation',
9
+ 'complete-task',
10
+ 'create-pr',
11
+ 'plan-task',
12
+ 'review-analysis',
13
+ 'review-code',
14
+ 'review-plan',
15
+ 'run-manual-validation',
16
+ 'run-task',
17
+ 'watch-pr'
18
+ ]);
19
+
20
+ function isTaskScopeSkill(skillName: string): boolean {
21
+ return TASK_SCOPE_SKILLS.has(skillName);
22
+ }
23
+
24
+ export { TASK_SCOPE_SKILLS, isTaskScopeSkill };
@@ -224,6 +224,22 @@ function withTaskExecutionLock<T>(
224
224
  };
225
225
  const serialized = `${JSON.stringify(owner)}\n`;
226
226
  let ownsFixed = false;
227
+ let deferRelease = false;
228
+ const release = (): void => {
229
+ try {
230
+ unlinkIfPresent(candidate);
231
+ } catch {
232
+ // Candidate cleanup is best-effort and must not replace the primary result.
233
+ }
234
+ if (ownsFixed) {
235
+ try {
236
+ const current = parseOwner(fs.readFileSync(fixed, 'utf8'), key);
237
+ if (current.token === token) unlinkIfPresent(fixed);
238
+ } catch {
239
+ // A missing or replaced fixed lock is not owned by this invocation.
240
+ }
241
+ }
242
+ };
227
243
  try {
228
244
  fs.mkdirSync(lockRoot, { recursive: true });
229
245
  cleanStaleCandidates(lockRoot, key, options.identityMatches ?? processIdentityMatches);
@@ -269,7 +285,12 @@ function withTaskExecutionLock<T>(
269
285
  );
270
286
  }
271
287
  unlinkIfPresent(candidate);
272
- return callback();
288
+ const value = callback();
289
+ if (value && typeof (value as { then?: unknown }).then === 'function') {
290
+ deferRelease = true;
291
+ return (value as unknown as Promise<unknown>).finally(release) as T;
292
+ }
293
+ return value;
273
294
  } catch (error) {
274
295
  if (error instanceof TaskExecutionLockError) throw error;
275
296
  if (error instanceof Error && error.name === 'OrchestrationStateError') throw error;
@@ -279,19 +300,7 @@ function withTaskExecutionLock<T>(
279
300
  { operation: ownsFixed ? 'callback' : 'acquire', errno: errno(error), key }
280
301
  );
281
302
  } finally {
282
- try {
283
- unlinkIfPresent(candidate);
284
- } catch {
285
- // Candidate cleanup is best-effort and must not replace the primary result.
286
- }
287
- if (ownsFixed) {
288
- try {
289
- const current = parseOwner(fs.readFileSync(fixed, 'utf8'), key);
290
- if (current.token === token) unlinkIfPresent(fixed);
291
- } catch {
292
- // A missing or replaced fixed lock is not owned by this invocation.
293
- }
294
- }
303
+ if (!deferRelease) release();
295
304
  }
296
305
  }
297
306
 
@@ -34,6 +34,7 @@ import { snapshotReview } from "../git/review-snapshot.ts";
34
34
  import { OrchestrationStateError, readRun } from "./orchestration.ts";
35
35
  import { resolveDeliveryTarget } from "./delivery-target.ts";
36
36
  import { readPrDeliveryFact } from "./pr-delivery-fact.ts";
37
+ import { validateLocalArtifact } from "./local-artifact-finalization.ts";
37
38
 
38
39
  const TASK_ENUMS = {
39
40
  type: ["feature", "bugfix", "refactor", "docs", "chore"],
@@ -88,7 +89,7 @@ let repoRoot = path.resolve(path.dirname(scriptPath), "..", "..");
88
89
 
89
90
  const PLATFORM_ADAPTERS: Record<string, (context: any, shared: any) => any> = {
90
91
  "platform-sync": checkPlatformSync,
91
- "platform-sync-preflight": (context: any, shared: any) => ({ ...checkPlatformSync(context, shared), type: "platform-sync-preflight" }),
92
+ "platform-sync-preflight": async (context: any, shared: any) => ({ ...await checkPlatformSync(context, shared), type: "platform-sync-preflight" }),
92
93
  "required-checks": checkRequiredChecks
93
94
  };
94
95
  const OPTIONAL_PLATFORM_CHECKS = new Set(["platform-sync"]);
@@ -107,7 +108,7 @@ const sharedUtils = {
107
108
  parsePrNumber
108
109
  };
109
110
 
110
- function runCheck(type: any, context: any, shared: any): any {
111
+ async function runCheck(type: any, context: any, shared: any): Promise<any> {
111
112
  switch (type) {
112
113
  case "task-meta":
113
114
  return checkTaskMeta(context);
@@ -147,7 +148,7 @@ function runCheck(type: any, context: any, shared: any): any {
147
148
  return failResult(type, `Unsupported check type '${type}'.`);
148
149
  }
149
150
 
150
- return adapter(context, shared);
151
+ return await adapter(context, shared);
151
152
  }
152
153
  }
153
154
  }
@@ -488,7 +489,7 @@ function checkTaskMeta({ taskDir, config }: any): any {
488
489
  );
489
490
  }
490
491
 
491
- function checkRequiredPrDelivery({ taskDir, repositoryRoot, mode }: any): any {
492
+ async function checkRequiredPrDelivery({ taskDir, repositoryRoot, mode }: any): Promise<any> {
492
493
  const task = loadTask(taskDir);
493
494
  if (!task.ok) return failResult('required-pr-delivery', task.message);
494
495
  let projectConfig: any = {};
@@ -503,9 +504,10 @@ function checkRequiredPrDelivery({ taskDir, repositoryRoot, mode }: any): any {
503
504
  if (factRead.status === 'invalid') return failResult('required-pr-delivery', factRead.error.message);
504
505
  const fact = factRead.status === 'valid' ? factRead.fact : null;
505
506
  const status = fact?.state || 'unbound';
506
- const prNumber = fact?.state === 'bound' ? fact.identity.number : null;
507
+ const prIdentity = fact?.state === 'bound' ? fact.identity.resource : null;
508
+ const prNumber = prIdentity?.kind === 'number' ? prIdentity.value : null;
507
509
  if (flow === 'disabled') return passResult('required-pr-delivery', 'Pull request delivery is disabled by project policy');
508
- if (flow === 'required' && (status !== 'bound' || prNumber === null)) {
510
+ if (flow === 'required' && (status !== 'bound' || !prIdentity)) {
509
511
  return failResult('required-pr-delivery', 'Project policy requires a bound pull request before completion');
510
512
  }
511
513
  if (flow !== 'required' && status === 'unbound') {
@@ -515,7 +517,7 @@ function checkRequiredPrDelivery({ taskDir, repositoryRoot, mode }: any): any {
515
517
  if (mode === 'gate') {
516
518
  return passResult('required-pr-delivery', 'Bound pull request shape is valid; merged-state inspection is reserved for hard preflight');
517
519
  }
518
- const inspected = inspectPlatformPullRequest(task.metadata.id, { cwd: repositoryRoot });
520
+ const inspected = await inspectPlatformPullRequest(task.metadata.id, { cwd: repositoryRoot });
519
521
  if (inspected.status === 'blocked') {
520
522
  return blockedResult(
521
523
  'required-pr-delivery',
@@ -527,7 +529,7 @@ function checkRequiredPrDelivery({ taskDir, repositoryRoot, mode }: any): any {
527
529
  const repository = inspected.platform.repository?.toLowerCase() || '';
528
530
  const taskBranch = String(task.metadata.branch || '').trim();
529
531
  const deliveryBaseRef = String(task.metadata.delivery_base_ref || projectConfig.delivery?.baseRef || '').trim();
530
- if (inspected.status !== 'no-op' || !pullRequest || pullRequest.number !== prNumber) {
532
+ if (inspected.status !== 'no-op' || !pullRequest) {
531
533
  return failResult('required-pr-delivery', 'Bound pull request identity could not be verified');
532
534
  }
533
535
  if (
@@ -542,7 +544,7 @@ function checkRequiredPrDelivery({ taskDir, repositoryRoot, mode }: any): any {
542
544
  if (pullRequest.state !== 'closed' || !pullRequest.mergedAt || !pullRequest.mergeCommitSha) {
543
545
  return failResult('required-pr-delivery', 'Project policy requires the bound pull request to be merged');
544
546
  }
545
- return passResult('required-pr-delivery', `Pull request delivery policy satisfied (merged PR #${prNumber})`);
547
+ return passResult('required-pr-delivery', `Pull request delivery policy satisfied (merged PR ${prNumber === null ? 'identity' : `#${prNumber}`})`);
546
548
  }
547
549
  return passResult('required-pr-delivery', `Pull request delivery policy satisfied (${status})`);
548
550
  }
@@ -581,7 +583,7 @@ function loadProjectName(): any {
581
583
  }
582
584
  }
583
585
 
584
- function checkArtifact({ taskDir, config, artifactFile }: any): any {
586
+ function checkArtifact({ taskDir, config, artifactFile, skillName }: any): any {
585
587
  const resolvedArtifact = resolveArtifactPath(taskDir, config.file_pattern, artifactFile);
586
588
  if (!resolvedArtifact.ok) {
587
589
  return failResult("artifact", resolvedArtifact.message);
@@ -599,6 +601,26 @@ function checkArtifact({ taskDir, config, artifactFile }: any): any {
599
601
 
600
602
  const content = fs.readFileSync(artifactPath, "utf8");
601
603
  const requiredSections = config.required_sections || [];
604
+ const localFamily = skillName === "analyze-task"
605
+ ? "analysis"
606
+ : skillName === "plan-task" ? "plan" : skillName === "code-task" ? "code" : null;
607
+ if (localFamily) {
608
+ const local = validateLocalArtifact(content, {
609
+ family: localFamily,
610
+ requiredSections: requiredSections.filter((section: unknown): section is string => typeof section === "string"),
611
+ requiredPatterns: (config.required_patterns || []).filter((pattern: unknown): pattern is string => typeof pattern === "string")
612
+ });
613
+ if (!local.ok) {
614
+ return failResult(
615
+ "artifact",
616
+ `${path.basename(artifactPath)} has local structural errors: ${local.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; ')}`
617
+ );
618
+ }
619
+ return passResult(
620
+ "artifact",
621
+ `${path.basename(artifactPath)} passed (${requiredSections.length} sections)`
622
+ );
623
+ }
602
624
  const missingSections = requiredSections.filter(
603
625
  (section: any) => !new RegExp(`^##\\s+${escapeRegExp(section)}\\s*$`, "m").test(content)
604
626
  );
@@ -1031,6 +1053,7 @@ function checkManualValidation({ taskDir }: any): any {
1031
1053
  // 1. Latest review-code artifact; none -> pass (no review, check not applicable).
1032
1054
  const review = findAuthoritativeReviewCodeArtifact(taskDir);
1033
1055
  if (!review.ok) {
1056
+ if (review.error) return failResult("manual-validation", `Review-code artifact is unavailable: ${review.error}`);
1034
1057
  return passResult("manual-validation", "No review-code artifact; manual validation not applicable");
1035
1058
  }
1036
1059
  // 2. Parse the numeric manual-validation count; unparseable/null -> fail closed
@@ -1096,9 +1119,10 @@ function checkManualValidation({ taskDir }: any): any {
1096
1119
  return passResult("manual-validation", `Manual validation completed → ${artifactName}`);
1097
1120
  }
1098
1121
 
1099
- function checkPostReviewCommit({ taskDir, config }: any): any {
1122
+ async function checkPostReviewCommit({ taskDir, config }: any): Promise<any> {
1100
1123
  const reviewArtifact = findAuthoritativeReviewCodeArtifact(taskDir);
1101
1124
  if (!reviewArtifact.ok) {
1125
+ if (reviewArtifact.error) return failResult("post-review-commit", `Review-code artifact is unavailable: ${reviewArtifact.error}`);
1102
1126
  return passResult("post-review-commit", "No review-code artifact; check inactive");
1103
1127
  }
1104
1128
 
@@ -1117,9 +1141,9 @@ function checkPostReviewCommit({ taskDir, config }: any): any {
1117
1141
  const lastReviewedCommit = task.ok ? (task.metadata.last_reviewed_commit || "").trim() : "";
1118
1142
  const globs = resolvePostReviewGlobs(config, loadPostReviewConfig(repoRoot));
1119
1143
  const hasPullRequest = fact?.state === 'bound';
1120
- let inspected: ReturnType<typeof inspectPlatformPullRequest> | null = null;
1144
+ let inspected: Awaited<ReturnType<typeof inspectPlatformPullRequest>> | null = null;
1121
1145
  if (hasPullRequest) {
1122
- inspected = inspectPlatformPullRequest(task.metadata.id, { cwd: gitRoot });
1146
+ inspected = await inspectPlatformPullRequest(task.metadata.id, { cwd: gitRoot });
1123
1147
  if (!inspected.pullRequest) {
1124
1148
  const message = inspected.error?.message || "PR merge snapshot is unavailable";
1125
1149
  const code = inspected.error?.code || "PR_INSPECTION_INVALID";
@@ -1132,7 +1156,7 @@ function checkPostReviewCommit({ taskDir, config }: any): any {
1132
1156
  inspected.pullRequest.mergedAt &&
1133
1157
  inspected.pullRequest.mergeCommitSha
1134
1158
  ) {
1135
- const relation = resolveMaterializedReviewedHeadRelation({
1159
+ const relation = await resolveMaterializedReviewedHeadRelation({
1136
1160
  cwd: gitRoot,
1137
1161
  platformType: inspected.platform.type,
1138
1162
  lastReviewedCommit,
@@ -1299,6 +1323,17 @@ function checkReviewFact({ taskDir, artifactFile }: any): any {
1299
1323
  return failResult("review-fact", resolvedArtifact.message);
1300
1324
  }
1301
1325
 
1326
+ const authoritative = findAuthoritativeReviewCodeArtifact(taskDir);
1327
+ if (!authoritative.ok) {
1328
+ return failResult("review-fact", authoritative.error ?? "No active review-code artifact is available");
1329
+ }
1330
+ if (path.basename(resolvedArtifact.path) !== authoritative.fileName) {
1331
+ return failResult(
1332
+ "review-fact",
1333
+ `review-code artifact ${path.basename(resolvedArtifact.path)} is not the current active artifact ${authoritative.fileName}; re-run review-code`
1334
+ );
1335
+ }
1336
+
1302
1337
  const task = loadTask(taskDir);
1303
1338
  if (!task.ok) {
1304
1339
  return failResult("review-fact", task.message);
@@ -1655,7 +1690,7 @@ function isBlank(value: any): any {
1655
1690
  return value === undefined || value === null || String(value).trim() === "";
1656
1691
  }
1657
1692
 
1658
- function verifyInProcess({ mode, skillName, taskDir, artifactFile, checks: requestedChecks, repositoryRoot }: any): any {
1693
+ async function verifyInProcess({ mode, skillName, taskDir, artifactFile, checks: requestedChecks, repositoryRoot }: any): Promise<any> {
1659
1694
  if (repositoryRoot) repoRoot = path.resolve(repositoryRoot);
1660
1695
  else {
1661
1696
  let cursor = path.resolve(taskDir);
@@ -1668,7 +1703,7 @@ function verifyInProcess({ mode, skillName, taskDir, artifactFile, checks: reque
1668
1703
  const checks = [];
1669
1704
  for (const [type, checkConfig] of Object.entries(verifyConfig.checks || {})) {
1670
1705
  if (checkConfig === null) continue;
1671
- const result = runCheck(type, {
1706
+ const result = await runCheck(type, {
1672
1707
  mode,
1673
1708
  skillName,
1674
1709
  taskDir: path.resolve(taskDir),
@@ -1688,7 +1723,7 @@ function verifyInProcess({ mode, skillName, taskDir, artifactFile, checks: reque
1688
1723
  if (config === null) return { skill: skillName, ...passResult(type, `Check '${type}' is disabled for skill '${skillName}'.`) };
1689
1724
  return {
1690
1725
  skill: skillName,
1691
- ...runCheck(type, {
1726
+ ...await runCheck(type, {
1692
1727
  skillName,
1693
1728
  taskDir: path.resolve(taskDir),
1694
1729
  artifactFile,
@@ -49,7 +49,7 @@ type TaskVerificationResult = {
49
49
  };
50
50
  type VerificationOptions = {
51
51
  repoRoot?: string;
52
- engine?: typeof verifyInProcess;
52
+ engine?: (input: Parameters<typeof verifyInProcess>[0]) => Record<string, unknown> | Promise<Record<string, unknown>>;
53
53
  manualOverride?: ManualOverrideCapability;
54
54
  };
55
55
 
@@ -91,7 +91,7 @@ function failure(request: { taskRef: string; event: string; artifact?: string },
91
91
  };
92
92
  }
93
93
 
94
- function verifyTaskEvent(request: { taskRef: string; event: string; artifact?: string }, options: VerificationOptions = {}): TaskVerificationResult {
94
+ async function verifyTaskEvent(request: { taskRef: string; event: string; artifact?: string }, options: VerificationOptions = {}): Promise<TaskVerificationResult> {
95
95
  const spec = VERIFICATION_CATALOG[request.event as VerificationEvent];
96
96
  if (!spec) return failure(request, 'VERIFY_EVENT_UNKNOWN', `unknown verification event '${request.event}'`);
97
97
  const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
@@ -113,7 +113,7 @@ function verifyTaskEvent(request: { taskRef: string; event: string; artifact?: s
113
113
  const invocations: VerificationInvocation[] = [];
114
114
  const engine = options.engine ?? verifyInProcess;
115
115
  if (spec.mode === 'gate') {
116
- const payload = engine({ mode: 'gate', skillName: spec.skill, taskDir: resolved.taskDir, artifactFile: request.artifact, checks: [], repositoryRoot: resolved.repoRoot }) as Record<string, unknown>;
116
+ const payload = await engine({ mode: 'gate', skillName: spec.skill, taskDir: resolved.taskDir, artifactFile: request.artifact, checks: [], repositoryRoot: resolved.repoRoot });
117
117
  const status = payload.gate as 'pass' | 'fail' | 'blocked';
118
118
  if (status !== 'pass' && allowsManualOverride(options.manualOverride, 'verification-engine', status === 'blocked' ? 'CHECK_BLOCKED' : 'CHECK_FAILED')) {
119
119
  invocations.push({ status: 'pass', exitCode: 0, payload: { ...payload, gate: 'pass', humanOverride: 'human-approved', originalGate: status } });
@@ -125,7 +125,7 @@ function verifyTaskEvent(request: { taskRef: string; event: string; artifact?: s
125
125
  invocations.push({ status, exitCode: ({ pass: 0, fail: 1, blocked: 2 } as const)[status], payload });
126
126
  } else {
127
127
  for (const check of spec.checks ?? []) {
128
- const payload = engine({ mode: 'checks', skillName: spec.skill, taskDir: resolved.taskDir, artifactFile: request.artifact, checks: [check], repositoryRoot: resolved.repoRoot }) as Record<string, unknown>;
128
+ const payload = await engine({ mode: 'checks', skillName: spec.skill, taskDir: resolved.taskDir, artifactFile: request.artifact, checks: [check], repositoryRoot: resolved.repoRoot });
129
129
  const status = payload.status as 'pass' | 'fail' | 'blocked';
130
130
  if (status !== 'pass' && allowsManualOverride(options.manualOverride, 'verification-engine', status === 'blocked' ? 'CHECK_BLOCKED' : 'CHECK_FAILED')) {
131
131
  invocations.push({ status: 'pass', exitCode: 0, payload: { ...payload, status: 'pass', humanOverride: 'human-approved', originalStatus: status } });
@@ -68,7 +68,7 @@ function sectionMutation(content: string): TaskMutation[] {
68
68
  const english = /^##\s+Activity Log\s*$/m.test(content);
69
69
  return [{
70
70
  kind: 'section', aliases: WORKFLOW_WARNING_HEADINGS, heading: english ? 'Workflow Warnings' : '工作流告警',
71
- body: `| ${WORKFLOW_WARNING_COLUMNS.join(' | ')} |\n|----|------|------|----------|------|--------|--------|---------|--------|-------------|------------|`
71
+ body: `| ${WORKFLOW_WARNING_COLUMNS.join(' | ')} |\n|----|------|------|----------|------|--------|---------|--------|-------------|------------|------------|`
72
72
  }];
73
73
  }
74
74
 
package/lib/task/write.ts CHANGED
@@ -16,6 +16,7 @@ import { allowsManualOverride } from './guard-override.ts';
16
16
  import type { ManualOverrideCapability } from './guard-override.ts';
17
17
  import { mutateTableRow, upsertSection } from './sections.ts';
18
18
  import { validateCurrentTaskContract } from './current-contract.ts';
19
+ import { invalidationBlocks, parseInvalidationDocument } from './invalidation.ts';
19
20
  import type {
20
21
  TableRowDeleteMutation,
21
22
  TableRowUpsertMutation
@@ -86,6 +87,8 @@ type TaskWriteErrorCode =
86
87
  | ResolveTaskRefErrorCode
87
88
  | 'TASK_STATE_MISMATCH'
88
89
  | 'TASK_CURRENT_CONTRACT_INVALID'
90
+ | 'TASK_INVALIDATION_INVALID'
91
+ | 'TASK_INVALIDATION_BLOCKED'
89
92
  | 'TASK_READ_FAILED'
90
93
  | 'TASK_DOCUMENT_INVALID'
91
94
  | 'MUTATION_INVALID'
@@ -161,6 +164,7 @@ type TaskWriteOptions = {
161
164
  randomSuffix?: () => string;
162
165
  fileSystem?: Partial<TaskFileSystem>;
163
166
  manualOverride?: ManualOverrideCapability;
167
+ invalidationContext?: 'standard' | 'source-completion' | 'reconcile';
164
168
  };
165
169
 
166
170
  const DEFAULT_FILE_SYSTEM: TaskFileSystem = {
@@ -275,6 +279,21 @@ function writeTask(request: TaskWriteRequest, options: TaskWriteOptions = {}): T
275
279
  if (!contract.ok) {
276
280
  return failure(request, identity, contract.code, contract.message);
277
281
  }
282
+ if (
283
+ invalidationBlocks(contract.invalidation)
284
+ && options.invalidationContext !== 'source-completion'
285
+ && options.invalidationContext !== 'reconcile'
286
+ ) {
287
+ return failure(
288
+ request,
289
+ identity,
290
+ 'TASK_INVALIDATION_BLOCKED',
291
+ 'task has an incomplete artifact invalidation operation; reconcile it before writing downstream state'
292
+ );
293
+ }
294
+ } else {
295
+ const invalidation = parseInvalidationDocument(original);
296
+ if (!invalidation.ok) return failure(request, identity, 'TASK_INVALIDATION_INVALID', invalidation.message);
278
297
  }
279
298
 
280
299
  let candidate = original;
package/lib/update.ts CHANGED
@@ -19,7 +19,7 @@ type UpdateConfig = {
19
19
  project: string;
20
20
  org: string;
21
21
  language: string;
22
- platform?: { type?: string };
22
+ platform?: { type?: string; providers?: Record<string, unknown> };
23
23
  prFlow?: 'required' | 'disabled';
24
24
  sandbox?: Record<string, unknown>;
25
25
  task?: { shortIdLength: number };
package/lib/version.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
+ import semver from 'semver';
2
3
 
3
4
  const packageJsonUrl = [
4
5
  new URL('../package.json', import.meta.url),
@@ -12,9 +13,22 @@ if (!packageJsonUrl) {
12
13
  const { version } = JSON.parse(readFileSync(packageJsonUrl, 'utf8'));
13
14
  const VERSION = `v${version}`;
14
15
  const AGENT_INFRA_VERSION_PATTERN = /^v\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
16
+ const LEGACY_COMPATIBILITY_CUTOFF_VERSION = '1.0.0';
15
17
 
16
18
  function isValidAgentInfraVersion(value: unknown): value is string {
17
19
  return typeof value === 'string' && AGENT_INFRA_VERSION_PATTERN.test(value);
18
20
  }
19
21
 
20
- export { VERSION, isValidAgentInfraVersion };
22
+ function isLegacyCompatibilityEnabled(runtimeVersion = VERSION): boolean {
23
+ const normalized = runtimeVersion.startsWith('v') ? runtimeVersion.slice(1) : runtimeVersion;
24
+ const parsed = semver.valid(normalized);
25
+ return parsed !== null && semver.lt(parsed, LEGACY_COMPATIBILITY_CUTOFF_VERSION);
26
+ }
27
+
28
+ function legacyCompatibilityError(resource: string, runtimeVersion = VERSION): Error & { code: string } {
29
+ return Object.assign(new Error(
30
+ `PLATFORM_IDENTITY_LEGACY_UNSUPPORTED: ${resource} requires migration to the current schema before v${LEGACY_COMPATIBILITY_CUTOFF_VERSION} runtime ${runtimeVersion}`
31
+ ), { code: 'PLATFORM_IDENTITY_LEGACY_UNSUPPORTED' });
32
+ }
33
+
34
+ export { LEGACY_COMPATIBILITY_CUTOFF_VERSION, VERSION, isLegacyCompatibilityEnabled, isValidAgentInfraVersion, legacyCompatibilityError };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fitlab-ai/agent-infra",
3
- "version": "0.9.12",
3
+ "version": "0.9.13",
4
4
  "description": "Bootstrap tool for AI multi-tool collaboration infrastructure — works with Claude Code, Codex, Antigravity CLI, and OpenCode",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -17,6 +17,12 @@
17
17
  "access": "public",
18
18
  "registry": "https://registry.npmjs.org/"
19
19
  },
20
+ "exports": {
21
+ "./platform-provider": {
22
+ "types": "./dist/lib/platform/provider-contract.d.ts",
23
+ "import": "./dist/lib/platform/provider-contract.js"
24
+ }
25
+ },
20
26
  "bin": {
21
27
  "agent-infra": "./dist/bin/cli.js",
22
28
  "ai": "./dist/bin/cli.js",
@@ -30,7 +30,7 @@ so you can quickly find "which ones to read" without opening each file.
30
30
  - [`lifecycle-orchestration.md`](lifecycle-orchestration.md) — Fresh executor/reviewer, one-use receipt, pause/recovery, and safe endpoint rules for `run-task`.
31
31
  - [`review-handshake.md`](review-handshake.md) — Three-stage bidirectional review handshake: four-state disposition, symmetric evidence, disagreement ledger, convergence and post-review commit gate.
32
32
  - [`review-method.md`](review-method.md) — Shared three-stage review method: multi-pass review, risk lenses, traceability, and finding evidence.
33
- - [`local-artifact-repair.md`](local-artifact-repair.md) — Model-per-case repair, mechanical safety gates, dynamic convergence, and result-output contract for failed review artifacts.
33
+ - [`local-artifact-repair.md`](local-artifact-repair.md) — Pre-completion local artifact validation for analysis/plan/code and model-per-case repair, safety gates, and convergence for failed review artifacts.
34
34
  - [`human-decision-context.md`](human-decision-context.md) — Self-contained context and canonical structure for new human-decision details.
35
35
  - [`task-short-id.md`](task-short-id.md) — Resolution, allocation and lifecycle of bare-number short ids.
36
36
  - [`milestone-inference.md`](milestone-inference.md) — Milestone inference for create-task / code-task / create-pr.
@@ -29,7 +29,7 @@
29
29
  - [`lifecycle-orchestration.md`](lifecycle-orchestration.md) — `run-task` 的 fresh executor/reviewer、一次性 receipt、暂停恢复与安全终点规则。
30
30
  - [`review-handshake.md`](review-handshake.md) — 三阶段双向审查握手协议:四态处置、对称证据、分歧账本、收敛与 post-review commit 门禁。
31
31
  - [`review-method.md`](review-method.md) — 三阶段共享检视方法:多遍检视、风险镜头、追踪与 finding 证据契约。
32
- - [`local-artifact-repair.md`](local-artifact-repair.md) — review artifact 失败后的模型逐例修复、机械安全门、动态收敛与结果输出契约。
32
+ - [`local-artifact-repair.md`](local-artifact-repair.md) — analysis/plan/code completed 前的本地产物校验、review artifact 失败后的模型逐例修复、机械安全门与收敛契约。
33
33
  - [`human-decision-context.md`](human-decision-context.md) — 新建人工裁决详情的自足上下文与规范结构。
34
34
  - [`task-short-id.md`](task-short-id.md) — 裸数字任务短号的解析、分配与生命周期。
35
35
  - [`milestone-inference.md`](milestone-inference.md) — create-task / code-task / create-pr 的 milestone 推断。
@@ -29,6 +29,6 @@ agent-infra-internal platform-pr sync {task-id} \
29
29
  --agent {standard-agent-token} --metadata --closing-issue
30
30
  ```
31
31
 
32
- Conflicting bindings do not change `task.md`. Metadata comes from the linked Issue, permission-bound items degrade independently, and an already-created PR is never rolled back.
32
+ Conflicting bindings do not change `task.md`. The shared core computes one `in:` target from task-bound diff/PR files and the repository mapping. A unique closing Issue is converged before the PR; other metadata comes from the linked Issue. Zero or multiple closing Issues update only the PR and return `degraded`; `in:` labels are never copied back from the Issue or removed from unrelated resources. Permission-bound items degrade independently, partial side effects return `blocked` with `IN_LABEL_SYNC_PARTIAL`, and an already-created PR is never rolled back.
33
33
 
34
34
  Statuses `planned|applied|no-op|degraded` exit 0, `failed` exits 1, and `blocked` exits 2.
@@ -39,7 +39,7 @@ agent-infra-internal platform-pr sync {task-id} \
39
39
  --agent {standard-agent-token} --metadata --closing-issue
40
40
  ```
41
41
 
42
- core 从关联 Issue 复制 type / `in:` labels、assignee、具体 milestone,并确保 closing association。权限不足返回逐项 `skipped` 和顶层 `degraded`;不得反向修改 Issue。
42
+ core 根据 task-bound diff/PR files 与仓库映射计算唯一 `in:` target;唯一 closing Issue 按 Issue→PR 顺序收敛,其他 metadata 从关联 Issue 复制。零或多个 closing Issue 只更新 PR 并返回 `degraded`;不得从 Issue 反向复制 `in:`,也不清除非 `in:` labels。权限不足逐项返回 `skipped`,部分副作用返回 `blocked` 与 `IN_LABEL_SYNC_PARTIAL`。
43
43
 
44
44
  ## 状态语义
45
45