@oscharko-dev/keiko-server 0.2.8 → 0.2.9

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 (281) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/chat-handlers.d.ts +18 -2
  3. package/dist/chat-handlers.d.ts.map +1 -1
  4. package/dist/chat-handlers.js +185 -3
  5. package/dist/command-runner-errors.d.ts +17 -0
  6. package/dist/command-runner-errors.d.ts.map +1 -0
  7. package/dist/command-runner-errors.js +37 -0
  8. package/dist/command-runner-evidence.d.ts +23 -0
  9. package/dist/command-runner-evidence.d.ts.map +1 -0
  10. package/dist/command-runner-evidence.js +69 -0
  11. package/dist/command-runner-routes.d.ts +7 -0
  12. package/dist/command-runner-routes.d.ts.map +1 -0
  13. package/dist/command-runner-routes.js +175 -0
  14. package/dist/command-runner.d.ts +29 -0
  15. package/dist/command-runner.d.ts.map +1 -0
  16. package/dist/command-runner.js +348 -0
  17. package/dist/conversation-prompt.d.ts +2 -2
  18. package/dist/conversation-prompt.d.ts.map +1 -1
  19. package/dist/conversation-prompt.js +17 -1
  20. package/dist/csp.d.ts.map +1 -1
  21. package/dist/csp.js +3 -0
  22. package/dist/deps.d.ts +27 -1
  23. package/dist/deps.d.ts.map +1 -1
  24. package/dist/deps.js +288 -13
  25. package/dist/discussion-prompt.d.ts +4 -0
  26. package/dist/discussion-prompt.d.ts.map +1 -0
  27. package/dist/discussion-prompt.js +19 -0
  28. package/dist/editor/agentActionAudit.d.ts +18 -0
  29. package/dist/editor/agentActionAudit.d.ts.map +1 -0
  30. package/dist/editor/agentActionAudit.js +80 -0
  31. package/dist/editor/agentRoutes.d.ts +1 -0
  32. package/dist/editor/agentRoutes.d.ts.map +1 -1
  33. package/dist/editor/agentRoutes.js +292 -55
  34. package/dist/editor/agentSessionRegistry.d.ts +35 -0
  35. package/dist/editor/agentSessionRegistry.d.ts.map +1 -0
  36. package/dist/editor/agentSessionRegistry.js +243 -0
  37. package/dist/editor/completionRoutes.d.ts.map +1 -1
  38. package/dist/editor/completionRoutes.js +5 -10
  39. package/dist/editor/languageRoutes.d.ts +12 -1
  40. package/dist/editor/languageRoutes.d.ts.map +1 -1
  41. package/dist/editor/languageRoutes.js +71 -8
  42. package/dist/editor/languageService.d.ts +3 -2
  43. package/dist/editor/languageService.d.ts.map +1 -1
  44. package/dist/editor/languageService.js +41 -3
  45. package/dist/editor/languageServiceHost.d.ts.map +1 -1
  46. package/dist/editor/languageServiceHost.js +2 -2
  47. package/dist/editor/lsp/hostLanguageOperation.d.ts +17 -0
  48. package/dist/editor/lsp/hostLanguageOperation.d.ts.map +1 -0
  49. package/dist/editor/lsp/hostLanguageOperation.js +436 -0
  50. package/dist/editor/lsp/hostLanguageProviders.d.ts +26 -0
  51. package/dist/editor/lsp/hostLanguageProviders.d.ts.map +1 -0
  52. package/dist/editor/lsp/hostLanguageProviders.js +161 -0
  53. package/dist/editor/lsp/lspFrameCodec.d.ts +13 -0
  54. package/dist/editor/lsp/lspFrameCodec.d.ts.map +1 -0
  55. package/dist/editor/lsp/lspFrameCodec.js +164 -0
  56. package/dist/editor/lsp/lspJsonRpcClient.d.ts +34 -0
  57. package/dist/editor/lsp/lspJsonRpcClient.d.ts.map +1 -0
  58. package/dist/editor/lsp/lspJsonRpcClient.js +173 -0
  59. package/dist/editor/lsp/lspLanguageProvider.d.ts +7 -0
  60. package/dist/editor/lsp/lspLanguageProvider.d.ts.map +1 -0
  61. package/dist/editor/lsp/lspLanguageProvider.js +29 -0
  62. package/dist/editor/lsp/lspLifecycleLedger.d.ts +5 -0
  63. package/dist/editor/lsp/lspLifecycleLedger.d.ts.map +1 -0
  64. package/dist/editor/lsp/lspLifecycleLedger.js +37 -0
  65. package/dist/editor/lsp/lspNodeAdapter.d.ts +31 -0
  66. package/dist/editor/lsp/lspNodeAdapter.d.ts.map +1 -0
  67. package/dist/editor/lsp/lspNodeAdapter.js +230 -0
  68. package/dist/editor/lsp/lspProcessManager.d.ts +24 -0
  69. package/dist/editor/lsp/lspProcessManager.d.ts.map +1 -0
  70. package/dist/editor/lsp/lspProcessManager.js +255 -0
  71. package/dist/editor/lsp/lspRestartThrottle.d.ts +6 -0
  72. package/dist/editor/lsp/lspRestartThrottle.d.ts.map +1 -0
  73. package/dist/editor/lsp/lspRestartThrottle.js +24 -0
  74. package/dist/editor/lsp/lspStatusRoute.d.ts +8 -0
  75. package/dist/editor/lsp/lspStatusRoute.d.ts.map +1 -0
  76. package/dist/editor/lsp/lspStatusRoute.js +22 -0
  77. package/dist/editor/lsp/lspTransport.d.ts +19 -0
  78. package/dist/editor/lsp/lspTransport.d.ts.map +1 -0
  79. package/dist/editor/lsp/lspTransport.js +55 -0
  80. package/dist/editor/lsp/testing/fakeLspProcess.d.ts +23 -0
  81. package/dist/editor/lsp/testing/fakeLspProcess.d.ts.map +1 -0
  82. package/dist/editor/lsp/testing/fakeLspProcess.js +132 -0
  83. package/dist/files.d.ts +45 -0
  84. package/dist/files.d.ts.map +1 -1
  85. package/dist/files.js +631 -7
  86. package/dist/gateway-readiness.js +3 -3
  87. package/dist/gateway-setup.d.ts +2 -0
  88. package/dist/gateway-setup.d.ts.map +1 -1
  89. package/dist/gateway-setup.js +275 -11
  90. package/dist/gitDelivery/actionSheetProjection.d.ts +30 -0
  91. package/dist/gitDelivery/actionSheetProjection.d.ts.map +1 -0
  92. package/dist/gitDelivery/actionSheetProjection.js +206 -0
  93. package/dist/gitDelivery/actionSheetRoutes.d.ts +29 -0
  94. package/dist/gitDelivery/actionSheetRoutes.d.ts.map +1 -0
  95. package/dist/gitDelivery/actionSheetRoutes.js +293 -0
  96. package/dist/gitDelivery/agentOperationsRoutes.d.ts +33 -0
  97. package/dist/gitDelivery/agentOperationsRoutes.d.ts.map +1 -0
  98. package/dist/gitDelivery/agentOperationsRoutes.js +405 -0
  99. package/dist/gitDelivery/commitRoutes.d.ts +23 -0
  100. package/dist/gitDelivery/commitRoutes.d.ts.map +1 -0
  101. package/dist/gitDelivery/commitRoutes.js +204 -0
  102. package/dist/gitDelivery/evidenceRoutes.d.ts +9 -0
  103. package/dist/gitDelivery/evidenceRoutes.d.ts.map +1 -0
  104. package/dist/gitDelivery/evidenceRoutes.js +101 -0
  105. package/dist/gitDelivery/execution.d.ts +38 -0
  106. package/dist/gitDelivery/execution.d.ts.map +1 -0
  107. package/dist/gitDelivery/execution.js +117 -0
  108. package/dist/gitDelivery/localMutationRoutes.d.ts +30 -0
  109. package/dist/gitDelivery/localMutationRoutes.d.ts.map +1 -0
  110. package/dist/gitDelivery/localMutationRoutes.js +165 -0
  111. package/dist/gitDelivery/mergeExecution.d.ts +63 -0
  112. package/dist/gitDelivery/mergeExecution.d.ts.map +1 -0
  113. package/dist/gitDelivery/mergeExecution.js +168 -0
  114. package/dist/gitDelivery/mergeRoutes.d.ts +12 -0
  115. package/dist/gitDelivery/mergeRoutes.d.ts.map +1 -0
  116. package/dist/gitDelivery/mergeRoutes.js +218 -0
  117. package/dist/gitDelivery/mutationEvidenceLedger.d.ts +23 -0
  118. package/dist/gitDelivery/mutationEvidenceLedger.d.ts.map +1 -0
  119. package/dist/gitDelivery/mutationEvidenceLedger.js +87 -0
  120. package/dist/gitDelivery/prExecution.d.ts +54 -0
  121. package/dist/gitDelivery/prExecution.d.ts.map +1 -0
  122. package/dist/gitDelivery/prExecution.js +192 -0
  123. package/dist/gitDelivery/prRoutes.d.ts +12 -0
  124. package/dist/gitDelivery/prRoutes.d.ts.map +1 -0
  125. package/dist/gitDelivery/prRoutes.js +256 -0
  126. package/dist/gitDelivery/pushExecution.d.ts +43 -0
  127. package/dist/gitDelivery/pushExecution.d.ts.map +1 -0
  128. package/dist/gitDelivery/pushExecution.js +124 -0
  129. package/dist/gitDelivery/pushRoutes.d.ts +12 -0
  130. package/dist/gitDelivery/pushRoutes.d.ts.map +1 -0
  131. package/dist/gitDelivery/pushRoutes.js +200 -0
  132. package/dist/gitDelivery/requestGuards.d.ts +15 -0
  133. package/dist/gitDelivery/requestGuards.d.ts.map +1 -0
  134. package/dist/gitDelivery/requestGuards.js +97 -0
  135. package/dist/gitDelivery/syncEvidence.d.ts +37 -0
  136. package/dist/gitDelivery/syncEvidence.d.ts.map +1 -0
  137. package/dist/gitDelivery/syncEvidence.js +85 -0
  138. package/dist/gitDelivery/syncExecution.d.ts +30 -0
  139. package/dist/gitDelivery/syncExecution.d.ts.map +1 -0
  140. package/dist/gitDelivery/syncExecution.js +266 -0
  141. package/dist/gitDelivery/syncRoutes.d.ts +13 -0
  142. package/dist/gitDelivery/syncRoutes.d.ts.map +1 -0
  143. package/dist/gitDelivery/syncRoutes.js +200 -0
  144. package/dist/gitPorcelainStatus.d.ts +15 -0
  145. package/dist/gitPorcelainStatus.d.ts.map +1 -0
  146. package/dist/gitPorcelainStatus.js +104 -0
  147. package/dist/gitRepositoryReads.d.ts +10 -0
  148. package/dist/gitRepositoryReads.d.ts.map +1 -0
  149. package/dist/gitRepositoryReads.js +314 -0
  150. package/dist/gitRepositoryRoutes.d.ts +7 -0
  151. package/dist/gitRepositoryRoutes.d.ts.map +1 -0
  152. package/dist/gitRepositoryRoutes.js +221 -0
  153. package/dist/gitRoutes.d.ts +66 -0
  154. package/dist/gitRoutes.d.ts.map +1 -0
  155. package/dist/gitRoutes.js +543 -0
  156. package/dist/governed-workflow.d.ts +2 -0
  157. package/dist/governed-workflow.d.ts.map +1 -1
  158. package/dist/governed-workflow.js +4 -0
  159. package/dist/grounded-qa.d.ts +11 -0
  160. package/dist/grounded-qa.d.ts.map +1 -1
  161. package/dist/grounded-qa.js +13 -4
  162. package/dist/headers.d.ts +4 -1
  163. package/dist/headers.d.ts.map +1 -1
  164. package/dist/headers.js +11 -4
  165. package/dist/index.d.ts +8 -1
  166. package/dist/index.d.ts.map +1 -1
  167. package/dist/index.js +9 -1
  168. package/dist/qualityIntelligence/figmaSnapshotRoutes.d.ts +1 -1
  169. package/dist/qualityIntelligence/figmaSnapshotRoutes.d.ts.map +1 -1
  170. package/dist/qualityIntelligence/figmaSnapshotRoutes.js +1 -1
  171. package/dist/read-handlers.d.ts +5 -0
  172. package/dist/read-handlers.d.ts.map +1 -1
  173. package/dist/read-handlers.js +57 -1
  174. package/dist/routes.d.ts.map +1 -1
  175. package/dist/routes.js +259 -6
  176. package/dist/run-engine.d.ts.map +1 -1
  177. package/dist/run-engine.js +3 -0
  178. package/dist/run-handlers.d.ts.map +1 -1
  179. package/dist/run-handlers.js +74 -4
  180. package/dist/run-request.d.ts +11 -0
  181. package/dist/run-request.d.ts.map +1 -1
  182. package/dist/run-request.js +158 -10
  183. package/dist/runtime/capabilityDetector.d.ts +38 -0
  184. package/dist/runtime/capabilityDetector.d.ts.map +1 -0
  185. package/dist/runtime/capabilityDetector.js +443 -0
  186. package/dist/runtime/capabilityRoutes.d.ts +9 -0
  187. package/dist/runtime/capabilityRoutes.d.ts.map +1 -0
  188. package/dist/runtime/capabilityRoutes.js +45 -0
  189. package/dist/runtime/containerEngineDetector.d.ts +17 -0
  190. package/dist/runtime/containerEngineDetector.d.ts.map +1 -0
  191. package/dist/runtime/containerEngineDetector.js +222 -0
  192. package/dist/runtime/containerRoutes.d.ts +8 -0
  193. package/dist/runtime/containerRoutes.d.ts.map +1 -0
  194. package/dist/runtime/containerRoutes.js +207 -0
  195. package/dist/runtime/containerRunner-errors.d.ts +18 -0
  196. package/dist/runtime/containerRunner-errors.d.ts.map +1 -0
  197. package/dist/runtime/containerRunner-errors.js +42 -0
  198. package/dist/runtime/containerRunner-evidence.d.ts +24 -0
  199. package/dist/runtime/containerRunner-evidence.d.ts.map +1 -0
  200. package/dist/runtime/containerRunner-evidence.js +74 -0
  201. package/dist/runtime/containerRunner.d.ts +37 -0
  202. package/dist/runtime/containerRunner.d.ts.map +1 -0
  203. package/dist/runtime/containerRunner.js +443 -0
  204. package/dist/server.d.ts.map +1 -1
  205. package/dist/server.js +24 -4
  206. package/dist/store/schema.d.ts +1 -1
  207. package/dist/store/schema.d.ts.map +1 -1
  208. package/dist/store/schema.js +62 -1
  209. package/dist/task-workspace/active-store.d.ts +21 -0
  210. package/dist/task-workspace/active-store.d.ts.map +1 -0
  211. package/dist/task-workspace/active-store.js +55 -0
  212. package/dist/task-workspace/authorization.d.ts +7 -0
  213. package/dist/task-workspace/authorization.d.ts.map +1 -0
  214. package/dist/task-workspace/authorization.js +54 -0
  215. package/dist/task-workspace/binding.d.ts +3 -0
  216. package/dist/task-workspace/binding.d.ts.map +1 -0
  217. package/dist/task-workspace/binding.js +22 -0
  218. package/dist/task-workspace/cleanup.d.ts +4 -0
  219. package/dist/task-workspace/cleanup.d.ts.map +1 -0
  220. package/dist/task-workspace/cleanup.js +428 -0
  221. package/dist/task-workspace/errors.d.ts +14 -0
  222. package/dist/task-workspace/errors.d.ts.map +1 -0
  223. package/dist/task-workspace/errors.js +81 -0
  224. package/dist/task-workspace/evidence.d.ts +32 -0
  225. package/dist/task-workspace/evidence.d.ts.map +1 -0
  226. package/dist/task-workspace/evidence.js +52 -0
  227. package/dist/task-workspace/field-safety.d.ts +3 -0
  228. package/dist/task-workspace/field-safety.d.ts.map +1 -0
  229. package/dist/task-workspace/field-safety.js +42 -0
  230. package/dist/task-workspace/health.d.ts +4 -0
  231. package/dist/task-workspace/health.d.ts.map +1 -0
  232. package/dist/task-workspace/health.js +163 -0
  233. package/dist/task-workspace/lifecycle.d.ts +3 -0
  234. package/dist/task-workspace/lifecycle.d.ts.map +1 -0
  235. package/dist/task-workspace/lifecycle.js +248 -0
  236. package/dist/task-workspace/locks.d.ts +13 -0
  237. package/dist/task-workspace/locks.d.ts.map +1 -0
  238. package/dist/task-workspace/locks.js +44 -0
  239. package/dist/task-workspace/managed-root.d.ts +7 -0
  240. package/dist/task-workspace/managed-root.d.ts.map +1 -0
  241. package/dist/task-workspace/managed-root.js +98 -0
  242. package/dist/task-workspace/mutex.d.ts +8 -0
  243. package/dist/task-workspace/mutex.d.ts.map +1 -0
  244. package/dist/task-workspace/mutex.js +82 -0
  245. package/dist/task-workspace/naming.d.ts +15 -0
  246. package/dist/task-workspace/naming.d.ts.map +1 -0
  247. package/dist/task-workspace/naming.js +0 -0
  248. package/dist/task-workspace/provisioning.d.ts +3 -0
  249. package/dist/task-workspace/provisioning.d.ts.map +1 -0
  250. package/dist/task-workspace/provisioning.js +528 -0
  251. package/dist/task-workspace/reconciliation.d.ts +15 -0
  252. package/dist/task-workspace/reconciliation.d.ts.map +1 -0
  253. package/dist/task-workspace/reconciliation.js +274 -0
  254. package/dist/task-workspace/repair.d.ts +3 -0
  255. package/dist/task-workspace/repair.d.ts.map +1 -0
  256. package/dist/task-workspace/repair.js +286 -0
  257. package/dist/task-workspace/routes.d.ts +19 -0
  258. package/dist/task-workspace/routes.d.ts.map +1 -0
  259. package/dist/task-workspace/routes.js +481 -0
  260. package/dist/task-workspace/store.d.ts +12 -0
  261. package/dist/task-workspace/store.d.ts.map +1 -0
  262. package/dist/task-workspace/store.js +128 -0
  263. package/dist/task-workspace/types.d.ts +170 -0
  264. package/dist/task-workspace/types.d.ts.map +1 -0
  265. package/dist/task-workspace/types.js +5 -0
  266. package/dist/voice-action-governance.d.ts +23 -0
  267. package/dist/voice-action-governance.d.ts.map +1 -0
  268. package/dist/voice-action-governance.js +126 -0
  269. package/dist/voice-handlers.d.ts +6 -0
  270. package/dist/voice-handlers.d.ts.map +1 -0
  271. package/dist/voice-handlers.js +570 -0
  272. package/dist/voice-realtime-grounded-tool.d.ts +31 -0
  273. package/dist/voice-realtime-grounded-tool.d.ts.map +1 -0
  274. package/dist/voice-realtime-grounded-tool.js +322 -0
  275. package/dist/voice-realtime.d.ts +69 -0
  276. package/dist/voice-realtime.d.ts.map +1 -0
  277. package/dist/voice-realtime.js +787 -0
  278. package/dist/workspace-state-handlers.d.ts +5 -0
  279. package/dist/workspace-state-handlers.d.ts.map +1 -0
  280. package/dist/workspace-state-handlers.js +106 -0
  281. package/package.json +20 -19
@@ -0,0 +1,405 @@
1
+ // Typed repository-operation facade for agents (Issue #1577, Epic #1571).
2
+ //
3
+ // This is an admission and dispatch layer only. It accepts a closed semantic operation envelope,
4
+ // rejects shell/provider-shaped input before delegation, and forwards to existing Git read /
5
+ // git-delivery route handlers. It does not import or create any new Git, gh, terminal, or provider
6
+ // adapter authority.
7
+ import { createHash } from "node:crypto";
8
+ import { Readable } from "node:stream";
9
+ import { GIT_REPOSITORY_AGENT_SCHEMA_VERSION, parseGitRepositoryAgentOperationRequest, } from "@oscharko-dev/keiko-contracts";
10
+ import { STREAMING } from "../routes.js";
11
+ import { handleGitBranches, handleGitDiff, handleGitStatus } from "../gitRoutes.js";
12
+ import { createGitDeliveryCommitRouteGroup } from "./commitRoutes.js";
13
+ import { createGitDeliveryLocalMutationRouteGroup } from "./localMutationRoutes.js";
14
+ import { createGitDeliveryMergeRouteGroup } from "./mergeRoutes.js";
15
+ import { createGitDeliveryPrRouteGroup } from "./prRoutes.js";
16
+ import { createGitDeliveryPushRouteGroup } from "./pushRoutes.js";
17
+ import { createGitDeliverySyncRouteGroup } from "./syncRoutes.js";
18
+ import { GitDeliveryBodyTooLargeError, hasOnlyAllowedKeys, isPlainObject, readGitDeliveryBody, scanForbiddenStrings, scanUnsafeFormatChars, } from "./requestGuards.js";
19
+ const SAFE_MESSAGES = {
20
+ GIT_AGENT_OPERATION_BAD_REQUEST: "The request body is not a valid repository operation.",
21
+ GIT_AGENT_OPERATION_PAYLOAD_TOO_LARGE: "The repository operation request exceeds the maximum permitted size.",
22
+ GIT_AGENT_OPERATION_FORBIDDEN_PAYLOAD: "The request contained a forbidden credential, header, URL, or unsafe text shape.",
23
+ GIT_AGENT_OPERATION_UNSUPPORTED: "The repository operation is not supported.",
24
+ };
25
+ const errResult = (status, code) => ({
26
+ status,
27
+ body: { error: { code, message: SAFE_MESSAGES[code] } },
28
+ });
29
+ const routeGroups = [
30
+ ...createGitDeliveryLocalMutationRouteGroup(),
31
+ ...createGitDeliveryCommitRouteGroup(),
32
+ ...createGitDeliveryPushRouteGroup(),
33
+ ...createGitDeliveryPrRouteGroup(),
34
+ ...createGitDeliveryMergeRouteGroup(),
35
+ ...createGitDeliverySyncRouteGroup(),
36
+ ];
37
+ // Defaults for the process-memory idempotency cache. The cap bounds worst-case memory against a client
38
+ // that streams many distinct idempotency keys; the TTL lets settled replay entries self-evict so the
39
+ // map self-cleans even for keys that are never queried again.
40
+ export const DEFAULT_IDEMPOTENCY_MAX_ENTRIES = 1024;
41
+ export const DEFAULT_IDEMPOTENCY_TTL_MS = 10 * 60 * 1000;
42
+ // Bounded LRU + TTL store for the agent-facade idempotency replay window. Exposes the Map subset the
43
+ // handler relies on (get / set / delete) plus `size` for tests. Overflow eviction targets the
44
+ // least-recently-used *settled* entry only; in-flight reservations are exempt.
45
+ export class IdempotencyCache {
46
+ entries = new Map();
47
+ maxEntries;
48
+ ttlMs;
49
+ now;
50
+ constructor(options = {}) {
51
+ this.maxEntries = Math.max(1, Math.floor(options.maxEntries ?? DEFAULT_IDEMPOTENCY_MAX_ENTRIES));
52
+ this.ttlMs = Math.max(1, Math.floor(options.ttlMs ?? DEFAULT_IDEMPOTENCY_TTL_MS));
53
+ this.now = options.now ?? Date.now;
54
+ }
55
+ get size() {
56
+ return this.entries.size;
57
+ }
58
+ get(key) {
59
+ const stored = this.entries.get(key);
60
+ if (stored === undefined)
61
+ return undefined;
62
+ if (stored.entry.result !== undefined && this.now() >= stored.expiresAt) {
63
+ this.entries.delete(key);
64
+ return undefined;
65
+ }
66
+ // Refresh LRU recency on a hit by reinserting at the tail; the TTL window is unchanged.
67
+ this.entries.delete(key);
68
+ this.entries.set(key, stored);
69
+ return stored.entry;
70
+ }
71
+ set(key, entry) {
72
+ this.entries.delete(key);
73
+ this.pruneExpired();
74
+ this.entries.set(key, { entry, expiresAt: this.now() + this.ttlMs });
75
+ this.evictOverflow();
76
+ }
77
+ delete(key) {
78
+ this.entries.delete(key);
79
+ }
80
+ pruneExpired() {
81
+ const now = this.now();
82
+ for (const [key, stored] of this.entries) {
83
+ if (stored.entry.result !== undefined && now >= stored.expiresAt) {
84
+ this.entries.delete(key);
85
+ }
86
+ }
87
+ }
88
+ evictOverflow() {
89
+ while (this.entries.size > this.maxEntries) {
90
+ let victim;
91
+ for (const [key, stored] of this.entries) {
92
+ if (stored.entry.pending === undefined) {
93
+ victim = key;
94
+ break;
95
+ }
96
+ }
97
+ if (victim === undefined)
98
+ break; // every entry is an in-flight reservation — cannot safely evict
99
+ this.entries.delete(victim);
100
+ }
101
+ }
102
+ }
103
+ const idempotencyCache = new IdempotencyCache();
104
+ function denied(request, denialReason, message) {
105
+ return {
106
+ schemaVersion: GIT_REPOSITORY_AGENT_SCHEMA_VERSION,
107
+ ...(request.operation === undefined ? {} : { operation: request.operation }),
108
+ ...(request.mode === undefined ? {} : { mode: request.mode }),
109
+ status: "denied",
110
+ denialReason,
111
+ message,
112
+ };
113
+ }
114
+ async function readParsed(req) {
115
+ let raw;
116
+ try {
117
+ raw = await readGitDeliveryBody(req);
118
+ }
119
+ catch (error) {
120
+ if (error instanceof GitDeliveryBodyTooLargeError) {
121
+ return { ok: false, result: errResult(413, "GIT_AGENT_OPERATION_PAYLOAD_TOO_LARGE") };
122
+ }
123
+ return { ok: false, result: errResult(400, "GIT_AGENT_OPERATION_BAD_REQUEST") };
124
+ }
125
+ try {
126
+ return { ok: true, value: JSON.parse(raw) };
127
+ }
128
+ catch {
129
+ return { ok: false, result: errResult(400, "GIT_AGENT_OPERATION_BAD_REQUEST") };
130
+ }
131
+ }
132
+ function makeRequest(body, base) {
133
+ const req = Readable.from([Buffer.from(JSON.stringify(body), "utf8")]);
134
+ req.method = "POST";
135
+ req.headers = { ...base.headers, "content-type": "application/json" };
136
+ return req;
137
+ }
138
+ function postContext(ctx, pattern, body) {
139
+ return {
140
+ req: makeRequest(body, ctx.req),
141
+ res: ctx.res,
142
+ params: {},
143
+ url: new URL(`http://127.0.0.1${pattern}`),
144
+ };
145
+ }
146
+ function readContext(ctx, path) {
147
+ return {
148
+ req: Readable.from([]),
149
+ res: ctx.res,
150
+ params: {},
151
+ url: new URL(`http://127.0.0.1${path}`),
152
+ };
153
+ }
154
+ function queryFor(path, entries) {
155
+ const url = new URL(`http://127.0.0.1${path}`);
156
+ for (const [key, value] of entries) {
157
+ if (value !== undefined && value !== "")
158
+ url.searchParams.set(key, value);
159
+ }
160
+ return `${url.pathname}${url.search}`;
161
+ }
162
+ const READ_PAYLOAD_KEYS = {
163
+ status: new Set(),
164
+ diff: new Set(["path", "scope"]),
165
+ "branch-list": new Set(),
166
+ };
167
+ function payloadOrEmpty(request) {
168
+ return request.payload ?? {};
169
+ }
170
+ function validatePayloadKeys(request, allowed) {
171
+ const payload = payloadOrEmpty(request);
172
+ if (!hasOnlyAllowedKeys(payload, allowed)) {
173
+ return errResult(400, "GIT_AGENT_OPERATION_BAD_REQUEST");
174
+ }
175
+ return undefined;
176
+ }
177
+ async function delegateRead(request, ctx, deps) {
178
+ if (request.operation !== "status" &&
179
+ request.operation !== "diff" &&
180
+ request.operation !== "branch-list") {
181
+ return errResult(400, "GIT_AGENT_OPERATION_BAD_REQUEST");
182
+ }
183
+ const keyError = validatePayloadKeys(request, READ_PAYLOAD_KEYS[request.operation]);
184
+ if (keyError !== undefined)
185
+ return keyError;
186
+ const payload = payloadOrEmpty(request);
187
+ if (request.operation === "status") {
188
+ return handleGitStatus(readContext(ctx, queryFor("/api/git/status", [["root", request.projectId]])), deps, deps.gitRouteOptions);
189
+ }
190
+ if (request.operation === "branch-list") {
191
+ return handleGitBranches(readContext(ctx, queryFor("/api/git/branches", [["root", request.projectId]])), deps, deps.gitRouteOptions);
192
+ }
193
+ return handleGitDiff(readContext(ctx, queryFor("/api/git/diff", [
194
+ ["root", request.projectId],
195
+ ["path", typeof payload.path === "string" ? payload.path : undefined],
196
+ ["scope", typeof payload.scope === "string" ? payload.scope : undefined],
197
+ ])), deps, deps.gitRouteOptions);
198
+ }
199
+ const WRITE_KEYS = {
200
+ status: new Set(),
201
+ diff: new Set(),
202
+ "branch-list": new Set(),
203
+ "branch-create": new Set(["branchName", "baseBranchName", "startPointRefHash"]),
204
+ "branch-switch": new Set(["branchName"]),
205
+ stage: new Set(["pathspecs", "includeUntracked"]),
206
+ unstage: new Set(["pathspecs"]),
207
+ commit: new Set(["messageDraft", "message", "allowEmpty"]),
208
+ fetch: new Set(["remote"]),
209
+ pull: new Set(["remote"]),
210
+ push: new Set([
211
+ "remoteAlias",
212
+ "remoteBranchName",
213
+ "sourceBranchName",
214
+ "forcePush",
215
+ "setUpstreamTracking",
216
+ ]),
217
+ "pull-request": new Set([
218
+ "kind",
219
+ "ownerAndRepo",
220
+ "headBranchName",
221
+ "baseBranchName",
222
+ "title",
223
+ "description",
224
+ "isDraft",
225
+ "prExternalId",
226
+ "convertToDraft",
227
+ "convertFromDraft",
228
+ ]),
229
+ merge: new Set([
230
+ "kind",
231
+ "ownerAndRepo",
232
+ "prExternalId",
233
+ "baseBranchName",
234
+ "headBranchName",
235
+ "mergeStrategy",
236
+ "deleteBranchAfterMerge",
237
+ "expectedHeadRefHash",
238
+ ]),
239
+ };
240
+ const STATIC_WRITE_PATTERNS = {
241
+ "branch-create": "/api/git-delivery/local-branch/create",
242
+ "branch-switch": "/api/git-delivery/local-branch/switch",
243
+ stage: "/api/git-delivery/staging/stage",
244
+ unstage: "/api/git-delivery/staging/unstage",
245
+ };
246
+ const PHASED_WRITE_PATTERNS = {
247
+ commit: "/api/git-delivery/commit",
248
+ fetch: "/api/git-delivery/fetch",
249
+ pull: "/api/git-delivery/pull",
250
+ push: "/api/git-delivery/push",
251
+ "pull-request": "/api/git-delivery/pr",
252
+ merge: "/api/git-delivery/merge",
253
+ };
254
+ function delegatedPattern(request) {
255
+ const staticPattern = STATIC_WRITE_PATTERNS[request.operation];
256
+ if (staticPattern !== undefined)
257
+ return staticPattern;
258
+ const phasedPrefix = PHASED_WRITE_PATTERNS[request.operation];
259
+ return phasedPrefix === undefined ? undefined : `${phasedPrefix}/${request.mode}`;
260
+ }
261
+ function delegatedBody(request) {
262
+ const payload = payloadOrEmpty(request);
263
+ const body = {
264
+ schemaVersion: "1",
265
+ projectId: request.projectId,
266
+ ...payload,
267
+ };
268
+ if (request.operation === "pull-request") {
269
+ body.body = typeof payload.description === "string" ? payload.description : "";
270
+ delete body.description;
271
+ }
272
+ if (request.operation === "merge") {
273
+ body.kind = "merge";
274
+ }
275
+ return body;
276
+ }
277
+ async function delegateWrite(request, ctx, deps) {
278
+ const keyError = validatePayloadKeys(request, WRITE_KEYS[request.operation]);
279
+ if (keyError !== undefined)
280
+ return keyError;
281
+ const pattern = delegatedPattern(request);
282
+ if (pattern === undefined)
283
+ return errResult(400, "GIT_AGENT_OPERATION_UNSUPPORTED");
284
+ const route = routeGroups.find((candidate) => candidate.pattern === pattern);
285
+ if (route === undefined)
286
+ return errResult(400, "GIT_AGENT_OPERATION_UNSUPPORTED");
287
+ const body = delegatedBody(request);
288
+ const outcome = await route.handler(postContext(ctx, pattern, body), deps);
289
+ return outcome === STREAMING ? errResult(400, "GIT_AGENT_OPERATION_UNSUPPORTED") : outcome;
290
+ }
291
+ function wrapDelegated(request, result, replay = false) {
292
+ return {
293
+ schemaVersion: GIT_REPOSITORY_AGENT_SCHEMA_VERSION,
294
+ operation: request.operation,
295
+ mode: request.mode,
296
+ status: "delegated",
297
+ routeStatus: result.status,
298
+ ...(replay ? { replay: true } : {}),
299
+ response: result.body,
300
+ };
301
+ }
302
+ function cacheKey(request) {
303
+ if (request.mode !== "execute" || request.idempotencyKey === undefined)
304
+ return undefined;
305
+ return `${request.projectId}\0${request.idempotencyKey}`;
306
+ }
307
+ function normalizedForDigest(value) {
308
+ if (Array.isArray(value))
309
+ return value.map(normalizedForDigest);
310
+ if (!isPlainObject(value))
311
+ return value;
312
+ return Object.fromEntries(Object.keys(value)
313
+ .sort()
314
+ .map((key) => [key, normalizedForDigest(value[key])]));
315
+ }
316
+ function fingerprintRequest(request) {
317
+ return createHash("sha256")
318
+ .update(JSON.stringify(normalizedForDigest(request)))
319
+ .digest("hex");
320
+ }
321
+ function deniedStatus(reason) {
322
+ return reason === "unsupported-direct-shell" ? 200 : 400;
323
+ }
324
+ async function parseAgentRequest(req) {
325
+ const read = await readParsed(req);
326
+ if (!read.ok)
327
+ return read;
328
+ const parsed = parseGitRepositoryAgentOperationRequest(read.value);
329
+ if (!parsed.ok) {
330
+ return {
331
+ ok: false,
332
+ result: {
333
+ status: deniedStatus(parsed.denialReason),
334
+ body: denied({}, parsed.denialReason, parsed.message),
335
+ },
336
+ };
337
+ }
338
+ if (scanForbiddenStrings(read.value)) {
339
+ return { ok: false, result: errResult(400, "GIT_AGENT_OPERATION_FORBIDDEN_PAYLOAD") };
340
+ }
341
+ if (scanUnsafeFormatChars(read.value) || !isPlainObject(read.value)) {
342
+ return { ok: false, result: errResult(400, "GIT_AGENT_OPERATION_BAD_REQUEST") };
343
+ }
344
+ return { ok: true, request: parsed.value, fingerprint: fingerprintRequest(parsed.value) };
345
+ }
346
+ function idempotencyConflict(request) {
347
+ return {
348
+ status: 409,
349
+ body: denied(request, "idempotency-conflict", "The idempotencyKey was already used for a different repository operation."),
350
+ };
351
+ }
352
+ function responseResult(body, replay = false) {
353
+ const response = body.status === "delegated" && replay ? { ...body, replay: true } : body;
354
+ return {
355
+ status: body.status === "delegated" ? body.routeStatus : 200,
356
+ body: response,
357
+ };
358
+ }
359
+ async function delegateRequest(request, ctx, deps) {
360
+ return request.mode === "read"
361
+ ? delegateRead(request, ctx, deps)
362
+ : delegateWrite(request, ctx, deps);
363
+ }
364
+ export async function handleGitAgentOperationWithDelegate(request, fingerprint, delegate, cache = idempotencyCache) {
365
+ const key = cacheKey(request);
366
+ if (key === undefined) {
367
+ const result = await delegate();
368
+ return { status: result.status, body: wrapDelegated(request, result) };
369
+ }
370
+ const cached = cache.get(key);
371
+ if (cached !== undefined) {
372
+ if (cached.fingerprint !== fingerprint)
373
+ return idempotencyConflict(request);
374
+ if (cached.result !== undefined)
375
+ return responseResult(cached.result, true);
376
+ if (cached.pending !== undefined)
377
+ return responseResult(await cached.pending, true);
378
+ }
379
+ const pending = delegate().then((result) => wrapDelegated(request, result));
380
+ cache.set(key, { fingerprint, pending });
381
+ try {
382
+ const body = await pending;
383
+ cache.set(key, { fingerprint, result: body });
384
+ return responseResult(body);
385
+ }
386
+ catch (error) {
387
+ const current = cache.get(key);
388
+ if (current?.pending === pending)
389
+ cache.delete(key);
390
+ throw error;
391
+ }
392
+ }
393
+ export async function handleGitAgentOperation(ctx, deps) {
394
+ const parsed = await parseAgentRequest(ctx.req);
395
+ if (!parsed.ok)
396
+ return parsed.result;
397
+ return handleGitAgentOperationWithDelegate(parsed.request, parsed.fingerprint, () => delegateRequest(parsed.request, ctx, deps));
398
+ }
399
+ export const GIT_AGENT_OPERATION_ROUTE_GROUP = [
400
+ {
401
+ method: "POST",
402
+ pattern: "/api/git/agent/operations",
403
+ handler: handleGitAgentOperation,
404
+ },
405
+ ];
@@ -0,0 +1,23 @@
1
+ import { type GitCommitChangeSummary, type GitCommitIntentAnalysis, type GitCommitMessagePolicy, type GitCommitMessageValidation } from "@oscharko-dev/keiko-contracts";
2
+ import type { RouteContext, RouteDefinition, RouteResult } from "../routes.js";
3
+ import type { UiHandlerDeps } from "../deps.js";
4
+ import { type GitDeliveryExecutionSeams } from "./execution.js";
5
+ export type GitDeliveryCommitErrorCode = "GIT_DELIVERY_COMMIT_BAD_REQUEST" | "GIT_DELIVERY_COMMIT_PAYLOAD_TOO_LARGE" | "GIT_DELIVERY_COMMIT_FORBIDDEN_PAYLOAD" | "GIT_DELIVERY_COMMIT_UNKNOWN_PROJECT" | "GIT_DELIVERY_COMMIT_WORKTREE_UNAVAILABLE";
6
+ export interface GitDeliveryCommitRouteOptions {
7
+ readonly execution?: GitDeliveryExecutionSeams;
8
+ readonly messagePolicy?: GitCommitMessagePolicy;
9
+ }
10
+ export interface GitDeliveryCommitPreviewBody {
11
+ readonly schemaVersion: "1";
12
+ readonly summary: GitCommitChangeSummary;
13
+ readonly intent: GitCommitIntentAnalysis;
14
+ readonly messageValidation: GitCommitMessageValidation;
15
+ readonly preflightFindingCodes: readonly string[];
16
+ readonly policyOutcome: string;
17
+ readonly policyBlockReason?: string;
18
+ }
19
+ export declare const createHandleCommitPreview: (options?: GitDeliveryCommitRouteOptions) => ((ctx: RouteContext, deps: UiHandlerDeps) => Promise<RouteResult>);
20
+ export declare const createHandleCommitExecute: (options?: GitDeliveryCommitRouteOptions) => ((ctx: RouteContext, deps: UiHandlerDeps) => Promise<RouteResult>);
21
+ export declare const createGitDeliveryCommitRouteGroup: (options?: GitDeliveryCommitRouteOptions) => readonly RouteDefinition[];
22
+ export declare const GIT_DELIVERY_COMMIT_ROUTE_GROUP: readonly RouteDefinition[];
23
+ //# sourceMappingURL=commitRoutes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commitRoutes.d.ts","sourceRoot":"","sources":["../../src/gitDelivery/commitRoutes.ts"],"names":[],"mappings":"AAeA,OAAO,EAML,KAAK,sBAAsB,EAC3B,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,KAAK,0BAA0B,EAGhC,MAAM,+BAA+B,CAAC;AAEvC,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC/E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAOL,KAAK,yBAAyB,EAC/B,MAAM,gBAAgB,CAAC;AAaxB,MAAM,MAAM,0BAA0B,GAClC,iCAAiC,GACjC,uCAAuC,GACvC,uCAAuC,GACvC,qCAAqC,GACrC,0CAA0C,CAAC;AAqB/C,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,SAAS,CAAC,EAAE,yBAAyB,CAAC;IAE/C,QAAQ,CAAC,aAAa,CAAC,EAAE,sBAAsB,CAAC;CACjD;AA6CD,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,aAAa,EAAE,GAAG,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,sBAAsB,CAAC;IACzC,QAAQ,CAAC,MAAM,EAAE,uBAAuB,CAAC;IACzC,QAAQ,CAAC,iBAAiB,EAAE,0BAA0B,CAAC;IACvD,QAAQ,CAAC,qBAAqB,EAAE,SAAS,MAAM,EAAE,CAAC;IAClD,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CACrC;AA0DD,eAAO,MAAM,yBAAyB,GACpC,UAAS,6BAAkC,KAC1C,CAAC,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,KAAK,OAAO,CAAC,WAAW,CAAC,CAoBnE,CAAC;AAuCF,eAAO,MAAM,yBAAyB,GACpC,UAAS,6BAAkC,KAC1C,CAAC,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,KAAK,OAAO,CAAC,WAAW,CAAC,CAyCnE,CAAC;AAIF,eAAO,MAAM,iCAAiC,GAC5C,UAAS,6BAAkC,KAC1C,SAAS,eAAe,EAW1B,CAAC;AAEF,eAAO,MAAM,+BAA+B,EAAE,SAAS,eAAe,EACjC,CAAC"}
@@ -0,0 +1,204 @@
1
+ // Governed local Git commit routes: read-only preview + governed execute (Issue #475, Epic #470).
2
+ //
3
+ // * POST /api/git-delivery/commit/preview — READ-ONLY. Builds the pre-commit verification context:
4
+ // staged scope, commit-intent quality warnings (mixed-scope / WIP / large-change), message-policy
5
+ // validation of the draft, preflight findings, and the policy decision. Never mutates, never
6
+ // records evidence.
7
+ // * POST /api/git-delivery/commit/execute — Governed. Enforces the message policy FIRST (the kernel
8
+ // only sees a byte length, so message rules are evaluated here with the pure contract validator);
9
+ // a violation blocks the commit with typed codes BEFORE the kernel runs. A valid message drives
10
+ // executeGovernedMutation (preflight + policy + approval + execute) and appends evidence.
11
+ //
12
+ // Content-free throughout: counts, structural area tokens, typed warning/violation/finding codes, and
13
+ // the deterministic suggestion scaffold only — never the message body, diff, or raw paths.
14
+ import { analyzeGitCommitIntent, evaluateGitPolicy, isGitDeliveryApprovalRequirement, KEIKO_DEFAULT_COMMIT_MESSAGE_POLICY, validateGitCommitMessage, } from "@oscharko-dev/keiko-contracts";
15
+ import { evaluateGitPreflight, summarizeStagedChangeset } from "@oscharko-dev/keiko-tools";
16
+ import { executeGovernedMutation, gitDeliveryMutationResponse, KEIKO_DEFAULT_LOCAL_GIT_POLICY_PACK, readStagedPathsFor, readWorktreeSnapshotFor, resolveProjectWorkspace, } from "./execution.js";
17
+ import { GitDeliveryBodyTooLargeError, hasOnlyAllowedKeys, isNonEmptyString, isPlainObject, readGitDeliveryBody, scanForbiddenStrings, scanUnsafeFormatChars, } from "./requestGuards.js";
18
+ const SAFE_MESSAGES = {
19
+ GIT_DELIVERY_COMMIT_BAD_REQUEST: "The request body is not a valid governed commit request.",
20
+ GIT_DELIVERY_COMMIT_PAYLOAD_TOO_LARGE: "The governed commit request exceeds the maximum size.",
21
+ GIT_DELIVERY_COMMIT_FORBIDDEN_PAYLOAD: "The request contained a forbidden field. Requests may not carry credentials, headers, or URLs.",
22
+ GIT_DELIVERY_COMMIT_UNKNOWN_PROJECT: "The requested project is not a known workspace.",
23
+ GIT_DELIVERY_COMMIT_WORKTREE_UNAVAILABLE: "The repository worktree could not be inspected. Confirm the project is a Git repository.",
24
+ };
25
+ const errResult = (status, code) => ({
26
+ status,
27
+ body: { error: { code, message: SAFE_MESSAGES[code] } },
28
+ });
29
+ const UTF8 = new TextEncoder();
30
+ async function readParsed(req) {
31
+ let raw;
32
+ try {
33
+ raw = await readGitDeliveryBody(req);
34
+ }
35
+ catch (error) {
36
+ const result = error instanceof GitDeliveryBodyTooLargeError
37
+ ? errResult(413, "GIT_DELIVERY_COMMIT_PAYLOAD_TOO_LARGE")
38
+ : errResult(400, "GIT_DELIVERY_COMMIT_BAD_REQUEST");
39
+ return { ok: false, result };
40
+ }
41
+ try {
42
+ return { ok: true, value: JSON.parse(raw) };
43
+ }
44
+ catch {
45
+ return { ok: false, result: errResult(400, "GIT_DELIVERY_COMMIT_BAD_REQUEST") };
46
+ }
47
+ }
48
+ // Envelope pre-checks shared by both handlers. Returns the validated object or an error RouteResult.
49
+ function preValidate(parsed, allowed) {
50
+ const bad = { ok: false, result: errResult(400, "GIT_DELIVERY_COMMIT_BAD_REQUEST") };
51
+ if (!isPlainObject(parsed) || !hasOnlyAllowedKeys(parsed, allowed))
52
+ return bad;
53
+ if (parsed.schemaVersion !== "1" || !isNonEmptyString(parsed.projectId))
54
+ return bad;
55
+ if (scanForbiddenStrings(parsed)) {
56
+ return { ok: false, result: errResult(400, "GIT_DELIVERY_COMMIT_FORBIDDEN_PAYLOAD") };
57
+ }
58
+ if (scanUnsafeFormatChars(parsed))
59
+ return bad;
60
+ return { ok: true, obj: parsed };
61
+ }
62
+ // ─── Preview (read-only) ──────────────────────────────────────────────────────────────────────
63
+ const PREVIEW_KEYS = new Set(["schemaVersion", "projectId", "messageDraft"]);
64
+ function buildPreviewBody(summary, messageDraft, policy, preflightCodes, policyOutcome, policyBlockReason) {
65
+ return {
66
+ schemaVersion: "1",
67
+ summary,
68
+ intent: analyzeGitCommitIntent({ summary, message: messageDraft }),
69
+ messageValidation: validateGitCommitMessage(messageDraft, policy),
70
+ preflightFindingCodes: preflightCodes,
71
+ policyOutcome,
72
+ ...(policyBlockReason !== undefined ? { policyBlockReason } : {}),
73
+ };
74
+ }
75
+ // Reads the live worktree and assembles the read-only preview. May throw if the worktree cannot be
76
+ // inspected (not a git repository); the handler maps that to a typed content-free error.
77
+ async function computePreview(workspace, messageDraft, policy, seams, now) {
78
+ const snapshot = await readWorktreeSnapshotFor(workspace, seams, now);
79
+ const stagedPaths = await readStagedPathsFor(workspace, seams, now);
80
+ const summary = summarizeStagedChangeset(stagedPaths);
81
+ const commitInputs = {
82
+ kind: "commit",
83
+ messageByteLength: UTF8.encode(messageDraft).length,
84
+ stagedPathCount: snapshot.stagedFileCount,
85
+ allowEmptyCommit: false,
86
+ };
87
+ const preflight = evaluateGitPreflight(commitInputs, snapshot);
88
+ const packs = seams.policyPacks ?? { repoPack: KEIKO_DEFAULT_LOCAL_GIT_POLICY_PACK };
89
+ const decision = evaluateGitPolicy(packs.orgPack, packs.repoPack, {
90
+ actionKind: "commit",
91
+ ...(snapshot.currentBranchName !== undefined
92
+ ? { targetBranchName: snapshot.currentBranchName }
93
+ : {}),
94
+ activeProviderCapabilities: [],
95
+ });
96
+ return buildPreviewBody(summary, messageDraft, policy, preflight.findings.map((f) => f.code), decision.outcome, decision.outcome === "blocked" ? decision.reason : undefined);
97
+ }
98
+ export const createHandleCommitPreview = (options = {}) => {
99
+ const seams = options.execution ?? {};
100
+ const policy = options.messagePolicy ?? KEIKO_DEFAULT_COMMIT_MESSAGE_POLICY;
101
+ const now = () => (seams.now ?? Date.now)();
102
+ return async (ctx, deps) => {
103
+ const read = await readParsed(ctx.req);
104
+ if (!read.ok)
105
+ return read.result;
106
+ const pre = preValidate(read.value, PREVIEW_KEYS);
107
+ if (!pre.ok)
108
+ return pre.result;
109
+ const messageDraft = typeof pre.obj.messageDraft === "string" ? pre.obj.messageDraft : "";
110
+ const workspace = resolveProjectWorkspace(deps, pre.obj.projectId);
111
+ if (workspace === undefined)
112
+ return errResult(404, "GIT_DELIVERY_COMMIT_UNKNOWN_PROJECT");
113
+ let body;
114
+ try {
115
+ body = await computePreview(workspace, messageDraft, policy, seams, now);
116
+ }
117
+ catch {
118
+ return errResult(409, "GIT_DELIVERY_COMMIT_WORKTREE_UNAVAILABLE");
119
+ }
120
+ return { status: 200, body: deps.redactor(body) };
121
+ };
122
+ };
123
+ // ─── Execute (governed, with message-policy gate) ───────────────────────────────────────────────
124
+ const EXECUTE_KEYS = new Set([
125
+ "schemaVersion",
126
+ "projectId",
127
+ "message",
128
+ "allowEmpty",
129
+ "approval",
130
+ ]);
131
+ const NO_APPROVAL = { required: false };
132
+ function validateExecute(obj) {
133
+ if (!isNonEmptyString(obj.message))
134
+ return undefined;
135
+ if (obj.allowEmpty !== undefined && typeof obj.allowEmpty !== "boolean")
136
+ return undefined;
137
+ const approval = obj.approval === undefined
138
+ ? NO_APPROVAL
139
+ : isGitDeliveryApprovalRequirement(obj.approval)
140
+ ? obj.approval
141
+ : undefined;
142
+ if (approval === undefined)
143
+ return undefined;
144
+ return {
145
+ projectId: obj.projectId,
146
+ message: obj.message,
147
+ allowEmpty: obj.allowEmpty === true,
148
+ approval,
149
+ };
150
+ }
151
+ export const createHandleCommitExecute = (options = {}) => {
152
+ const seams = options.execution ?? {};
153
+ const policy = options.messagePolicy ?? KEIKO_DEFAULT_COMMIT_MESSAGE_POLICY;
154
+ return async (ctx, deps) => {
155
+ const read = await readParsed(ctx.req);
156
+ if (!read.ok)
157
+ return read.result;
158
+ const pre = preValidate(read.value, EXECUTE_KEYS);
159
+ if (!pre.ok)
160
+ return pre.result;
161
+ const req = validateExecute(pre.obj);
162
+ if (req === undefined)
163
+ return errResult(400, "GIT_DELIVERY_COMMIT_BAD_REQUEST");
164
+ const workspace = resolveProjectWorkspace(deps, req.projectId);
165
+ if (workspace === undefined)
166
+ return errResult(404, "GIT_DELIVERY_COMMIT_UNKNOWN_PROJECT");
167
+ // Message-policy gate (AC2): a policy-violating message blocks the commit BEFORE the kernel runs.
168
+ const validation = validateGitCommitMessage(req.message, policy);
169
+ if (!validation.ok) {
170
+ return {
171
+ status: 200,
172
+ body: deps.redactor({
173
+ schemaVersion: "1",
174
+ status: "blocked",
175
+ actionKind: "commit",
176
+ blockReason: "message-policy",
177
+ messageViolations: validation.violations,
178
+ }),
179
+ };
180
+ }
181
+ let result;
182
+ try {
183
+ result = await executeGovernedMutation({ kind: "commit", message: req.message, allowEmpty: req.allowEmpty }, req.approval, workspace, deps, seams);
184
+ }
185
+ catch {
186
+ return errResult(409, "GIT_DELIVERY_COMMIT_WORKTREE_UNAVAILABLE");
187
+ }
188
+ return { status: 200, body: deps.redactor(gitDeliveryMutationResponse(result)) };
189
+ };
190
+ };
191
+ // ─── Route group ───────────────────────────────────────────────────────────────────────────────
192
+ export const createGitDeliveryCommitRouteGroup = (options = {}) => [
193
+ {
194
+ method: "POST",
195
+ pattern: "/api/git-delivery/commit/preview",
196
+ handler: createHandleCommitPreview(options),
197
+ },
198
+ {
199
+ method: "POST",
200
+ pattern: "/api/git-delivery/commit/execute",
201
+ handler: createHandleCommitExecute(options),
202
+ },
203
+ ];
204
+ export const GIT_DELIVERY_COMMIT_ROUTE_GROUP = createGitDeliveryCommitRouteGroup();
@@ -0,0 +1,9 @@
1
+ import type { RouteContext, RouteDefinition, RouteResult } from "../routes.js";
2
+ import type { UiHandlerDeps } from "../deps.js";
3
+ export interface GitDeliveryEvidenceRouteOptions {
4
+ readonly now?: (() => number) | undefined;
5
+ }
6
+ export declare const createHandleGitDeliveryEvidenceExport: (options?: GitDeliveryEvidenceRouteOptions) => ((ctx: RouteContext, deps: UiHandlerDeps) => RouteResult);
7
+ export declare const handleGitDeliveryEvidenceExport: (ctx: RouteContext, deps: UiHandlerDeps) => RouteResult;
8
+ export declare const GIT_DELIVERY_EVIDENCE_ROUTE_GROUP: readonly RouteDefinition[];
9
+ //# sourceMappingURL=evidenceRoutes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evidenceRoutes.d.ts","sourceRoot":"","sources":["../../src/gitDelivery/evidenceRoutes.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC/E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAqEhD,MAAM,WAAW,+BAA+B;IAE9C,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC;CAC3C;AAED,eAAO,MAAM,qCAAqC,GAChD,UAAS,+BAAoC,KAC5C,CAAC,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,KAAK,WAAW,CA0B1D,CAAC;AAEF,eAAO,MAAM,+BAA+B,QA5BlC,YAAY,QAAQ,aAAa,KAAK,WA4BsC,CAAC;AAIvF,eAAO,MAAM,iCAAiC,EAAE,SAAS,eAAe,EAMvE,CAAC"}