devglide 0.1.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 (252) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +338 -0
  3. package/bin/claude-md-template.js +94 -0
  4. package/bin/devglide.js +387 -0
  5. package/package.json +85 -0
  6. package/pnpm-workspace.yaml +3 -0
  7. package/src/apps/coder/.turbo/turbo-lint.log +5 -0
  8. package/src/apps/coder/package.json +16 -0
  9. package/src/apps/coder/public/favicon.svg +7 -0
  10. package/src/apps/coder/public/page.css +275 -0
  11. package/src/apps/coder/public/page.js +528 -0
  12. package/src/apps/coder/server.js +3 -0
  13. package/src/apps/documentation/public/page.css +597 -0
  14. package/src/apps/documentation/public/page.js +609 -0
  15. package/src/apps/kanban/.turbo/turbo-lint.log +97 -0
  16. package/src/apps/kanban/.turbo/turbo-typecheck.log +5 -0
  17. package/src/apps/kanban/package.json +32 -0
  18. package/src/apps/kanban/public/favicon.svg +7 -0
  19. package/src/apps/kanban/public/page.css +1010 -0
  20. package/src/apps/kanban/public/page.js +1730 -0
  21. package/src/apps/kanban/public/vendor/marked.min.js +6 -0
  22. package/src/apps/kanban/public/vendor/sortable.min.js +2 -0
  23. package/src/apps/kanban/src/db.ts +319 -0
  24. package/src/apps/kanban/src/index.ts +14 -0
  25. package/src/apps/kanban/src/mcp-helpers.test.ts +88 -0
  26. package/src/apps/kanban/src/mcp-helpers.ts +60 -0
  27. package/src/apps/kanban/src/mcp.ts +59 -0
  28. package/src/apps/kanban/src/routes/attachments.ts +161 -0
  29. package/src/apps/kanban/src/routes/features.ts +233 -0
  30. package/src/apps/kanban/src/routes/issues.ts +373 -0
  31. package/src/apps/kanban/src/tools/feature-tools.ts +164 -0
  32. package/src/apps/kanban/src/tools/item-tools.ts +307 -0
  33. package/src/apps/kanban/src/tools/versioned-entry-tools.ts +72 -0
  34. package/src/apps/kanban/tsconfig.check.json +9 -0
  35. package/src/apps/kanban/tsconfig.json +9 -0
  36. package/src/apps/keymap/.turbo/turbo-lint.log +5 -0
  37. package/src/apps/keymap/package.json +16 -0
  38. package/src/apps/keymap/public/page.css +275 -0
  39. package/src/apps/keymap/public/page.js +294 -0
  40. package/src/apps/keymap/server.js +25 -0
  41. package/src/apps/log/.turbo/turbo-build.log +5 -0
  42. package/src/apps/log/.turbo/turbo-lint.log +45 -0
  43. package/src/apps/log/.turbo/turbo-typecheck.log +5 -0
  44. package/src/apps/log/node_modules/.bin/tsc +21 -0
  45. package/src/apps/log/node_modules/.bin/tsserver +21 -0
  46. package/src/apps/log/node_modules/.bin/tsx +21 -0
  47. package/src/apps/log/package.json +36 -0
  48. package/src/apps/log/public/console-sniffer.js +221 -0
  49. package/src/apps/log/public/favicon.svg +7 -0
  50. package/src/apps/log/public/page.css +322 -0
  51. package/src/apps/log/public/page.js +463 -0
  52. package/src/apps/log/src/index.ts +9 -0
  53. package/src/apps/log/src/mcp.ts +122 -0
  54. package/src/apps/log/src/routes/log.ts +333 -0
  55. package/src/apps/log/src/routes/status.ts +25 -0
  56. package/src/apps/log/src/server-sniffer.ts +118 -0
  57. package/src/apps/log/src/services/file-patterns.ts +39 -0
  58. package/src/apps/log/src/services/file-tailer.ts +228 -0
  59. package/src/apps/log/src/services/line-parser.ts +94 -0
  60. package/src/apps/log/src/services/log-writer.ts +39 -0
  61. package/src/apps/log/tsconfig.json +8 -0
  62. package/src/apps/prompts/.turbo/turbo-build.log +5 -0
  63. package/src/apps/prompts/.turbo/turbo-lint.log +24 -0
  64. package/src/apps/prompts/.turbo/turbo-typecheck.log +5 -0
  65. package/src/apps/prompts/mcp.ts +175 -0
  66. package/src/apps/prompts/node_modules/.bin/tsc +21 -0
  67. package/src/apps/prompts/node_modules/.bin/tsserver +21 -0
  68. package/src/apps/prompts/node_modules/.bin/tsx +21 -0
  69. package/src/apps/prompts/package.json +25 -0
  70. package/src/apps/prompts/public/page.css +315 -0
  71. package/src/apps/prompts/public/page.js +541 -0
  72. package/src/apps/prompts/services/prompt-store.ts +212 -0
  73. package/src/apps/prompts/src/index.ts +9 -0
  74. package/src/apps/prompts/tsconfig.json +8 -0
  75. package/src/apps/prompts/types.ts +27 -0
  76. package/src/apps/shell/.turbo/turbo-build.log +5 -0
  77. package/src/apps/shell/.turbo/turbo-lint.log +34 -0
  78. package/src/apps/shell/.turbo/turbo-typecheck.log +5 -0
  79. package/src/apps/shell/package.json +35 -0
  80. package/src/apps/shell/public/favicon.svg +7 -0
  81. package/src/apps/shell/public/page.css +407 -0
  82. package/src/apps/shell/public/page.js +1577 -0
  83. package/src/apps/shell/src/index.ts +150 -0
  84. package/src/apps/shell/src/mcp.ts +398 -0
  85. package/src/apps/shell/src/shell-types.ts +41 -0
  86. package/src/apps/shell/tsconfig.json +8 -0
  87. package/src/apps/test/.turbo/turbo-build.log +5 -0
  88. package/src/apps/test/.turbo/turbo-lint.log +27 -0
  89. package/src/apps/test/.turbo/turbo-typecheck.log +5 -0
  90. package/src/apps/test/node_modules/.bin/tsc +21 -0
  91. package/src/apps/test/node_modules/.bin/tsserver +21 -0
  92. package/src/apps/test/node_modules/.bin/tsx +21 -0
  93. package/src/apps/test/node_modules/.bin/uuid +21 -0
  94. package/src/apps/test/package.json +35 -0
  95. package/src/apps/test/public/favicon.svg +7 -0
  96. package/src/apps/test/public/page.css +499 -0
  97. package/src/apps/test/public/page.js +417 -0
  98. package/src/apps/test/public/scenario-runner.js +450 -0
  99. package/src/apps/test/src/index.ts +9 -0
  100. package/src/apps/test/src/mcp.ts +192 -0
  101. package/src/apps/test/src/routes/trigger.ts +285 -0
  102. package/src/apps/test/src/services/scenario-broadcaster.ts +60 -0
  103. package/src/apps/test/src/services/scenario-manager.ts +361 -0
  104. package/src/apps/test/src/services/scenario-store.ts +145 -0
  105. package/src/apps/test/tsconfig.json +8 -0
  106. package/src/apps/vocabulary/.turbo/turbo-build.log +5 -0
  107. package/src/apps/vocabulary/.turbo/turbo-lint.log +25 -0
  108. package/src/apps/vocabulary/.turbo/turbo-typecheck.log +5 -0
  109. package/src/apps/vocabulary/mcp.ts +173 -0
  110. package/src/apps/vocabulary/node_modules/.bin/tsc +21 -0
  111. package/src/apps/vocabulary/node_modules/.bin/tsserver +21 -0
  112. package/src/apps/vocabulary/node_modules/.bin/tsx +21 -0
  113. package/src/apps/vocabulary/package.json +25 -0
  114. package/src/apps/vocabulary/public/page.css +247 -0
  115. package/src/apps/vocabulary/public/page.js +444 -0
  116. package/src/apps/vocabulary/services/vocabulary-store.ts +179 -0
  117. package/src/apps/vocabulary/src/index.ts +10 -0
  118. package/src/apps/vocabulary/tsconfig.json +8 -0
  119. package/src/apps/vocabulary/types.ts +22 -0
  120. package/src/apps/voice/.turbo/turbo-build.log +5 -0
  121. package/src/apps/voice/.turbo/turbo-lint.log +43 -0
  122. package/src/apps/voice/.turbo/turbo-typecheck.log +5 -0
  123. package/src/apps/voice/node_modules/.bin/openai +21 -0
  124. package/src/apps/voice/node_modules/.bin/tsc +21 -0
  125. package/src/apps/voice/node_modules/.bin/tsserver +21 -0
  126. package/src/apps/voice/node_modules/.bin/tsx +21 -0
  127. package/src/apps/voice/package.json +35 -0
  128. package/src/apps/voice/public/favicon.svg +7 -0
  129. package/src/apps/voice/public/page.css +388 -0
  130. package/src/apps/voice/public/page.js +718 -0
  131. package/src/apps/voice/src/index.ts +10 -0
  132. package/src/apps/voice/src/mcp.ts +70 -0
  133. package/src/apps/voice/src/providers/index.ts +85 -0
  134. package/src/apps/voice/src/providers/openai-compatible.ts +94 -0
  135. package/src/apps/voice/src/providers/types.ts +27 -0
  136. package/src/apps/voice/src/routes/config.ts +118 -0
  137. package/src/apps/voice/src/routes/transcribe.ts +90 -0
  138. package/src/apps/voice/src/services/config-store.ts +129 -0
  139. package/src/apps/voice/src/services/stats.ts +108 -0
  140. package/src/apps/voice/src/transcribe.ts +11 -0
  141. package/src/apps/voice/src/utils/mime.ts +16 -0
  142. package/src/apps/voice/tsconfig.json +8 -0
  143. package/src/apps/workflow/.turbo/turbo-build.log +5 -0
  144. package/src/apps/workflow/.turbo/turbo-lint.log +96 -0
  145. package/src/apps/workflow/.turbo/turbo-typecheck.log +5 -0
  146. package/src/apps/workflow/engine/executors/decision-executor.ts +87 -0
  147. package/src/apps/workflow/engine/executors/file-executor.ts +90 -0
  148. package/src/apps/workflow/engine/executors/git-executor.ts +137 -0
  149. package/src/apps/workflow/engine/executors/http-executor.ts +65 -0
  150. package/src/apps/workflow/engine/executors/index.ts +28 -0
  151. package/src/apps/workflow/engine/executors/kanban-executor.ts +154 -0
  152. package/src/apps/workflow/engine/executors/llm-executor.ts +46 -0
  153. package/src/apps/workflow/engine/executors/log-executor.ts +62 -0
  154. package/src/apps/workflow/engine/executors/loop-executor.ts +14 -0
  155. package/src/apps/workflow/engine/executors/shell-executor.ts +107 -0
  156. package/src/apps/workflow/engine/executors/sub-workflow-executor.ts +61 -0
  157. package/src/apps/workflow/engine/executors/test-executor.ts +73 -0
  158. package/src/apps/workflow/engine/executors/trigger-executor.ts +39 -0
  159. package/src/apps/workflow/engine/expression-evaluator.ts +117 -0
  160. package/src/apps/workflow/engine/graph-runner.ts +438 -0
  161. package/src/apps/workflow/engine/node-executor.ts +104 -0
  162. package/src/apps/workflow/engine/node-registry.ts +15 -0
  163. package/src/apps/workflow/engine/variable-resolver.ts +109 -0
  164. package/src/apps/workflow/mcp.ts +223 -0
  165. package/src/apps/workflow/node_modules/.bin/tsc +21 -0
  166. package/src/apps/workflow/node_modules/.bin/tsserver +21 -0
  167. package/src/apps/workflow/node_modules/.bin/tsx +21 -0
  168. package/src/apps/workflow/package.json +25 -0
  169. package/src/apps/workflow/public/editor/canvas.js +366 -0
  170. package/src/apps/workflow/public/editor/drag-manager.js +326 -0
  171. package/src/apps/workflow/public/editor/edge-renderer.js +235 -0
  172. package/src/apps/workflow/public/editor/history-manager.js +147 -0
  173. package/src/apps/workflow/public/editor/layout-engine.js +159 -0
  174. package/src/apps/workflow/public/editor/node-renderer.js +199 -0
  175. package/src/apps/workflow/public/editor/selection-manager.js +193 -0
  176. package/src/apps/workflow/public/favicon.svg +7 -0
  177. package/src/apps/workflow/public/models/node-types.js +300 -0
  178. package/src/apps/workflow/public/models/workflow-model.js +257 -0
  179. package/src/apps/workflow/public/page.css +406 -0
  180. package/src/apps/workflow/public/page.js +658 -0
  181. package/src/apps/workflow/public/panels/inspector.js +360 -0
  182. package/src/apps/workflow/public/panels/palette.js +106 -0
  183. package/src/apps/workflow/public/panels/run-view.js +275 -0
  184. package/src/apps/workflow/public/panels/toolbar.js +232 -0
  185. package/src/apps/workflow/public/panels/workflow-list.js +237 -0
  186. package/src/apps/workflow/public/state/store.js +47 -0
  187. package/src/apps/workflow/services/custom-node-loader.ts +48 -0
  188. package/src/apps/workflow/services/legacy-converter.ts +72 -0
  189. package/src/apps/workflow/services/run-manager.ts +190 -0
  190. package/src/apps/workflow/services/workflow-store.ts +424 -0
  191. package/src/apps/workflow/services/workflow-validator.test.ts +103 -0
  192. package/src/apps/workflow/services/workflow-validator.ts +98 -0
  193. package/src/apps/workflow/src/index.ts +10 -0
  194. package/src/apps/workflow/templates/ci-pipeline.json +18 -0
  195. package/src/apps/workflow/templates/code-review.json +22 -0
  196. package/src/apps/workflow/templates/kanban-testing.json +24 -0
  197. package/src/apps/workflow/tsconfig.json +8 -0
  198. package/src/apps/workflow/types.ts +268 -0
  199. package/src/packages/auth-middleware.ts +14 -0
  200. package/src/packages/design-tokens/.turbo/turbo-build.log +10 -0
  201. package/src/packages/design-tokens/STYLEGUIDE.md +414 -0
  202. package/src/packages/design-tokens/build.js +413 -0
  203. package/src/packages/design-tokens/demo/index.html +1367 -0
  204. package/src/packages/design-tokens/demo/proposition-a.html +717 -0
  205. package/src/packages/design-tokens/demo/proposition-b.html +1239 -0
  206. package/src/packages/design-tokens/demo/proposition-c.html +1049 -0
  207. package/src/packages/design-tokens/dist/tailwind-preset.js +115 -0
  208. package/src/packages/design-tokens/dist/tokens.css +345 -0
  209. package/src/packages/design-tokens/dist/tokens.d.ts +229 -0
  210. package/src/packages/design-tokens/dist/tokens.js +386 -0
  211. package/src/packages/design-tokens/package.json +25 -0
  212. package/src/packages/design-tokens/tokens.json +228 -0
  213. package/src/packages/devtools-middleware.ts +22 -0
  214. package/src/packages/eslint-config/index.js +63 -0
  215. package/src/packages/eslint-config/node_modules/.bin/eslint +21 -0
  216. package/src/packages/eslint-config/package.json +18 -0
  217. package/src/packages/json-file-store.ts +232 -0
  218. package/src/packages/mcp-utils/.turbo/turbo-build.log +5 -0
  219. package/src/packages/mcp-utils/dist/index.d.ts +33 -0
  220. package/src/packages/mcp-utils/dist/index.d.ts.map +1 -0
  221. package/src/packages/mcp-utils/dist/index.js +126 -0
  222. package/src/packages/mcp-utils/dist/index.js.map +1 -0
  223. package/src/packages/mcp-utils/node_modules/.bin/tsc +21 -0
  224. package/src/packages/mcp-utils/node_modules/.bin/tsserver +21 -0
  225. package/src/packages/mcp-utils/package.json +32 -0
  226. package/src/packages/mcp-utils/src/index.ts +171 -0
  227. package/src/packages/mcp-utils/tsconfig.json +9 -0
  228. package/src/packages/paths.ts +18 -0
  229. package/src/packages/project-context/index.js +55 -0
  230. package/src/packages/project-context/package.json +13 -0
  231. package/src/packages/project-store.ts +127 -0
  232. package/src/packages/server-sniffer.ts +132 -0
  233. package/src/packages/shared-assets/favicon.svg +7 -0
  234. package/src/packages/shared-assets/keymap-registry.js +512 -0
  235. package/src/packages/shared-assets/logo.svg +6 -0
  236. package/src/packages/shared-assets/package.json +11 -0
  237. package/src/packages/shared-assets/ui-utils.js +48 -0
  238. package/src/packages/shared-assets/voice-widget.d.ts +37 -0
  239. package/src/packages/shared-assets/voice-widget.js +695 -0
  240. package/src/packages/shared-types/.turbo/turbo-build.log +5 -0
  241. package/src/packages/shared-types/dist/index.d.ts +39 -0
  242. package/src/packages/shared-types/dist/index.d.ts.map +1 -0
  243. package/src/packages/shared-types/node_modules/.bin/tsc +21 -0
  244. package/src/packages/shared-types/node_modules/.bin/tsserver +21 -0
  245. package/src/packages/shared-types/package.json +25 -0
  246. package/src/packages/shared-types/src/index.ts +41 -0
  247. package/src/packages/shared-types/tsconfig.json +11 -0
  248. package/src/packages/tsconfig/base.json +15 -0
  249. package/src/packages/tsconfig/next.json +14 -0
  250. package/src/packages/tsconfig/node.json +11 -0
  251. package/src/packages/tsconfig/package.json +10 -0
  252. package/turbo.json +25 -0
@@ -0,0 +1,228 @@
1
+ import { watch, type FSWatcher } from "chokidar";
2
+ import fsp from "fs/promises";
3
+ import path from "path";
4
+ import crypto from "crypto";
5
+ import { LogWriter } from "./log-writer.js";
6
+ import { parseLine } from "./line-parser.js";
7
+ import { recordSession, getTargetPaths } from "../routes/log.js";
8
+ import {
9
+ INCLUDE_PATTERNS,
10
+ IGNORED_GLOBS,
11
+ MAX_DEPTH,
12
+ MAX_WATCHED_FILES,
13
+ MAX_FILE_SIZE,
14
+ } from "./file-patterns.js";
15
+
16
+ function fileSessionId(filePath: string): string {
17
+ const hash = crypto.createHash("md5").update(filePath).digest("hex").slice(0, 12);
18
+ return `file-${hash}`;
19
+ }
20
+
21
+ function filetailTargetPath(projectPath: string, filePath: string): string {
22
+ const basename = path.basename(filePath, ".log");
23
+ return path.join(projectPath, `${basename}.filetail.log`);
24
+ }
25
+
26
+ export class FileTailer {
27
+ private watcher: FSWatcher | null = null;
28
+ private offsets = new Map<string, number>();
29
+ private partials = new Map<string, string>();
30
+ private changeQueues = new Map<string, Promise<void>>();
31
+ private logWriter = new LogWriter();
32
+ private projectPath: string | null = null;
33
+ private watchedCount = 0;
34
+
35
+ async start(projectPath: string): Promise<void> {
36
+ this.stop();
37
+ this.projectPath = projectPath;
38
+
39
+ const globs = INCLUDE_PATTERNS.map((p) => path.join(projectPath, p));
40
+
41
+ this.watcher = watch(globs, {
42
+ ignored: IGNORED_GLOBS,
43
+ depth: MAX_DEPTH,
44
+ persistent: true,
45
+ ignoreInitial: false,
46
+ awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50 },
47
+ });
48
+
49
+ this.watcher.on("add", (filePath: string) => this.onAdd(filePath));
50
+ this.watcher.on("change", (filePath: string) => this.onChange(filePath));
51
+ this.watcher.on("unlink", (filePath: string) => this.onUnlink(filePath));
52
+ this.watcher.on("error", (err: unknown) => {
53
+ console.error("[file-tailer] watcher error:", (err as Error).message);
54
+ });
55
+
56
+ console.log(`[file-tailer] Watching for log files in ${projectPath}`);
57
+ }
58
+
59
+ stop(): void {
60
+ if (this.watcher) {
61
+ this.watcher.close().catch(() => {});
62
+ this.watcher = null;
63
+ }
64
+ this.offsets.clear();
65
+ this.partials.clear();
66
+ this.changeQueues.clear();
67
+ this.watchedCount = 0;
68
+ if (this.projectPath) {
69
+ console.log(`[file-tailer] Stopped watching ${this.projectPath}`);
70
+ }
71
+ this.projectPath = null;
72
+ }
73
+
74
+ private async onAdd(filePath: string): Promise<void> {
75
+ if (this.watchedCount >= MAX_WATCHED_FILES) return;
76
+
77
+ // Skip files already managed by DevGlide sniffers (browser/server)
78
+ const absPath = path.resolve(filePath);
79
+ const managedPaths = getTargetPaths();
80
+ if (managedPaths.some((p) => path.resolve(p) === absPath)) {
81
+ console.log(`[file-tailer] Skipping managed file: ${filePath}`);
82
+ return;
83
+ }
84
+
85
+ try {
86
+ const stat = await fsp.stat(filePath);
87
+ if (stat.size > MAX_FILE_SIZE) {
88
+ console.log(`[file-tailer] Skipping large file: ${filePath} (${(stat.size / 1024 / 1024).toFixed(1)}MB)`);
89
+ return;
90
+ }
91
+
92
+ // Peek at first line — skip if it looks like a DevGlide JSONL file
93
+ if (stat.size > 0 && (await this.isDevGlideJsonl(filePath))) {
94
+ console.log(`[file-tailer] Skipping DevGlide JSONL file: ${filePath}`);
95
+ return;
96
+ }
97
+
98
+ // Start from end of file — only tail new content
99
+ this.offsets.set(filePath, stat.size);
100
+ this.partials.set(filePath, "");
101
+ this.watchedCount++;
102
+
103
+ const sessionId = fileSessionId(filePath);
104
+ const targetPath = filetailTargetPath(this.projectPath!, filePath);
105
+ const basename = path.basename(filePath);
106
+
107
+ const entry = {
108
+ type: "FILE_SESSION_START",
109
+ session: sessionId,
110
+ ts: new Date().toISOString(),
111
+ message: `Tailing ${basename}`,
112
+ targetPath,
113
+ persistent: true,
114
+ source: filePath,
115
+ };
116
+
117
+ recordSession(entry);
118
+ await this.logWriter.append(targetPath, entry);
119
+
120
+ console.log(`[file-tailer] Tailing: ${filePath}`);
121
+ } catch (err) {
122
+ console.error(`[file-tailer] Failed to add ${filePath}:`, (err as Error).message);
123
+ }
124
+ }
125
+
126
+ /** Serialize change processing per file to prevent duplicate reads from concurrent events */
127
+ private onChange(filePath: string): void {
128
+ const prev = this.changeQueues.get(filePath) || Promise.resolve();
129
+ const next = prev.then(() => this.processChange(filePath)).catch(() => {});
130
+ this.changeQueues.set(filePath, next);
131
+ }
132
+
133
+ private async processChange(filePath: string): Promise<void> {
134
+ const prevOffset = this.offsets.get(filePath);
135
+ if (prevOffset === undefined) return;
136
+
137
+ try {
138
+ const stat = await fsp.stat(filePath);
139
+ const currentSize = stat.size;
140
+
141
+ // File was truncated/rotated — reset to beginning
142
+ if (currentSize < prevOffset) {
143
+ this.offsets.set(filePath, 0);
144
+ this.partials.set(filePath, "");
145
+ return this.processChange(filePath);
146
+ }
147
+
148
+ // No new data
149
+ if (currentSize === prevOffset) return;
150
+
151
+ const bytesToRead = currentSize - prevOffset;
152
+ const buffer = Buffer.alloc(bytesToRead);
153
+
154
+ const fd = await fsp.open(filePath, "r");
155
+ try {
156
+ await fd.read(buffer, 0, bytesToRead, prevOffset);
157
+ } finally {
158
+ await fd.close();
159
+ }
160
+
161
+ this.offsets.set(filePath, currentSize);
162
+
163
+ const chunk = buffer.toString("utf-8");
164
+ const partial = this.partials.get(filePath) || "";
165
+ const combined = partial + chunk;
166
+ const lines = combined.split("\n");
167
+
168
+ // Last element is either empty (line ended with \n) or an incomplete line
169
+ this.partials.set(filePath, lines.pop()!);
170
+
171
+ if (lines.length === 0) return;
172
+
173
+ const sessionId = fileSessionId(filePath);
174
+ const targetPath = filetailTargetPath(this.projectPath!, filePath);
175
+
176
+ for (const line of lines) {
177
+ if (!line.trim()) continue;
178
+
179
+ const parsed = parseLine(line);
180
+ const entry: Record<string, unknown> = {
181
+ type: parsed.type,
182
+ session: sessionId,
183
+ ts: parsed.ts,
184
+ message: parsed.message,
185
+ targetPath,
186
+ source: filePath,
187
+ };
188
+
189
+ recordSession(entry);
190
+ await this.logWriter.append(targetPath, entry);
191
+ }
192
+ } catch (err) {
193
+ // File may have been deleted between stat and read
194
+ if ((err as NodeJS.ErrnoException).code !== "ENOENT") {
195
+ console.error(`[file-tailer] Error reading ${filePath}:`, (err as Error).message);
196
+ }
197
+ }
198
+ }
199
+
200
+ /** Check if a file is a DevGlide JSONL log (has session + type fields on first line) */
201
+ private async isDevGlideJsonl(filePath: string): Promise<boolean> {
202
+ try {
203
+ const fd = await fsp.open(filePath, "r");
204
+ try {
205
+ const buf = Buffer.alloc(512);
206
+ const { bytesRead } = await fd.read(buf, 0, 512, 0);
207
+ if (bytesRead === 0) return false;
208
+ const firstLine = buf.toString("utf-8", 0, bytesRead).split("\n")[0].trim();
209
+ if (!firstLine.startsWith("{")) return false;
210
+ const obj = JSON.parse(firstLine);
211
+ return typeof obj.session === "string" && typeof obj.type === "string";
212
+ } finally {
213
+ await fd.close();
214
+ }
215
+ } catch {
216
+ return false;
217
+ }
218
+ }
219
+
220
+ private onUnlink(filePath: string): void {
221
+ if (this.offsets.has(filePath)) {
222
+ this.offsets.delete(filePath);
223
+ this.partials.delete(filePath);
224
+ this.watchedCount--;
225
+ console.log(`[file-tailer] Removed: ${filePath}`);
226
+ }
227
+ }
228
+ }
@@ -0,0 +1,94 @@
1
+ // ── Parse raw log lines into structured entries ─────────────────────────────
2
+
3
+ export interface ParsedLine {
4
+ type: "FILE_LOG" | "FILE_WARN" | "FILE_ERROR" | "FILE_DEBUG";
5
+ message: string;
6
+ ts: string;
7
+ }
8
+
9
+ const LEVEL_MAP: Record<string, ParsedLine["type"]> = {
10
+ debug: "FILE_DEBUG",
11
+ info: "FILE_LOG",
12
+ log: "FILE_LOG",
13
+ notice: "FILE_LOG",
14
+ warn: "FILE_WARN",
15
+ warning: "FILE_WARN",
16
+ error: "FILE_ERROR",
17
+ err: "FILE_ERROR",
18
+ fatal: "FILE_ERROR",
19
+ critical: "FILE_ERROR",
20
+ crit: "FILE_ERROR",
21
+ };
22
+
23
+ /**
24
+ * Regex for common log formats:
25
+ * [2024-01-15 10:30:45] ERROR: something happened
26
+ * 2024-01-15T10:30:45.123Z WARN something happened
27
+ * [ERROR] something happened
28
+ */
29
+ const COMMON_RE =
30
+ /^\[?(\d{4}[-/]\d{2}[-/]\d{2}[T ]\d{2}:\d{2}:\d{2}[.\d]*Z?)\]?\s+(\w+):?\s+(.*)/;
31
+ const LEVEL_ONLY_RE = /^\[(\w+)\]:?\s+(.*)/;
32
+
33
+ function mapLevel(raw: string): ParsedLine["type"] {
34
+ return LEVEL_MAP[raw.toLowerCase()] || "FILE_LOG";
35
+ }
36
+
37
+ export function parseLine(raw: string): ParsedLine {
38
+ const trimmed = raw.trimEnd();
39
+ if (!trimmed) {
40
+ return { type: "FILE_LOG", message: "", ts: new Date().toISOString() };
41
+ }
42
+
43
+ // Strategy 1: JSON line
44
+ if (trimmed.startsWith("{")) {
45
+ try {
46
+ const obj = JSON.parse(trimmed);
47
+ const level =
48
+ obj.level || obj.severity || obj.log_level || obj.loglevel || "";
49
+ const message =
50
+ obj.msg || obj.message || obj.text || obj.body || trimmed;
51
+ const ts =
52
+ obj.time ||
53
+ obj.timestamp ||
54
+ obj.ts ||
55
+ obj.datetime ||
56
+ obj.date ||
57
+ new Date().toISOString();
58
+ return {
59
+ type: mapLevel(String(level)),
60
+ message: String(message),
61
+ ts: String(ts),
62
+ };
63
+ } catch {
64
+ // Not valid JSON — fall through
65
+ }
66
+ }
67
+
68
+ // Strategy 2: Common timestamp + level format
69
+ const common = COMMON_RE.exec(trimmed);
70
+ if (common) {
71
+ return {
72
+ type: mapLevel(common[2]),
73
+ message: common[3],
74
+ ts: common[1],
75
+ };
76
+ }
77
+
78
+ // Strategy 2b: Level-only prefix [ERROR] message
79
+ const levelOnly = LEVEL_ONLY_RE.exec(trimmed);
80
+ if (levelOnly && LEVEL_MAP[levelOnly[1].toLowerCase()]) {
81
+ return {
82
+ type: mapLevel(levelOnly[1]),
83
+ message: levelOnly[2],
84
+ ts: new Date().toISOString(),
85
+ };
86
+ }
87
+
88
+ // Strategy 3: Plain text fallback
89
+ return {
90
+ type: "FILE_LOG",
91
+ message: trimmed,
92
+ ts: new Date().toISOString(),
93
+ };
94
+ }
@@ -0,0 +1,39 @@
1
+ import fs from "fs/promises";
2
+ import path from "path";
3
+
4
+ /**
5
+ * Appends JSONL log entries to files on disk.
6
+ * Uses per-file write queues to ensure sequential writes without corruption.
7
+ */
8
+ export class LogWriter {
9
+ private queues = new Map<string, Promise<void>>();
10
+
11
+ async append(targetPath: string, entry: Record<string, unknown>): Promise<void> {
12
+ const line = JSON.stringify(entry) + "\n";
13
+ await this.enqueue(targetPath, async () => {
14
+ const dir = path.dirname(targetPath);
15
+ await fs.mkdir(dir, { recursive: true });
16
+ await fs.appendFile(targetPath, line, "utf-8");
17
+ });
18
+ }
19
+
20
+ async clear(targetPath: string): Promise<void> {
21
+ await this.enqueue(targetPath, async () => {
22
+ const dir = path.dirname(targetPath);
23
+ await fs.mkdir(dir, { recursive: true });
24
+ await fs.writeFile(targetPath, "", "utf-8");
25
+ });
26
+ }
27
+
28
+ private enqueue(key: string, fn: () => Promise<void>): Promise<void> {
29
+ const prev = this.queues.get(key) || Promise.resolve();
30
+ const next = prev.then(fn, fn).finally(() => {
31
+ // Delete queue entry once it drains to prevent unbounded growth
32
+ if (this.queues.get(key) === next) {
33
+ this.queues.delete(key);
34
+ }
35
+ });
36
+ this.queues.set(key, next);
37
+ return next;
38
+ }
39
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../packages/tsconfig/node.json",
3
+ "compilerOptions": {
4
+ "noEmit": true
5
+ },
6
+ "include": ["src"],
7
+ "exclude": ["node_modules", "dist"]
8
+ }
@@ -0,0 +1,5 @@
1
+  WARN  Issue while reading "/home/runner/_work/devglide/devglide/.npmrc". Failed to replace env in config: ${NODE_AUTH_TOKEN}
2
+
3
+ > @devglide/prompts@0.1.0 build /home/runner/_work/devglide/devglide/src/apps/prompts
4
+ > tsc
5
+
@@ -0,0 +1,24 @@
1
+  WARN  Issue while reading "/home/runner/_work/devglide/devglide/.npmrc". Failed to replace env in config: ${NODE_AUTH_TOKEN}
2
+
3
+ > @devglide/prompts@0.1.0 lint /home/runner/_work/devglide/devglide/src/apps/prompts
4
+ > eslint .
5
+
6
+
7
+ /home/runner/_work/devglide/devglide/src/apps/prompts/mcp.ts
8
+ 2:1 warning There should be at least one empty line between import groups import/order
9
+ 3:1 warning There should be at least one empty line between import groups import/order
10
+ 4:1 warning `../../packages/mcp-utils/src/index.js` import should occur before import of `./services/prompt-store.js` import/order
11
+
12
+ /home/runner/_work/devglide/devglide/src/apps/prompts/services/prompt-store.ts
13
+ 2:1 warning There should be at least one empty line between import groups import/order
14
+ 4:1 warning `../../../project-context.js` import should occur before type import of `../types.js` import/order
15
+ 5:1 warning `../../../packages/paths.js` import should occur before type import of `../types.js` import/order
16
+ 6:1 warning `../../../packages/json-file-store.js` import should occur before type import of `../types.js` import/order
17
+
18
+ /home/runner/_work/devglide/devglide/src/apps/prompts/src/index.ts
19
+ 2:1 warning There should be at least one empty line between import groups import/order
20
+ 3:1 warning `@devglide/mcp-utils` import should occur before import of `../mcp.js` import/order
21
+
22
+ ✖ 9 problems (0 errors, 9 warnings)
23
+ 0 errors and 9 warnings potentially fixable with the `--fix` option.
24
+
@@ -0,0 +1,5 @@
1
+  WARN  Issue while reading "/home/runner/_work/devglide/devglide/.npmrc". Failed to replace env in config: ${NODE_AUTH_TOKEN}
2
+
3
+ > @devglide/prompts@0.1.0 typecheck /home/runner/_work/devglide/devglide/src/apps/prompts
4
+ > tsc --noEmit
5
+
@@ -0,0 +1,175 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { z } from 'zod';
3
+ import { PromptStore } from './services/prompt-store.js';
4
+ import { jsonResult, errorResult, createDevglideMcpServer } from '../../packages/mcp-utils/src/index.js';
5
+
6
+ export function createPromptsMcpServer(): McpServer {
7
+ const server = createDevglideMcpServer(
8
+ 'devglide-prompts',
9
+ '0.1.0',
10
+ 'Reusable prompt library with variable interpolation and ratings',
11
+ {
12
+ instructions: [
13
+ '## Prompts — Usage Conventions',
14
+ '',
15
+ '### Purpose',
16
+ '- The prompt library stores reusable LLM prompt templates with {{variable}} placeholders.',
17
+ '- Use it to avoid rewriting the same prompt from scratch in every conversation.',
18
+ '',
19
+ '### Managing prompts',
20
+ '- Use `prompts_list` and `prompts_render` to reuse existing prompts before writing new ones from scratch.',
21
+ '- Use `prompts_get` to retrieve the full prompt including content and variables.',
22
+ '- Use `prompts_add` to save a new reusable prompt template.',
23
+ '- Use `prompts_update` to refine content, adjust rating, or add evaluation notes.',
24
+ '- Use `prompts_remove` to delete a prompt by ID.',
25
+ '- Use `prompts_context` to get all prompts as compiled markdown for LLM context injection.',
26
+ ],
27
+ },
28
+ );
29
+
30
+ const store = PromptStore.getInstance();
31
+
32
+ // ── 1. prompts_list ───────────────────────────────────────────────────────
33
+
34
+ server.tool(
35
+ 'prompts_list',
36
+ 'List all prompts. Optionally filter by category, tags, or keyword search.',
37
+ {
38
+ category: z.string().optional().describe('Filter by category'),
39
+ tags: z.string().optional().describe('JSON array of tags to filter by (all must match)'),
40
+ search: z.string().optional().describe('Text search across title, description, category, and tags'),
41
+ },
42
+ async ({ category, tags, search }) => {
43
+ let parsedTags: string[] | undefined;
44
+ if (tags) {
45
+ try { parsedTags = JSON.parse(tags); } catch { return errorResult('Invalid JSON for tags'); }
46
+ }
47
+ const entries = await store.list({ category, tags: parsedTags, search });
48
+ return jsonResult(entries);
49
+ },
50
+ );
51
+
52
+ // ── 2. prompts_get ────────────────────────────────────────────────────────
53
+
54
+ server.tool(
55
+ 'prompts_get',
56
+ 'Get the full prompt by ID, including content and detected variables.',
57
+ {
58
+ id: z.string().describe('Prompt ID'),
59
+ },
60
+ async ({ id }) => {
61
+ const entry = await store.get(id);
62
+ if (!entry) return errorResult(`Prompt "${id}" not found`);
63
+ return jsonResult(entry);
64
+ },
65
+ );
66
+
67
+ // ── 3. prompts_render ─────────────────────────────────────────────────────
68
+
69
+ server.tool(
70
+ 'prompts_render',
71
+ 'Render a prompt template by substituting {{varName}} placeholders with provided values.',
72
+ {
73
+ id: z.string().describe('Prompt ID'),
74
+ vars: z.string().optional().describe('JSON object mapping variable names to values, e.g. {"name":"World"}'),
75
+ },
76
+ async ({ id, vars }) => {
77
+ let parsedVars: Record<string, string> = {};
78
+ if (vars) {
79
+ try { parsedVars = JSON.parse(vars); } catch { return errorResult('Invalid JSON for vars'); }
80
+ }
81
+ const rendered = await store.render(id, parsedVars);
82
+ if (rendered === null) return errorResult(`Prompt "${id}" not found`);
83
+ return { content: [{ type: 'text' as const, text: rendered }] };
84
+ },
85
+ );
86
+
87
+ // ── 4. prompts_add ────────────────────────────────────────────────────────
88
+
89
+ server.tool(
90
+ 'prompts_add',
91
+ 'Add a new prompt template to the library.',
92
+ {
93
+ title: z.string().describe('Human-readable name for this prompt'),
94
+ content: z.string().describe('Prompt text; use {{varName}} for variable placeholders'),
95
+ description: z.string().optional().describe('What this prompt does'),
96
+ category: z.string().optional().describe('Grouping category (e.g. "code-review", "refactor")'),
97
+ tags: z.string().optional().describe('JSON array of tag strings'),
98
+ model: z.string().optional().describe('Preferred model hint (e.g. "claude-opus-4-6")'),
99
+ temperature: z.number().min(0).max(2).optional().describe('Preferred temperature hint'),
100
+ rating: z.number().int().min(1).max(5).optional().describe('Quality rating 1–5'),
101
+ notes: z.string().optional().describe('Evaluation notes'),
102
+ scope: z.enum(['project', 'global']).optional().describe('Save as project-scoped or global (default: project if active, else global)'),
103
+ },
104
+ async ({ title, content, description, category, tags, model, temperature, rating, notes, scope }) => {
105
+ let parsedTags: string[] = [];
106
+ if (tags) {
107
+ try { parsedTags = JSON.parse(tags); } catch { return errorResult('Invalid JSON for tags'); }
108
+ }
109
+ const entry = await store.save({ title, content, description, category, tags: parsedTags, model, temperature, rating, notes, scope });
110
+ return jsonResult(entry);
111
+ },
112
+ );
113
+
114
+ // ── 5. prompts_update ─────────────────────────────────────────────────────
115
+
116
+ server.tool(
117
+ 'prompts_update',
118
+ 'Update an existing prompt by ID.',
119
+ {
120
+ id: z.string().describe('Prompt ID'),
121
+ title: z.string().optional().describe('New title'),
122
+ content: z.string().optional().describe('New prompt content'),
123
+ description: z.string().nullable().optional().describe('New description (null to clear)'),
124
+ category: z.string().nullable().optional().describe('New category (null to clear)'),
125
+ tags: z.string().nullable().optional().describe('JSON array of tag strings (null to clear)'),
126
+ model: z.string().nullable().optional().describe('Preferred model hint (null to clear)'),
127
+ temperature: z.number().min(0).max(2).nullable().optional().describe('Preferred temperature (null to clear)'),
128
+ rating: z.number().int().min(1).max(5).nullable().optional().describe('Quality rating 1–5 (null to clear)'),
129
+ notes: z.string().nullable().optional().describe('Evaluation notes (null to clear)'),
130
+ },
131
+ async ({ id, title, content, description, category, tags, model, temperature, rating, notes }) => {
132
+ let parsedTags: string[] | null | undefined;
133
+ if (tags === null) {
134
+ parsedTags = [];
135
+ } else if (tags) {
136
+ try { parsedTags = JSON.parse(tags); } catch { return errorResult('Invalid JSON for tags'); }
137
+ }
138
+
139
+ const updated = await store.update(id, { title, content, description, category, tags: parsedTags, model, temperature, rating, notes });
140
+ if (!updated) return errorResult('Prompt not found');
141
+ return jsonResult(updated);
142
+ },
143
+ );
144
+
145
+ // ── 6. prompts_remove ─────────────────────────────────────────────────────
146
+
147
+ server.tool(
148
+ 'prompts_remove',
149
+ 'Delete a prompt by ID.',
150
+ {
151
+ id: z.string().describe('Prompt ID'),
152
+ },
153
+ async ({ id }) => {
154
+ const deleted = await store.delete(id);
155
+ if (!deleted) return errorResult('Prompt not found');
156
+ return jsonResult({ ok: true });
157
+ },
158
+ );
159
+
160
+ // ── 7. prompts_context ──────────────────────────────────────────────────
161
+
162
+ server.tool(
163
+ 'prompts_context',
164
+ 'Get all prompts as compiled markdown for LLM context injection.',
165
+ {},
166
+ async () => {
167
+ const markdown = await store.getCompiledContext();
168
+ return {
169
+ content: [{ type: 'text' as const, text: markdown || 'No prompts defined.' }],
170
+ };
171
+ },
172
+ );
173
+
174
+ return server;
175
+ }
@@ -0,0 +1,21 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*|*MINGW*|*MSYS*)
6
+ if command -v cygpath > /dev/null 2>&1; then
7
+ basedir=`cygpath -w "$basedir"`
8
+ fi
9
+ ;;
10
+ esac
11
+
12
+ if [ -z "$NODE_PATH" ]; then
13
+ export NODE_PATH="/home/runner/_work/devglide/devglide/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/_work/devglide/devglide/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/_work/devglide/devglide/node_modules/.pnpm/node_modules"
14
+ else
15
+ export NODE_PATH="/home/runner/_work/devglide/devglide/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/_work/devglide/devglide/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/_work/devglide/devglide/node_modules/.pnpm/node_modules:$NODE_PATH"
16
+ fi
17
+ if [ -x "$basedir/node" ]; then
18
+ exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@"
19
+ else
20
+ exec node "$basedir/../typescript/bin/tsc" "$@"
21
+ fi
@@ -0,0 +1,21 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*|*MINGW*|*MSYS*)
6
+ if command -v cygpath > /dev/null 2>&1; then
7
+ basedir=`cygpath -w "$basedir"`
8
+ fi
9
+ ;;
10
+ esac
11
+
12
+ if [ -z "$NODE_PATH" ]; then
13
+ export NODE_PATH="/home/runner/_work/devglide/devglide/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/_work/devglide/devglide/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/_work/devglide/devglide/node_modules/.pnpm/node_modules"
14
+ else
15
+ export NODE_PATH="/home/runner/_work/devglide/devglide/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/_work/devglide/devglide/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/_work/devglide/devglide/node_modules/.pnpm/node_modules:$NODE_PATH"
16
+ fi
17
+ if [ -x "$basedir/node" ]; then
18
+ exec "$basedir/node" "$basedir/../typescript/bin/tsserver" "$@"
19
+ else
20
+ exec node "$basedir/../typescript/bin/tsserver" "$@"
21
+ fi
@@ -0,0 +1,21 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*|*MINGW*|*MSYS*)
6
+ if command -v cygpath > /dev/null 2>&1; then
7
+ basedir=`cygpath -w "$basedir"`
8
+ fi
9
+ ;;
10
+ esac
11
+
12
+ if [ -z "$NODE_PATH" ]; then
13
+ export NODE_PATH="/home/runner/_work/devglide/devglide/node_modules/.pnpm/tsx@4.21.0/node_modules/tsx/node_modules:/home/runner/_work/devglide/devglide/node_modules/.pnpm/tsx@4.21.0/node_modules:/home/runner/_work/devglide/devglide/node_modules/.pnpm/node_modules"
14
+ else
15
+ export NODE_PATH="/home/runner/_work/devglide/devglide/node_modules/.pnpm/tsx@4.21.0/node_modules/tsx/node_modules:/home/runner/_work/devglide/devglide/node_modules/.pnpm/tsx@4.21.0/node_modules:/home/runner/_work/devglide/devglide/node_modules/.pnpm/node_modules:$NODE_PATH"
16
+ fi
17
+ if [ -x "$basedir/node" ]; then
18
+ exec "$basedir/node" "$basedir/../tsx/dist/cli.mjs" "$@"
19
+ else
20
+ exec node "$basedir/../tsx/dist/cli.mjs" "$@"
21
+ fi
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@devglide/prompts",
3
+ "version": "0.1.0",
4
+ "description": "Reusable prompt library with variable interpolation and ratings",
5
+ "type": "module",
6
+ "main": "dist/src/index.js",
7
+ "scripts": {
8
+ "dev": "tsx watch src/index.ts",
9
+ "build": "tsc",
10
+ "start": "node dist/src/index.js",
11
+ "clean": "rm -rf dist",
12
+ "typecheck": "tsc --noEmit",
13
+ "lint": "eslint ."
14
+ },
15
+ "private": true,
16
+ "dependencies": {
17
+ "@devglide/mcp-utils": "workspace:*",
18
+ "@modelcontextprotocol/sdk": "^1.12.1",
19
+ "zod": "^3.25.49"
20
+ },
21
+ "devDependencies": {
22
+ "tsx": "^4.19.4",
23
+ "typescript": "^5.8.0"
24
+ }
25
+ }