vibeman 0.0.0 → 0.0.1

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 (220) hide show
  1. package/README.md +12 -0
  2. package/dist/index.js +116 -0
  3. package/dist/runtime/api/.tsbuildinfo +1 -0
  4. package/dist/runtime/api/agent/agent-service.d.ts +226 -0
  5. package/dist/runtime/api/agent/agent-service.js +901 -0
  6. package/dist/runtime/api/agent/ai-providers/claude-code-adapter.d.ts +61 -0
  7. package/dist/runtime/api/agent/ai-providers/claude-code-adapter.js +373 -0
  8. package/dist/runtime/api/agent/ai-providers/codex-cli-provider.d.ts +34 -0
  9. package/dist/runtime/api/agent/ai-providers/codex-cli-provider.js +281 -0
  10. package/dist/runtime/api/agent/ai-providers/index.d.ts +9 -0
  11. package/dist/runtime/api/agent/ai-providers/index.js +7 -0
  12. package/dist/runtime/api/agent/ai-providers/types.d.ts +180 -0
  13. package/dist/runtime/api/agent/ai-providers/types.js +5 -0
  14. package/dist/runtime/api/agent/codex-cli-provider.test.d.ts +1 -0
  15. package/dist/runtime/api/agent/codex-cli-provider.test.js +88 -0
  16. package/dist/runtime/api/agent/core-agent-service.d.ts +119 -0
  17. package/dist/runtime/api/agent/core-agent-service.js +267 -0
  18. package/dist/runtime/api/agent/parsers.d.ts +15 -0
  19. package/dist/runtime/api/agent/parsers.js +241 -0
  20. package/dist/runtime/api/agent/prompt-service.d.ts +17 -0
  21. package/dist/runtime/api/agent/prompt-service.js +340 -0
  22. package/dist/runtime/api/agent/routing-policy.d.ts +188 -0
  23. package/dist/runtime/api/agent/routing-policy.js +246 -0
  24. package/dist/runtime/api/api/router-helpers.d.ts +32 -0
  25. package/dist/runtime/api/api/router-helpers.js +31 -0
  26. package/dist/runtime/api/api/routers/ai.d.ts +188 -0
  27. package/dist/runtime/api/api/routers/ai.js +410 -0
  28. package/dist/runtime/api/api/routers/executions.d.ts +98 -0
  29. package/dist/runtime/api/api/routers/executions.js +103 -0
  30. package/dist/runtime/api/api/routers/git.d.ts +45 -0
  31. package/dist/runtime/api/api/routers/git.js +35 -0
  32. package/dist/runtime/api/api/routers/settings.d.ts +139 -0
  33. package/dist/runtime/api/api/routers/settings.js +113 -0
  34. package/dist/runtime/api/api/routers/tasks.d.ts +141 -0
  35. package/dist/runtime/api/api/routers/tasks.js +238 -0
  36. package/dist/runtime/api/api/routers/workflows.d.ts +268 -0
  37. package/dist/runtime/api/api/routers/workflows.js +308 -0
  38. package/dist/runtime/api/api/routers/worktrees.d.ts +102 -0
  39. package/dist/runtime/api/api/routers/worktrees.js +80 -0
  40. package/dist/runtime/api/api/trpc.d.ts +118 -0
  41. package/dist/runtime/api/api/trpc.js +34 -0
  42. package/dist/runtime/api/index.d.ts +9 -0
  43. package/dist/runtime/api/index.js +125 -0
  44. package/dist/runtime/api/lib/id-generator.d.ts +70 -0
  45. package/dist/runtime/api/lib/id-generator.js +123 -0
  46. package/dist/runtime/api/lib/image-paste-drop-extension.d.ts +26 -0
  47. package/dist/runtime/api/lib/image-paste-drop-extension.js +125 -0
  48. package/dist/runtime/api/lib/logger.d.ts +11 -0
  49. package/dist/runtime/api/lib/logger.js +188 -0
  50. package/dist/runtime/api/lib/markdown-utils.d.ts +8 -0
  51. package/dist/runtime/api/lib/markdown-utils.js +282 -0
  52. package/dist/runtime/api/lib/markdown-utils.test.d.ts +1 -0
  53. package/dist/runtime/api/lib/markdown-utils.test.js +348 -0
  54. package/dist/runtime/api/lib/server/agent-service-singleton.d.ts +6 -0
  55. package/dist/runtime/api/lib/server/agent-service-singleton.js +27 -0
  56. package/dist/runtime/api/lib/server/git-service-singleton.d.ts +6 -0
  57. package/dist/runtime/api/lib/server/git-service-singleton.js +47 -0
  58. package/dist/runtime/api/lib/server/project-root.d.ts +2 -0
  59. package/dist/runtime/api/lib/server/project-root.js +38 -0
  60. package/dist/runtime/api/lib/server/task-service-singleton.d.ts +7 -0
  61. package/dist/runtime/api/lib/server/task-service-singleton.js +58 -0
  62. package/dist/runtime/api/lib/server/vibing-orchestrator-singleton.d.ts +7 -0
  63. package/dist/runtime/api/lib/server/vibing-orchestrator-singleton.js +57 -0
  64. package/dist/runtime/api/lib/tiptap-utils.clamp-selection.test.d.ts +1 -0
  65. package/dist/runtime/api/lib/tiptap-utils.clamp-selection.test.js +27 -0
  66. package/dist/runtime/api/lib/tiptap-utils.d.ts +130 -0
  67. package/dist/runtime/api/lib/tiptap-utils.js +327 -0
  68. package/dist/runtime/api/lib/trpc/client.d.ts +1 -0
  69. package/dist/runtime/api/lib/trpc/client.js +5 -0
  70. package/dist/runtime/api/lib/trpc/server.d.ts +822 -0
  71. package/dist/runtime/api/lib/trpc/server.js +11 -0
  72. package/dist/runtime/api/lib/trpc/ws-server.d.ts +8 -0
  73. package/dist/runtime/api/lib/trpc/ws-server.js +33 -0
  74. package/dist/runtime/api/persistence/database-service.d.ts +14 -0
  75. package/dist/runtime/api/persistence/database-service.js +74 -0
  76. package/dist/runtime/api/persistence/execution-log-persistence.d.ts +90 -0
  77. package/dist/runtime/api/persistence/execution-log-persistence.js +410 -0
  78. package/dist/runtime/api/persistence/execution-log-persistence.test.d.ts +1 -0
  79. package/dist/runtime/api/persistence/execution-log-persistence.test.js +170 -0
  80. package/dist/runtime/api/router.d.ts +825 -0
  81. package/dist/runtime/api/router.js +56 -0
  82. package/dist/runtime/api/settings-service.d.ts +110 -0
  83. package/dist/runtime/api/settings-service.js +611 -0
  84. package/dist/runtime/api/tasks/file-watcher.d.ts +23 -0
  85. package/dist/runtime/api/tasks/file-watcher.js +88 -0
  86. package/dist/runtime/api/tasks/task-file-parser.d.ts +13 -0
  87. package/dist/runtime/api/tasks/task-file-parser.js +161 -0
  88. package/dist/runtime/api/tasks/task-service.d.ts +36 -0
  89. package/dist/runtime/api/tasks/task-service.js +173 -0
  90. package/dist/runtime/api/types/index.d.ts +179 -0
  91. package/dist/runtime/api/types/index.js +1 -0
  92. package/dist/runtime/api/types/settings.d.ts +81 -0
  93. package/dist/runtime/api/types/settings.js +2 -0
  94. package/dist/runtime/api/types.d.ts +2 -0
  95. package/dist/runtime/api/types.js +1 -0
  96. package/dist/runtime/api/utils/env.d.ts +6 -0
  97. package/dist/runtime/api/utils/env.js +12 -0
  98. package/dist/runtime/api/utils/stripNextEnv.d.ts +7 -0
  99. package/dist/runtime/api/utils/stripNextEnv.js +22 -0
  100. package/dist/runtime/api/utils/title-slug.d.ts +6 -0
  101. package/dist/runtime/api/utils/title-slug.js +77 -0
  102. package/dist/runtime/api/utils/url.d.ts +2 -0
  103. package/dist/runtime/api/utils/url.js +19 -0
  104. package/dist/runtime/api/vcs/git-history-service.d.ts +57 -0
  105. package/dist/runtime/api/vcs/git-history-service.js +228 -0
  106. package/dist/runtime/api/vcs/git-service.d.ts +127 -0
  107. package/dist/runtime/api/vcs/git-service.js +284 -0
  108. package/dist/runtime/api/vcs/worktree-service.d.ts +93 -0
  109. package/dist/runtime/api/vcs/worktree-service.js +506 -0
  110. package/dist/runtime/api/vcs/worktree-service.test.d.ts +1 -0
  111. package/dist/runtime/api/vcs/worktree-service.test.js +20 -0
  112. package/dist/runtime/api/workflows/quality-pipeline.d.ts +58 -0
  113. package/dist/runtime/api/workflows/quality-pipeline.js +400 -0
  114. package/dist/runtime/api/workflows/vibing-orchestrator.d.ts +313 -0
  115. package/dist/runtime/api/workflows/vibing-orchestrator.js +1861 -0
  116. package/dist/runtime/web/.next/BUILD_ID +1 -0
  117. package/dist/runtime/web/.next/app-build-manifest.json +59 -0
  118. package/dist/runtime/web/.next/app-path-routes-manifest.json +7 -0
  119. package/dist/runtime/web/.next/build-manifest.json +33 -0
  120. package/dist/runtime/web/.next/package.json +1 -0
  121. package/dist/runtime/web/.next/prerender-manifest.json +61 -0
  122. package/dist/runtime/web/.next/react-loadable-manifest.json +39 -0
  123. package/dist/runtime/web/.next/required-server-files.json +334 -0
  124. package/dist/runtime/web/.next/routes-manifest.json +62 -0
  125. package/dist/runtime/web/.next/server/app/_not-found/page.js +2 -0
  126. package/dist/runtime/web/.next/server/app/_not-found/page.js.nft.json +1 -0
  127. package/dist/runtime/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -0
  128. package/dist/runtime/web/.next/server/app/_not-found.html +7 -0
  129. package/dist/runtime/web/.next/server/app/_not-found.meta +8 -0
  130. package/dist/runtime/web/.next/server/app/_not-found.rsc +22 -0
  131. package/dist/runtime/web/.next/server/app/api/health/route.js +1 -0
  132. package/dist/runtime/web/.next/server/app/api/health/route.js.nft.json +1 -0
  133. package/dist/runtime/web/.next/server/app/api/health/route_client-reference-manifest.js +1 -0
  134. package/dist/runtime/web/.next/server/app/api/images/[...path]/route.js +1 -0
  135. package/dist/runtime/web/.next/server/app/api/images/[...path]/route.js.nft.json +1 -0
  136. package/dist/runtime/web/.next/server/app/api/images/[...path]/route_client-reference-manifest.js +1 -0
  137. package/dist/runtime/web/.next/server/app/api/upload/route.js +1 -0
  138. package/dist/runtime/web/.next/server/app/api/upload/route.js.nft.json +1 -0
  139. package/dist/runtime/web/.next/server/app/api/upload/route_client-reference-manifest.js +1 -0
  140. package/dist/runtime/web/.next/server/app/index.html +7 -0
  141. package/dist/runtime/web/.next/server/app/index.meta +7 -0
  142. package/dist/runtime/web/.next/server/app/index.rsc +27 -0
  143. package/dist/runtime/web/.next/server/app/page.js +147 -0
  144. package/dist/runtime/web/.next/server/app/page.js.nft.json +1 -0
  145. package/dist/runtime/web/.next/server/app/page_client-reference-manifest.js +1 -0
  146. package/dist/runtime/web/.next/server/app-paths-manifest.json +7 -0
  147. package/dist/runtime/web/.next/server/chunks/217.js +1 -0
  148. package/dist/runtime/web/.next/server/chunks/383.js +6 -0
  149. package/dist/runtime/web/.next/server/chunks/458.js +1 -0
  150. package/dist/runtime/web/.next/server/chunks/576.js +18 -0
  151. package/dist/runtime/web/.next/server/chunks/635.js +22 -0
  152. package/dist/runtime/web/.next/server/chunks/761.js +1 -0
  153. package/dist/runtime/web/.next/server/chunks/777.js +3 -0
  154. package/dist/runtime/web/.next/server/chunks/825.js +1 -0
  155. package/dist/runtime/web/.next/server/chunks/838.js +1 -0
  156. package/dist/runtime/web/.next/server/chunks/973.js +15 -0
  157. package/dist/runtime/web/.next/server/functions-config-manifest.json +4 -0
  158. package/dist/runtime/web/.next/server/middleware-build-manifest.js +1 -0
  159. package/dist/runtime/web/.next/server/middleware-manifest.json +6 -0
  160. package/dist/runtime/web/.next/server/middleware-react-loadable-manifest.js +1 -0
  161. package/dist/runtime/web/.next/server/next-font-manifest.js +1 -0
  162. package/dist/runtime/web/.next/server/next-font-manifest.json +1 -0
  163. package/dist/runtime/web/.next/server/pages/404.html +7 -0
  164. package/dist/runtime/web/.next/server/pages/500.html +1 -0
  165. package/dist/runtime/web/.next/server/pages/_app.js +1 -0
  166. package/dist/runtime/web/.next/server/pages/_app.js.nft.json +1 -0
  167. package/dist/runtime/web/.next/server/pages/_document.js +1 -0
  168. package/dist/runtime/web/.next/server/pages/_document.js.nft.json +1 -0
  169. package/dist/runtime/web/.next/server/pages/_error.js +19 -0
  170. package/dist/runtime/web/.next/server/pages/_error.js.nft.json +1 -0
  171. package/dist/runtime/web/.next/server/pages-manifest.json +6 -0
  172. package/dist/runtime/web/.next/server/server-reference-manifest.js +1 -0
  173. package/dist/runtime/web/.next/server/server-reference-manifest.json +1 -0
  174. package/dist/runtime/web/.next/server/webpack-runtime.js +1 -0
  175. package/dist/runtime/web/.next/static/1HR8N0rJkCvFRtbTPJMyH/_buildManifest.js +1 -0
  176. package/dist/runtime/web/.next/static/1HR8N0rJkCvFRtbTPJMyH/_ssgManifest.js +1 -0
  177. package/dist/runtime/web/.next/static/chunks/18-15c10d3288afef2e.js +1 -0
  178. package/dist/runtime/web/.next/static/chunks/1c0ca389.537bbe362e3ffbd9.js +3 -0
  179. package/dist/runtime/web/.next/static/chunks/22747d63-ad5da0c19f4cfe41.js +71 -0
  180. package/dist/runtime/web/.next/static/chunks/277-0142a939f08738c3.js +63 -0
  181. package/dist/runtime/web/.next/static/chunks/355.056c2645878a799a.js +1 -0
  182. package/dist/runtime/web/.next/static/chunks/420.a5ccf151c9e2b2f1.js +1 -0
  183. package/dist/runtime/web/.next/static/chunks/439.1be0c6242fd248d5.js +15 -0
  184. package/dist/runtime/web/.next/static/chunks/440.c52e7c0f797e22b2.js +1 -0
  185. package/dist/runtime/web/.next/static/chunks/575-e2478287c27da87b.js +1 -0
  186. package/dist/runtime/web/.next/static/chunks/691.920d88c115087314.js +1 -0
  187. package/dist/runtime/web/.next/static/chunks/765-e838910065b50c3d.js +1 -0
  188. package/dist/runtime/web/.next/static/chunks/87c73c54-09e1ba5c70e60a51.js +1 -0
  189. package/dist/runtime/web/.next/static/chunks/891cff7f.0f71fc028f87e683.js +1 -0
  190. package/dist/runtime/web/.next/static/chunks/8bb4d8db-3e2aa02b0a2384b9.js +1 -0
  191. package/dist/runtime/web/.next/static/chunks/9af238c7-271a911d4e99ab18.js +1 -0
  192. package/dist/runtime/web/.next/static/chunks/app/_not-found/page-1cb74d1cba27d0ab.js +1 -0
  193. package/dist/runtime/web/.next/static/chunks/app/api/health/route-105a61ae865ba536.js +1 -0
  194. package/dist/runtime/web/.next/static/chunks/app/api/images/[...path]/route-105a61ae865ba536.js +1 -0
  195. package/dist/runtime/web/.next/static/chunks/app/api/upload/route-105a61ae865ba536.js +1 -0
  196. package/dist/runtime/web/.next/static/chunks/app/layout-dc0cfd29075b2160.js +1 -0
  197. package/dist/runtime/web/.next/static/chunks/app/page-f34a8b196b18850b.js +1 -0
  198. package/dist/runtime/web/.next/static/chunks/cac567b0-5b77dd12911823cd.js +1 -0
  199. package/dist/runtime/web/.next/static/chunks/framework-2518f1345b5b2806.js +1 -0
  200. package/dist/runtime/web/.next/static/chunks/main-17665e5e39de9a8a.js +1 -0
  201. package/dist/runtime/web/.next/static/chunks/main-app-c0b0f5ba4f7f9d75.js +1 -0
  202. package/dist/runtime/web/.next/static/chunks/pages/_app-d6f6b3bbc3d81ee1.js +1 -0
  203. package/dist/runtime/web/.next/static/chunks/pages/_error-75a96cf1997cc3b9.js +1 -0
  204. package/dist/runtime/web/.next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  205. package/dist/runtime/web/.next/static/chunks/webpack-c8de37305b4635cf.js +1 -0
  206. package/dist/runtime/web/.next/static/css/08c950681f1a9a92.css +1 -0
  207. package/dist/runtime/web/.next/static/css/2728291c68f99cb1.css +3 -0
  208. package/dist/runtime/web/.next/static/css/521bd69cc298cd1a.css +1 -0
  209. package/dist/runtime/web/.next/static/css/537e22821e101b87.css +1 -0
  210. package/dist/runtime/web/.next/static/media/19cfc7226ec3afaa-s.woff2 +0 -0
  211. package/dist/runtime/web/.next/static/media/21350d82a1f187e9-s.woff2 +0 -0
  212. package/dist/runtime/web/.next/static/media/8e9860b6e62d6359-s.woff2 +0 -0
  213. package/dist/runtime/web/.next/static/media/ba9851c3c22cd980-s.woff2 +0 -0
  214. package/dist/runtime/web/.next/static/media/c5fe6dc8356a8c31-s.woff2 +0 -0
  215. package/dist/runtime/web/.next/static/media/df0a9ae256c0569c-s.woff2 +0 -0
  216. package/dist/runtime/web/.next/static/media/e4af272ccee01ff0-s.p.woff2 +0 -0
  217. package/dist/runtime/web/package.json +65 -0
  218. package/dist/runtime/web/server.js +44 -0
  219. package/dist/tsconfig.tsbuildinfo +1 -0
  220. package/package.json +80 -7
@@ -0,0 +1,11 @@
1
+ import { createRouter } from '../../router.js';
2
+ import { getTaskService } from '../server/task-service-singleton.js';
3
+ import { getGitService } from '../server/git-service-singleton.js';
4
+ import { getVibingOrchestrator, getVibingOrchestratorAsync, } from '../server/vibing-orchestrator-singleton.js';
5
+ // Build the application router for use by the standalone API server.
6
+ // Note: This module no longer starts any WebSocket servers automatically.
7
+ // Runtime bootstrapping happens in the API server entrypoint.
8
+ const taskService = getTaskService();
9
+ const gitService = getGitService();
10
+ const workflowOrchestrator = getVibingOrchestrator();
11
+ export const appRouter = createRouter(taskService, gitService, workflowOrchestrator, getVibingOrchestratorAsync);
@@ -0,0 +1,8 @@
1
+ import type { AppRouter } from './server.js';
2
+ declare global {
3
+ var __VIBEMAN_WSS__: {
4
+ started: boolean;
5
+ } | undefined;
6
+ }
7
+ export declare function startTRPCWebSocketServer(router: AppRouter): void;
8
+ export {};
@@ -0,0 +1,33 @@
1
+ import { applyWSSHandler } from '@trpc/server/adapters/ws';
2
+ import { WebSocketServer } from 'ws';
3
+ import { getTaskService } from '../server/task-service-singleton.js';
4
+ export function startTRPCWebSocketServer(router) {
5
+ if (typeof global.__VIBEMAN_WSS__ === 'object' && global.__VIBEMAN_WSS__?.started) {
6
+ return; // already started
7
+ }
8
+ // Decide WS port: prefer explicit env; else (+1) from HTTP port
9
+ const httpPort = Number(process.env.PORT || 3000);
10
+ const wsPort = Number(process.env.NEXT_PUBLIC_VIBEMAN_WS_PORT || process.env.VIBEMAN_WS_PORT || httpPort + 1);
11
+ try {
12
+ const wss = new WebSocketServer({ port: wsPort, path: '/api/trpc' });
13
+ // Handle binding errors gracefully (e.g., EADDRINUSE across HMR workers)
14
+ wss.on('error', (err) => {
15
+ const code = (err && err.code) || '';
16
+ if (code === 'EADDRINUSE') {
17
+ console.warn(`[vibeman] WS port ${wsPort} in use; skipping secondary start.`);
18
+ }
19
+ else {
20
+ console.warn('[vibeman] WS server error:', err);
21
+ }
22
+ });
23
+ wss.on('listening', () => {
24
+ const createContext = () => ({ taskService: getTaskService() });
25
+ applyWSSHandler({ wss, router, createContext });
26
+ console.log(`📡 tRPC WebSocket listening ws://localhost:${wsPort}/api/trpc`);
27
+ global.__VIBEMAN_WSS__ = { started: true };
28
+ });
29
+ }
30
+ catch (err) {
31
+ console.warn('[vibeman] WS server not started:', err instanceof Error ? err.message : err);
32
+ }
33
+ }
@@ -0,0 +1,14 @@
1
+ export declare class DatabaseService<T extends object> {
2
+ private filePath;
3
+ private dbPromise;
4
+ constructor(filePath: string);
5
+ private ensureDir;
6
+ private getDb;
7
+ getAll(): Promise<Record<string, T>>;
8
+ setAll(value: Record<string, T>): Promise<void>;
9
+ get(id: string): Promise<T | undefined>;
10
+ set(id: string, value: T): Promise<void>;
11
+ delete(id: string): Promise<void>;
12
+ clear(): Promise<void>;
13
+ getFilePath(): string;
14
+ }
@@ -0,0 +1,74 @@
1
+ import { promises as fs } from 'fs';
2
+ import path from 'path';
3
+ import { JSONFilePreset } from 'lowdb/node';
4
+ // A tiny generic wrapper around lowdb that stores a single
5
+ // object map `{ [id: string]: T }` in a JSON file.
6
+ // No domain/business logic here — just generic CRUD helpers.
7
+ export class DatabaseService {
8
+ constructor(filePath) {
9
+ this.dbPromise = null;
10
+ // If filePath is absolute, use it as-is; otherwise resolve relative to cwd
11
+ this.filePath = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath);
12
+ }
13
+ async ensureDir() {
14
+ const dir = path.dirname(this.filePath);
15
+ try {
16
+ await fs.access(dir);
17
+ }
18
+ catch {
19
+ await fs.mkdir(dir, { recursive: true });
20
+ }
21
+ }
22
+ async getDb() {
23
+ if (!this.dbPromise) {
24
+ await this.ensureDir();
25
+ this.dbPromise = (async () => {
26
+ const db = await JSONFilePreset(this.filePath, {});
27
+ // Lightweight migration: if file accidentally has array shape, convert to id map
28
+ const raw = db.data;
29
+ if (Array.isArray(raw)) {
30
+ const migrated = {};
31
+ for (const item of raw) {
32
+ if (item && typeof item.id === 'string')
33
+ migrated[item.id] = item;
34
+ }
35
+ db.data = migrated;
36
+ await db.write();
37
+ }
38
+ return db;
39
+ })();
40
+ }
41
+ return this.dbPromise;
42
+ }
43
+ async getAll() {
44
+ const db = await this.getDb();
45
+ return { ...db.data };
46
+ }
47
+ async setAll(value) {
48
+ const db = await this.getDb();
49
+ db.data = { ...value };
50
+ await db.write();
51
+ }
52
+ async get(id) {
53
+ const db = await this.getDb();
54
+ return db.data[id];
55
+ }
56
+ async set(id, value) {
57
+ const db = await this.getDb();
58
+ db.data[id] = value;
59
+ await db.write();
60
+ }
61
+ async delete(id) {
62
+ const db = await this.getDb();
63
+ delete db.data[id];
64
+ await db.write();
65
+ }
66
+ async clear() {
67
+ const db = await this.getDb();
68
+ db.data = {};
69
+ await db.write();
70
+ }
71
+ getFilePath() {
72
+ return this.filePath;
73
+ }
74
+ }
@@ -0,0 +1,90 @@
1
+ import type { AgentExecution } from '../types/index.js';
2
+ export interface ExecutionLogEntry {
3
+ timestamp: string;
4
+ level: 'info' | 'warn' | 'error';
5
+ message: string;
6
+ data?: Record<string, unknown>;
7
+ }
8
+ export interface ExecutionMetadata {
9
+ executionId: string;
10
+ taskId: string;
11
+ workflowId?: string;
12
+ startTime: string;
13
+ endTime?: string;
14
+ status: AgentExecution['status'];
15
+ error?: string;
16
+ worktreePath?: string;
17
+ }
18
+ export interface ExecutionIndexEntry {
19
+ executionId: string;
20
+ taskId: string;
21
+ workflowId?: string;
22
+ status: AgentExecution['status'];
23
+ startTime: string;
24
+ endTime?: string;
25
+ dirPath: string;
26
+ }
27
+ export declare class ExecutionLogPersistence {
28
+ private dataDir;
29
+ private logsDir;
30
+ private indexDb;
31
+ constructor();
32
+ /**
33
+ * Ensure logs directory exists
34
+ */
35
+ private ensureLogsDir;
36
+ /**
37
+ * Get execution log directory path, grouped by task ID
38
+ * .vibeman/execution-logs/{taskId}/{executionId-YYYYMMDD_HHMMSS}/
39
+ */
40
+ private getExecutionDirFor;
41
+ /**
42
+ * Get existing execution directory (for reading)
43
+ */
44
+ private findExecutionDir;
45
+ /**
46
+ * Start logging for an execution
47
+ */
48
+ startExecution(execution: AgentExecution, workflowId?: string): Promise<void>;
49
+ /**
50
+ * Append a log entry
51
+ */
52
+ appendLog(executionId: string, entry: ExecutionLogEntry): Promise<void>;
53
+ /**
54
+ * Log a simple message
55
+ */
56
+ logMessage(executionId: string, message: string, level?: ExecutionLogEntry['level'], data?: Record<string, unknown>): Promise<void>;
57
+ /**
58
+ * Complete execution and update metadata
59
+ */
60
+ completeExecution(executionId: string, status: AgentExecution['status'], error?: string): Promise<void>;
61
+ /**
62
+ * Read execution logs
63
+ */
64
+ readExecutionLogs(executionId: string): Promise<{
65
+ metadata: ExecutionMetadata | null;
66
+ logs: ExecutionLogEntry[];
67
+ }>;
68
+ /**
69
+ * List all executions with metadata
70
+ */
71
+ listExecutions(): Promise<ExecutionMetadata[]>;
72
+ /**
73
+ * Clean up old execution logs
74
+ */
75
+ cleanupOldLogs(maxAgeDays?: number): Promise<number>;
76
+ /**
77
+ * Remove execution logs for a specific task
78
+ */
79
+ removeExecutionsByTask(taskId: string): Promise<number>;
80
+ /**
81
+ * Get execution log statistics
82
+ */
83
+ getStats(): Promise<{
84
+ total: number;
85
+ byStatus: Record<string, number>;
86
+ totalLogFiles: number;
87
+ oldestExecution?: string;
88
+ newestExecution?: string;
89
+ }>;
90
+ }
@@ -0,0 +1,410 @@
1
+ import { promises as fs } from 'fs';
2
+ import path from 'path';
3
+ import { log } from '../lib/logger.js';
4
+ import { DatabaseService } from './database-service.js';
5
+ import { getVibeDir } from '../lib/server/project-root.js';
6
+ export class ExecutionLogPersistence {
7
+ constructor() {
8
+ this.dataDir = getVibeDir();
9
+ this.logsDir = path.join(this.dataDir, 'execution-logs');
10
+ this.indexDb = new DatabaseService(path.join(this.logsDir, 'index.json'));
11
+ }
12
+ /**
13
+ * Ensure logs directory exists
14
+ */
15
+ async ensureLogsDir() {
16
+ try {
17
+ await fs.access(this.logsDir);
18
+ }
19
+ catch {
20
+ await fs.mkdir(this.logsDir, { recursive: true });
21
+ }
22
+ }
23
+ /**
24
+ * Get execution log directory path, grouped by task ID
25
+ * .vibeman/execution-logs/{taskId}/{executionId-YYYYMMDD_HHMMSS}/
26
+ */
27
+ getExecutionDirFor(execution) {
28
+ const timestamp = new Date()
29
+ .toISOString()
30
+ .replace(/[-:]/g, '')
31
+ .replace(/\..+/, '')
32
+ .replace('T', '_');
33
+ const safeTask = String(execution.taskId).replace(/[^\w.-]+/g, '_');
34
+ const name = `${execution.id}-${timestamp}`;
35
+ return path.join(this.logsDir, safeTask, name);
36
+ }
37
+ /**
38
+ * Get existing execution directory (for reading)
39
+ */
40
+ async findExecutionDir(executionId) {
41
+ try {
42
+ await this.ensureLogsDir();
43
+ // Prefer index lookup (non-throwing)
44
+ const idx = await this.indexDb.get(executionId).catch(() => undefined);
45
+ if (idx?.dirPath) {
46
+ return idx.dirPath;
47
+ }
48
+ // Walk task directories
49
+ const taskDirs = await fs.readdir(this.logsDir, { withFileTypes: true });
50
+ for (const taskEntry of taskDirs) {
51
+ if (!taskEntry.isDirectory())
52
+ continue;
53
+ const taskPath = path.join(this.logsDir, taskEntry.name);
54
+ const execDirs = await fs.readdir(taskPath, { withFileTypes: true });
55
+ for (const ed of execDirs) {
56
+ if (ed.isDirectory() && ed.name.includes(executionId)) {
57
+ return path.join(taskPath, ed.name);
58
+ }
59
+ }
60
+ }
61
+ }
62
+ catch (error) {
63
+ log.error('Failed to find execution directory', error, 'execution-log-persistence:findExecutionDir');
64
+ }
65
+ return null;
66
+ }
67
+ /**
68
+ * Start logging for an execution
69
+ */
70
+ async startExecution(execution, workflowId) {
71
+ try {
72
+ await this.ensureLogsDir();
73
+ const executionDir = this.getExecutionDirFor(execution);
74
+ await fs.mkdir(executionDir, { recursive: true });
75
+ // Save metadata
76
+ const metadata = {
77
+ executionId: execution.id,
78
+ taskId: execution.taskId,
79
+ workflowId,
80
+ startTime: execution.startTime,
81
+ status: execution.status,
82
+ worktreePath: execution.worktree?.path,
83
+ };
84
+ await fs.writeFile(path.join(executionDir, 'metadata.json'), JSON.stringify(metadata, null, 2));
85
+ // Update index (best-effort)
86
+ try {
87
+ await this.indexDb.set(execution.id, {
88
+ executionId: execution.id,
89
+ taskId: execution.taskId,
90
+ workflowId,
91
+ status: execution.status,
92
+ startTime: execution.startTime,
93
+ dirPath: executionDir,
94
+ });
95
+ }
96
+ catch (error) {
97
+ log.error('Failed to update execution index on start', error, 'execution-log-persistence:startExecution');
98
+ }
99
+ // Update latest symlink per task (best-effort)
100
+ try {
101
+ const taskDir = path.dirname(executionDir);
102
+ const latestLink = path.join(taskDir, 'latest');
103
+ // Remove existing symlink if any (ignore if missing)
104
+ await fs.unlink(latestLink).catch(() => undefined);
105
+ await fs.symlink(executionDir, latestLink);
106
+ }
107
+ catch (error) {
108
+ log.warn('Failed to update latest symlink', error, 'execution-log-persistence:startExecution');
109
+ }
110
+ // Create initial log file
111
+ const initialLog = {
112
+ timestamp: new Date().toISOString(),
113
+ level: 'info',
114
+ message: `Execution started for task ${execution.taskId}`,
115
+ data: {
116
+ executionId: execution.id,
117
+ taskId: execution.taskId,
118
+ workingDirectory: execution.workingDirectory,
119
+ },
120
+ };
121
+ await this.appendLog(execution.id, initialLog);
122
+ }
123
+ catch (error) {
124
+ log.error(`Failed to start execution logging for ${execution.id}`, error, 'execution-log-persistence:startExecution');
125
+ }
126
+ }
127
+ /**
128
+ * Append a log entry
129
+ */
130
+ async appendLog(executionId, entry) {
131
+ try {
132
+ const executionDir = await this.findExecutionDir(executionId);
133
+ if (!executionDir)
134
+ return;
135
+ const logLine = JSON.stringify(entry) + '\n';
136
+ await fs.appendFile(path.join(executionDir, 'execution.log'), logLine);
137
+ }
138
+ catch (error) {
139
+ log.error(`Failed to append log for ${executionId}`, error, 'execution-log-persistence:appendLog');
140
+ }
141
+ }
142
+ /**
143
+ * Log a simple message
144
+ */
145
+ async logMessage(executionId, message, level = 'info', data) {
146
+ const entry = {
147
+ timestamp: new Date().toISOString(),
148
+ level,
149
+ message,
150
+ data,
151
+ };
152
+ await this.appendLog(executionId, entry);
153
+ }
154
+ /**
155
+ * Complete execution and update metadata
156
+ */
157
+ async completeExecution(executionId, status, error) {
158
+ try {
159
+ const executionDir = await this.findExecutionDir(executionId);
160
+ if (!executionDir)
161
+ return;
162
+ // Update metadata
163
+ const metadataPath = path.join(executionDir, 'metadata.json');
164
+ try {
165
+ const existingMetadata = JSON.parse(await fs.readFile(metadataPath, 'utf-8'));
166
+ existingMetadata.endTime = new Date().toISOString();
167
+ existingMetadata.status = status;
168
+ if (error)
169
+ existingMetadata.error = error;
170
+ await fs.writeFile(metadataPath, JSON.stringify(existingMetadata, null, 2));
171
+ }
172
+ catch (metaError) {
173
+ log.error('Failed to update execution metadata', metaError, 'execution-log-persistence:completeExecution');
174
+ }
175
+ // Final log entry
176
+ const finalLog = {
177
+ timestamp: new Date().toISOString(),
178
+ level: status === 'completed' ? 'info' : 'error',
179
+ message: `Execution ${status}${error ? `: ${error}` : ''}`,
180
+ data: { executionId, status, error },
181
+ };
182
+ await this.appendLog(executionId, finalLog);
183
+ // Update index with status/endTime (best-effort)
184
+ try {
185
+ const metaPath = path.join(executionDir, 'metadata.json');
186
+ const md = JSON.parse(await fs.readFile(metaPath, 'utf-8'));
187
+ await this.indexDb.set(executionId, {
188
+ executionId,
189
+ taskId: md.taskId,
190
+ workflowId: md.workflowId,
191
+ status: md.status,
192
+ startTime: md.startTime,
193
+ endTime: md.endTime,
194
+ dirPath: executionDir,
195
+ });
196
+ }
197
+ catch (error) {
198
+ log.error('Failed to update execution index on complete', error, 'execution-log-persistence:completeExecution');
199
+ }
200
+ }
201
+ catch (error) {
202
+ log.error(`Failed to complete execution logging for ${executionId}`, error, 'execution-log-persistence:completeExecution');
203
+ }
204
+ }
205
+ /**
206
+ * Read execution logs
207
+ */
208
+ async readExecutionLogs(executionId) {
209
+ try {
210
+ const executionDir = await this.findExecutionDir(executionId);
211
+ if (!executionDir) {
212
+ return { metadata: null, logs: [] };
213
+ }
214
+ // Read metadata
215
+ let metadata = null;
216
+ try {
217
+ const metadataContent = await fs.readFile(path.join(executionDir, 'metadata.json'), 'utf-8');
218
+ metadata = JSON.parse(metadataContent);
219
+ }
220
+ catch (error) {
221
+ if (error && error.code === 'ENOENT') {
222
+ // Expected when metadata was pruned or not yet written; return null without error noise
223
+ log.debug('Execution metadata not found (ENOENT)', { executionId, executionDir }, 'execution-log-persistence:readExecutionLogs');
224
+ metadata = null;
225
+ }
226
+ else {
227
+ log.error('Failed to read execution metadata', error, 'execution-log-persistence:readExecutionLogs');
228
+ }
229
+ }
230
+ // Read logs
231
+ const logs = [];
232
+ try {
233
+ const logContent = await fs.readFile(path.join(executionDir, 'execution.log'), 'utf-8');
234
+ for (const line of logContent.trim().split('\n')) {
235
+ if (line) {
236
+ try {
237
+ logs.push(JSON.parse(line));
238
+ }
239
+ catch {
240
+ // Skip malformed log lines
241
+ }
242
+ }
243
+ }
244
+ }
245
+ catch (error) {
246
+ if (error && error.code === 'ENOENT') {
247
+ // No log file yet; treat as empty without logging error
248
+ log.debug('Execution log file not found (ENOENT)', { executionId, executionDir }, 'execution-log-persistence:readExecutionLogs');
249
+ }
250
+ else {
251
+ log.error('Failed to read execution logs', error, 'execution-log-persistence:readExecutionLogs');
252
+ }
253
+ }
254
+ return { metadata, logs };
255
+ }
256
+ catch (error) {
257
+ log.error(`Failed to read execution logs for ${executionId}`, error, 'execution-log-persistence:readExecutionLogs');
258
+ return { metadata: null, logs: [] };
259
+ }
260
+ }
261
+ /**
262
+ * List all executions with metadata
263
+ */
264
+ async listExecutions() {
265
+ try {
266
+ await this.ensureLogsDir();
267
+ const executions = [];
268
+ // Iterate task directories and collect metadata
269
+ const taskDirs = await fs.readdir(this.logsDir, { withFileTypes: true });
270
+ for (const taskEntry of taskDirs) {
271
+ if (!taskEntry.isDirectory())
272
+ continue;
273
+ const taskPath = path.join(this.logsDir, taskEntry.name);
274
+ let execDirs = [];
275
+ try {
276
+ execDirs = await fs.readdir(taskPath, { withFileTypes: true });
277
+ }
278
+ catch {
279
+ execDirs = [];
280
+ }
281
+ for (const ed of execDirs) {
282
+ if (!ed.isDirectory())
283
+ continue;
284
+ try {
285
+ const metadataPath = path.join(taskPath, ed.name, 'metadata.json');
286
+ const metadata = JSON.parse(await fs.readFile(metadataPath, 'utf-8'));
287
+ executions.push(metadata);
288
+ }
289
+ catch {
290
+ // skip invalid dirs
291
+ }
292
+ }
293
+ }
294
+ // Sort by start time (most recent first)
295
+ return executions.sort((a, b) => new Date(b.startTime).getTime() - new Date(a.startTime).getTime());
296
+ }
297
+ catch (error) {
298
+ log.error('Failed to list executions', error, 'execution-log-persistence:listExecutions');
299
+ return [];
300
+ }
301
+ }
302
+ /**
303
+ * Clean up old execution logs
304
+ */
305
+ async cleanupOldLogs(maxAgeDays = 30) {
306
+ try {
307
+ const executions = await this.listExecutions();
308
+ const cutoffTime = Date.now() - maxAgeDays * 24 * 60 * 60 * 1000;
309
+ let removedCount = 0;
310
+ for (const execution of executions) {
311
+ const executionTime = new Date(execution.endTime || execution.startTime).getTime();
312
+ if (executionTime < cutoffTime &&
313
+ ['completed', 'failed', 'cancelled'].includes(execution.status)) {
314
+ const executionDir = await this.findExecutionDir(execution.executionId);
315
+ if (executionDir) {
316
+ try {
317
+ await fs.rm(executionDir, { recursive: true, force: true });
318
+ removedCount++;
319
+ // Remove from index (non-throwing)
320
+ await this.indexDb.delete(execution.executionId).catch(() => undefined);
321
+ }
322
+ catch (error) {
323
+ log.error(`Failed to remove execution log directory ${executionDir}`, error, 'execution-log-persistence:cleanupOldLogs');
324
+ }
325
+ }
326
+ }
327
+ }
328
+ if (removedCount > 0) {
329
+ log.warn(`Cleaned up ${removedCount} old execution logs`, { removedCount, maxAgeDays }, 'execution-log-persistence:cleanupOldLogs');
330
+ }
331
+ return removedCount;
332
+ }
333
+ catch (error) {
334
+ log.error('Failed to cleanup old logs', error, 'execution-log-persistence:cleanupOldLogs');
335
+ return 0;
336
+ }
337
+ }
338
+ /**
339
+ * Remove execution logs for a specific task
340
+ */
341
+ async removeExecutionsByTask(taskId) {
342
+ try {
343
+ const executions = await this.listExecutions();
344
+ let removedCount = 0;
345
+ for (const execution of executions) {
346
+ if (execution.taskId === taskId) {
347
+ const executionDir = await this.findExecutionDir(execution.executionId);
348
+ if (executionDir) {
349
+ try {
350
+ await fs.rm(executionDir, { recursive: true, force: true });
351
+ removedCount++;
352
+ await this.indexDb.delete(execution.executionId).catch(() => undefined);
353
+ }
354
+ catch (error) {
355
+ log.error(`Failed to remove execution log directory ${executionDir}`, error, 'execution-log-persistence:cleanupOldLogs');
356
+ }
357
+ }
358
+ }
359
+ }
360
+ if (removedCount > 0) {
361
+ log.warn(`Removed ${removedCount} execution logs for task ${taskId}`, { removedCount, taskId }, 'execution-log-persistence:removeExecutionsByTask');
362
+ }
363
+ return removedCount;
364
+ }
365
+ catch (error) {
366
+ log.error(`Failed to remove executions for task ${taskId}`, error, 'execution-log-persistence:removeExecutionsByTask');
367
+ return 0;
368
+ }
369
+ }
370
+ /**
371
+ * Get execution log statistics
372
+ */
373
+ async getStats() {
374
+ try {
375
+ const executions = await this.listExecutions();
376
+ const stats = {
377
+ total: executions.length,
378
+ byStatus: {},
379
+ totalLogFiles: executions.length,
380
+ oldestExecution: undefined,
381
+ newestExecution: undefined,
382
+ };
383
+ let oldest = null;
384
+ let newest = null;
385
+ for (const execution of executions) {
386
+ // Count by status
387
+ stats.byStatus[execution.status] = (stats.byStatus[execution.status] || 0) + 1;
388
+ // Find oldest/newest
389
+ const startTime = new Date(execution.startTime);
390
+ if (!oldest || startTime < oldest) {
391
+ oldest = startTime;
392
+ stats.oldestExecution = execution.executionId;
393
+ }
394
+ if (!newest || startTime > newest) {
395
+ newest = startTime;
396
+ stats.newestExecution = execution.executionId;
397
+ }
398
+ }
399
+ return stats;
400
+ }
401
+ catch (error) {
402
+ log.error('Failed to get execution log stats', error, 'execution-log-persistence:getStats');
403
+ return {
404
+ total: 0,
405
+ byStatus: {},
406
+ totalLogFiles: 0,
407
+ };
408
+ }
409
+ }
410
+ }