ndomo 0.1.0

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 (247) hide show
  1. package/.bun-version +1 -0
  2. package/.dockerignore +79 -0
  3. package/.editorconfig +18 -0
  4. package/.env.example +19 -0
  5. package/.github/CODEOWNERS +8 -0
  6. package/.github/ISSUE_TEMPLATE/bug_report.yml +62 -0
  7. package/.github/ISSUE_TEMPLATE/config.yml +2 -0
  8. package/.github/ISSUE_TEMPLATE/feature_request.yml +34 -0
  9. package/.github/dependabot.yml +36 -0
  10. package/.github/pull_request_template.md +24 -0
  11. package/.github/release.yml +30 -0
  12. package/.github/workflows/gitleaks.yml +28 -0
  13. package/.github/workflows/release-please.yml +27 -0
  14. package/.github/workflows/smoke.yml +29 -0
  15. package/.husky/commit-msg +1 -0
  16. package/CHANGELOG.md +114 -0
  17. package/Dockerfile +32 -0
  18. package/README.es.md +174 -0
  19. package/README.md +187 -0
  20. package/agents/chronicler.md +98 -0
  21. package/agents/ci-smith.md +136 -0
  22. package/agents/craftsman.md +341 -0
  23. package/agents/deploy-smith.md +138 -0
  24. package/agents/foreman.md +377 -0
  25. package/agents/go-smith.md +164 -0
  26. package/agents/guild.md +188 -0
  27. package/agents/inspector.md +83 -0
  28. package/agents/js-smith.md +127 -0
  29. package/agents/ops-scout.md +173 -0
  30. package/agents/painter.md +200 -0
  31. package/agents/python-smith.md +120 -0
  32. package/agents/ranger.md +307 -0
  33. package/agents/release-smith.md +165 -0
  34. package/agents/rust-smith.md +159 -0
  35. package/agents/sage.md +178 -0
  36. package/agents/scout.md +144 -0
  37. package/agents/scribe.md +156 -0
  38. package/agents/smith.md +201 -0
  39. package/agents/vue-smith.md +155 -0
  40. package/agents/warden.md +216 -0
  41. package/agents/zig-smith.md +156 -0
  42. package/bin/ndomo-analyses.ts +4 -0
  43. package/bin/ndomo-status.ts +4 -0
  44. package/biome.json +57 -0
  45. package/bun.lock +514 -0
  46. package/commitlint.config.js +3 -0
  47. package/config/ndomo.config.json +258 -0
  48. package/config/ndomo.schema.json +166 -0
  49. package/docs/agents.md +375 -0
  50. package/docs/bugs/plan-create-orphan-fk.md +131 -0
  51. package/docs/bugs/task_create_batch-order-index-collision.md +158 -0
  52. package/docs/configuration.md +276 -0
  53. package/docs/database.md +364 -0
  54. package/docs/features/feature-flexible-builder-v1.md +724 -0
  55. package/docs/features/feature-flexible-builder-v2.md +882 -0
  56. package/docs/features/feature-flexible-builder.md +974 -0
  57. package/docs/http-server.md +244 -0
  58. package/docs/installation.md +259 -0
  59. package/docs/integrations.md +129 -0
  60. package/docs/operations/anti-pattern-sub-agent-verify-2026-06-21.md +32 -0
  61. package/docs/operations/audit-v1.md +417 -0
  62. package/docs/operations/audit-v2.md +197 -0
  63. package/docs/operations/audit-v3.md +306 -0
  64. package/docs/operations/db-optimize-foundations.md +123 -0
  65. package/docs/operations/verify-gate-architecture.md +82 -0
  66. package/docs/workflows.md +448 -0
  67. package/opencode.json +5 -0
  68. package/package.json +65 -0
  69. package/release-please-config.json +11 -0
  70. package/scripts/dev-bust-cache.sh +164 -0
  71. package/scripts/install.sh +688 -0
  72. package/scripts/smoke-e2e.ts +704 -0
  73. package/scripts/smoke-hot.ts +417 -0
  74. package/scripts/smoke-http.sh +228 -0
  75. package/scripts/smoke-v4.ts +256 -0
  76. package/scripts/smoke-v5.ts +397 -0
  77. package/scripts/smoke.sh +9 -0
  78. package/scripts/uninstall.sh +224 -0
  79. package/skills/api-security-best-practices/SKILL.md +915 -0
  80. package/skills/bash-scripting/SKILL.md +201 -0
  81. package/skills/bun/SKILL.md +313 -0
  82. package/skills/cavecrew/SKILL.md +82 -0
  83. package/skills/caveman/SKILL.md +74 -0
  84. package/skills/caveman-review/README.md +33 -0
  85. package/skills/caveman-review/SKILL.md +55 -0
  86. package/skills/find-skills/SKILL.md +142 -0
  87. package/skills/frontend-design/LICENSE.txt +177 -0
  88. package/skills/frontend-design/SKILL.md +55 -0
  89. package/skills/golang-patterns/SKILL.md +674 -0
  90. package/skills/golang-security/SKILL.md +185 -0
  91. package/skills/golang-security/evals/evals.json +595 -0
  92. package/skills/golang-security/references/architecture.md +268 -0
  93. package/skills/golang-security/references/checklist.md +80 -0
  94. package/skills/golang-security/references/cookies.md +200 -0
  95. package/skills/golang-security/references/cryptography.md +424 -0
  96. package/skills/golang-security/references/filesystem.md +285 -0
  97. package/skills/golang-security/references/injection.md +315 -0
  98. package/skills/golang-security/references/logging.md +163 -0
  99. package/skills/golang-security/references/memory-safety.md +241 -0
  100. package/skills/golang-security/references/network.md +253 -0
  101. package/skills/golang-security/references/secrets.md +189 -0
  102. package/skills/golang-security/references/third-party.md +159 -0
  103. package/skills/golang-security/references/threat-modeling.md +189 -0
  104. package/skills/golang-testing/SKILL.md +720 -0
  105. package/skills/grill-me/SKILL.md +7 -0
  106. package/skills/javascript-testing-patterns/SKILL.md +537 -0
  107. package/skills/javascript-testing-patterns/references/advanced-testing-patterns.md +513 -0
  108. package/skills/modern-javascript-patterns/SKILL.md +43 -0
  109. package/skills/modern-javascript-patterns/references/advanced-patterns.md +487 -0
  110. package/skills/modern-javascript-patterns/references/details.md +457 -0
  111. package/skills/python-anti-patterns/SKILL.md +349 -0
  112. package/skills/python-design-patterns/SKILL.md +85 -0
  113. package/skills/python-design-patterns/references/details.md +353 -0
  114. package/skills/python-error-handling/SKILL.md +193 -0
  115. package/skills/python-error-handling/references/details.md +171 -0
  116. package/skills/python-testing-patterns/SKILL.md +278 -0
  117. package/skills/python-testing-patterns/references/advanced-patterns.md +411 -0
  118. package/skills/python-testing-patterns/references/details.md +349 -0
  119. package/skills/rust-patterns/SKILL.md +500 -0
  120. package/skills/rust-testing/SKILL.md +501 -0
  121. package/skills/security-review/SKILL.md +504 -0
  122. package/skills/security-review/cloud-infrastructure-security.md +361 -0
  123. package/skills/vue-best-practices/SKILL.md +154 -0
  124. package/skills/vue-best-practices/references/animation-class-based-technique.md +254 -0
  125. package/skills/vue-best-practices/references/animation-state-driven-technique.md +291 -0
  126. package/skills/vue-best-practices/references/component-async.md +97 -0
  127. package/skills/vue-best-practices/references/component-data-flow.md +307 -0
  128. package/skills/vue-best-practices/references/component-fallthrough-attrs.md +174 -0
  129. package/skills/vue-best-practices/references/component-keep-alive.md +137 -0
  130. package/skills/vue-best-practices/references/component-slots.md +216 -0
  131. package/skills/vue-best-practices/references/component-suspense.md +228 -0
  132. package/skills/vue-best-practices/references/component-teleport.md +108 -0
  133. package/skills/vue-best-practices/references/component-transition-group.md +128 -0
  134. package/skills/vue-best-practices/references/component-transition.md +125 -0
  135. package/skills/vue-best-practices/references/composables.md +290 -0
  136. package/skills/vue-best-practices/references/directives.md +162 -0
  137. package/skills/vue-best-practices/references/perf-avoid-component-abstraction-in-lists.md +159 -0
  138. package/skills/vue-best-practices/references/perf-v-once-v-memo-directives.md +182 -0
  139. package/skills/vue-best-practices/references/perf-virtualize-large-lists.md +187 -0
  140. package/skills/vue-best-practices/references/plugins.md +166 -0
  141. package/skills/vue-best-practices/references/reactivity.md +344 -0
  142. package/skills/vue-best-practices/references/render-functions.md +201 -0
  143. package/skills/vue-best-practices/references/sfc.md +310 -0
  144. package/skills/vue-best-practices/references/state-management.md +135 -0
  145. package/skills/vue-best-practices/references/updated-hook-performance.md +187 -0
  146. package/skills/vue-pinia-best-practices/SKILL.md +21 -0
  147. package/skills/vue-pinia-best-practices/reference/pinia-no-active-pinia-error.md +248 -0
  148. package/skills/vue-pinia-best-practices/reference/pinia-setup-store-return-all-state.md +227 -0
  149. package/skills/vue-pinia-best-practices/reference/pinia-store-destructuring-breaks-reactivity.md +193 -0
  150. package/skills/vue-pinia-best-practices/reference/state-url-for-ephemeral-filters.md +238 -0
  151. package/skills/vue-pinia-best-practices/reference/state-use-pinia-for-large-apps.md +262 -0
  152. package/skills/vue-pinia-best-practices/reference/store-method-binding-parentheses.md +191 -0
  153. package/skills/zig-0.16/SKILL.md +840 -0
  154. package/skills/zig-0.16/scripts/check-zig-version.sh +21 -0
  155. package/src/cli/analyses.ts +280 -0
  156. package/src/cli/index.ts +108 -0
  157. package/src/cli/serve.ts +192 -0
  158. package/src/cli/smoke.ts +131 -0
  159. package/src/cli/status.test.ts +204 -0
  160. package/src/cli/status.ts +263 -0
  161. package/src/cli/vacuum.test.ts +82 -0
  162. package/src/cli/vacuum.ts +96 -0
  163. package/src/config/schema.test.ts +88 -0
  164. package/src/config/schema.ts +64 -0
  165. package/src/db/analyses-migration.test.ts +210 -0
  166. package/src/db/analyses.test.ts +466 -0
  167. package/src/db/analyses.ts +375 -0
  168. package/src/db/auto-checkpoint.ts +131 -0
  169. package/src/db/client.test.ts +129 -0
  170. package/src/db/client.ts +55 -0
  171. package/src/db/fts-escape.ts +20 -0
  172. package/src/db/incidents.test.ts +201 -0
  173. package/src/db/incidents.ts +93 -0
  174. package/src/db/index.ts +86 -0
  175. package/src/db/migrations-v13.test.ts +141 -0
  176. package/src/db/migrations-v8.test.ts +301 -0
  177. package/src/db/migrations.ts +147 -0
  178. package/src/db/plan-archive.test.ts +180 -0
  179. package/src/db/plan-archive.ts +274 -0
  180. package/src/db/plan-create.test.ts +276 -0
  181. package/src/db/plan-create.ts +78 -0
  182. package/src/db/plan-files.test.ts +289 -0
  183. package/src/db/plan-update-status.ts +287 -0
  184. package/src/db/plans.test.ts +490 -0
  185. package/src/db/plans.ts +534 -0
  186. package/src/db/resolve-project-dir.test.ts +143 -0
  187. package/src/db/resolve-project-dir.ts +75 -0
  188. package/src/db/rollbacks.test.ts +150 -0
  189. package/src/db/rollbacks.ts +67 -0
  190. package/src/db/schema.ts +907 -0
  191. package/src/db/sessions.test.ts +80 -0
  192. package/src/db/sessions.ts +135 -0
  193. package/src/db/shutdown.test.ts +147 -0
  194. package/src/db/shutdown.ts +45 -0
  195. package/src/db/tasks.test.ts +921 -0
  196. package/src/db/tasks.ts +747 -0
  197. package/src/db/types.ts +619 -0
  198. package/src/http/__tests__/auth.test.ts +196 -0
  199. package/src/http/__tests__/routes.test.ts +465 -0
  200. package/src/http/__tests__/sse.test.ts +317 -0
  201. package/src/http/auth.ts +72 -0
  202. package/src/http/middleware/cors.ts +53 -0
  203. package/src/http/middleware/security-headers.ts +21 -0
  204. package/src/http/routes/events.ts +112 -0
  205. package/src/http/routes/health.ts +51 -0
  206. package/src/http/routes/plans.ts +66 -0
  207. package/src/http/routes/sessions.ts +50 -0
  208. package/src/http/routes/tasks.ts +60 -0
  209. package/src/http/server.ts +95 -0
  210. package/src/http/sse.ts +116 -0
  211. package/src/index.ts +37 -0
  212. package/src/lib.ts +65 -0
  213. package/src/mem/scoped.ts +65 -0
  214. package/src/orchestrator/background.test.ts +268 -0
  215. package/src/orchestrator/background.ts +293 -0
  216. package/src/orchestrator/memory-hook.ts +182 -0
  217. package/src/orchestrator/reconciler.ts +123 -0
  218. package/src/orchestrator/scheduler.test.ts +300 -0
  219. package/src/orchestrator/scheduler.ts +243 -0
  220. package/src/plugin.test.ts +2574 -0
  221. package/src/plugin.ts +1690 -0
  222. package/src/sdk/client.ts +66 -0
  223. package/src/worktrees/manager.ts +236 -0
  224. package/src/worktrees/state.ts +87 -0
  225. package/tests/integration/ranger-flow.test.ts +257 -0
  226. package/tools/analysis_archive.ts +28 -0
  227. package/tools/analysis_create.ts +55 -0
  228. package/tools/analysis_get.ts +33 -0
  229. package/tools/analysis_link_plan.ts +44 -0
  230. package/tools/analysis_list.ts +48 -0
  231. package/tools/analysis_search.ts +36 -0
  232. package/tools/analysis_update.ts +44 -0
  233. package/tools/plan_approve.ts +31 -0
  234. package/tools/plan_create.ts +58 -0
  235. package/tools/plan_get.ts +40 -0
  236. package/tools/plan_list.ts +37 -0
  237. package/tools/plan_search.ts +34 -0
  238. package/tools/plan_update_status.ts +71 -0
  239. package/tools/session_checkpoint.ts +31 -0
  240. package/tools/session_end.ts +26 -0
  241. package/tools/session_start.ts +43 -0
  242. package/tools/task_create_batch.ts +70 -0
  243. package/tools/task_list.ts +35 -0
  244. package/tools/task_next_for_agent.ts +30 -0
  245. package/tools/task_search.ts +34 -0
  246. package/tools/task_update_status.ts +37 -0
  247. package/tsconfig.json +31 -0
@@ -0,0 +1,50 @@
1
+ // ─── Sessions Routes ──────────────────────────────────────────────────────────
2
+ /**
3
+ * GET /api/sessions — list sessions (optional: planId, limit)
4
+ * GET /api/sessions/active — list active sessions (endedAt === null)
5
+ * GET /api/sessions/:id — get single session by id
6
+ */
7
+ import type { Database } from "bun:sqlite";
8
+ import { Elysia, t } from "elysia";
9
+ import { getSession, listSessions } from "../../db/sessions.ts";
10
+
11
+ export function sessionsRoute(db: Database) {
12
+ return new Elysia({ name: "sessions" })
13
+ .get(
14
+ "/api/sessions",
15
+ async ({ query }) => {
16
+ const opts: { planId?: string; limit?: number } = {};
17
+ if (query.planId) opts.planId = query.planId;
18
+ if (query.limit) opts.limit = query.limit;
19
+ return listSessions(db, opts);
20
+ },
21
+ {
22
+ query: t.Object({
23
+ planId: t.Optional(t.String()),
24
+ limit: t.Optional(t.Number({ minimum: 1, maximum: 100 })),
25
+ }),
26
+ },
27
+ )
28
+ .get(
29
+ "/api/sessions/active",
30
+ async () => {
31
+ // Fetch all recent sessions and filter active (endedAt === null)
32
+ const all = listSessions(db, { limit: 20 });
33
+ return all.filter((s) => s.endedAt === null);
34
+ },
35
+ )
36
+ .get(
37
+ "/api/sessions/:id",
38
+ async ({ params: { id }, set }) => {
39
+ const session = getSession(db, id);
40
+ if (!session) {
41
+ set.status = 404;
42
+ return { error: "not_found", message: `session ${id} not found` };
43
+ }
44
+ return session;
45
+ },
46
+ {
47
+ params: t.Object({ id: t.String() }),
48
+ },
49
+ );
50
+ }
@@ -0,0 +1,60 @@
1
+ // ─── Tasks Routes ─────────────────────────────────────────────────────────────
2
+ /**
3
+ * GET /api/tasks — list tasks by planId (required), optional: status
4
+ * GET /api/tasks/search — FTS5 search (required: q, optional: limit)
5
+ * GET /api/tasks/:id — get single task by id
6
+ */
7
+ import type { Database } from "bun:sqlite";
8
+ import { Elysia, t } from "elysia";
9
+ import { getTask, listTasksByPlan, searchTasks } from "../../db/tasks.ts";
10
+ import type { TaskStatus } from "../../db/types.ts";
11
+
12
+ const TaskStatusValues = ["pending", "running", "done", "failed", "blocked"] as const;
13
+
14
+ export function tasksRoute(db: Database) {
15
+ return new Elysia({ name: "tasks" })
16
+ .get(
17
+ "/api/tasks",
18
+ async ({ query, set }) => {
19
+ if (!query.planId) {
20
+ set.status = 422;
21
+ return { error: "validation_error", message: "planId is required" };
22
+ }
23
+ const opts: { status?: TaskStatus } = {};
24
+ if (query.status) opts.status = query.status as TaskStatus;
25
+ return listTasksByPlan(db, query.planId, opts);
26
+ },
27
+ {
28
+ query: t.Object({
29
+ planId: t.String(),
30
+ status: t.Optional(t.UnionEnum(TaskStatusValues)),
31
+ }),
32
+ },
33
+ )
34
+ .get(
35
+ "/api/tasks/search",
36
+ async ({ query }) => {
37
+ return searchTasks(db, query.q, query.limit ?? 20);
38
+ },
39
+ {
40
+ query: t.Object({
41
+ q: t.String({ minLength: 1 }),
42
+ limit: t.Optional(t.Number({ minimum: 1, maximum: 100 })),
43
+ }),
44
+ },
45
+ )
46
+ .get(
47
+ "/api/tasks/:id",
48
+ async ({ params: { id }, set }) => {
49
+ const task = getTask(db, id);
50
+ if (!task) {
51
+ set.status = 404;
52
+ return { error: "not_found", message: `task ${id} not found` };
53
+ }
54
+ return task;
55
+ },
56
+ {
57
+ params: t.Object({ id: t.String() }),
58
+ },
59
+ );
60
+ }
@@ -0,0 +1,95 @@
1
+ // ─── HTTP Server Builder ──────────────────────────────────────────────────────
2
+ /**
3
+ * Builds and starts the Elysia HTTP server for ndomo.
4
+ *
5
+ * Encapsulation order:
6
+ * 1. securityHeaders (global)
7
+ * 2. corsMiddleware (global)
8
+ * 3. httpBasicAuth (global — applied to /api/* via guard, exempt /health)
9
+ * 4. health route (no auth)
10
+ * 5. /api/plans, /api/tasks, /api/sessions (auth required)
11
+ */
12
+ import type { Database } from "bun:sqlite";
13
+ import type { OpencodeClient } from "@opencode-ai/sdk/client";
14
+ import { Elysia } from "elysia";
15
+ import type { HttpConfig } from "../config/schema.ts";
16
+ import { httpBasicAuth } from "./auth.ts";
17
+ import { corsMiddleware } from "./middleware/cors.ts";
18
+ import { securityHeaders } from "./middleware/security-headers.ts";
19
+ import { eventsRoute } from "./routes/events.ts";
20
+ import { healthRoute } from "./routes/health.ts";
21
+ import { plansRoute } from "./routes/plans.ts";
22
+ import { sessionsRoute } from "./routes/sessions.ts";
23
+ import { tasksRoute } from "./routes/tasks.ts";
24
+
25
+ interface BuildHttpServerArgs {
26
+ db: Database;
27
+ httpConfig: HttpConfig;
28
+ /** OpenCode SDK client for SSE events. If null/undefined, /api/events returns 503. */
29
+ sdkClient?: OpencodeClient;
30
+ }
31
+
32
+ export interface HttpServerHandle {
33
+ port: number;
34
+ stop: () => Promise<void>;
35
+ }
36
+
37
+ /**
38
+ * Build the Elysia app with all middleware and routes.
39
+ * Returns the app instance and a listen function.
40
+ *
41
+ * @throws if port is out of range 1-65535
42
+ * @throws if auth is required but OPENCODE_SERVER_PASSWORD is not set
43
+ */
44
+ export async function buildHttpServer(args: BuildHttpServerArgs) {
45
+ const { db, httpConfig } = args;
46
+
47
+ // Validate port range
48
+ if (httpConfig.port < 1 || httpConfig.port > 65535) {
49
+ throw new Error(`Invalid HTTP port: ${httpConfig.port}. Must be 1-65535.`);
50
+ }
51
+
52
+ // If auth required but password not set, fail fast (do not start)
53
+ if (httpConfig.auth.required && !process.env.OPENCODE_SERVER_PASSWORD) {
54
+ throw new Error(
55
+ "HTTP auth is required but OPENCODE_SERVER_PASSWORD is not set. Cannot start server.",
56
+ );
57
+ }
58
+
59
+ // Build the protected API sub-app first
60
+ const apiProtected = new Elysia({ name: "api-protected" })
61
+ .use(httpBasicAuth(httpConfig))
62
+ .use(plansRoute(db))
63
+ .use(tasksRoute(db))
64
+ .use(sessionsRoute(db))
65
+ .use(eventsRoute(args.sdkClient ?? null));
66
+
67
+ // Compose the full app
68
+ const app = new Elysia({ name: "ndomo-http" })
69
+ .use(securityHeaders)
70
+ .use(corsMiddleware(httpConfig.cors.origins))
71
+ .use(healthRoute(db))
72
+ .use(apiProtected);
73
+
74
+ return {
75
+ app: app as unknown as Elysia,
76
+ listen: async (port: number): Promise<HttpServerHandle> => {
77
+ const server = app.listen(port);
78
+ return {
79
+ port,
80
+ stop: async () => {
81
+ server.stop();
82
+ },
83
+ };
84
+ },
85
+ };
86
+ }
87
+
88
+ /**
89
+ * Convenience: build + listen in one call.
90
+ * Returns the running server handle.
91
+ */
92
+ export async function startHttpServer(args: BuildHttpServerArgs): Promise<HttpServerHandle> {
93
+ const { listen } = await buildHttpServer(args);
94
+ return listen(args.httpConfig.port);
95
+ }
@@ -0,0 +1,116 @@
1
+ // ─── SSE Format Helpers ──────────────────────────────────────────────────────
2
+ /**
3
+ * Pure functions for encoding Server-Sent Events (SSE) format.
4
+ *
5
+ * SSE spec: https://html.spec.whatwg.org/multipage/server-sent-events.html
6
+ * Format: `field: value\n` lines terminated by `\n` (blank line = end of event).
7
+ *
8
+ * All functions return strings — callers encode to Uint8Array themselves.
9
+ */
10
+ import type { ReadableStreamDefaultController } from "node:stream/web";
11
+
12
+ /**
13
+ * Encode a single event in SSE format.
14
+ *
15
+ * @param opts.eventName - optional event name (maps to `event:` field)
16
+ * @param opts.data - JSON-serializable payload (multi-line data splits into multiple `data:` lines)
17
+ * @param opts.id - optional event ID (for Last-Event-ID resume)
18
+ * @returns SSE-formatted string ending with `\n\n`
19
+ */
20
+ export function formatSseEvent(opts: { eventName?: string; data: unknown; id?: string }): string {
21
+ const lines: string[] = [];
22
+ if (opts.eventName) lines.push(`event: ${opts.eventName}`);
23
+ if (opts.id) lines.push(`id: ${opts.id}`);
24
+ // Multi-line data must split each line into a separate `data:` field
25
+ const dataStr = JSON.stringify(opts.data);
26
+ for (const line of dataStr.split("\n")) lines.push(`data: ${line}`);
27
+ return `${lines.join("\n")}\n\n`;
28
+ }
29
+
30
+ /**
31
+ * SSE keepalive comment — prevents proxy/load-balancer timeouts.
32
+ * Format: `: keepalive\n\n` (colon = comment, ignored by clients).
33
+ */
34
+ export function formatKeepalive(): string {
35
+ return ": keepalive\n\n\n";
36
+ }
37
+
38
+ /**
39
+ * Writer interface for SSE streams.
40
+ *
41
+ * Abstracts the ReadableStream controller + abort signal into a
42
+ * clean API that handles encoding, error suppression, and cleanup.
43
+ */
44
+ export interface SseWriter {
45
+ /** Write a named event with JSON data. */
46
+ write(eventName: string | null, data: unknown): void;
47
+ /** Write a keepalive comment to prevent proxy timeouts. */
48
+ writeKeepalive(): void;
49
+ /** Close the stream. */
50
+ end(): void;
51
+ /** Register a cleanup callback invoked on abort. */
52
+ onCleanup(cb: () => void): void;
53
+ }
54
+
55
+ /**
56
+ * Wrap a Node ReadableStream controller + abort signal into a SseWriter.
57
+ *
58
+ * The writer suppresses all write errors (controller already closed)
59
+ * and runs registered cleanup callbacks when the abort signal fires.
60
+ *
61
+ * @param controller - the ReadableStream controller to write to
62
+ * @param signal - abort signal (from request.signal) for cleanup
63
+ */
64
+ export function createSseWriter(
65
+ controller: ReadableStreamDefaultController<Uint8Array>,
66
+ signal: AbortSignal,
67
+ ): SseWriter {
68
+ const encoder = new TextEncoder();
69
+ const cleanups: Array<() => void> = [];
70
+
71
+ signal.addEventListener("abort", () => {
72
+ for (const cb of cleanups) {
73
+ try {
74
+ cb();
75
+ } catch {
76
+ /* ignore cleanup errors */
77
+ }
78
+ }
79
+ try {
80
+ controller.close();
81
+ } catch {
82
+ /* already closed */
83
+ }
84
+ });
85
+
86
+ return {
87
+ write(eventName, data) {
88
+ try {
89
+ const chunk = formatSseEvent({
90
+ ...(eventName !== null ? { eventName } : {}),
91
+ data,
92
+ });
93
+ controller.enqueue(encoder.encode(chunk));
94
+ } catch {
95
+ // Controller closed — ignore
96
+ }
97
+ },
98
+ writeKeepalive() {
99
+ try {
100
+ controller.enqueue(encoder.encode(formatKeepalive()));
101
+ } catch {
102
+ /* ignore */
103
+ }
104
+ },
105
+ end() {
106
+ try {
107
+ controller.close();
108
+ } catch {
109
+ /* ignore */
110
+ }
111
+ },
112
+ onCleanup(cb) {
113
+ cleanups.push(cb);
114
+ },
115
+ };
116
+ }
package/src/index.ts ADDED
@@ -0,0 +1,37 @@
1
+ /**
2
+ * ndomo — OpenCode multi-agent plugin.
3
+ *
4
+ * Entry point. Import as a plugin in opencode.json:
5
+ * ```jsonc
6
+ * { "plugin": ["ndomo"] }
7
+ * ```
8
+ */
9
+
10
+ export type {
11
+ BackgroundTask,
12
+ DispatchOptions,
13
+ IntegrityReport,
14
+ MemoryEntry,
15
+ ReconciliationReport,
16
+ RoutingDecision,
17
+ TaskRequest,
18
+ TaskResult,
19
+ Worktree,
20
+ WorktreeState,
21
+ } from "./lib.ts";
22
+ export {
23
+ BackgroundDispatcher,
24
+ canRunParallel,
25
+ cavemanCompress,
26
+ createWorktree,
27
+ getProjectTag,
28
+ listActive,
29
+ memorySearchOptions,
30
+ prepareForMemory,
31
+ reconcileResults,
32
+ removeWorktree,
33
+ routeTask,
34
+ shouldStoreMemory,
35
+ verifyIntegrity,
36
+ } from "./lib.ts";
37
+ export { NdomoPlugin, type NdomoPluginOptions } from "./plugin.ts";
package/src/lib.ts ADDED
@@ -0,0 +1,65 @@
1
+ /**
2
+ * ndomo — OpenCode multi-agent orchestrator.
3
+ *
4
+ * Entry point that re-exports all public APIs.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * import { routeTask, BackgroundDispatcher, cavemanCompress } from "ndomo";
9
+ * ```
10
+ */
11
+
12
+ // Memory: scoped tag helpers
13
+ export {
14
+ getAllTags,
15
+ getProjectTag,
16
+ getUserTag,
17
+ memoryAddOptions,
18
+ memorySearchOptions,
19
+ } from "./mem/scoped.ts";
20
+
21
+ // Orchestrator: background dispatcher
22
+ export {
23
+ BackgroundDispatcher,
24
+ type BackgroundTask,
25
+ type DispatchOptions,
26
+ } from "./orchestrator/background.ts";
27
+ // Orchestrator: memory hooks
28
+ export {
29
+ cavemanCompress,
30
+ type MemoryEntry,
31
+ prepareForMemory,
32
+ shouldStoreMemory,
33
+ } from "./orchestrator/memory-hook.ts";
34
+ // Orchestrator: result reconciliation
35
+ export {
36
+ type ReconciliationReport,
37
+ reconcileResults,
38
+ type TaskResult,
39
+ } from "./orchestrator/reconciler.ts";
40
+ // Orchestrator: scheduler
41
+ export {
42
+ canRunParallel,
43
+ type RoutingDecision,
44
+ routeTask,
45
+ type TaskRequest,
46
+ } from "./orchestrator/scheduler.ts";
47
+
48
+ // Worktrees: git worktree manager
49
+ export {
50
+ cleanup,
51
+ createWorktree,
52
+ getWorktree,
53
+ listActive,
54
+ loadState,
55
+ removeWorktree,
56
+ saveState,
57
+ type Worktree,
58
+ type WorktreeState,
59
+ } from "./worktrees/manager.ts";
60
+
61
+ // Worktrees: integrity verification
62
+ export {
63
+ type IntegrityReport,
64
+ verifyIntegrity,
65
+ } from "./worktrees/state.ts";
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Project-scoped memory helpers wrapping opencode-mem/tags.
3
+ * Provides convenience functions for tag resolution and memory operations.
4
+ */
5
+
6
+ import { getProjectTagInfo, getTags, getUserTagInfo } from "opencode-mem/tags";
7
+
8
+ /**
9
+ * Get the project tag for the current (or specified) working directory.
10
+ *
11
+ * @param cwd - Working directory. Defaults to process.cwd().
12
+ * @returns Project tag string (e.g. "project:ndomo:abc123").
13
+ */
14
+ export function getProjectTag(cwd?: string): string {
15
+ return getProjectTagInfo(cwd ?? process.cwd()).tag;
16
+ }
17
+
18
+ /**
19
+ * Get the user tag for the current system user.
20
+ *
21
+ * @returns User tag string (e.g. "user:nico:xyz789").
22
+ */
23
+ export function getUserTag(): string {
24
+ return getUserTagInfo().tag;
25
+ }
26
+
27
+ /**
28
+ * Get both user and project tags for a directory.
29
+ *
30
+ * @param cwd - Working directory. Defaults to process.cwd().
31
+ * @returns Object with user and project TagInfo.
32
+ */
33
+ export function getAllTags(cwd?: string) {
34
+ return getTags(cwd ?? process.cwd());
35
+ }
36
+
37
+ /**
38
+ * Build options object for a memory search operation.
39
+ *
40
+ * @param query - Search query text.
41
+ * @param scope - Search scope: "project" (default) or "all-projects".
42
+ * @returns Options object for the opencode-mem search API.
43
+ */
44
+ export function memorySearchOptions(query: string, scope: "project" | "all-projects" = "project") {
45
+ return {
46
+ mode: "search" as const,
47
+ query,
48
+ scope,
49
+ };
50
+ }
51
+
52
+ /**
53
+ * Build options object for a memory add operation.
54
+ *
55
+ * @param content - Content to store in memory.
56
+ * @param topic - Topic/category for the memory entry.
57
+ * @returns Options object for the opencode-mem add API.
58
+ */
59
+ export function memoryAddOptions(content: string, topic: string) {
60
+ return {
61
+ mode: "add" as const,
62
+ content,
63
+ topic,
64
+ };
65
+ }