jfl 0.8.1 → 0.9.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/dist/commands/doctor.d.ts +1 -0
  2. package/dist/commands/doctor.d.ts.map +1 -1
  3. package/dist/commands/doctor.js +30 -1
  4. package/dist/commands/doctor.js.map +1 -1
  5. package/dist/commands/ide.d.ts +2 -1
  6. package/dist/commands/ide.d.ts.map +1 -1
  7. package/dist/commands/ide.js +60 -1
  8. package/dist/commands/ide.js.map +1 -1
  9. package/dist/commands/init-from-service.d.ts +15 -0
  10. package/dist/commands/init-from-service.d.ts.map +1 -0
  11. package/dist/commands/init-from-service.js +541 -0
  12. package/dist/commands/init-from-service.js.map +1 -0
  13. package/dist/commands/init.d.ts +1 -0
  14. package/dist/commands/init.d.ts.map +1 -1
  15. package/dist/commands/init.js +32 -1
  16. package/dist/commands/init.js.map +1 -1
  17. package/dist/commands/kanban.d.ts.map +1 -1
  18. package/dist/commands/kanban.js +13 -4
  19. package/dist/commands/kanban.js.map +1 -1
  20. package/dist/commands/linear.d.ts +41 -0
  21. package/dist/commands/linear.d.ts.map +1 -0
  22. package/dist/commands/linear.js +715 -0
  23. package/dist/commands/linear.js.map +1 -0
  24. package/dist/commands/peter.d.ts.map +1 -1
  25. package/dist/commands/peter.js +232 -25
  26. package/dist/commands/peter.js.map +1 -1
  27. package/dist/commands/services.d.ts.map +1 -1
  28. package/dist/commands/services.js +146 -0
  29. package/dist/commands/services.js.map +1 -1
  30. package/dist/commands/setup.d.ts.map +1 -1
  31. package/dist/commands/setup.js +173 -13
  32. package/dist/commands/setup.js.map +1 -1
  33. package/dist/commands/telemetry-monitor.d.ts +11 -0
  34. package/dist/commands/telemetry-monitor.d.ts.map +1 -0
  35. package/dist/commands/telemetry-monitor.js +224 -0
  36. package/dist/commands/telemetry-monitor.js.map +1 -0
  37. package/dist/commands/telemetry-test.d.ts +11 -0
  38. package/dist/commands/telemetry-test.d.ts.map +1 -0
  39. package/dist/commands/telemetry-test.js +67 -0
  40. package/dist/commands/telemetry-test.js.map +1 -0
  41. package/dist/commands/tenet-agents.d.ts +13 -0
  42. package/dist/commands/tenet-agents.d.ts.map +1 -0
  43. package/dist/commands/tenet-agents.js +191 -0
  44. package/dist/commands/tenet-agents.js.map +1 -0
  45. package/dist/commands/tenet-setup.d.ts +19 -0
  46. package/dist/commands/tenet-setup.d.ts.map +1 -0
  47. package/dist/commands/tenet-setup.js +131 -0
  48. package/dist/commands/tenet-setup.js.map +1 -0
  49. package/dist/commands/train.d.ts +18 -0
  50. package/dist/commands/train.d.ts.map +1 -1
  51. package/dist/commands/train.js +182 -0
  52. package/dist/commands/train.js.map +1 -1
  53. package/dist/commands/whoami.d.ts +2 -0
  54. package/dist/commands/whoami.d.ts.map +1 -0
  55. package/dist/commands/whoami.js +24 -0
  56. package/dist/commands/whoami.js.map +1 -0
  57. package/dist/index.js +159 -10
  58. package/dist/index.js.map +1 -1
  59. package/dist/lib/advanced-setup.d.ts +78 -0
  60. package/dist/lib/advanced-setup.d.ts.map +1 -0
  61. package/dist/lib/advanced-setup.js +433 -0
  62. package/dist/lib/advanced-setup.js.map +1 -0
  63. package/dist/lib/agent-config.d.ts +33 -0
  64. package/dist/lib/agent-config.d.ts.map +1 -1
  65. package/dist/lib/agent-config.js +26 -0
  66. package/dist/lib/agent-config.js.map +1 -1
  67. package/dist/lib/counterfactual-training-bridge.d.ts +114 -0
  68. package/dist/lib/counterfactual-training-bridge.d.ts.map +1 -0
  69. package/dist/lib/counterfactual-training-bridge.js +322 -0
  70. package/dist/lib/counterfactual-training-bridge.js.map +1 -0
  71. package/dist/lib/discovery-agent.d.ts +48 -0
  72. package/dist/lib/discovery-agent.d.ts.map +1 -0
  73. package/dist/lib/discovery-agent.js +111 -0
  74. package/dist/lib/discovery-agent.js.map +1 -0
  75. package/dist/lib/flow-engine.d.ts.map +1 -1
  76. package/dist/lib/flow-engine.js +46 -8
  77. package/dist/lib/flow-engine.js.map +1 -1
  78. package/dist/lib/gtm-generator.d.ts +29 -0
  79. package/dist/lib/gtm-generator.d.ts.map +1 -0
  80. package/dist/lib/gtm-generator.js +252 -0
  81. package/dist/lib/gtm-generator.js.map +1 -0
  82. package/dist/lib/hub-health.d.ts +40 -0
  83. package/dist/lib/hub-health.d.ts.map +1 -0
  84. package/dist/lib/hub-health.js +89 -0
  85. package/dist/lib/hub-health.js.map +1 -0
  86. package/dist/lib/invariant-monitor.d.ts +6 -2
  87. package/dist/lib/invariant-monitor.d.ts.map +1 -1
  88. package/dist/lib/invariant-monitor.js +89 -2
  89. package/dist/lib/invariant-monitor.js.map +1 -1
  90. package/dist/lib/journal-analyzer.d.ts +71 -0
  91. package/dist/lib/journal-analyzer.d.ts.map +1 -0
  92. package/dist/lib/journal-analyzer.js +306 -0
  93. package/dist/lib/journal-analyzer.js.map +1 -0
  94. package/dist/lib/linear-client.d.ts +73 -0
  95. package/dist/lib/linear-client.d.ts.map +1 -0
  96. package/dist/lib/linear-client.js +112 -0
  97. package/dist/lib/linear-client.js.map +1 -0
  98. package/dist/lib/linear-id-map.d.ts +20 -0
  99. package/dist/lib/linear-id-map.d.ts.map +1 -0
  100. package/dist/lib/linear-id-map.js +57 -0
  101. package/dist/lib/linear-id-map.js.map +1 -0
  102. package/dist/lib/linear-kanban.d.ts +66 -0
  103. package/dist/lib/linear-kanban.d.ts.map +1 -0
  104. package/dist/lib/linear-kanban.js +175 -0
  105. package/dist/lib/linear-kanban.js.map +1 -0
  106. package/dist/lib/onboarding.d.ts +40 -0
  107. package/dist/lib/onboarding.d.ts.map +1 -0
  108. package/dist/lib/onboarding.js +213 -0
  109. package/dist/lib/onboarding.js.map +1 -0
  110. package/dist/lib/physical-world-model.d.ts +50 -0
  111. package/dist/lib/physical-world-model.d.ts.map +1 -0
  112. package/dist/lib/physical-world-model.js +251 -0
  113. package/dist/lib/physical-world-model.js.map +1 -0
  114. package/dist/lib/planning-loop.d.ts +157 -0
  115. package/dist/lib/planning-loop.d.ts.map +1 -0
  116. package/dist/lib/planning-loop.js +537 -0
  117. package/dist/lib/planning-loop.js.map +1 -0
  118. package/dist/lib/policy-head.d.ts +13 -0
  119. package/dist/lib/policy-head.d.ts.map +1 -1
  120. package/dist/lib/policy-head.js +168 -2
  121. package/dist/lib/policy-head.js.map +1 -1
  122. package/dist/lib/resource-optimizer-middleware.d.ts +39 -0
  123. package/dist/lib/resource-optimizer-middleware.d.ts.map +1 -0
  124. package/dist/lib/resource-optimizer-middleware.js +222 -0
  125. package/dist/lib/resource-optimizer-middleware.js.map +1 -0
  126. package/dist/lib/resource-optimizer.d.ts +71 -0
  127. package/dist/lib/resource-optimizer.d.ts.map +1 -0
  128. package/dist/lib/resource-optimizer.js +228 -0
  129. package/dist/lib/resource-optimizer.js.map +1 -0
  130. package/dist/lib/rl-manager.d.ts +74 -0
  131. package/dist/lib/rl-manager.d.ts.map +1 -0
  132. package/dist/lib/rl-manager.js +244 -0
  133. package/dist/lib/rl-manager.js.map +1 -0
  134. package/dist/lib/service-analyzer.d.ts +76 -0
  135. package/dist/lib/service-analyzer.d.ts.map +1 -0
  136. package/dist/lib/service-analyzer.js +704 -0
  137. package/dist/lib/service-analyzer.js.map +1 -0
  138. package/dist/lib/service-gtm.js +2 -2
  139. package/dist/lib/service-gtm.js.map +1 -1
  140. package/dist/lib/service-questionnaire.d.ts +11 -0
  141. package/dist/lib/service-questionnaire.d.ts.map +1 -0
  142. package/dist/lib/service-questionnaire.js +89 -0
  143. package/dist/lib/service-questionnaire.js.map +1 -0
  144. package/dist/lib/setup/agent-generator.d.ts +2 -0
  145. package/dist/lib/setup/agent-generator.d.ts.map +1 -1
  146. package/dist/lib/setup/agent-generator.js +128 -4
  147. package/dist/lib/setup/agent-generator.js.map +1 -1
  148. package/dist/lib/setup/flow-generator.d.ts +10 -0
  149. package/dist/lib/setup/flow-generator.d.ts.map +1 -0
  150. package/dist/lib/setup/flow-generator.js +113 -0
  151. package/dist/lib/setup/flow-generator.js.map +1 -0
  152. package/dist/lib/setup/invariant-bridge.d.ts +91 -0
  153. package/dist/lib/setup/invariant-bridge.d.ts.map +1 -0
  154. package/dist/lib/setup/invariant-bridge.js +384 -0
  155. package/dist/lib/setup/invariant-bridge.js.map +1 -0
  156. package/dist/lib/setup/spec-generator.d.ts +41 -5
  157. package/dist/lib/setup/spec-generator.d.ts.map +1 -1
  158. package/dist/lib/setup/spec-generator.js +503 -29
  159. package/dist/lib/setup/spec-generator.js.map +1 -1
  160. package/dist/lib/stratus-client.js +1 -1
  161. package/dist/lib/stratus-client.js.map +1 -1
  162. package/dist/lib/surface-agent.d.ts +78 -0
  163. package/dist/lib/surface-agent.d.ts.map +1 -0
  164. package/dist/lib/surface-agent.js +105 -0
  165. package/dist/lib/surface-agent.js.map +1 -0
  166. package/dist/lib/surface-coordination-example.d.ts +30 -0
  167. package/dist/lib/surface-coordination-example.d.ts.map +1 -0
  168. package/dist/lib/surface-coordination-example.js +164 -0
  169. package/dist/lib/surface-coordination-example.js.map +1 -0
  170. package/dist/lib/telemetry/physical-world-collector.d.ts +15 -0
  171. package/dist/lib/telemetry/physical-world-collector.d.ts.map +1 -0
  172. package/dist/lib/telemetry/physical-world-collector.js +177 -0
  173. package/dist/lib/telemetry/physical-world-collector.js.map +1 -0
  174. package/dist/lib/telemetry/training-bridge.d.ts +51 -0
  175. package/dist/lib/telemetry/training-bridge.d.ts.map +1 -0
  176. package/dist/lib/telemetry/training-bridge.js +185 -0
  177. package/dist/lib/telemetry/training-bridge.js.map +1 -0
  178. package/dist/lib/telemetry.d.ts +2 -1
  179. package/dist/lib/telemetry.d.ts.map +1 -1
  180. package/dist/lib/telemetry.js +23 -2
  181. package/dist/lib/telemetry.js.map +1 -1
  182. package/dist/lib/tenet-board-agent.d.ts +52 -0
  183. package/dist/lib/tenet-board-agent.d.ts.map +1 -0
  184. package/dist/lib/tenet-board-agent.js +226 -0
  185. package/dist/lib/tenet-board-agent.js.map +1 -0
  186. package/dist/lib/tenet-ide-agent.d.ts +40 -0
  187. package/dist/lib/tenet-ide-agent.d.ts.map +1 -0
  188. package/dist/lib/tenet-ide-agent.js +199 -0
  189. package/dist/lib/tenet-ide-agent.js.map +1 -0
  190. package/dist/lib/workspace/data-pipeline.d.ts.map +1 -1
  191. package/dist/lib/workspace/data-pipeline.js +27 -5
  192. package/dist/lib/workspace/data-pipeline.js.map +1 -1
  193. package/dist/lib/workspace/sidebar-runner.d.ts +13 -0
  194. package/dist/lib/workspace/sidebar-runner.d.ts.map +1 -0
  195. package/dist/lib/workspace/sidebar-runner.js +419 -0
  196. package/dist/lib/workspace/sidebar-runner.js.map +1 -0
  197. package/dist/lib/workspace/surface-registry.d.ts.map +1 -1
  198. package/dist/lib/workspace/surface-registry.js +4 -1
  199. package/dist/lib/workspace/surface-registry.js.map +1 -1
  200. package/dist/lib/workspace/surfaces/agent-overview.d.ts +3 -3
  201. package/dist/lib/workspace/surfaces/agent-overview.d.ts.map +1 -1
  202. package/dist/lib/workspace/surfaces/agent-overview.js +3 -3
  203. package/dist/lib/workspace/surfaces/agent-overview.js.map +1 -1
  204. package/dist/lib/workspace/surfaces/index.d.ts +3 -0
  205. package/dist/lib/workspace/surfaces/index.d.ts.map +1 -1
  206. package/dist/lib/workspace/surfaces/index.js +3 -0
  207. package/dist/lib/workspace/surfaces/index.js.map +1 -1
  208. package/dist/lib/workspace/surfaces/kanban.d.ts +15 -0
  209. package/dist/lib/workspace/surfaces/kanban.d.ts.map +1 -0
  210. package/dist/lib/workspace/surfaces/kanban.js +43 -0
  211. package/dist/lib/workspace/surfaces/kanban.js.map +1 -0
  212. package/dist/lib/workspace/surfaces/physical-world.d.ts +15 -0
  213. package/dist/lib/workspace/surfaces/physical-world.d.ts.map +1 -0
  214. package/dist/lib/workspace/surfaces/physical-world.js +37 -0
  215. package/dist/lib/workspace/surfaces/physical-world.js.map +1 -0
  216. package/dist/lib/workspace/surfaces/sidebar.d.ts +22 -0
  217. package/dist/lib/workspace/surfaces/sidebar.d.ts.map +1 -0
  218. package/dist/lib/workspace/surfaces/sidebar.js +90 -0
  219. package/dist/lib/workspace/surfaces/sidebar.js.map +1 -0
  220. package/dist/types/flows.d.ts +2 -1
  221. package/dist/types/flows.d.ts.map +1 -1
  222. package/dist/types/physical-world-model.d.ts +65 -0
  223. package/dist/types/physical-world-model.d.ts.map +1 -0
  224. package/dist/types/physical-world-model.js +43 -0
  225. package/dist/types/physical-world-model.js.map +1 -0
  226. package/dist/types/telemetry.d.ts +37 -0
  227. package/dist/types/telemetry.d.ts.map +1 -1
  228. package/dist/types/world-model.d.ts.map +1 -1
  229. package/dist/types/world-model.js +14 -7
  230. package/dist/types/world-model.js.map +1 -1
  231. package/dist/utils/context-hub-port.d.ts.map +1 -1
  232. package/dist/utils/context-hub-port.js +6 -1
  233. package/dist/utils/context-hub-port.js.map +1 -1
  234. package/package.json +3 -2
  235. package/packages/pi/extensions/index.ts +34 -6
  236. package/scripts/telemetry-dashboard.sh +44 -0
  237. package/scripts/test-planning-loop-e2e.ts +181 -0
  238. package/scripts/test-server-inference.ts +49 -0
  239. package/scripts/test-state-sensitivity.ts +32 -0
  240. package/scripts/train/v2/benchmark.py +661 -0
  241. package/scripts/train/v2/generate_balanced.py +439 -0
  242. package/scripts/train/v2/generate_hard_negatives.py +219 -0
  243. package/scripts/train/v2/infer.py +149 -36
  244. package/scripts/train/v2/infer_server.py +224 -0
  245. package/scripts/train/v2/online_train.py +576 -0
  246. package/scripts/train/v2/precompute.py +24 -6
  247. package/template/CLAUDE.md +74 -132
@@ -0,0 +1,704 @@
1
+ /**
2
+ * Service Analyzer
3
+ *
4
+ * Deep codebase analysis for existing software projects.
5
+ * Two-pass: static file scan + optional LLM synthesis.
6
+ *
7
+ * @purpose Deep-analyze a codebase to generate GTM context and tailored questionnaire
8
+ */
9
+ import { existsSync, readFileSync, readdirSync, statSync } from "fs";
10
+ import { join, relative, extname } from "path";
11
+ import { execSync } from "child_process";
12
+ import { extractServiceMetadata } from "./service-detector.js";
13
+ const SKIP_DIRS = new Set([
14
+ "node_modules", "dist", "build", "out", ".next", ".git", ".jfl", ".claude",
15
+ ".tenet", "__pycache__", ".venv", "venv", "target", "vendor", ".turbo",
16
+ "coverage", ".nyc_output", ".cache", ".parcel-cache", "tmp", "temp",
17
+ ]);
18
+ const ENTRY_POINT_CANDIDATES = [
19
+ "src/index.ts", "src/index.tsx", "src/main.ts", "src/main.tsx",
20
+ "src/app.ts", "src/server.ts", "src/cli.ts",
21
+ "app/page.tsx", "app/layout.tsx", "pages/index.tsx",
22
+ "index.ts", "index.js", "main.ts", "main.go", "main.rs",
23
+ "server.ts", "server.js", "server.py",
24
+ "app.ts", "app.js", "app.py",
25
+ "cmd/main.go", "src/main.rs", "src/lib.rs",
26
+ ];
27
+ const MAX_FILES = 200;
28
+ const MAX_PREVIEW_LINES = 100;
29
+ export function detectExistingProject(dir) {
30
+ const indicators = [
31
+ "package.json", "Cargo.toml", "go.mod", "pyproject.toml",
32
+ "Gemfile", "setup.py", "requirements.txt", "pom.xml",
33
+ "build.gradle", "CMakeLists.txt", "Makefile",
34
+ ];
35
+ const hasIndicator = indicators.some(f => existsSync(join(dir, f)));
36
+ const hasSourceDir = existsSync(join(dir, "src")) || existsSync(join(dir, "app")) || existsSync(join(dir, "lib"));
37
+ const isAlreadyJfl = existsSync(join(dir, ".jfl", "config.json"));
38
+ return (hasIndicator || hasSourceDir) && !isAlreadyJfl;
39
+ }
40
+ export function deepScan(servicePath) {
41
+ const metadata = extractServiceMetadata(servicePath);
42
+ return {
43
+ metadata,
44
+ readme: readReadme(servicePath),
45
+ fileTree: collectFileTree(servicePath),
46
+ entryPoints: collectEntryPoints(servicePath),
47
+ apiRoutes: detectApiRoutes(servicePath),
48
+ database: detectDatabase(servicePath),
49
+ auth: detectAuth(servicePath),
50
+ ci: detectCI(servicePath),
51
+ testing: detectTesting(servicePath),
52
+ envVars: detectEnvVars(servicePath),
53
+ openApiSpec: detectOpenApiSpec(servicePath),
54
+ gitHistory: collectGitHistory(servicePath),
55
+ monorepo: detectMonorepo(servicePath),
56
+ workspaces: detectWorkspaces(servicePath),
57
+ packageDescription: readPackageDescription(servicePath),
58
+ scripts: readPackageScripts(servicePath),
59
+ };
60
+ }
61
+ function readReadme(dir) {
62
+ const candidates = ["README.md", "README.MD", "readme.md", "README", "readme"];
63
+ for (const name of candidates) {
64
+ const p = join(dir, name);
65
+ if (existsSync(p)) {
66
+ const content = readFileSync(p, "utf-8");
67
+ return content.length > 15000 ? content.slice(0, 15000) + "\n...[truncated]" : content;
68
+ }
69
+ }
70
+ return null;
71
+ }
72
+ function collectFileTree(dir, maxFiles = MAX_FILES) {
73
+ const files = [];
74
+ function walk(current, depth) {
75
+ if (depth > 8 || files.length >= maxFiles)
76
+ return;
77
+ let entries;
78
+ try {
79
+ entries = readdirSync(current);
80
+ }
81
+ catch {
82
+ return;
83
+ }
84
+ for (const name of entries) {
85
+ if (files.length >= maxFiles)
86
+ break;
87
+ const fullPath = join(current, name);
88
+ let isDir = false;
89
+ try {
90
+ isDir = statSync(fullPath).isDirectory();
91
+ }
92
+ catch {
93
+ continue;
94
+ }
95
+ if (isDir) {
96
+ if (SKIP_DIRS.has(name) || name.startsWith("."))
97
+ continue;
98
+ walk(fullPath, depth + 1);
99
+ }
100
+ else {
101
+ files.push(relative(dir, fullPath));
102
+ }
103
+ }
104
+ }
105
+ walk(dir, 0);
106
+ return files.sort();
107
+ }
108
+ function collectEntryPoints(dir) {
109
+ const results = [];
110
+ for (const candidate of ENTRY_POINT_CANDIDATES) {
111
+ const fullPath = join(dir, candidate);
112
+ if (existsSync(fullPath)) {
113
+ try {
114
+ const content = readFileSync(fullPath, "utf-8");
115
+ const lines = content.split("\n").slice(0, MAX_PREVIEW_LINES);
116
+ results.push({ path: candidate, preview: lines.join("\n") });
117
+ }
118
+ catch {
119
+ // skip unreadable
120
+ }
121
+ }
122
+ if (results.length >= 6)
123
+ break;
124
+ }
125
+ return results;
126
+ }
127
+ function detectApiRoutes(dir) {
128
+ const routes = [];
129
+ const routeDirs = [
130
+ "src/routes", "src/api", "routes", "api",
131
+ "app/api", "pages/api", "src/app/api",
132
+ "src/controllers", "controllers",
133
+ ];
134
+ for (const routeDir of routeDirs) {
135
+ const fullPath = join(dir, routeDir);
136
+ if (!existsSync(fullPath))
137
+ continue;
138
+ try {
139
+ const files = collectFileTree(fullPath, 50);
140
+ for (const f of files) {
141
+ const ext = extname(f);
142
+ if ([".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".rs"].includes(ext)) {
143
+ routes.push(join(routeDir, f));
144
+ }
145
+ }
146
+ }
147
+ catch {
148
+ // skip
149
+ }
150
+ }
151
+ return routes;
152
+ }
153
+ function detectDatabase(dir) {
154
+ if (existsSync(join(dir, "prisma", "schema.prisma"))) {
155
+ const schema = readFileSync(join(dir, "prisma", "schema.prisma"), "utf-8");
156
+ const truncated = schema.length > 5000 ? schema.slice(0, 5000) + "\n...[truncated]" : schema;
157
+ return { type: "prisma", schema: truncated };
158
+ }
159
+ if (existsSync(join(dir, "drizzle.config.ts")) || existsSync(join(dir, "drizzle.config.js"))) {
160
+ return { type: "drizzle" };
161
+ }
162
+ const pkgPath = join(dir, "package.json");
163
+ if (existsSync(pkgPath)) {
164
+ try {
165
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
166
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
167
+ if (allDeps["mongoose"] || allDeps["mongodb"])
168
+ return { type: "mongodb" };
169
+ if (allDeps["pg"] || allDeps["postgres"] || allDeps["@neondatabase/serverless"])
170
+ return { type: "postgresql" };
171
+ if (allDeps["mysql2"] || allDeps["mysql"])
172
+ return { type: "mysql" };
173
+ if (allDeps["better-sqlite3"] || allDeps["sqlite3"])
174
+ return { type: "sqlite" };
175
+ if (allDeps["redis"] || allDeps["ioredis"])
176
+ return { type: "redis" };
177
+ if (allDeps["typeorm"])
178
+ return { type: "typeorm" };
179
+ if (allDeps["sequelize"])
180
+ return { type: "sequelize" };
181
+ if (allDeps["knex"])
182
+ return { type: "knex" };
183
+ }
184
+ catch {
185
+ // skip
186
+ }
187
+ }
188
+ const pyFiles = ["requirements.txt", "pyproject.toml"];
189
+ for (const f of pyFiles) {
190
+ const p = join(dir, f);
191
+ if (existsSync(p)) {
192
+ const content = readFileSync(p, "utf-8").toLowerCase();
193
+ if (content.includes("sqlalchemy"))
194
+ return { type: "sqlalchemy" };
195
+ if (content.includes("django"))
196
+ return { type: "django-orm" };
197
+ if (content.includes("tortoise"))
198
+ return { type: "tortoise-orm" };
199
+ if (content.includes("pymongo"))
200
+ return { type: "mongodb" };
201
+ }
202
+ }
203
+ if (existsSync(join(dir, "go.mod"))) {
204
+ const goMod = readFileSync(join(dir, "go.mod"), "utf-8").toLowerCase();
205
+ if (goMod.includes("gorm"))
206
+ return { type: "gorm" };
207
+ if (goMod.includes("sqlx"))
208
+ return { type: "sqlx" };
209
+ if (goMod.includes("pgx"))
210
+ return { type: "postgresql" };
211
+ }
212
+ return null;
213
+ }
214
+ function detectAuth(dir) {
215
+ const authFiles = [
216
+ "src/auth.ts", "src/lib/auth.ts", "src/middleware/auth.ts",
217
+ "auth.ts", "lib/auth.ts", "middleware/auth.ts",
218
+ "src/auth/index.ts", "app/api/auth/[...nextauth]/route.ts",
219
+ ];
220
+ for (const f of authFiles) {
221
+ if (existsSync(join(dir, f))) {
222
+ return { detected: true, type: f.includes("nextauth") ? "next-auth" : "custom" };
223
+ }
224
+ }
225
+ const pkgPath = join(dir, "package.json");
226
+ if (existsSync(pkgPath)) {
227
+ try {
228
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
229
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
230
+ if (allDeps["next-auth"] || allDeps["@auth/core"])
231
+ return { detected: true, type: "next-auth" };
232
+ if (allDeps["passport"])
233
+ return { detected: true, type: "passport" };
234
+ if (allDeps["@clerk/nextjs"] || allDeps["@clerk/clerk-sdk-node"])
235
+ return { detected: true, type: "clerk" };
236
+ if (allDeps["@supabase/supabase-js"])
237
+ return { detected: true, type: "supabase-auth" };
238
+ if (allDeps["firebase-admin"] || allDeps["firebase"])
239
+ return { detected: true, type: "firebase-auth" };
240
+ if (allDeps["jsonwebtoken"] || allDeps["jose"])
241
+ return { detected: true, type: "jwt" };
242
+ }
243
+ catch {
244
+ // skip
245
+ }
246
+ }
247
+ return null;
248
+ }
249
+ function detectCI(dir) {
250
+ return {
251
+ github_actions: existsSync(join(dir, ".github", "workflows")),
252
+ docker: existsSync(join(dir, "Dockerfile")) || existsSync(join(dir, "docker-compose.yml")),
253
+ fly: existsSync(join(dir, "fly.toml")),
254
+ vercel: existsSync(join(dir, "vercel.json")) || existsSync(join(dir, ".vercel")),
255
+ };
256
+ }
257
+ function detectTesting(dir) {
258
+ let framework = null;
259
+ let testCount = 0;
260
+ const pkgPath = join(dir, "package.json");
261
+ if (existsSync(pkgPath)) {
262
+ try {
263
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
264
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
265
+ if (allDeps["vitest"])
266
+ framework = "vitest";
267
+ else if (allDeps["jest"])
268
+ framework = "jest";
269
+ else if (allDeps["mocha"])
270
+ framework = "mocha";
271
+ else if (allDeps["ava"])
272
+ framework = "ava";
273
+ }
274
+ catch {
275
+ // skip
276
+ }
277
+ }
278
+ if (existsSync(join(dir, "pytest.ini")) || existsSync(join(dir, "conftest.py"))) {
279
+ framework = "pytest";
280
+ }
281
+ try {
282
+ const result = execSync(`find . -not -path '*/node_modules/*' -not -path '*/dist/*' -not -path '*/.git/*' \\( -name '*.test.*' -o -name '*.spec.*' -o -name 'test_*' \\) | wc -l`, { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 }).trim();
283
+ testCount = parseInt(result, 10) || 0;
284
+ }
285
+ catch {
286
+ // skip
287
+ }
288
+ return { framework, testCount };
289
+ }
290
+ function detectEnvVars(dir) {
291
+ const vars = new Set();
292
+ const envFiles = [".env.example", ".env.template", ".env.sample", ".env.local.example"];
293
+ for (const f of envFiles) {
294
+ const p = join(dir, f);
295
+ if (existsSync(p)) {
296
+ const content = readFileSync(p, "utf-8");
297
+ const matches = content.matchAll(/^([A-Z][A-Z0-9_]+)=/gm);
298
+ for (const m of matches) {
299
+ vars.add(m[1]);
300
+ }
301
+ }
302
+ }
303
+ try {
304
+ const result = execSync(`grep -roh 'process\\.env\\.[A-Z][A-Z0-9_]*' src/ app/ lib/ 2>/dev/null | sort -u | head -30`, { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 }).trim();
305
+ for (const line of result.split("\n")) {
306
+ const match = line.match(/process\.env\.([A-Z][A-Z0-9_]+)/);
307
+ if (match)
308
+ vars.add(match[1]);
309
+ }
310
+ }
311
+ catch {
312
+ // skip
313
+ }
314
+ return Array.from(vars).sort();
315
+ }
316
+ function detectOpenApiSpec(dir) {
317
+ const candidates = [
318
+ "openapi.yaml", "openapi.yml", "openapi.json",
319
+ "swagger.yaml", "swagger.yml", "swagger.json",
320
+ "api.yaml", "api.yml", "api.json",
321
+ "docs/openapi.yaml", "docs/openapi.yml",
322
+ ];
323
+ for (const candidate of candidates) {
324
+ const p = join(dir, candidate);
325
+ if (existsSync(p)) {
326
+ const content = readFileSync(p, "utf-8");
327
+ return content.length > 5000 ? content.slice(0, 5000) + "\n...[truncated]" : content;
328
+ }
329
+ }
330
+ return null;
331
+ }
332
+ function collectGitHistory(dir) {
333
+ const result = { commits: 0, contributors: [], firstCommit: "", recentCommits: [] };
334
+ try {
335
+ result.commits = parseInt(execSync("git rev-list --count HEAD", { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 }).trim(), 10) || 0;
336
+ }
337
+ catch {
338
+ return result;
339
+ }
340
+ try {
341
+ const contributors = execSync("git shortlog -sn --no-merges HEAD | head -10", { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 }).trim();
342
+ result.contributors = contributors.split("\n").map(l => l.replace(/^\s*\d+\s+/, "").trim()).filter(Boolean);
343
+ }
344
+ catch {
345
+ // skip
346
+ }
347
+ try {
348
+ result.firstCommit = execSync("git log --reverse --format='%ai' | head -1", { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 }).trim();
349
+ }
350
+ catch {
351
+ // skip
352
+ }
353
+ try {
354
+ const recent = execSync("git log --oneline -20", { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 }).trim();
355
+ result.recentCommits = recent.split("\n").filter(Boolean);
356
+ }
357
+ catch {
358
+ // skip
359
+ }
360
+ return result;
361
+ }
362
+ function detectMonorepo(dir) {
363
+ const pkgPath = join(dir, "package.json");
364
+ if (existsSync(pkgPath)) {
365
+ try {
366
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
367
+ if (pkg.workspaces)
368
+ return true;
369
+ }
370
+ catch {
371
+ // skip
372
+ }
373
+ }
374
+ if (existsSync(join(dir, "pnpm-workspace.yaml")))
375
+ return true;
376
+ if (existsSync(join(dir, "lerna.json")))
377
+ return true;
378
+ if (existsSync(join(dir, "turbo.json")))
379
+ return true;
380
+ if (existsSync(join(dir, "nx.json")))
381
+ return true;
382
+ return false;
383
+ }
384
+ function detectWorkspaces(dir) {
385
+ const pkgPath = join(dir, "package.json");
386
+ if (existsSync(pkgPath)) {
387
+ try {
388
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
389
+ if (Array.isArray(pkg.workspaces))
390
+ return pkg.workspaces;
391
+ if (Array.isArray(pkg.workspaces?.packages))
392
+ return pkg.workspaces.packages;
393
+ }
394
+ catch {
395
+ // skip
396
+ }
397
+ }
398
+ if (existsSync(join(dir, "pnpm-workspace.yaml"))) {
399
+ try {
400
+ const content = readFileSync(join(dir, "pnpm-workspace.yaml"), "utf-8");
401
+ const matches = content.matchAll(/- ['"]?([^'"]+)['"]?/g);
402
+ return Array.from(matches).map(m => m[1]);
403
+ }
404
+ catch {
405
+ // skip
406
+ }
407
+ }
408
+ return [];
409
+ }
410
+ function readPackageDescription(dir) {
411
+ const pkgPath = join(dir, "package.json");
412
+ if (existsSync(pkgPath)) {
413
+ try {
414
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
415
+ return pkg.description || null;
416
+ }
417
+ catch {
418
+ // skip
419
+ }
420
+ }
421
+ return null;
422
+ }
423
+ function readPackageScripts(dir) {
424
+ const pkgPath = join(dir, "package.json");
425
+ if (existsSync(pkgPath)) {
426
+ try {
427
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
428
+ return pkg.scripts || {};
429
+ }
430
+ catch {
431
+ // skip
432
+ }
433
+ }
434
+ return {};
435
+ }
436
+ export function buildAnalysisPrompt(scan) {
437
+ const sections = [];
438
+ sections.push(`# Codebase Analysis Request
439
+
440
+ Analyze this software project and produce a structured assessment. Be specific — cite file names, dependencies, and patterns you see.
441
+
442
+ ## Service Metadata (auto-detected)
443
+ - Name: ${scan.metadata.name}
444
+ - Type: ${scan.metadata.type}
445
+ - Version: ${scan.metadata.version}
446
+ - Port: ${scan.metadata.port || "unknown"}
447
+ - Dependencies: ${scan.metadata.dependencies.join(", ") || "none detected"}
448
+ `);
449
+ if (scan.packageDescription) {
450
+ sections.push(`## Package Description\n${scan.packageDescription}\n`);
451
+ }
452
+ if (scan.readme) {
453
+ sections.push(`## README\n\`\`\`\n${scan.readme}\n\`\`\`\n`);
454
+ }
455
+ sections.push(`## File Tree (${scan.fileTree.length} files)\n\`\`\`\n${scan.fileTree.join("\n")}\n\`\`\`\n`);
456
+ if (scan.entryPoints.length > 0) {
457
+ sections.push("## Entry Points\n");
458
+ for (const ep of scan.entryPoints) {
459
+ sections.push(`### ${ep.path}\n\`\`\`\n${ep.preview}\n\`\`\`\n`);
460
+ }
461
+ }
462
+ if (scan.apiRoutes.length > 0) {
463
+ sections.push(`## API Routes\n${scan.apiRoutes.map(r => `- ${r}`).join("\n")}\n`);
464
+ }
465
+ if (scan.database) {
466
+ sections.push(`## Database\n- Type: ${scan.database.type}`);
467
+ if (scan.database.schema) {
468
+ sections.push(`\n\`\`\`prisma\n${scan.database.schema}\n\`\`\``);
469
+ }
470
+ sections.push("");
471
+ }
472
+ if (scan.auth) {
473
+ sections.push(`## Authentication\n- Detected: ${scan.auth.detected}\n- Type: ${scan.auth.type || "unknown"}\n`);
474
+ }
475
+ sections.push(`## CI/CD
476
+ - GitHub Actions: ${scan.ci.github_actions}
477
+ - Docker: ${scan.ci.docker}
478
+ - Fly.io: ${scan.ci.fly}
479
+ - Vercel: ${scan.ci.vercel}
480
+ `);
481
+ sections.push(`## Testing
482
+ - Framework: ${scan.testing.framework || "none detected"}
483
+ - Test files: ${scan.testing.testCount}
484
+ `);
485
+ if (scan.envVars.length > 0) {
486
+ sections.push(`## Environment Variables\n${scan.envVars.map(v => `- ${v}`).join("\n")}\n`);
487
+ }
488
+ if (scan.gitHistory.commits > 0) {
489
+ sections.push(`## Git History
490
+ - Commits: ${scan.gitHistory.commits}
491
+ - Contributors: ${scan.gitHistory.contributors.join(", ") || "unknown"}
492
+ - First commit: ${scan.gitHistory.firstCommit || "unknown"}
493
+ - Recent commits:\n${scan.gitHistory.recentCommits.map(c => ` - ${c}`).join("\n")}
494
+ `);
495
+ }
496
+ if (scan.monorepo) {
497
+ sections.push(`## Monorepo\n- Workspaces: ${scan.workspaces.join(", ")}\n`);
498
+ }
499
+ if (Object.keys(scan.scripts).length > 0) {
500
+ sections.push(`## Package Scripts\n${Object.entries(scan.scripts).map(([k, v]) => `- ${k}: ${v}`).join("\n")}\n`);
501
+ }
502
+ sections.push(`## Instructions
503
+
504
+ Respond with ONLY a JSON object (no markdown fencing) matching this exact shape:
505
+
506
+ {
507
+ "confident": {
508
+ "purpose": "2-3 sentence description of what this project does",
509
+ "architecture": "1-2 sentence description of how it's built",
510
+ "keyFeatures": ["feature 1", "feature 2", ...],
511
+ "techStack": ["tech 1", "tech 2", ...],
512
+ "deploymentTarget": "where it's deployed or null"
513
+ },
514
+ "uncertain": ["thing I couldn't determine 1", "thing 2", ...],
515
+ "questions": [
516
+ {
517
+ "id": "unique-id",
518
+ "question": "Question text shown to user",
519
+ "why": "Brief explanation of why this matters for GTM",
520
+ "type": "text",
521
+ "default": "Best guess pre-fill or empty string",
522
+ "category": "vision|audience|roadmap|brand|narrative|deployment|monetization"
523
+ }
524
+ ]
525
+ }
526
+
527
+ Generate 8-15 questions. Prioritize questions where you DON'T have a confident answer. For questions where you CAN infer an answer from the codebase, provide a "default" value. Categories must be one of: vision, audience, roadmap, brand, narrative, deployment, monetization.
528
+
529
+ Focus questions on GTM (go-to-market) context: who is this for, what problem does it solve, how will they find it, what's the timeline, what's the competitive angle.`);
530
+ return sections.join("\n");
531
+ }
532
+ export function buildDocGenerationPrompt(scan, analysis, answers) {
533
+ const sections = [];
534
+ sections.push(`# Generate GTM Knowledge Documents
535
+
536
+ Based on the codebase analysis and user's answers, generate the complete set of GTM knowledge documents.
537
+
538
+ ## What We Know (from codebase analysis)
539
+
540
+ **Purpose:** ${analysis.confident.purpose}
541
+ **Architecture:** ${analysis.confident.architecture}
542
+ **Key Features:** ${analysis.confident.keyFeatures.join(", ")}
543
+ **Tech Stack:** ${analysis.confident.techStack.join(", ")}
544
+ **Deployment:** ${analysis.confident.deploymentTarget || "Unknown"}
545
+
546
+ ## User's Answers
547
+ `);
548
+ for (const [id, answer] of Object.entries(answers)) {
549
+ sections.push(`- **${id}**: ${answer}`);
550
+ }
551
+ sections.push(`
552
+ ## Service Details
553
+ - Name: ${scan.metadata.name}
554
+ - Type: ${scan.metadata.type}
555
+ - Database: ${scan.database?.type || "none"}
556
+ - Auth: ${scan.auth?.type || "none"}
557
+ - Test framework: ${scan.testing.framework || "none"}
558
+ - Test files: ${scan.testing.testCount}
559
+ - API routes: ${scan.apiRoutes.length}
560
+
561
+ ## Instructions
562
+
563
+ Generate a JSON object (no markdown fencing) with these keys. Each value is the FULL markdown content for that file. Use real content from the analysis and answers — never leave placeholders like "{fill this in}".
564
+
565
+ {
566
+ "VISION.md": "full markdown content...",
567
+ "NARRATIVE.md": "full markdown content...",
568
+ "THESIS.md": "full markdown content...",
569
+ "ROADMAP.md": "full markdown content...",
570
+ "BRAND_BRIEF.md": "full markdown content...",
571
+ "PRODUCT_SPEC.md": "full markdown content..."
572
+ }
573
+
574
+ ### Document Guidelines
575
+
576
+ **VISION.md** — Start with "# [Project Name]", include: the vision (2-3 paragraphs), who it's for, what problem it solves, the one-liner. Mark "Status: EMERGENT" unless the user was very definitive.
577
+
578
+ **NARRATIVE.md** — The story: how you explain it at a party, the before/after, emotional hook, key messages. Include casual and formal pitch variants.
579
+
580
+ **THESIS.md** — Why this will win: unfair advantages, timing, unique insight, competitive positioning.
581
+
582
+ **ROADMAP.md** — Phases with dates if provided. Current state assessment based on what exists in the codebase. Next milestones. Include "Phase 0: Current State" showing what's already built.
583
+
584
+ **BRAND_BRIEF.md** — Brand direction: vibe, personality, audience expectations. Can be minimal if user didn't provide much — that's fine, mark as "Needs refinement."
585
+
586
+ **PRODUCT_SPEC.md** — Technical product spec: features table (with status based on what exists), tech stack, architecture description, API routes, database schema summary, environment variables, deployment setup. This should be dense and useful for an engineer picking up the project.`);
587
+ return sections.join("\n");
588
+ }
589
+ export async function synthesizeWithLLM(prompt) {
590
+ const Anthropic = (await import("@anthropic-ai/sdk")).default;
591
+ const apiKey = process.env.ANTHROPIC_API_KEY;
592
+ if (!apiKey) {
593
+ throw new Error("ANTHROPIC_API_KEY not set");
594
+ }
595
+ const anthropic = new Anthropic({ apiKey });
596
+ const message = await anthropic.messages.create({
597
+ model: "claude-sonnet-4-20250514",
598
+ max_tokens: 8192,
599
+ messages: [{ role: "user", content: prompt }],
600
+ });
601
+ const textBlock = message.content.find(b => b.type === "text");
602
+ return textBlock?.text || "";
603
+ }
604
+ export function getStaticFallbackQuestions() {
605
+ return [
606
+ {
607
+ id: "purpose",
608
+ question: "What does this project do? (2-3 sentences)",
609
+ why: "Core of your VISION.md — everything else builds on this",
610
+ type: "text",
611
+ default: "",
612
+ category: "vision",
613
+ },
614
+ {
615
+ id: "audience",
616
+ question: "Who is this for? (specific person, not 'everyone')",
617
+ why: "Defines your messaging, positioning, and where to find users",
618
+ type: "text",
619
+ default: "",
620
+ category: "audience",
621
+ },
622
+ {
623
+ id: "problem",
624
+ question: "What problem does it solve for them?",
625
+ why: "The pain point drives your narrative and content",
626
+ type: "text",
627
+ default: "",
628
+ category: "vision",
629
+ },
630
+ {
631
+ id: "one-liner",
632
+ question: "What's your rough one-liner?",
633
+ why: "Used everywhere: Twitter bio, landing page, pitch",
634
+ type: "text",
635
+ default: "",
636
+ category: "narrative",
637
+ },
638
+ {
639
+ id: "party-pitch",
640
+ question: "How would you explain this at a party?",
641
+ why: "Your casual pitch — tests if the concept is clear",
642
+ type: "text",
643
+ default: "",
644
+ category: "narrative",
645
+ },
646
+ {
647
+ id: "before-after",
648
+ question: "What's the before/after? (their life before vs after)",
649
+ why: "Drives emotional connection in content and marketing",
650
+ type: "text",
651
+ default: "",
652
+ category: "narrative",
653
+ },
654
+ {
655
+ id: "launch-date",
656
+ question: "When do you want to launch? (date or timeframe)",
657
+ why: "Sets the countdown — everything works backward from this",
658
+ type: "text",
659
+ default: "",
660
+ category: "roadmap",
661
+ },
662
+ {
663
+ id: "mvp",
664
+ question: "What's the first thing that needs to work? (MVP)",
665
+ why: "Scopes the roadmap and prevents over-building",
666
+ type: "text",
667
+ default: "",
668
+ category: "roadmap",
669
+ },
670
+ {
671
+ id: "why-you",
672
+ question: "Why will YOU win? (not just anyone — you specifically)",
673
+ why: "Your unfair advantage — core of the thesis",
674
+ type: "text",
675
+ default: "",
676
+ category: "monetization",
677
+ },
678
+ {
679
+ id: "why-now",
680
+ question: "Why now? What changed that makes this possible/needed?",
681
+ why: "Timing thesis — investors and users both care about this",
682
+ type: "text",
683
+ default: "",
684
+ category: "monetization",
685
+ },
686
+ {
687
+ id: "vibe",
688
+ question: "What's the vibe? (professional, playful, bold, minimal, techy, human)",
689
+ why: "Sets brand direction for all visual and written content",
690
+ type: "text",
691
+ default: "",
692
+ category: "brand",
693
+ },
694
+ {
695
+ id: "deploy-target",
696
+ question: "Where is this deployed (or where will it be)?",
697
+ why: "Informs product spec and deployment docs",
698
+ type: "text",
699
+ default: "",
700
+ category: "deployment",
701
+ },
702
+ ];
703
+ }
704
+ //# sourceMappingURL=service-analyzer.js.map