@a5c-ai/kradle 5.0.1-staging.3abdf9534c25

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 (295) hide show
  1. package/Dockerfile +31 -0
  2. package/README.md +187 -0
  3. package/bin/kradle-demo.mjs +23 -0
  4. package/bin/kradle-server.mjs +14 -0
  5. package/dist/kradle-controller-ui.json +3482 -0
  6. package/dist/kradle-lifecycle.json +201 -0
  7. package/dist/kradle-runtime-snapshot.json +3125 -0
  8. package/dist/kradle-summary.json +724 -0
  9. package/docs/README.md +61 -0
  10. package/docs/agents/README.md +83 -0
  11. package/docs/agents/acceptance-test-matrix.md +193 -0
  12. package/docs/agents/agent-mux-adapter-contract.md +167 -0
  13. package/docs/agents/agent-mux-source-map.md +310 -0
  14. package/docs/agents/agent-run-memory-import-spec.md +256 -0
  15. package/docs/agents/agent-stack-management-spec.md +421 -0
  16. package/docs/agents/api-contract-spec.md +309 -0
  17. package/docs/agents/artifacts-writeback-spec.md +145 -0
  18. package/docs/agents/chart-packaging-spec.md +128 -0
  19. package/docs/agents/ci-orchestration-spec.md +140 -0
  20. package/docs/agents/context-assembly-spec.md +219 -0
  21. package/docs/agents/controller-reconciliation-spec.md +255 -0
  22. package/docs/agents/crd-schema-spec.md +315 -0
  23. package/docs/agents/decision-log-open-questions.md +169 -0
  24. package/docs/agents/developer-implementation-checklist.md +329 -0
  25. package/docs/agents/dispatching-design.md +262 -0
  26. package/docs/agents/gaps-agent-mux-to-kradle-crds.md +298 -0
  27. package/docs/agents/glossary.md +66 -0
  28. package/docs/agents/implementation-blueprint.md +324 -0
  29. package/docs/agents/implementation-rollout-slices.md +251 -0
  30. package/docs/agents/memory-context-integration-spec.md +194 -0
  31. package/docs/agents/memory-ontology-schema-spec.md +253 -0
  32. package/docs/agents/memory-operations-runbook.md +121 -0
  33. package/docs/agents/mvp-vertical-slice-spec.md +146 -0
  34. package/docs/agents/observability-audit-spec.md +265 -0
  35. package/docs/agents/operator-runbook.md +174 -0
  36. package/docs/agents/org-memory-api-payload-examples.md +333 -0
  37. package/docs/agents/org-memory-controller-sequence-spec.md +181 -0
  38. package/docs/agents/org-memory-e2e-fixture-plan.md +161 -0
  39. package/docs/agents/org-memory-ui-implementation-map.md +114 -0
  40. package/docs/agents/org-memory-vertical-slice-spec.md +168 -0
  41. package/docs/agents/org-resource-model-delta-spec.md +111 -0
  42. package/docs/agents/org-route-resource-model-spec.md +183 -0
  43. package/docs/agents/org-scoping-namespace-spec.md +114 -0
  44. package/docs/agents/rbac-secrets-management-spec.md +406 -0
  45. package/docs/agents/repository-page-integration-spec.md +255 -0
  46. package/docs/agents/resource-contract-examples.md +808 -0
  47. package/docs/agents/resource-relationship-map.md +190 -0
  48. package/docs/agents/security-threat-model.md +188 -0
  49. package/docs/agents/shared-memory-company-brain-spec.md +358 -0
  50. package/docs/agents/storage-migration-spec.md +168 -0
  51. package/docs/agents/subagent-orchestration-spec.md +152 -0
  52. package/docs/agents/system-overview.md +88 -0
  53. package/docs/agents/tools-mcp-skills-spec.md +189 -0
  54. package/docs/agents/traceability-matrix.md +79 -0
  55. package/docs/agents/ui-flow-spec.md +211 -0
  56. package/docs/agents/ui-ux-system-spec.md +426 -0
  57. package/docs/agents/workspace-lifecycle-spec.md +166 -0
  58. package/docs/architecture-spec.md +78 -0
  59. package/docs/architecture-v2.md +2759 -0
  60. package/docs/components/control-plane.md +78 -0
  61. package/docs/components/data-plane.md +69 -0
  62. package/docs/components/hooks-events.md +67 -0
  63. package/docs/components/identity-rbac-policy.md +73 -0
  64. package/docs/components/kubevela-oam.md +70 -0
  65. package/docs/components/operations-publishing.md +81 -0
  66. package/docs/components/runners-ci.md +66 -0
  67. package/docs/components/web-ui.md +94 -0
  68. package/docs/crd-behaviors-and-relationships.md +3926 -0
  69. package/docs/external/README.md +47 -0
  70. package/docs/external/bidirectional-sync-design.md +134 -0
  71. package/docs/external/cicd-interface.md +64 -0
  72. package/docs/external/external-backend-controllers.md +170 -0
  73. package/docs/external/external-backend-crds.md +234 -0
  74. package/docs/external/external-backend-ui-spec.md +151 -0
  75. package/docs/external/external-backend-ux-flows.md +115 -0
  76. package/docs/external/external-object-mapping.md +125 -0
  77. package/docs/external/git-forge-interface.md +68 -0
  78. package/docs/external/github-integration-design.md +151 -0
  79. package/docs/external/issue-tracking-interface.md +66 -0
  80. package/docs/external/provider-capability-manifests.md +204 -0
  81. package/docs/external/provider-catalog.md +139 -0
  82. package/docs/external/provider-rollout-testing.md +78 -0
  83. package/docs/external/research-results.md +48 -0
  84. package/docs/external/security-auth-permissions.md +81 -0
  85. package/docs/external/sync-state-machines.md +108 -0
  86. package/docs/external/unified-external-backend-model.md +107 -0
  87. package/docs/external/user-facing-changes.md +67 -0
  88. package/docs/gaps.md +161 -0
  89. package/docs/install.md +94 -0
  90. package/docs/integration-and-design-decisions.md +1530 -0
  91. package/docs/kradle-design.md +334 -0
  92. package/docs/local-minikube.md +55 -0
  93. package/docs/ontology/README.md +32 -0
  94. package/docs/ontology/bounded-contexts.md +29 -0
  95. package/docs/ontology/events-and-hooks.md +32 -0
  96. package/docs/ontology/oam-kubevela.md +32 -0
  97. package/docs/ontology/operations-and-release.md +25 -0
  98. package/docs/ontology/personas-and-actors.md +32 -0
  99. package/docs/ontology/policies-and-invariants.md +33 -0
  100. package/docs/ontology/problem-space.md +30 -0
  101. package/docs/ontology/resource-contracts.md +40 -0
  102. package/docs/ontology/resource-taxonomy.md +42 -0
  103. package/docs/ontology/runners-and-ci.md +29 -0
  104. package/docs/ontology/solution-space.md +24 -0
  105. package/docs/ontology/storage-and-data-boundaries.md +29 -0
  106. package/docs/ontology/validation-matrix.md +24 -0
  107. package/docs/ontology/web-ui-excellent-flows.md +32 -0
  108. package/docs/ontology/workflows.md +39 -0
  109. package/docs/ontology/world.md +35 -0
  110. package/docs/openapi.yaml +1291 -0
  111. package/docs/product-requirements.md +62 -0
  112. package/docs/requirements-v2.md +235 -0
  113. package/docs/roadmap-mvp.md +87 -0
  114. package/docs/sdk-api-reference.md +1108 -0
  115. package/docs/system-requirements.md +90 -0
  116. package/docs/system-spec-v2.md +1230 -0
  117. package/docs/tests/README.md +53 -0
  118. package/docs/tests/agent-qa-plan.md +63 -0
  119. package/docs/tests/browser-ui-tests.md +62 -0
  120. package/docs/tests/ci-quality-gates.md +48 -0
  121. package/docs/tests/coverage-model.md +64 -0
  122. package/docs/tests/e2e-scenario-tests.md +53 -0
  123. package/docs/tests/fixtures-test-data.md +63 -0
  124. package/docs/tests/observability-reliability-tests.md +54 -0
  125. package/docs/tests/product-test-matrix.md +145 -0
  126. package/docs/tests/qa-adoption-roadmap.md +130 -0
  127. package/docs/tests/qa-automation-plan.md +101 -0
  128. package/docs/tests/security-compliance-tests.md +57 -0
  129. package/docs/tests/test-framework-tools.md +88 -0
  130. package/docs/tests/test-suite-layout.md +121 -0
  131. package/docs/tests/unit-integration-tests.md +48 -0
  132. package/docs/todo-kyverno +714 -0
  133. package/docs/todos.md +4 -0
  134. package/docs/user-stories.md +78 -0
  135. package/docs/web-console-spec.md +533 -0
  136. package/examples/minikube-demo.yaml +190 -0
  137. package/examples/oam-application.yaml +23 -0
  138. package/examples/policy-kyverno-pr-title.yaml +18 -0
  139. package/package.json +66 -0
  140. package/scripts/build.mjs +29 -0
  141. package/scripts/setup-minikube.mjs +65 -0
  142. package/scripts/smoke.mjs +37 -0
  143. package/scripts/validate-doc-coverage.mjs +152 -0
  144. package/scripts/validate-package.mjs +95 -0
  145. package/scripts/validate-ui.mjs +305 -0
  146. package/src/agent-adapter-controller.js +169 -0
  147. package/src/agent-approval-controller.js +170 -0
  148. package/src/agent-context-bundles.js +242 -0
  149. package/src/agent-dispatch-controller.js +549 -0
  150. package/src/agent-gateway-config-controller.js +147 -0
  151. package/src/agent-identity-migration.js +115 -0
  152. package/src/agent-memory-controller.js +357 -0
  153. package/src/agent-memory-import.js +327 -0
  154. package/src/agent-memory-query.js +292 -0
  155. package/src/agent-memory-repository-source-controller.js +255 -0
  156. package/src/agent-mux-client.js +589 -0
  157. package/src/agent-permission-review.js +250 -0
  158. package/src/agent-persona-controller.js +135 -0
  159. package/src/agent-project-controller.js +117 -0
  160. package/src/agent-prompt-composition.js +55 -0
  161. package/src/agent-provider-config-controller.js +151 -0
  162. package/src/agent-secret-config-grant-controller.js +282 -0
  163. package/src/agent-session-transcript-controller.js +189 -0
  164. package/src/agent-stack-controller.js +421 -0
  165. package/src/agent-subagent-controller.js +160 -0
  166. package/src/agent-transport-binding-controller.js +121 -0
  167. package/src/agent-trigger-controller.js +387 -0
  168. package/src/agent-workspace-controller.js +702 -0
  169. package/src/agent-writeback-controller.js +302 -0
  170. package/src/api-controller.js +621 -0
  171. package/src/argocd-gitops.js +43 -0
  172. package/src/artifact-registry-controller.js +542 -0
  173. package/src/assistant-runtime.js +284 -0
  174. package/src/async-controller.js +207 -0
  175. package/src/audit-controller.js +191 -0
  176. package/src/auth.js +310 -0
  177. package/src/component-catalog.js +41 -0
  178. package/src/control-plane.js +136 -0
  179. package/src/controller-client.js +112 -0
  180. package/src/controller-ui.js +620 -0
  181. package/src/data-plane.js +179 -0
  182. package/src/event-bus.js +397 -0
  183. package/src/external/conflict-controller.js +225 -0
  184. package/src/external/github/auth.js +96 -0
  185. package/src/external/github/cicd.js +180 -0
  186. package/src/external/github/git-forge.js +240 -0
  187. package/src/external/github/index.js +144 -0
  188. package/src/external/github/issue-tracking.js +163 -0
  189. package/src/external/provider-adapter.js +161 -0
  190. package/src/external/provider-resource-factory.js +221 -0
  191. package/src/external/sync-controller.js +235 -0
  192. package/src/external/webhook-controller.js +144 -0
  193. package/src/external/write-controller.js +283 -0
  194. package/src/gitea-backend.js +131 -0
  195. package/src/gitea-service.js +173 -0
  196. package/src/handoff.js +98 -0
  197. package/src/health-probes.js +134 -0
  198. package/src/hooks-events.js +63 -0
  199. package/src/hooks-lifecycle.js +117 -0
  200. package/src/http-server.js +409 -0
  201. package/src/identity-policy.js +86 -0
  202. package/src/index.js +71 -0
  203. package/src/jitsi-agent-bridge.js +141 -0
  204. package/src/jitsi-meeting-controller.js +291 -0
  205. package/src/jitsi-sync-controller.js +198 -0
  206. package/src/kradle-inference-service-controller.js +246 -0
  207. package/src/kubernetes-controller-async.js +531 -0
  208. package/src/kubernetes-controller.js +904 -0
  209. package/src/kubernetes-resource-gateway.js +48 -0
  210. package/src/model-route-controller.js +364 -0
  211. package/src/notification-controller.js +178 -0
  212. package/src/operations.js +112 -0
  213. package/src/org-scoping.js +5 -0
  214. package/src/resource-model.js +282 -0
  215. package/src/runner-controller.js +272 -0
  216. package/src/runners-ci.js +48 -0
  217. package/src/runtime.js +196 -0
  218. package/src/snapshot-cache.js +157 -0
  219. package/src/virtual-model-controller.js +538 -0
  220. package/src/virtual-model-hook-bridge.js +200 -0
  221. package/src/web-ui.js +40 -0
  222. package/tests/agent-adapter-controller.test.js +361 -0
  223. package/tests/agent-approval-controller.test.js +173 -0
  224. package/tests/agent-context-bundles.test.js +278 -0
  225. package/tests/agent-dispatch-controller.test.js +679 -0
  226. package/tests/agent-gateway-config-controller.test.js +386 -0
  227. package/tests/agent-identity-migration.test.js +87 -0
  228. package/tests/agent-memory-controller.test.js +461 -0
  229. package/tests/agent-memory-import-snapshot.test.js +477 -0
  230. package/tests/agent-memory-query.test.js +404 -0
  231. package/tests/agent-memory-repository-source.test.js +514 -0
  232. package/tests/agent-mux-client.test.js +389 -0
  233. package/tests/agent-mux-integration.test.js +971 -0
  234. package/tests/agent-permission-review-v2.test.js +317 -0
  235. package/tests/agent-permission-review.test.js +209 -0
  236. package/tests/agent-persona-controller.test.js +127 -0
  237. package/tests/agent-project-controller.test.js +302 -0
  238. package/tests/agent-prompt-composition.test.js +76 -0
  239. package/tests/agent-provider-config-controller.test.js +376 -0
  240. package/tests/agent-resources.test.js +303 -0
  241. package/tests/agent-secret-config-grant.test.js +231 -0
  242. package/tests/agent-session-transcript-controller.test.js +499 -0
  243. package/tests/agent-stack-controller.test.js +283 -0
  244. package/tests/agent-subagent-controller.test.js +201 -0
  245. package/tests/agent-transport-binding-controller.test.js +294 -0
  246. package/tests/agent-trigger-controller.test.js +271 -0
  247. package/tests/agent-trigger-routes.test.js +190 -0
  248. package/tests/agent-trigger-sources.test.js +245 -0
  249. package/tests/agent-workspace-controller.test.js +181 -0
  250. package/tests/agent-writeback.test.js +292 -0
  251. package/tests/approval-persistence.test.js +171 -0
  252. package/tests/artifact-registry.test.js +511 -0
  253. package/tests/assistant-runtime.test.js +506 -0
  254. package/tests/async-controller.test.js +252 -0
  255. package/tests/audit-controller.test.js +227 -0
  256. package/tests/codespace-controller.test.js +318 -0
  257. package/tests/controller-client.test.js +133 -0
  258. package/tests/deployment.test.js +527 -0
  259. package/tests/e2e/lifecycle.test.js +120 -0
  260. package/tests/event-bus-integration.test.js +355 -0
  261. package/tests/external-github-forge.test.js +560 -0
  262. package/tests/external-github-issues-cicd.test.js +520 -0
  263. package/tests/external-integration.test.js +470 -0
  264. package/tests/external-persistence.test.js +415 -0
  265. package/tests/external-provider-adapter.test.js +365 -0
  266. package/tests/external-resource-model.test.js +223 -0
  267. package/tests/external-webhook-sync.test.js +287 -0
  268. package/tests/external-write-conflict.test.js +353 -0
  269. package/tests/gitea-service.test.js +253 -0
  270. package/tests/health-check-real.test.js +165 -0
  271. package/tests/health-probes.test.js +90 -0
  272. package/tests/hooks-lifecycle.test.js +364 -0
  273. package/tests/integration/full-flow.test.js +266 -0
  274. package/tests/jitsi-agent-bridge.test.js +119 -0
  275. package/tests/jitsi-helm-integration.test.js +77 -0
  276. package/tests/jitsi-meeting-controller.test.js +170 -0
  277. package/tests/jitsi-resource-model.test.js +73 -0
  278. package/tests/jitsi-sync-controller.test.js +112 -0
  279. package/tests/kradle-inference-service.test.js +689 -0
  280. package/tests/kradle.test.js +779 -0
  281. package/tests/memory-search-wiring.test.js +270 -0
  282. package/tests/model-route-controller.test.js +733 -0
  283. package/tests/notification-controller.test.js +196 -0
  284. package/tests/notification-integration.test.js +179 -0
  285. package/tests/org-scoping.test.js +687 -0
  286. package/tests/runner-controller.test.js +327 -0
  287. package/tests/runner-integration.test.js +231 -0
  288. package/tests/session-cookie-hmac.test.js +151 -0
  289. package/tests/snapshot-performance.test.js +315 -0
  290. package/tests/sse-events.test.js +107 -0
  291. package/tests/virtual-model-controller.test.js +877 -0
  292. package/tests/virtual-model-hook-bridge.test.js +384 -0
  293. package/tests/webhook-trigger.test.js +198 -0
  294. package/tests/workspace-volumes.test.js +312 -0
  295. package/tests/writeback-persistence.test.js +207 -0
@@ -0,0 +1,173 @@
1
+ import { createGiteaBackend } from './gitea-backend.js';
2
+
3
+ /**
4
+ * Gitea service layer that wraps gitea-backend.js for repo code-browsing operations.
5
+ *
6
+ * Returns null when Gitea is not configured so call-sites can fall back to mock data.
7
+ *
8
+ * @param {{ giteaUrl?: string, token?: string, fetchImpl?: Function }} [options]
9
+ * @returns {GiteaService|null}
10
+ */
11
+ export function createGiteaService(options = {}) {
12
+ const giteaUrl = options.giteaUrl || process.env.KRADLE_GITEA_HTTP_URL;
13
+ if (!giteaUrl) return null; // Gitea not configured — callers must fall back
14
+
15
+ const backend = createGiteaBackend({
16
+ baseUrl: giteaUrl,
17
+ token: options.token || process.env.KRADLE_GITEA_TOKEN,
18
+ fetchImpl: options.fetchImpl || globalThis.fetch,
19
+ });
20
+
21
+ // Low-level fetch helper that bypasses the backend's opinionated error handling
22
+ // so service methods can gracefully return null on 404 rather than throwing.
23
+ const root = giteaUrl.replace(/\/$/, '');
24
+ const fetchImpl = options.fetchImpl || globalThis.fetch;
25
+ const token = options.token || process.env.KRADLE_GITEA_TOKEN;
26
+
27
+ async function rawRequest(path) {
28
+ if (!fetchImpl) throw new Error('Gitea service requires a fetch implementation');
29
+ const response = await fetchImpl(`${root}/api/v1${path}`, {
30
+ method: 'GET',
31
+ headers: {
32
+ Accept: 'application/json',
33
+ ...(token ? { Authorization: `token ${token}` } : {}),
34
+ },
35
+ });
36
+ if (response.status === 404) return null;
37
+ if (!response.ok) {
38
+ throw new Error(`Gitea GET ${path} failed with ${response.status}`);
39
+ }
40
+ return response.json();
41
+ }
42
+
43
+ async function rawRawRequest(path) {
44
+ if (!fetchImpl) throw new Error('Gitea service requires a fetch implementation');
45
+ const response = await fetchImpl(`${root}/api/v1${path}`, {
46
+ method: 'GET',
47
+ headers: {
48
+ Accept: 'text/plain',
49
+ ...(token ? { Authorization: `token ${token}` } : {}),
50
+ },
51
+ });
52
+ if (response.status === 404) return null;
53
+ if (!response.ok) {
54
+ throw new Error(`Gitea GET ${path} failed with ${response.status}`);
55
+ }
56
+ return response.text();
57
+ }
58
+
59
+ return {
60
+ available: true,
61
+ baseUrl: root,
62
+
63
+ /**
64
+ * List tree entries for a repository path at a given ref.
65
+ * Uses GET /api/v1/repos/{owner}/{repo}/contents/{path}?ref={ref}
66
+ *
67
+ * @param {string} org
68
+ * @param {string} repo
69
+ * @param {string} ref — branch name, tag, or commit SHA
70
+ * @param {string} [path='']
71
+ * @returns {Promise<Array<{path:string,type:'blob'|'tree',size:number}>|null>}
72
+ */
73
+ async listTree(org, repo, ref, path = '') {
74
+ const encodedPath = path ? encodeURIComponent(path).replace(/%2F/g, '/') : '';
75
+ const apiPath = `/repos/${encodeURIComponent(org)}/${encodeURIComponent(repo)}/contents/${encodedPath}?ref=${encodeURIComponent(ref)}`;
76
+ const data = await rawRequest(apiPath);
77
+ if (data === null) return null;
78
+
79
+ // Gitea returns an array for directories, an object for files
80
+ const entries = Array.isArray(data) ? data : [data];
81
+ return entries.map((entry) => ({
82
+ path: entry.path || entry.name,
83
+ type: entry.type === 'dir' ? 'tree' : 'blob',
84
+ size: entry.size || 0,
85
+ sha: entry.sha,
86
+ name: entry.name,
87
+ }));
88
+ },
89
+
90
+ /**
91
+ * Get the raw text content of a file.
92
+ * Uses GET /api/v1/repos/{owner}/{repo}/raw/{filepath}?ref={ref}
93
+ *
94
+ * @param {string} org
95
+ * @param {string} repo
96
+ * @param {string} ref
97
+ * @param {string} path
98
+ * @returns {Promise<string|null>}
99
+ */
100
+ async getBlob(org, repo, ref, path) {
101
+ const encodedPath = path ? encodeURIComponent(path).replace(/%2F/g, '/') : '';
102
+ const apiPath = `/repos/${encodeURIComponent(org)}/${encodeURIComponent(repo)}/raw/${encodedPath}?ref=${encodeURIComponent(ref)}`;
103
+ return rawRawRequest(apiPath);
104
+ },
105
+
106
+ /**
107
+ * List branches for a repository.
108
+ * Uses GET /api/v1/repos/{owner}/{repo}/branches
109
+ *
110
+ * @param {string} org
111
+ * @param {string} repo
112
+ * @returns {Promise<Array<{name:string,sha:string,protected:boolean}>|null>}
113
+ */
114
+ async listBranches(org, repo) {
115
+ const data = await rawRequest(`/repos/${encodeURIComponent(org)}/${encodeURIComponent(repo)}/branches`);
116
+ if (data === null) return null;
117
+ return data.map((b) => ({
118
+ name: b.name,
119
+ sha: b.commit?.id || b.commit?.sha || '',
120
+ protected: b.protected || false,
121
+ }));
122
+ },
123
+
124
+ /**
125
+ * Get file metadata + base64-decoded content for a path.
126
+ * Uses GET /api/v1/repos/{owner}/{repo}/contents/{path}?ref={ref}
127
+ *
128
+ * @param {string} org
129
+ * @param {string} repo
130
+ * @param {string} ref
131
+ * @param {string} path
132
+ * @returns {Promise<{path:string,content:string,size:number,sha:string,encoding:string}|null>}
133
+ */
134
+ async getFileContent(org, repo, ref, path) {
135
+ const encodedPath = path ? encodeURIComponent(path).replace(/%2F/g, '/') : '';
136
+ const apiPath = `/repos/${encodeURIComponent(org)}/${encodeURIComponent(repo)}/contents/${encodedPath}?ref=${encodeURIComponent(ref)}`;
137
+ const data = await rawRequest(apiPath);
138
+ if (data === null || Array.isArray(data)) return null; // null = not found, array = directory
139
+
140
+ // Gitea returns content as base64 — decode it
141
+ const rawContent = data.content
142
+ ? Buffer.from(data.content.replace(/\n/g, ''), 'base64').toString('utf8')
143
+ : '';
144
+
145
+ return {
146
+ path: data.path || path,
147
+ content: rawContent,
148
+ size: data.size || Buffer.byteLength(rawContent, 'utf8'),
149
+ sha: data.sha,
150
+ encoding: 'utf-8',
151
+ lastCommit: data.last_commit_sha || null,
152
+ };
153
+ },
154
+
155
+ /**
156
+ * Create a repository inside an org.
157
+ * Delegates to the underlying gitea-backend.
158
+ *
159
+ * @param {string} org
160
+ * @param {string} name
161
+ * @param {{ private?: boolean, defaultBranch?: string, description?: string }} [repoOptions]
162
+ */
163
+ async createRepository(org, name, repoOptions = {}) {
164
+ return backend.createRepository({
165
+ owner: org,
166
+ name,
167
+ private: repoOptions.private ?? true,
168
+ defaultBranch: repoOptions.defaultBranch || 'main',
169
+ description: repoOptions.description || '',
170
+ });
171
+ },
172
+ };
173
+ }
package/src/handoff.js ADDED
@@ -0,0 +1,98 @@
1
+ export const HANDOFF_COMMANDS = Object.freeze({
2
+ build: 'npm run build',
3
+ demo: 'npm run demo',
4
+ serve: 'npm run serve',
5
+ check: 'npm run check',
6
+ test: 'npm test',
7
+ smoke: 'npm run smoke',
8
+ validateDocs: 'npm run validate:docs',
9
+ e2e: 'npm run e2e',
10
+ packageCheck: 'npm run package:check',
11
+ setupMinikube: 'npm run setup:minikube -- --dry-run',
12
+ dev: 'npm run dev',
13
+ uiBuild: 'npm run ui:build',
14
+ uiValidate: 'npm run ui:validate'
15
+ });
16
+
17
+ export const HANDOFF_DOCS = Object.freeze([
18
+ 'docs/README.md',
19
+ 'docs/product-requirements.md',
20
+ 'docs/system-requirements.md',
21
+ 'docs/architecture-spec.md',
22
+ 'docs/user-stories.md',
23
+ 'docs/roadmap-mvp.md',
24
+ 'docs/local-minikube.md',
25
+ 'docs/install.md',
26
+ 'docs/ontology/README.md'
27
+ ]);
28
+
29
+ export function createKradleHandoffSummary(demo, { packageInfo = {}, generatedAt = new Date().toISOString() } = {}) {
30
+ const smoke = demo.smoke || { ok: true, assertions: [] };
31
+ return {
32
+ project: 'Kradle',
33
+ description: 'Kubernetes-native forge runtime with Argo CD and Kradle-managed repository hosting',
34
+ package: {
35
+ name: packageInfo.name || 'kradle',
36
+ version: packageInfo.version || '0.1.0',
37
+ private: packageInfo.private !== false
38
+ },
39
+ entrypoints: {
40
+ library: './src/index.js',
41
+ cli: './bin/kradle-demo.mjs',
42
+ runtimeServer: './bin/kradle-server.mjs',
43
+ webPreview: './public/index.html',
44
+ nextUi: './apps/web',
45
+ generatedSummary: './dist/kradle-summary.json',
46
+ lifecycleSnapshot: './dist/kradle-lifecycle.json',
47
+ helmChart: './charts/kradle',
48
+ minikubeSetup: './scripts/setup-minikube.mjs'
49
+ },
50
+ commands: { ...HANDOFF_COMMANDS },
51
+ docs: [...HANDOFF_DOCS],
52
+ components: demo.components || [],
53
+ lifecycle: demo.lifecycle || null,
54
+ resources: Object.fromEntries(Object.entries(demo.resources).map(([key, value]) => [key, value?.kind || 'workflow'])),
55
+ storage: demo.controlPlane.storageReport(),
56
+ excellentFlows: demo.ui.dashboard.excellentFlows,
57
+ agents: demo.agents ? {
58
+ stacks: demo.agents.stacks?.count || 0,
59
+ runs: demo.agents.runs?.count || 0,
60
+ activeRuns: demo.agents.runs?.active?.length || 0,
61
+ rules: demo.agents.rules?.count || 0,
62
+ sessions: demo.agents.sessions?.count || 0,
63
+ workspaces: demo.agents.workspaces?.count || 0,
64
+ pendingApprovals: demo.agents.approvals?.pending?.length || 0
65
+ } : null,
66
+ operations: { chartPackage: demo.operations.chartPackage, localSetup: demo.operations.localSetup },
67
+ releaseGates: demo.operations.releaseGates,
68
+ smoke: {
69
+ ok: smoke.ok,
70
+ assertions: smoke.assertions.map(([name, passed]) => ({ name, passed }))
71
+ },
72
+ generatedAt
73
+ };
74
+ }
75
+
76
+ export function formatHandoffSummary(summary) {
77
+ const lines = [
78
+ `${summary.project} ${summary.package.version} — ${summary.description}`,
79
+ '',
80
+ 'Entrypoints:',
81
+ ...Object.entries(summary.entrypoints).map(([name, target]) => `- ${name}: ${target}`),
82
+ '',
83
+ 'Commands:',
84
+ ...Object.entries(summary.commands).map(([name, command]) => `- ${name}: ${command}`),
85
+ '',
86
+ 'Storage boundary:',
87
+ `- etcd: ${summary.storage.etcd.join(', ')}`,
88
+ `- postgres: ${summary.storage.postgres.join(', ')}`,
89
+ '',
90
+ 'Excellent flows:',
91
+ ...summary.excellentFlows.map((flow) => `- ${flow}`),
92
+ '',
93
+ `Smoke: ${summary.smoke.ok ? 'pass' : 'fail'}`
94
+ ];
95
+ return `${lines.join('\n')}\n`;
96
+ }
97
+
98
+
@@ -0,0 +1,134 @@
1
+ import { execFile as execFileCallback } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ import { globalEventBus } from './event-bus.js';
4
+
5
+ const execFileAsync = promisify(execFileCallback);
6
+ const DEFAULT_TIMEOUT_MS = 3000;
7
+
8
+ function elapsed(startedAt) {
9
+ return Date.now() - startedAt;
10
+ }
11
+
12
+ function trimSlash(value) {
13
+ return String(value || '').replace(/\/+$/, '');
14
+ }
15
+
16
+ function redactSecretText(value) {
17
+ return String(value || '')
18
+ .replace(/([a-z][a-z0-9+.-]*:\/\/)([^:@/\s]+):([^@/\s]+)@/gi, '$1[redacted]:[redacted]@')
19
+ .replace(/sk-ant-[A-Za-z0-9_-]+/g, '[redacted]')
20
+ .replace(/sk-[A-Za-z0-9_-]{12,}/g, '[redacted]')
21
+ .replace(/(token|password|secret|api[_-]?key)=([^&\s]+)/gi, '$1=[redacted]');
22
+ }
23
+
24
+ function redactedError(error) {
25
+ return redactSecretText(error?.message || error || 'unknown error');
26
+ }
27
+
28
+ function redactedUrl(url) {
29
+ if (!url) return url;
30
+ try {
31
+ const parsed = new URL(url);
32
+ if (parsed.username) parsed.username = '[redacted]';
33
+ if (parsed.password) parsed.password = '[redacted]';
34
+ for (const key of [...parsed.searchParams.keys()]) {
35
+ if (/token|password|secret|api[_-]?key/i.test(key)) parsed.searchParams.set(key, '[redacted]');
36
+ }
37
+ return parsed.toString();
38
+ } catch {
39
+ return redactSecretText(url);
40
+ }
41
+ }
42
+
43
+ function sanitizeStatus(status) {
44
+ if (!status || typeof status !== 'object') return status;
45
+ return Object.fromEntries(Object.entries(status).map(([key, value]) => {
46
+ if (key === 'reason' || key === 'error' || key === 'url') return [key, redactSecretText(value)];
47
+ return [key, value];
48
+ }));
49
+ }
50
+
51
+ async function httpProbe({ name, url, fetchImpl, timeoutMs }) {
52
+ if (!url) return { name, status: 'not configured', reason: 'missing-url' };
53
+ const startedAt = Date.now();
54
+ try {
55
+ const signal = typeof AbortSignal?.timeout === 'function'
56
+ ? AbortSignal.timeout(timeoutMs)
57
+ : undefined;
58
+ const response = await fetchImpl(url, { signal });
59
+ return response?.ok
60
+ ? { name, status: 'ok', latencyMs: elapsed(startedAt), url: redactedUrl(url) }
61
+ : { name, status: 'error', reason: `http-${response?.status || 'unknown'}`, latencyMs: elapsed(startedAt), url: redactedUrl(url) };
62
+ } catch (error) {
63
+ return { name, status: 'error', reason: 'request-failed', latencyMs: elapsed(startedAt), error: redactedError(error), url: redactedUrl(url) };
64
+ }
65
+ }
66
+
67
+ async function kubernetesProbe({ env, execFileImpl, timeoutMs }) {
68
+ const startedAt = Date.now();
69
+ const command = env.KRADLE_KUBECTL || 'kubectl';
70
+ try {
71
+ const result = await execFileImpl(command, ['cluster-info'], { timeout: timeoutMs });
72
+ const output = [result?.stdout, result?.stderr].filter(Boolean).join('\n').trim();
73
+ return { status: 'ok', reason: 'cluster-info', latencyMs: elapsed(startedAt), context: output || null };
74
+ } catch (error) {
75
+ return { status: 'error', reason: 'cluster-info-failed', latencyMs: elapsed(startedAt), error: redactedError(error) };
76
+ }
77
+ }
78
+
79
+ function assistantProbe(env) {
80
+ const key = env.ANTHROPIC_API_KEY || env.KRADLE_ASSISTANT_API_KEY || '';
81
+ if (!key) return { status: 'not configured', reason: 'missing-key' };
82
+ if (/^sk-ant-[A-Za-z0-9_-]{12,}$/.test(key) || /^sk-[A-Za-z0-9_-]{12,}$/.test(key)) {
83
+ return { status: 'ok', reason: 'valid-format' };
84
+ }
85
+ return { status: 'error', reason: 'invalid-format' };
86
+ }
87
+
88
+ function eventTransportProbe(eventBus) {
89
+ try {
90
+ const status = sanitizeStatus(eventBus?.status?.());
91
+ if (!status) return { status: 'unknown', reason: 'no-event-bus' };
92
+ return {
93
+ status: status.status === 'ok' ? 'ok' : status.status || 'unknown',
94
+ ...status,
95
+ };
96
+ } catch (error) {
97
+ return { status: 'error', reason: 'status-failed', error: redactedError(error) };
98
+ }
99
+ }
100
+
101
+ export async function collectKradleHealthProbes(options = {}) {
102
+ const env = options.env || process.env;
103
+ const timeoutMs = Number(options.timeoutMs || env.KRADLE_HEALTH_TIMEOUT_MS || env.KRADLE_KUBECTL_TIMEOUT_MS || DEFAULT_TIMEOUT_MS);
104
+ const fetchImpl = options.fetchImpl || globalThis.fetch;
105
+ const execFileImpl = options.execFileImpl || execFileAsync;
106
+ const eventBus = options.eventBus || globalEventBus;
107
+ const giteaUrl = env.KRADLE_GITEA_HTTP_URL ? `${trimSlash(env.KRADLE_GITEA_HTTP_URL)}/api/v1/version` : '';
108
+ const agentMuxBase = env.AGENT_MUX_URL || env.AGENT_GATEWAY_URL || '';
109
+ const agentMuxUrl = agentMuxBase ? `${trimSlash(agentMuxBase)}/healthz` : '';
110
+ const controllerUrl = env.KRADLE_CONTROLLER_URL ? `${trimSlash(env.KRADLE_CONTROLLER_URL)}/healthz` : '';
111
+
112
+ const [kubernetes, gitea, agentMux, controller, assistant, eventTransport] = await Promise.all([
113
+ kubernetesProbe({ env, execFileImpl, timeoutMs }),
114
+ httpProbe({ name: 'gitea', url: giteaUrl, fetchImpl, timeoutMs }),
115
+ httpProbe({ name: 'agentMux', url: agentMuxUrl, fetchImpl, timeoutMs }),
116
+ httpProbe({ name: 'controller', url: controllerUrl, fetchImpl, timeoutMs }),
117
+ Promise.resolve(assistantProbe(env)),
118
+ Promise.resolve(eventTransportProbe(eventBus)),
119
+ ]);
120
+
121
+ return {
122
+ kubernetes,
123
+ gitea,
124
+ agentMux,
125
+ agentGateway: agentMux,
126
+ controller,
127
+ assistant,
128
+ eventTransport,
129
+ };
130
+ }
131
+
132
+ export function healthStatusValue(probe) {
133
+ return probe?.status || 'unknown';
134
+ }
@@ -0,0 +1,63 @@
1
+ import { createHmac } from 'node:crypto';
2
+ import { createResource } from './resource-model.js';
3
+
4
+ export class WebhookBus {
5
+ constructor({ controlPlane, secret = 'kradle-dev-secret' }) { this.controlPlane = controlPlane; this.secret = secret; }
6
+
7
+ subscribe({ name, namespace = 'kradle-org-default', organizationRef = 'default', url, events = ['pullrequest.created'], mode = 'active' }, user) {
8
+ return this.controlPlane.create(createResource('WebhookSubscription', { name, namespace }, {
9
+ organizationRef, url, events, signing: { algorithm: 'hmac-sha256', secretRef: `${name}-secret` }, mode
10
+ }, { ready: true }), user);
11
+ }
12
+
13
+ sign(payload) { return createHmac('sha256', this.secret).update(JSON.stringify(payload)).digest('hex'); }
14
+
15
+ deliver({ subscriptionName, namespace = 'kradle-org-default', organizationRef = 'default', eventType, payload, response = { status: 202, body: 'accepted' } }, user) {
16
+ const subscription = this.controlPlane.get('WebhookSubscription', namespace, subscriptionName);
17
+ if (!subscription) throw new Error(`WebhookSubscription ${subscriptionName} not found`);
18
+ if (!subscription.spec.events.includes(eventType)) throw new Error(`${subscriptionName} does not subscribe to ${eventType}`);
19
+ const delivery = createResource('WebhookDelivery', {
20
+ name: `${subscriptionName}-${Date.now()}-${this.controlPlane.list('WebhookDelivery', { namespace }).items.length + 1}`,
21
+ namespace,
22
+ labels: { subscription: subscriptionName, eventType }
23
+ }, {
24
+ organizationRef: subscription.spec.organizationRef || organizationRef,
25
+ subscription: subscriptionName,
26
+ url: subscription.spec.url,
27
+ eventType,
28
+ payload,
29
+ signature: this.sign(payload),
30
+ replayOf: payload.replayOf || null
31
+ }, {
32
+ phase: response.status >= 200 && response.status < 300 ? 'Delivered' : 'Failed',
33
+ response,
34
+ attempts: 1
35
+ });
36
+ return this.controlPlane.create(delivery, user);
37
+ }
38
+
39
+ replay(delivery, user) {
40
+ return this.deliver({
41
+ subscriptionName: delivery.spec.subscription,
42
+ namespace: delivery.metadata.namespace,
43
+ organizationRef: delivery.spec.organizationRef,
44
+ eventType: delivery.spec.eventType,
45
+ payload: { ...delivery.spec.payload, replayOf: delivery.metadata.name },
46
+ response: { status: 202, body: 'replayed' }
47
+ }, user);
48
+ }
49
+
50
+ inspect(delivery) {
51
+ return {
52
+ name: delivery.metadata.name,
53
+ subscription: delivery.spec.subscription,
54
+ eventType: delivery.spec.eventType,
55
+ phase: delivery.status.phase,
56
+ attempts: delivery.status.attempts,
57
+ response: delivery.status.response,
58
+ signature: delivery.spec.signature,
59
+ replayOf: delivery.spec.replayOf,
60
+ replayable: Boolean(delivery.spec.subscription && delivery.spec.eventType)
61
+ };
62
+ }
63
+ }
@@ -0,0 +1,117 @@
1
+ export const HOOKS_LIFECYCLE_BOUNDARY = {
2
+ role: 'hooks-lifecycle',
3
+ scope: 'Lifecycle event emission at key dispatch flow transitions',
4
+ owns: ['lifecycle event emission'],
5
+ delegatesTo: ['event-bus'],
6
+ mustNotOwn: ['event storage', 'dispatch logic', 'session management']
7
+ };
8
+
9
+ /**
10
+ * Create a hooks lifecycle emitter that broadcasts structured events to an event bus.
11
+ * @param {{ emit: Function }} eventBus
12
+ * @returns {object}
13
+ */
14
+ export function createHooksLifecycleEmitter(eventBus) {
15
+ if (!eventBus || typeof eventBus.emit !== 'function') {
16
+ throw new Error('createHooksLifecycleEmitter requires an eventBus with an emit method');
17
+ }
18
+
19
+ return {
20
+ emitRunCreated(run) {
21
+ eventBus.emit({
22
+ type: 'hook',
23
+ event: 'RUN_CREATED',
24
+ runId: run.metadata?.name,
25
+ stack: run.spec?.agentStack,
26
+ timestamp: new Date().toISOString(),
27
+ });
28
+ },
29
+
30
+ emitRunCompleted(run, result) {
31
+ const startedAt = run.status?.queuedAt ? new Date(run.status.queuedAt) : null;
32
+ const now = new Date();
33
+ const duration = startedAt ? now - startedAt : null;
34
+ eventBus.emit({
35
+ type: 'hook',
36
+ event: 'RUN_COMPLETED',
37
+ runId: run.metadata?.name,
38
+ result: result?.phase || 'Completed',
39
+ duration,
40
+ timestamp: now.toISOString(),
41
+ });
42
+ },
43
+
44
+ emitStepStarted(run, step) {
45
+ eventBus.emit({
46
+ type: 'hook',
47
+ event: 'STEP_STARTED',
48
+ runId: run.metadata?.name,
49
+ step,
50
+ timestamp: new Date().toISOString(),
51
+ });
52
+ },
53
+
54
+ emitStepEnded(run, step, result) {
55
+ eventBus.emit({
56
+ type: 'hook',
57
+ event: 'STEP_ENDED',
58
+ runId: run.metadata?.name,
59
+ step,
60
+ result,
61
+ timestamp: new Date().toISOString(),
62
+ });
63
+ },
64
+
65
+ emitApprovalRequested(approval) {
66
+ eventBus.emit({
67
+ type: 'hook',
68
+ event: 'APPROVAL_REQUESTED',
69
+ approvalId: approval.metadata?.name,
70
+ action: approval.spec?.action,
71
+ requestedBy: approval.spec?.requestedBy,
72
+ timestamp: new Date().toISOString(),
73
+ });
74
+ },
75
+
76
+ emitApprovalDecided(approval) {
77
+ eventBus.emit({
78
+ type: 'hook',
79
+ event: 'APPROVAL_DECIDED',
80
+ approvalId: approval.metadata?.name,
81
+ action: approval.spec?.action,
82
+ decision: approval.status?.decision,
83
+ timestamp: new Date().toISOString(),
84
+ });
85
+ },
86
+
87
+ emitWorkspaceProvisioned(workspace) {
88
+ eventBus.emit({
89
+ type: 'hook',
90
+ event: 'WORKSPACE_PROVISIONED',
91
+ workspaceId: workspace.metadata?.name,
92
+ repository: workspace.spec?.repository,
93
+ timestamp: new Date().toISOString(),
94
+ });
95
+ },
96
+
97
+ emitSessionStarted(session) {
98
+ eventBus.emit({
99
+ type: 'hook',
100
+ event: 'SESSION_STARTED',
101
+ sessionId: session.sessionId || session.metadata?.name,
102
+ runId: session.runId,
103
+ timestamp: new Date().toISOString(),
104
+ });
105
+ },
106
+
107
+ emitSessionEnded(session) {
108
+ eventBus.emit({
109
+ type: 'hook',
110
+ event: 'SESSION_ENDED',
111
+ sessionId: session.sessionId || session.metadata?.name,
112
+ runId: session.runId,
113
+ timestamp: new Date().toISOString(),
114
+ });
115
+ },
116
+ };
117
+ }