@fitlab-ai/agent-infra 0.9.13 → 0.9.14

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 (280) hide show
  1. package/bin/cli.ts +26 -9
  2. package/dist/bin/cli.js +173 -157
  3. package/dist/bin/internal-cli.js +42 -21
  4. package/dist/lib/agent-client.js +5 -0
  5. package/dist/lib/internal/agent-client.js +5 -2
  6. package/dist/lib/internal/cli-route-inventory.js +152 -0
  7. package/dist/lib/internal/codex-lifecycle.js +9 -6
  8. package/dist/lib/internal/codex-sandbox-controller.js +8 -2
  9. package/dist/lib/internal/git-workflow.js +10 -6
  10. package/dist/lib/internal/platform-checks.js +16 -4
  11. package/dist/lib/internal/platform-comment.js +16 -4
  12. package/dist/lib/internal/platform-context.js +4 -1
  13. package/dist/lib/internal/platform-issue.js +12 -4
  14. package/dist/lib/internal/platform-metadata.js +88 -0
  15. package/dist/lib/internal/platform-pr-review.js +16 -3
  16. package/dist/lib/internal/platform-pr.js +24 -10
  17. package/dist/lib/internal/platform-release-notes.js +6 -3
  18. package/dist/lib/internal/platform-security.js +93 -0
  19. package/dist/lib/internal/pr-review-grade.js +14 -3
  20. package/dist/lib/internal/release-workflow.js +19 -5
  21. package/dist/lib/internal/sandbox-control.js +22 -5
  22. package/dist/lib/internal/task-activity.js +13 -4
  23. package/dist/lib/internal/task-artifact.js +186 -27
  24. package/dist/lib/internal/task-context.js +4 -1
  25. package/dist/lib/internal/task-create.js +48 -15
  26. package/dist/lib/internal/task-delivery.js +4 -1
  27. package/dist/lib/internal/task-event.js +4 -1
  28. package/dist/lib/internal/task-finalization.js +4 -1
  29. package/dist/lib/internal/task-invalidation.js +4 -1
  30. package/dist/lib/internal/task-ledger.js +12 -1
  31. package/dist/lib/internal/task-lifecycle.js +4 -1
  32. package/dist/lib/internal/task-operation-registry.js +421 -0
  33. package/dist/lib/internal/task-orchestration.js +12 -0
  34. package/dist/lib/internal/task-override.js +10 -3
  35. package/dist/lib/internal/task-qualification.js +90 -0
  36. package/dist/lib/internal/task-review.js +4 -1
  37. package/dist/lib/internal/task-short-id.js +11 -1
  38. package/dist/lib/internal/task-snapshot.js +4 -1
  39. package/dist/lib/internal/task-validate.js +7 -2
  40. package/dist/lib/internal/task-verify.js +5 -1
  41. package/dist/lib/internal/task-warning.js +8 -1
  42. package/dist/lib/platform/comment-safety.js +226 -0
  43. package/dist/lib/platform/github-provider.js +233 -6
  44. package/dist/lib/platform/issue-comments.js +146 -34
  45. package/dist/lib/platform/pr-change-report.js +6 -12
  46. package/dist/lib/platform/pr-summary.js +28 -7
  47. package/dist/lib/platform/provider-contract.d.ts +67 -1
  48. package/dist/lib/platform/provider-contract.js +6 -2
  49. package/dist/lib/platform/provider-validation.js +76 -3
  50. package/dist/lib/platform/pull-requests.js +115 -170
  51. package/dist/lib/platform/repository-metadata.js +377 -0
  52. package/dist/lib/platform/security-alerts.js +155 -0
  53. package/dist/lib/platform/verification-sync.js +33 -26
  54. package/dist/lib/process-data/index.js +7 -0
  55. package/dist/lib/run/index.js +5 -0
  56. package/dist/lib/sandbox/commands/control-status.js +22 -0
  57. package/dist/lib/sandbox/commands/create.js +32 -6
  58. package/dist/lib/sandbox/commands/ls.js +5 -0
  59. package/dist/lib/sandbox/commands/rm.js +827 -120
  60. package/dist/lib/sandbox/commands/show.js +16 -0
  61. package/dist/lib/sandbox/control/client.js +83 -9
  62. package/dist/lib/sandbox/control/completed-reentry.js +85 -0
  63. package/dist/lib/sandbox/control/container-identity.js +28 -31
  64. package/dist/lib/sandbox/control/executor.js +149 -14
  65. package/dist/lib/sandbox/control/lifecycle.js +621 -127
  66. package/dist/lib/sandbox/control/native-file-lock.js +177 -0
  67. package/dist/lib/sandbox/control/server.js +305 -35
  68. package/dist/lib/sandbox/control/state.js +5 -3
  69. package/dist/lib/sandbox/control/task-view.js +183 -0
  70. package/dist/lib/sandbox/engines/authority.js +289 -0
  71. package/dist/lib/sandbox/engines/docker-exact-discovery.js +97 -0
  72. package/dist/lib/sandbox/index.js +5 -0
  73. package/dist/lib/sandbox/recovery.js +21 -1
  74. package/dist/lib/sandbox/workspace-view.js +2 -1
  75. package/dist/lib/sandbox/worktree-safety.js +4 -1
  76. package/dist/lib/server/index.js +8 -0
  77. package/dist/lib/task/artifact-lifecycle.js +42 -13
  78. package/dist/lib/task/artifact-operations.js +520 -0
  79. package/dist/lib/task/artifact-repair-intent.js +59 -0
  80. package/dist/lib/task/artifact-schema.js +135 -0
  81. package/dist/lib/task/capabilities.js +72 -1
  82. package/dist/lib/task/control-authority.js +4 -0
  83. package/dist/lib/task/create-service.js +103 -4
  84. package/dist/lib/task/create.js +25 -0
  85. package/dist/lib/task/events.js +186 -67
  86. package/dist/lib/task/index.js +11 -3
  87. package/dist/lib/task/local-artifact-finalization.js +75 -150
  88. package/dist/lib/task/orchestration.js +192 -52
  89. package/dist/lib/task/qualification-audit.js +490 -0
  90. package/dist/lib/task/qualification-confirmation.js +109 -0
  91. package/dist/lib/task/qualification-intents.js +148 -0
  92. package/dist/lib/task/review-artifacts.js +22 -23
  93. package/dist/lib/task/review-finalization.js +121 -4
  94. package/dist/lib/task/sections.js +6 -4
  95. package/dist/lib/task/verification-config.js +11 -0
  96. package/dist/lib/task/verification-engine.js +54 -4
  97. package/dist/lifecycle-build-manifest.json +3 -3
  98. package/lib/agent-client.ts +9 -0
  99. package/lib/internal/agent-client.ts +4 -2
  100. package/lib/internal/cli-route-inventory.ts +140 -0
  101. package/lib/internal/codex-lifecycle.ts +8 -6
  102. package/lib/internal/codex-sandbox-controller.ts +7 -2
  103. package/lib/internal/git-workflow.ts +9 -6
  104. package/lib/internal/platform-checks.ts +12 -4
  105. package/lib/internal/platform-comment.ts +12 -4
  106. package/lib/internal/platform-context.ts +3 -1
  107. package/lib/internal/platform-issue.ts +11 -4
  108. package/lib/internal/platform-metadata.ts +70 -0
  109. package/lib/internal/platform-pr-review.ts +14 -3
  110. package/lib/internal/platform-pr.ts +23 -10
  111. package/lib/internal/platform-release-notes.ts +5 -3
  112. package/lib/internal/platform-security.ts +83 -0
  113. package/lib/internal/pr-review-grade.ts +10 -3
  114. package/lib/internal/release-workflow.ts +17 -5
  115. package/lib/internal/sandbox-control.ts +19 -5
  116. package/lib/internal/task-activity.ts +12 -4
  117. package/lib/internal/task-artifact.ts +164 -25
  118. package/lib/internal/task-context.ts +3 -1
  119. package/lib/internal/task-create.ts +76 -15
  120. package/lib/internal/task-delivery.ts +3 -1
  121. package/lib/internal/task-event.ts +3 -1
  122. package/lib/internal/task-finalization.ts +3 -1
  123. package/lib/internal/task-invalidation.ts +3 -1
  124. package/lib/internal/task-ledger.ts +11 -1
  125. package/lib/internal/task-lifecycle.ts +3 -1
  126. package/lib/internal/task-operation-registry.ts +482 -0
  127. package/lib/internal/task-orchestration.ts +12 -0
  128. package/lib/internal/task-override.ts +9 -3
  129. package/lib/internal/task-qualification.ts +61 -0
  130. package/lib/internal/task-review.ts +3 -1
  131. package/lib/internal/task-short-id.ts +10 -1
  132. package/lib/internal/task-snapshot.ts +3 -1
  133. package/lib/internal/task-validate.ts +6 -2
  134. package/lib/internal/task-verify.ts +4 -1
  135. package/lib/internal/task-warning.ts +7 -1
  136. package/lib/platform/comment-safety.ts +250 -0
  137. package/lib/platform/github-provider.ts +229 -7
  138. package/lib/platform/issue-comments.ts +160 -35
  139. package/lib/platform/pr-change-report.ts +6 -12
  140. package/lib/platform/pr-summary.ts +27 -6
  141. package/lib/platform/provider-contract.ts +78 -3
  142. package/lib/platform/provider-validation.ts +85 -3
  143. package/lib/platform/pull-requests.ts +126 -178
  144. package/lib/platform/repository-metadata.ts +453 -0
  145. package/lib/platform/security-alerts.ts +191 -0
  146. package/lib/platform/verification-sync.ts +32 -28
  147. package/lib/process-data/index.ts +7 -0
  148. package/lib/run/index.ts +5 -0
  149. package/lib/sandbox/commands/control-status.ts +26 -0
  150. package/lib/sandbox/commands/create.ts +46 -6
  151. package/lib/sandbox/commands/ls.ts +7 -0
  152. package/lib/sandbox/commands/rm.ts +1000 -87
  153. package/lib/sandbox/commands/show.ts +16 -0
  154. package/lib/sandbox/control/client.ts +103 -9
  155. package/lib/sandbox/control/completed-reentry.ts +98 -0
  156. package/lib/sandbox/control/container-identity.ts +27 -43
  157. package/lib/sandbox/control/executor.ts +177 -12
  158. package/lib/sandbox/control/lifecycle.ts +629 -17
  159. package/lib/sandbox/control/native-file-lock.ts +209 -0
  160. package/lib/sandbox/control/protocol.ts +5 -2
  161. package/lib/sandbox/control/server.ts +319 -37
  162. package/lib/sandbox/control/state.ts +6 -3
  163. package/lib/sandbox/control/task-view.ts +240 -0
  164. package/lib/sandbox/engines/authority.ts +365 -0
  165. package/lib/sandbox/engines/docker-exact-discovery.ts +132 -0
  166. package/lib/sandbox/index.ts +7 -0
  167. package/lib/sandbox/recovery.ts +21 -1
  168. package/lib/sandbox/workspace-view.ts +9 -3
  169. package/lib/sandbox/worktree-safety.ts +4 -1
  170. package/lib/server/index.ts +10 -0
  171. package/lib/task/artifact-lifecycle.ts +38 -16
  172. package/lib/task/artifact-operations.ts +626 -0
  173. package/lib/task/artifact-repair-intent.ts +71 -0
  174. package/lib/task/artifact-schema.ts +192 -0
  175. package/lib/task/capabilities.ts +70 -1
  176. package/lib/task/control-authority.ts +11 -1
  177. package/lib/task/create-service.ts +131 -6
  178. package/lib/task/create.ts +27 -0
  179. package/lib/task/events.ts +161 -65
  180. package/lib/task/index.ts +16 -3
  181. package/lib/task/local-artifact-finalization.ts +87 -218
  182. package/lib/task/orchestration.ts +172 -49
  183. package/lib/task/qualification-audit.ts +599 -0
  184. package/lib/task/qualification-confirmation.ts +117 -0
  185. package/lib/task/qualification-intents.ts +150 -0
  186. package/lib/task/review-artifacts.ts +21 -22
  187. package/lib/task/review-finalization.ts +133 -4
  188. package/lib/task/sections.ts +6 -4
  189. package/lib/task/verification-config.ts +12 -0
  190. package/lib/task/verification-engine.ts +55 -4
  191. package/package.json +6 -1
  192. package/templates/.agents/rules/README.en.md +2 -0
  193. package/templates/.agents/rules/README.zh-CN.md +2 -0
  194. package/templates/.agents/rules/decision-qualification.en.md +22 -0
  195. package/templates/.agents/rules/decision-qualification.zh-CN.md +22 -0
  196. package/templates/.agents/rules/evidence-reporting.en.md +28 -0
  197. package/templates/.agents/rules/evidence-reporting.zh-CN.md +28 -0
  198. package/templates/.agents/rules/label-milestone-setup.en.md +7 -7
  199. package/templates/.agents/rules/label-milestone-setup.zh-CN.md +7 -7
  200. package/templates/.agents/rules/local-artifact-repair.en.md +5 -1
  201. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +5 -1
  202. package/templates/.agents/rules/security-alerts.en.md +8 -8
  203. package/templates/.agents/rules/security-alerts.zh-CN.md +8 -8
  204. package/templates/.agents/skills/analyze-task/SKILL.en.md +15 -2
  205. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +19 -2
  206. package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -12
  207. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -12
  208. package/templates/.agents/skills/close-codescan/SKILL.en.md +2 -2
  209. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +2 -2
  210. package/templates/.agents/skills/close-dependabot/SKILL.en.md +2 -2
  211. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +2 -2
  212. package/templates/.agents/skills/code-task/SKILL.en.md +14 -1
  213. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +14 -1
  214. package/templates/.agents/skills/code-task/config/verify.en.json +5 -14
  215. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +5 -14
  216. package/templates/.agents/skills/code-task/reference/code-rules.en.md +5 -4
  217. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +5 -4
  218. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +1 -1
  219. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +1 -1
  220. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +3 -1
  221. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +3 -1
  222. package/templates/.agents/skills/code-task/reference/report-template.en.md +42 -15
  223. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +39 -12
  224. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +5 -1
  225. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +5 -1
  226. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
  227. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
  228. package/templates/.agents/skills/complete-task/SKILL.en.md +9 -22
  229. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +9 -22
  230. package/templates/.agents/skills/create-task/SKILL.en.md +8 -0
  231. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +7 -0
  232. package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
  233. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
  234. package/templates/.agents/skills/import-dependabot/SKILL.en.md +1 -1
  235. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
  236. package/templates/.agents/skills/init-labels/SKILL.en.md +8 -8
  237. package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +8 -8
  238. package/templates/.agents/skills/init-milestones/SKILL.en.md +9 -11
  239. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +9 -11
  240. package/templates/.agents/skills/plan-task/SKILL.en.md +14 -1
  241. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +16 -1
  242. package/templates/.agents/skills/plan-task/config/verify.en.json +4 -14
  243. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +4 -14
  244. package/templates/.agents/skills/review-analysis/SKILL.en.md +14 -1
  245. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +14 -1
  246. package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -18
  247. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -18
  248. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +50 -23
  249. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +45 -18
  250. package/templates/.agents/skills/review-code/SKILL.en.md +14 -1
  251. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +14 -1
  252. package/templates/.agents/skills/review-code/config/verify.en.json +1 -23
  253. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -23
  254. package/templates/.agents/skills/review-code/reference/report-template.en.md +49 -22
  255. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +45 -18
  256. package/templates/.agents/skills/review-plan/SKILL.en.md +14 -1
  257. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +14 -1
  258. package/templates/.agents/skills/review-plan/config/verify.en.json +1 -18
  259. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -18
  260. package/templates/.agents/skills/review-plan/reference/report-template.en.md +49 -22
  261. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +45 -18
  262. package/templates/.agents/skills/review-pr/SKILL.en.md +5 -1
  263. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +5 -1
  264. package/templates/.agents/skills/review-pr/reference/report-template.en.md +4 -3
  265. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +4 -3
  266. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +5 -1
  267. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +5 -1
  268. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +3 -1
  269. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +3 -1
  270. package/templates/.agents/skills/test/SKILL.en.md +13 -2
  271. package/templates/.agents/skills/test/SKILL.zh-CN.md +13 -2
  272. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +14 -0
  273. package/templates/.agents/templates/task.en.md +6 -0
  274. package/templates/.agents/templates/task.zh-CN.md +6 -0
  275. package/templates/.agents/scripts/security-alerts.github.sh +0 -151
  276. package/templates/.agents/scripts/security-alerts.sh +0 -31
  277. package/templates/.agents/skills/init-labels/scripts/init-labels.github.sh +0 -146
  278. package/templates/.agents/skills/init-labels/scripts/init-labels.sh +0 -32
  279. package/templates/.agents/skills/init-milestones/scripts/init-milestones.github.sh +0 -294
  280. package/templates/.agents/skills/init-milestones/scripts/init-milestones.sh +0 -6
@@ -0,0 +1,453 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ import type { PlatformClient, LoadedContext } from './context.ts';
6
+ import { resolvePlatformProviderContext } from './context.ts';
7
+ import {
8
+ providerError,
9
+ providerOperationContext,
10
+ providerStatus
11
+ } from './provider-bridge.ts';
12
+ import type {
13
+ LabelDefinition,
14
+ LabelReconciliation,
15
+ MilestoneDefinition,
16
+ MilestoneInitialization,
17
+ PlatformError,
18
+ ProviderResult
19
+ } from './provider-contract.ts';
20
+ import { platformResult } from './types.ts';
21
+ import type { PlatformResult } from './types.ts';
22
+
23
+ type RepositoryMetadataOptions = {
24
+ cwd?: string;
25
+ client?: PlatformClient;
26
+ platformType?: string;
27
+ };
28
+
29
+ type MilestoneBaseline = {
30
+ version: string;
31
+ source: string;
32
+ };
33
+
34
+ type MilestonePlan = {
35
+ baseline: MilestoneBaseline;
36
+ history: boolean;
37
+ desired: MilestoneDefinition[];
38
+ };
39
+
40
+ type LabelsResult = PlatformResult & {
41
+ operation: 'init-labels';
42
+ labels: {
43
+ commonCount: number;
44
+ inCount: number;
45
+ created: string[];
46
+ updated: string[];
47
+ removed: string[];
48
+ skipped: string[];
49
+ };
50
+ };
51
+
52
+ type MilestonesResult = PlatformResult & {
53
+ operation: 'init-milestones';
54
+ milestones: {
55
+ baseline: MilestoneBaseline | null;
56
+ history: boolean;
57
+ desired: string[];
58
+ created: string[];
59
+ skipped: string[];
60
+ };
61
+ };
62
+
63
+ const COMMON_LABELS: readonly LabelDefinition[] = [
64
+ { name: 'type: bug', color: 'DED6F9', description: 'A general bug' },
65
+ { name: 'type: enhancement', color: 'DED6F9', description: 'A general enhancement' },
66
+ { name: 'type: feature', color: 'DED6F9', description: 'A general feature' },
67
+ { name: 'type: documentation', color: 'DED6F9', description: 'A documentation task' },
68
+ { name: 'type: dependency-upgrade', color: 'DED6F9', description: 'A dependency upgrade' },
69
+ { name: 'type: task', color: 'DED6F9', description: 'A general task' },
70
+ { name: 'status: waiting-for-triage', color: 'FCF1C4', description: "An issue we've not yet triaged or decided on" },
71
+ { name: 'status: waiting-for-feedback', color: 'FCF1C4', description: 'We need additional information before we can continue' },
72
+ { name: 'status: feedback-provided', color: 'FCF1C4', description: 'Feedback has been provided' },
73
+ { name: 'status: feedback-reminder', color: 'FCF1C4', description: "We've sent a reminder that we need additional information before we can continue" },
74
+ { name: 'status: pending-design-work', color: 'FCF1C4', description: 'Needs design work before any code can be developed' },
75
+ { name: 'status: in-progress', color: 'FCF1C4', description: 'Work is actively being developed' },
76
+ { name: 'status: on-hold', color: 'FCF1C4', description: "We can't start working on this issue yet" },
77
+ { name: 'status: blocked', color: 'FCF1C4', description: "An issue that's blocked on an external project change" },
78
+ { name: 'status: declined', color: 'FCF1C4', description: "A suggestion or change that we don't feel we should currently apply" },
79
+ { name: 'status: duplicate', color: 'FCF1C4', description: 'A duplicate of another issue' },
80
+ { name: 'status: invalid', color: 'FCF1C4', description: "An issue that we don't feel is valid" },
81
+ { name: 'status: superseded', color: 'FCF1C4', description: 'An issue that has been superseded by another' },
82
+ { name: 'status: bulk-closed', color: 'FCF1C4', description: "An outdated, unresolved issue that's closed in bulk as part of a cleaning process" },
83
+ { name: 'status: ideal-for-contribution', color: 'FCF1C4', description: 'An issue that a contributor can help us with' },
84
+ { name: 'status: backported', color: 'FCF1C4', description: 'An issue that has been backported to maintenance branches' },
85
+ { name: 'status: waiting-for-internal-feedback', color: 'FCF1C4', description: 'An issue that needs input from a member or another team' },
86
+ { name: 'good first issue', color: 'F9D9E6', description: 'Good for newcomers' },
87
+ { name: 'help wanted', color: '008672', description: 'Extra attention is needed' },
88
+ { name: 'dependencies', color: '0366D6', description: 'Pull requests that update a dependency file' }
89
+ ];
90
+
91
+ type ParsedVersion = {
92
+ tag: string;
93
+ major: string;
94
+ minor: string;
95
+ patch: string;
96
+ prerelease: string;
97
+ };
98
+
99
+ function validIdentifiers(value: string, prerelease: boolean): boolean {
100
+ if (!value) return false;
101
+ return value.split('.').every((identifier) =>
102
+ /^[0-9A-Za-z-]+$/.test(identifier)
103
+ && (!prerelease || !/^\d+$/.test(identifier) || /^(0|[1-9]\d*)$/.test(identifier))
104
+ );
105
+ }
106
+
107
+ function parseVersionTag(tag: string): ParsedVersion | null {
108
+ if (!tag.startsWith('v')) return null;
109
+ let version = tag.slice(1);
110
+ const plusAt = version.indexOf('+');
111
+ if (plusAt >= 0) {
112
+ const build = version.slice(plusAt + 1);
113
+ if (!validIdentifiers(build, false)) return null;
114
+ version = version.slice(0, plusAt);
115
+ }
116
+
117
+ let prerelease = '';
118
+ const dashAt = version.indexOf('-');
119
+ if (dashAt >= 0) {
120
+ prerelease = version.slice(dashAt + 1);
121
+ if (!validIdentifiers(prerelease, true)) return null;
122
+ version = version.slice(0, dashAt);
123
+ }
124
+
125
+ const core = version.split('.');
126
+ if (core.length !== 3 || !core.every((part) => /^(0|[1-9]\d*)$/.test(part))) return null;
127
+ return { tag, major: core[0]!, minor: core[1]!, patch: core[2]!, prerelease };
128
+ }
129
+
130
+ function compareDecimal(left: string, right: string): number {
131
+ if (left.length !== right.length) return left.length > right.length ? 1 : -1;
132
+ if (left === right) return 0;
133
+ return left > right ? 1 : -1;
134
+ }
135
+
136
+ function compareAscii(left: string, right: string): number {
137
+ const alphabet = '+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
138
+ const limit = Math.min(left.length, right.length);
139
+ for (let index = 0; index < limit; index += 1) {
140
+ const leftRank = alphabet.indexOf(left[index]!);
141
+ const rightRank = alphabet.indexOf(right[index]!);
142
+ if (leftRank !== rightRank) return leftRank > rightRank ? 1 : -1;
143
+ }
144
+ if (left.length === right.length) return 0;
145
+ return left.length > right.length ? 1 : -1;
146
+ }
147
+
148
+ function comparePrerelease(left: string, right: string): number {
149
+ if (!left || !right) {
150
+ if (left === right) return 0;
151
+ return left ? -1 : 1;
152
+ }
153
+ const leftIds = left.split('.');
154
+ const rightIds = right.split('.');
155
+ const limit = Math.min(leftIds.length, rightIds.length);
156
+ for (let index = 0; index < limit; index += 1) {
157
+ const leftId = leftIds[index]!;
158
+ const rightId = rightIds[index]!;
159
+ const leftNumeric = /^\d+$/.test(leftId);
160
+ const rightNumeric = /^\d+$/.test(rightId);
161
+ const comparison = leftNumeric && rightNumeric
162
+ ? compareDecimal(leftId, rightId)
163
+ : leftNumeric !== rightNumeric
164
+ ? leftNumeric ? -1 : 1
165
+ : compareAscii(leftId, rightId);
166
+ if (comparison !== 0) return comparison;
167
+ }
168
+ if (leftIds.length === rightIds.length) return 0;
169
+ return leftIds.length > rightIds.length ? 1 : -1;
170
+ }
171
+
172
+ function compareVersions(left: ParsedVersion, right: ParsedVersion): number {
173
+ for (const [leftPart, rightPart] of [
174
+ [left.major, right.major],
175
+ [left.minor, right.minor],
176
+ [left.patch, right.patch]
177
+ ] as const) {
178
+ const comparison = compareDecimal(leftPart, rightPart);
179
+ if (comparison !== 0) return comparison;
180
+ }
181
+ const prerelease = comparePrerelease(left.prerelease, right.prerelease);
182
+ return prerelease !== 0 ? prerelease : compareAscii(left.tag, right.tag);
183
+ }
184
+
185
+ function incrementDecimal(value: string): string {
186
+ const digits = value.split('');
187
+ let carry = 1;
188
+ for (let index = digits.length - 1; index >= 0 && carry; index -= 1) {
189
+ const next = Number(digits[index]!) + carry;
190
+ digits[index] = String(next % 10);
191
+ carry = next === 10 ? 1 : 0;
192
+ }
193
+ return carry ? `1${digits.join('')}` : digits.join('');
194
+ }
195
+
196
+ function buildMilestonePlan(tags: readonly string[], history: boolean): MilestonePlan {
197
+ const parsed = tags.flatMap((tag) => {
198
+ const version = parseVersionTag(tag);
199
+ return version ? [version] : [];
200
+ });
201
+ const selected = parsed.reduce<ParsedVersion | null>((best, candidate) =>
202
+ !best || compareVersions(candidate, best) > 0 ? candidate : best, null);
203
+ const baseline: MilestoneBaseline = selected
204
+ ? { version: `${selected.major}.${selected.minor}.${selected.patch}`, source: `git tag ${selected.tag}` }
205
+ : { version: '0.1.0', source: 'compatibility default' };
206
+ const [major, minor, patch] = baseline.version.split('.');
207
+ const desired: MilestoneDefinition[] = [];
208
+ const titles = new Set<string>();
209
+ const add = (title: string, description: string, state: 'open' | 'closed') => {
210
+ if (titles.has(title)) return;
211
+ titles.add(title);
212
+ desired.push({ title, description, state });
213
+ };
214
+
215
+ const line = `${major}.${minor}.x`;
216
+ const version = selected
217
+ ? `${major}.${minor}.${incrementDecimal(patch!)}`
218
+ : baseline.version;
219
+ add('General Backlog', 'All unsorted backlogged tasks may be completed in a future version.', 'open');
220
+ add(line, `Issues that we want to resolve in ${major}.${minor} line.`, 'open');
221
+ add(version, `Issues that we want to release in v${version}.`, 'open');
222
+ if (patch === '0') {
223
+ const nextMinor = `${major}.${incrementDecimal(minor!)}.0`;
224
+ const nextLine = `${major}.${incrementDecimal(minor!)}.x`;
225
+ add(nextMinor, `Issues that we want to release in v${nextMinor}.`, 'open');
226
+ add(nextLine, `Issues that we want to resolve in ${major}.${incrementDecimal(minor!)} line.`, 'open');
227
+ }
228
+
229
+ if (history) {
230
+ for (const historical of parsed) {
231
+ const historicalVersion = `${historical.major}.${historical.minor}.${historical.patch}`;
232
+ add(`${historical.major}.${historical.minor}.x`, `Issues that we want to resolve in ${historical.major}.${historical.minor} line.`, 'open');
233
+ add(historicalVersion, `Issues that we want to release in v${historicalVersion}.`, 'closed');
234
+ }
235
+ }
236
+ return { baseline, history, desired };
237
+ }
238
+
239
+ function buildLabelDefinitions(config: unknown): LabelDefinition[] {
240
+ const labels = config && typeof config === 'object' && !Array.isArray(config)
241
+ ? (config as Record<string, unknown>).labels
242
+ : undefined;
243
+ const mapping = labels && typeof labels === 'object' && !Array.isArray(labels)
244
+ ? (labels as Record<string, unknown>).in
245
+ : undefined;
246
+ if (mapping === undefined) return [...COMMON_LABELS];
247
+ if (!mapping || typeof mapping !== 'object' || Array.isArray(mapping)) throw new Error('labels.in must be an object');
248
+ const dynamic = Object.keys(mapping).sort().map((key) => {
249
+ if (!key || /[\r\n\t]/.test(key)) throw new Error('labels.in keys must be non-empty single-line values');
250
+ return { name: `in: ${key}`, color: 'BFD4F2', description: `Module label for ${key}` };
251
+ });
252
+ return [...COMMON_LABELS, ...dynamic];
253
+ }
254
+
255
+ function configAt(root: string): unknown {
256
+ try {
257
+ return JSON.parse(fs.readFileSync(path.join(root, '.agents', '.airc.json'), 'utf8')) as unknown;
258
+ } catch {
259
+ return {};
260
+ }
261
+ }
262
+
263
+ function baseResult<T extends PlatformResult>(
264
+ status: PlatformResult['status'],
265
+ context: PlatformResult,
266
+ extra: Partial<T> = {}
267
+ ): T {
268
+ return {
269
+ ...platformResult(status, {
270
+ platform: context.platform,
271
+ capabilities: context.capabilities,
272
+ operations: context.operations,
273
+ error: context.error
274
+ }),
275
+ ...extra
276
+ } as T;
277
+ }
278
+
279
+ function unsupported(providerType: string, operation: string): PlatformError {
280
+ return {
281
+ code: 'PLATFORM_CAPABILITY_UNSUPPORTED',
282
+ message: `Platform '${providerType}' does not provide ${operation}`,
283
+ retryable: false
284
+ };
285
+ }
286
+
287
+ function operationStatus(changed: boolean): 'applied' | 'no-op' {
288
+ return changed ? 'applied' : 'no-op';
289
+ }
290
+
291
+ function errorStatus(error: PlatformError): PlatformResult['status'] {
292
+ return error.code === 'PLATFORM_CAPABILITY_UNSUPPORTED' ? 'degraded' : providerStatus(error);
293
+ }
294
+
295
+ function initialLabelsResult(context: PlatformResult, labels: LabelDefinition[]): LabelsResult {
296
+ return baseResult<LabelsResult>('degraded', context, {
297
+ operation: 'init-labels',
298
+ labels: {
299
+ commonCount: COMMON_LABELS.length,
300
+ inCount: labels.filter((item) => item.name.startsWith('in:')).length,
301
+ created: [], updated: [], removed: [], skipped: []
302
+ },
303
+ error: context.error || { code: 'PLATFORM_CAPABILITY_UNSUPPORTED', message: 'Label initialization is unavailable', retryable: false }
304
+ });
305
+ }
306
+
307
+ function initialMilestonesResult(context: PlatformResult, history: boolean): MilestonesResult {
308
+ return baseResult<MilestonesResult>('degraded', context, {
309
+ operation: 'init-milestones',
310
+ milestones: { baseline: null, history, desired: [], created: [], skipped: [] },
311
+ error: context.error || { code: 'PLATFORM_CAPABILITY_UNSUPPORTED', message: 'Milestone initialization is unavailable', retryable: false }
312
+ });
313
+ }
314
+
315
+ function labelsFromReconciliation(initial: LabelsResult, value: LabelReconciliation | undefined): LabelsResult['labels'] {
316
+ if (!value) return initial.labels;
317
+ return {
318
+ ...initial.labels,
319
+ created: value.created,
320
+ updated: value.updated,
321
+ removed: value.removed,
322
+ skipped: value.skipped
323
+ };
324
+ }
325
+
326
+ function milestonesFromInitialization(initial: MilestonesResult, value: MilestoneInitialization | undefined): MilestonesResult['milestones'] {
327
+ if (!value) return initial.milestones;
328
+ return {
329
+ ...initial.milestones,
330
+ created: value.created,
331
+ skipped: value.skipped
332
+ };
333
+ }
334
+
335
+ function usableContext(loaded: { ok: true; value: LoadedContext } | { ok: false; context: PlatformResult }): loaded is { ok: true; value: LoadedContext } {
336
+ return loaded.ok && Boolean(loaded.value.context.platform.repository);
337
+ }
338
+
339
+ async function initializeLabels(
340
+ input: { cleanupStaleIn?: boolean } = {},
341
+ options: RepositoryMetadataOptions = {}
342
+ ): Promise<LabelsResult> {
343
+ const loaded = await resolvePlatformProviderContext({ cwd: options.cwd, client: options.client, platformType: options.platformType });
344
+ const context = loaded.ok ? loaded.value.context : loaded.context;
345
+ let definitions: LabelDefinition[];
346
+ try {
347
+ definitions = buildLabelDefinitions(loaded.ok ? configAt(loaded.value.repositoryRoot) : {});
348
+ } catch (error) {
349
+ return {
350
+ ...initialLabelsResult(context, []),
351
+ status: 'failed',
352
+ error: { code: 'LABEL_CONFIG_INVALID', message: error instanceof Error ? error.message : String(error), retryable: false }
353
+ };
354
+ }
355
+ const initial = initialLabelsResult(context, definitions);
356
+ if (!usableContext(loaded)) return { ...initial, error: initial.error || context.error };
357
+ const operation = loaded.value.provider.repositoryMetadata?.reconcileLabels;
358
+ if (!operation) return { ...initial, error: unsupported(loaded.value.provider.type, 'repositoryMetadata.reconcileLabels') };
359
+ const reconciled: ProviderResult<LabelReconciliation> = await operation({
360
+ context: providerOperationContext(loaded.value),
361
+ desired: definitions,
362
+ cleanupStaleIn: input.cleanupStaleIn === true,
363
+ mutation: { idempotencyKey: 'labels:init' }
364
+ });
365
+ if (!reconciled.ok) return {
366
+ ...initial,
367
+ status: errorStatus(reconciled.error),
368
+ changed: reconciled.value?.changed || false,
369
+ operations: [{ name: 'reconcile-labels', status: 'failed', reasonCode: reconciled.error.code }],
370
+ labels: labelsFromReconciliation(initial, reconciled.value),
371
+ error: providerError(reconciled.error, 'LABEL_INITIALIZATION_FAILED')
372
+ };
373
+ return {
374
+ ...initial,
375
+ status: operationStatus(reconciled.value.changed),
376
+ changed: reconciled.value.changed,
377
+ operations: [{ name: 'reconcile-labels', status: operationStatus(reconciled.value.changed), reasonCode: null }],
378
+ labels: {
379
+ commonCount: COMMON_LABELS.length,
380
+ inCount: definitions.length - COMMON_LABELS.length,
381
+ created: reconciled.value.created,
382
+ updated: reconciled.value.updated,
383
+ removed: reconciled.value.removed,
384
+ skipped: reconciled.value.skipped
385
+ },
386
+ error: null
387
+ };
388
+ }
389
+
390
+ function gitTags(cwd: string): string[] {
391
+ const output = execFileSync('git', ['tag', '--list', 'v*'], { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
392
+ return output ? output.split(/\r?\n/).filter(Boolean) : [];
393
+ }
394
+
395
+ async function initializeMilestones(
396
+ input: { history?: boolean } = {},
397
+ options: RepositoryMetadataOptions = {}
398
+ ): Promise<MilestonesResult> {
399
+ const history = input.history === true;
400
+ const loaded = await resolvePlatformProviderContext({ cwd: options.cwd, client: options.client, platformType: options.platformType });
401
+ const context = loaded.ok ? loaded.value.context : loaded.context;
402
+ const initial = initialMilestonesResult(context, history);
403
+ if (!usableContext(loaded)) return initial;
404
+ let plan: MilestonePlan;
405
+ try {
406
+ plan = buildMilestonePlan(gitTags(loaded.value.workingDirectory), history);
407
+ } catch (error) {
408
+ return {
409
+ ...initial,
410
+ status: 'failed',
411
+ error: { code: 'MILESTONE_TAG_DISCOVERY_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false }
412
+ };
413
+ }
414
+ const operation = loaded.value.provider.repositoryMetadata?.reconcileMilestones;
415
+ if (!operation) return {
416
+ ...initial,
417
+ milestones: { ...initial.milestones, baseline: plan.baseline, history, desired: plan.desired.map((item) => item.title) },
418
+ error: unsupported(loaded.value.provider.type, 'repositoryMetadata.reconcileMilestones')
419
+ };
420
+ const reconciled = await operation({
421
+ context: providerOperationContext(loaded.value),
422
+ desired: plan.desired,
423
+ mutation: { idempotencyKey: `milestones:init:${plan.baseline.version}:${history}` }
424
+ });
425
+ if (!reconciled.ok) return {
426
+ ...initial,
427
+ status: errorStatus(reconciled.error),
428
+ changed: reconciled.value?.changed || false,
429
+ operations: [{ name: 'reconcile-milestones', status: 'failed', reasonCode: reconciled.error.code }],
430
+ milestones: milestonesFromInitialization({
431
+ ...initial,
432
+ milestones: { ...initial.milestones, baseline: plan.baseline, history, desired: plan.desired.map((item) => item.title) }
433
+ }, reconciled.value),
434
+ error: providerError(reconciled.error, 'MILESTONE_INITIALIZATION_FAILED')
435
+ };
436
+ return {
437
+ ...initial,
438
+ status: operationStatus(reconciled.value.changed),
439
+ changed: reconciled.value.changed,
440
+ operations: [{ name: 'reconcile-milestones', status: operationStatus(reconciled.value.changed), reasonCode: null }],
441
+ milestones: {
442
+ baseline: plan.baseline,
443
+ history,
444
+ desired: plan.desired.map((item) => item.title),
445
+ created: reconciled.value.created,
446
+ skipped: reconciled.value.skipped
447
+ },
448
+ error: null
449
+ };
450
+ }
451
+
452
+ export { buildLabelDefinitions, buildMilestonePlan, initializeLabels, initializeMilestones };
453
+ export type { LabelsResult, MilestoneBaseline, MilestonePlan, MilestonesResult, RepositoryMetadataOptions };
@@ -0,0 +1,191 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ import type { PlatformClient } from './context.ts';
5
+ import { resolvePlatformProviderContext } from './context.ts';
6
+ import {
7
+ providerError,
8
+ providerOperationContext,
9
+ providerStatus
10
+ } from './provider-bridge.ts';
11
+ import type {
12
+ PlatformError,
13
+ SecurityAlertKind,
14
+ SecurityAlertSnapshot
15
+ } from './provider-contract.ts';
16
+ import { platformResult } from './types.ts';
17
+ import type { PlatformResult } from './types.ts';
18
+
19
+ type SecurityAlertOptions = {
20
+ cwd?: string;
21
+ client?: PlatformClient;
22
+ platformType?: string;
23
+ };
24
+
25
+ type SecurityAlertResult = PlatformResult & {
26
+ operation: 'read' | 'dismiss';
27
+ alert: SecurityAlertSnapshot | null;
28
+ data: SecurityAlertSnapshot['data'] | null;
29
+ };
30
+
31
+ function baseResult(
32
+ operation: SecurityAlertResult['operation'],
33
+ status: PlatformResult['status'],
34
+ context: PlatformResult,
35
+ overrides: Partial<SecurityAlertResult> = {}
36
+ ): SecurityAlertResult {
37
+ return {
38
+ ...platformResult(status, {
39
+ platform: context.platform,
40
+ capabilities: context.capabilities,
41
+ operations: context.operations,
42
+ error: context.error,
43
+ changed: false
44
+ }),
45
+ operation,
46
+ alert: null,
47
+ data: null,
48
+ ...overrides
49
+ };
50
+ }
51
+
52
+ function unsupported(providerType: string): PlatformError {
53
+ return {
54
+ code: 'PLATFORM_CAPABILITY_UNSUPPORTED',
55
+ message: `Platform '${providerType}' does not provide securityAlerts`,
56
+ retryable: false
57
+ };
58
+ }
59
+
60
+ function resultStatus(error: PlatformError): PlatformResult['status'] {
61
+ return error.code === 'PLATFORM_CAPABILITY_UNSUPPORTED' ? 'degraded' : providerStatus(error);
62
+ }
63
+
64
+ function validKind(value: string | undefined): value is SecurityAlertKind {
65
+ return value === 'dependabot' || value === 'code-scanning';
66
+ }
67
+
68
+ function validNumber(value: number): boolean {
69
+ return Number.isSafeInteger(value) && value > 0;
70
+ }
71
+
72
+ function unavailable(
73
+ operation: SecurityAlertResult['operation'],
74
+ context: PlatformResult,
75
+ error?: PlatformError
76
+ ): SecurityAlertResult {
77
+ const status = context.status === 'blocked' || context.status === 'failed' ? context.status : 'degraded';
78
+ return baseResult(operation, status, context, {
79
+ error: error || context.error || { code: 'PLATFORM_CAPABILITY_UNSUPPORTED', message: 'Security alert operations are unavailable', retryable: false }
80
+ });
81
+ }
82
+
83
+ async function readSecurityAlert(
84
+ input: { kind: SecurityAlertKind; number: number },
85
+ options: SecurityAlertOptions = {}
86
+ ): Promise<SecurityAlertResult> {
87
+ if (!validKind(input.kind)) return baseResult('read', 'failed', platformResult('failed'), {
88
+ error: { code: 'SECURITY_KIND_INVALID', message: 'Alert kind must be dependabot or code-scanning', retryable: false }
89
+ });
90
+ if (!validNumber(input.number)) return baseResult('read', 'failed', platformResult('failed'), {
91
+ error: { code: 'SECURITY_NUMBER_INVALID', message: 'Alert number must be a positive integer', retryable: false }
92
+ });
93
+
94
+ const loaded = await resolvePlatformProviderContext({ cwd: options.cwd, client: options.client, platformType: options.platformType });
95
+ const context = loaded.ok ? loaded.value.context : loaded.context;
96
+ if (!loaded.ok || !context.platform.repository) return unavailable('read', context);
97
+ const inspect = loaded.value.provider.securityAlerts?.inspect;
98
+ if (!inspect) return unavailable('read', context, unsupported(loaded.value.provider.type));
99
+ const result = await inspect({ context: providerOperationContext(loaded.value), kind: input.kind, number: input.number });
100
+ if (!result.ok) return {
101
+ ...baseResult('read', resultStatus(result.error), context),
102
+ error: providerError(result.error, 'SECURITY_ALERT_READ_FAILED')
103
+ };
104
+ return {
105
+ ...baseResult('read', 'applied', context, {
106
+ alert: result.value,
107
+ data: result.value.data,
108
+ operations: [{ name: 'inspect-alert', status: 'applied', reasonCode: null }]
109
+ }),
110
+ changed: false,
111
+ error: null
112
+ };
113
+ }
114
+
115
+ async function dismissSecurityAlert(
116
+ input: { kind: SecurityAlertKind; number: number; reason: string; comment: string },
117
+ options: SecurityAlertOptions = {}
118
+ ): Promise<SecurityAlertResult> {
119
+ if (!validKind(input.kind)) return baseResult('dismiss', 'failed', platformResult('failed'), {
120
+ error: { code: 'SECURITY_KIND_INVALID', message: 'Alert kind must be dependabot or code-scanning', retryable: false }
121
+ });
122
+ if (!validNumber(input.number)) return baseResult('dismiss', 'failed', platformResult('failed'), {
123
+ error: { code: 'SECURITY_NUMBER_INVALID', message: 'Alert number must be a positive integer', retryable: false }
124
+ });
125
+ if (!input.reason || typeof input.comment !== 'string') return baseResult('dismiss', 'failed', platformResult('failed'), {
126
+ error: { code: 'SECURITY_DISMISS_INPUT_INVALID', message: 'Dismissal reason and comment are required', retryable: false }
127
+ });
128
+
129
+ const loaded = await resolvePlatformProviderContext({ cwd: options.cwd, client: options.client, platformType: options.platformType });
130
+ const context = loaded.ok ? loaded.value.context : loaded.context;
131
+ if (!loaded.ok || !context.platform.repository) return unavailable('dismiss', context);
132
+ const inspect = loaded.value.provider.securityAlerts?.inspect;
133
+ const dismiss = loaded.value.provider.securityAlerts?.dismiss;
134
+ if (!inspect || !dismiss) return unavailable('dismiss', context, unsupported(loaded.value.provider.type));
135
+ const current = await inspect({ context: providerOperationContext(loaded.value), kind: input.kind, number: input.number });
136
+ if (!current.ok) return {
137
+ ...baseResult('dismiss', resultStatus(current.error), context),
138
+ error: providerError(current.error, 'SECURITY_ALERT_READ_FAILED')
139
+ };
140
+ if (current.value.state === 'dismissed' || current.value.state === 'fixed') return {
141
+ ...baseResult('dismiss', 'no-op', context, {
142
+ alert: current.value,
143
+ data: current.value.data,
144
+ operations: [{ name: 'inspect-alert', status: 'no-op', reasonCode: 'ALREADY_CLOSED' }]
145
+ }),
146
+ error: null
147
+ };
148
+ if (current.value.state !== 'open') return {
149
+ ...baseResult('dismiss', 'failed', context, {
150
+ alert: current.value,
151
+ data: current.value.data,
152
+ operations: [{ name: 'inspect-alert', status: 'failed', reasonCode: 'INVALID_STATE' }]
153
+ }),
154
+ error: { code: 'SECURITY_RESPONSE_INVALID', message: 'The platform returned an alert without a valid state', retryable: false }
155
+ };
156
+ const changed = await dismiss({
157
+ context: providerOperationContext(loaded.value),
158
+ kind: input.kind,
159
+ number: input.number,
160
+ reason: input.reason,
161
+ comment: input.comment,
162
+ mutation: { idempotencyKey: `security-alert:dismiss:${input.kind}:${input.number}` }
163
+ });
164
+ if (!changed.ok) return {
165
+ ...baseResult('dismiss', resultStatus(changed.error), context, {
166
+ alert: current.value,
167
+ data: current.value.data,
168
+ operations: [{ name: 'dismiss-alert', status: 'failed', reasonCode: null }]
169
+ }),
170
+ error: providerError(changed.error, 'SECURITY_ALERT_DISMISS_FAILED')
171
+ };
172
+ return {
173
+ ...baseResult('dismiss', changed.value.changed ? 'applied' : 'no-op', context, {
174
+ operations: [{ name: 'dismiss-alert', status: changed.value.changed ? 'applied' : 'no-op', reasonCode: null }]
175
+ }),
176
+ changed: changed.value.changed,
177
+ error: null
178
+ };
179
+ }
180
+
181
+ function readCommentFile(commentFile: string, cwd = process.cwd()): { ok: true; value: string } | { ok: false; error: PlatformError } {
182
+ try {
183
+ const requested = path.resolve(cwd, commentFile);
184
+ return { ok: true, value: fs.readFileSync(requested, 'utf8') };
185
+ } catch {
186
+ return { ok: false, error: { code: 'SECURITY_DISMISS_INPUT_INVALID', message: 'Dismissal comment file could not be read', retryable: false } };
187
+ }
188
+ }
189
+
190
+ export { dismissSecurityAlert, readCommentFile, readSecurityAlert };
191
+ export type { SecurityAlertOptions, SecurityAlertResult };