praana 0.5.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 (204) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +124 -0
  3. package/bin/praana.js +17 -0
  4. package/bin/pran.js +17 -0
  5. package/dist/app-banner.d.ts +11 -0
  6. package/dist/app-banner.js +161 -0
  7. package/dist/app-controller.d.ts +44 -0
  8. package/dist/app-controller.js +143 -0
  9. package/dist/app-identity.d.ts +18 -0
  10. package/dist/app-identity.js +52 -0
  11. package/dist/auto-compact.d.ts +16 -0
  12. package/dist/auto-compact.js +101 -0
  13. package/dist/cli-args.d.ts +14 -0
  14. package/dist/cli-args.js +69 -0
  15. package/dist/compile-classic.d.ts +21 -0
  16. package/dist/compile-classic.js +106 -0
  17. package/dist/compiler.d.ts +75 -0
  18. package/dist/compiler.js +406 -0
  19. package/dist/config.d.ts +3 -0
  20. package/dist/config.js +433 -0
  21. package/dist/context-engine/activity-log.d.ts +9 -0
  22. package/dist/context-engine/activity-log.js +109 -0
  23. package/dist/context-engine/artifact-store.d.ts +32 -0
  24. package/dist/context-engine/artifact-store.js +272 -0
  25. package/dist/context-engine/bm25.d.ts +3 -0
  26. package/dist/context-engine/bm25.js +32 -0
  27. package/dist/context-engine/checkpoint.d.ts +34 -0
  28. package/dist/context-engine/checkpoint.js +430 -0
  29. package/dist/context-engine/classify.d.ts +3 -0
  30. package/dist/context-engine/classify.js +60 -0
  31. package/dist/context-engine/db.d.ts +73 -0
  32. package/dist/context-engine/db.js +505 -0
  33. package/dist/context-engine/distiller.d.ts +30 -0
  34. package/dist/context-engine/distiller.js +67 -0
  35. package/dist/context-engine/engine-compiler.d.ts +23 -0
  36. package/dist/context-engine/engine-compiler.js +297 -0
  37. package/dist/context-engine/error-tracker.d.ts +21 -0
  38. package/dist/context-engine/error-tracker.js +74 -0
  39. package/dist/context-engine/event-lineage.d.ts +26 -0
  40. package/dist/context-engine/event-lineage.js +120 -0
  41. package/dist/context-engine/extraction.d.ts +26 -0
  42. package/dist/context-engine/extraction.js +83 -0
  43. package/dist/context-engine/index.d.ts +82 -0
  44. package/dist/context-engine/index.js +238 -0
  45. package/dist/context-engine/scoring.d.ts +13 -0
  46. package/dist/context-engine/scoring.js +47 -0
  47. package/dist/context-engine/state-snapshot.d.ts +8 -0
  48. package/dist/context-engine/state-snapshot.js +50 -0
  49. package/dist/context-engine/summarize.d.ts +6 -0
  50. package/dist/context-engine/summarize.js +32 -0
  51. package/dist/context-engine/telemetry.d.ts +25 -0
  52. package/dist/context-engine/telemetry.js +64 -0
  53. package/dist/context-engine/turn-digest.d.ts +50 -0
  54. package/dist/context-engine/turn-digest.js +250 -0
  55. package/dist/context-engine/turn-ledger.d.ts +18 -0
  56. package/dist/context-engine/turn-ledger.js +184 -0
  57. package/dist/context-engine/turn-recorder.d.ts +24 -0
  58. package/dist/context-engine/turn-recorder.js +88 -0
  59. package/dist/context-engine/types.d.ts +201 -0
  60. package/dist/context-engine/types.js +4 -0
  61. package/dist/context-pressure.d.ts +19 -0
  62. package/dist/context-pressure.js +36 -0
  63. package/dist/distillers/generic.d.ts +14 -0
  64. package/dist/distillers/generic.js +93 -0
  65. package/dist/distillers/git-diff.d.ts +8 -0
  66. package/dist/distillers/git-diff.js +119 -0
  67. package/dist/distillers/index.d.ts +2 -0
  68. package/dist/distillers/index.js +16 -0
  69. package/dist/distillers/npm-test.d.ts +8 -0
  70. package/dist/distillers/npm-test.js +50 -0
  71. package/dist/distillers/rg-results.d.ts +8 -0
  72. package/dist/distillers/rg-results.js +28 -0
  73. package/dist/distillers/tsc-errors.d.ts +8 -0
  74. package/dist/distillers/tsc-errors.js +52 -0
  75. package/dist/event-log.d.ts +56 -0
  76. package/dist/event-log.js +214 -0
  77. package/dist/llm.d.ts +29 -0
  78. package/dist/llm.js +155 -0
  79. package/dist/logger.d.ts +94 -0
  80. package/dist/logger.js +287 -0
  81. package/dist/main.d.ts +1 -0
  82. package/dist/main.js +54 -0
  83. package/dist/memory/confidence.d.ts +7 -0
  84. package/dist/memory/confidence.js +37 -0
  85. package/dist/memory/consolidation.d.ts +26 -0
  86. package/dist/memory/consolidation.js +166 -0
  87. package/dist/memory/db.d.ts +40 -0
  88. package/dist/memory/db.js +283 -0
  89. package/dist/memory/dedup.d.ts +6 -0
  90. package/dist/memory/dedup.js +50 -0
  91. package/dist/memory/embedder-factory.d.ts +3 -0
  92. package/dist/memory/embedder-factory.js +81 -0
  93. package/dist/memory/embeddings.d.ts +15 -0
  94. package/dist/memory/embeddings.js +67 -0
  95. package/dist/memory/index.d.ts +9 -0
  96. package/dist/memory/index.js +11 -0
  97. package/dist/memory/ollama-summarizer.d.ts +19 -0
  98. package/dist/memory/ollama-summarizer.js +72 -0
  99. package/dist/memory/openai-summarizer.d.ts +21 -0
  100. package/dist/memory/openai-summarizer.js +51 -0
  101. package/dist/memory/store.d.ts +61 -0
  102. package/dist/memory/store.js +502 -0
  103. package/dist/memory/summarizer-factory.d.ts +3 -0
  104. package/dist/memory/summarizer-factory.js +69 -0
  105. package/dist/memory/summarizer.d.ts +4 -0
  106. package/dist/memory/summarizer.js +112 -0
  107. package/dist/memory/types.d.ts +87 -0
  108. package/dist/memory/types.js +17 -0
  109. package/dist/model-context.d.ts +15 -0
  110. package/dist/model-context.js +212 -0
  111. package/dist/project-detector.d.ts +37 -0
  112. package/dist/project-detector.js +604 -0
  113. package/dist/render.d.ts +15 -0
  114. package/dist/render.js +46 -0
  115. package/dist/session.d.ts +118 -0
  116. package/dist/session.js +809 -0
  117. package/dist/skills/index.d.ts +69 -0
  118. package/dist/skills/index.js +885 -0
  119. package/dist/skills/types.d.ts +93 -0
  120. package/dist/skills/types.js +8 -0
  121. package/dist/slash-commands.d.ts +14 -0
  122. package/dist/slash-commands.js +301 -0
  123. package/dist/state-graph.d.ts +38 -0
  124. package/dist/state-graph.js +255 -0
  125. package/dist/status-bar.d.ts +54 -0
  126. package/dist/status-bar.js +184 -0
  127. package/dist/thinking-display.d.ts +21 -0
  128. package/dist/thinking-display.js +37 -0
  129. package/dist/tool-summary.d.ts +4 -0
  130. package/dist/tool-summary.js +67 -0
  131. package/dist/tools/index.d.ts +925 -0
  132. package/dist/tools/index.js +86 -0
  133. package/dist/tools/knowledge.d.ts +140 -0
  134. package/dist/tools/knowledge.js +260 -0
  135. package/dist/tools/memory.d.ts +39 -0
  136. package/dist/tools/memory.js +300 -0
  137. package/dist/tools/search-code.d.ts +134 -0
  138. package/dist/tools/search-code.js +390 -0
  139. package/dist/tools/system.d.ts +16 -0
  140. package/dist/tools/system.js +499 -0
  141. package/dist/tools/tool-def.d.ts +6 -0
  142. package/dist/tools/tool-def.js +3 -0
  143. package/dist/turn-control.d.ts +51 -0
  144. package/dist/turn-control.js +210 -0
  145. package/dist/turn.d.ts +20 -0
  146. package/dist/turn.js +624 -0
  147. package/dist/types.d.ts +233 -0
  148. package/dist/types.js +4 -0
  149. package/dist/ui/readline-ui.d.ts +2 -0
  150. package/dist/ui/readline-ui.js +176 -0
  151. package/dist/ui/tui/app.d.ts +13 -0
  152. package/dist/ui/tui/app.js +270 -0
  153. package/dist/ui/tui/busy-indicator.d.ts +2 -0
  154. package/dist/ui/tui/busy-indicator.js +13 -0
  155. package/dist/ui/tui/components/gutter-rule.d.ts +5 -0
  156. package/dist/ui/tui/components/gutter-rule.js +9 -0
  157. package/dist/ui/tui/components/inline-tool-row.d.ts +10 -0
  158. package/dist/ui/tui/components/inline-tool-row.js +8 -0
  159. package/dist/ui/tui/components/prompt-input.d.ts +20 -0
  160. package/dist/ui/tui/components/prompt-input.js +120 -0
  161. package/dist/ui/tui/components/system-line.d.ts +5 -0
  162. package/dist/ui/tui/components/system-line.js +6 -0
  163. package/dist/ui/tui/components/thinking-block.d.ts +11 -0
  164. package/dist/ui/tui/components/thinking-block.js +31 -0
  165. package/dist/ui/tui/components/toast-line.d.ts +4 -0
  166. package/dist/ui/tui/components/toast-line.js +8 -0
  167. package/dist/ui/tui/components/tool-result-line.d.ts +5 -0
  168. package/dist/ui/tui/components/tool-result-line.js +6 -0
  169. package/dist/ui/tui/components/turn-footer.d.ts +5 -0
  170. package/dist/ui/tui/components/turn-footer.js +7 -0
  171. package/dist/ui/tui/components/user-block.d.ts +6 -0
  172. package/dist/ui/tui/components/user-block.js +6 -0
  173. package/dist/ui/tui/logo-banner.d.ts +5 -0
  174. package/dist/ui/tui/logo-banner.js +8 -0
  175. package/dist/ui/tui/markdown-render.d.ts +16 -0
  176. package/dist/ui/tui/markdown-render.js +218 -0
  177. package/dist/ui/tui/palette.d.ts +12 -0
  178. package/dist/ui/tui/palette.js +13 -0
  179. package/dist/ui/tui/reasoning-summary.d.ts +12 -0
  180. package/dist/ui/tui/reasoning-summary.js +27 -0
  181. package/dist/ui/tui/reducer.d.ts +92 -0
  182. package/dist/ui/tui/reducer.js +260 -0
  183. package/dist/ui/tui/run.d.ts +3 -0
  184. package/dist/ui/tui/run.js +40 -0
  185. package/dist/ui/tui/sink.d.ts +4 -0
  186. package/dist/ui/tui/sink.js +89 -0
  187. package/dist/ui/tui/status-bar-view.d.ts +5 -0
  188. package/dist/ui/tui/status-bar-view.js +44 -0
  189. package/dist/ui/tui/terminal-height.d.ts +12 -0
  190. package/dist/ui/tui/terminal-height.js +20 -0
  191. package/dist/ui/tui/terminal-width.d.ts +2 -0
  192. package/dist/ui/tui/terminal-width.js +5 -0
  193. package/dist/ui/tui/tool-display.d.ts +23 -0
  194. package/dist/ui/tui/tool-display.js +217 -0
  195. package/dist/ui/tui/transcript-line.d.ts +12 -0
  196. package/dist/ui/tui/transcript-line.js +43 -0
  197. package/dist/ui/tui/transcript-replay.d.ts +12 -0
  198. package/dist/ui/tui/transcript-replay.js +117 -0
  199. package/dist/ui-events.d.ts +39 -0
  200. package/dist/ui-events.js +33 -0
  201. package/dist/ui.d.ts +77 -0
  202. package/dist/ui.js +179 -0
  203. package/package.json +73 -0
  204. package/praana.config.example.toml +231 -0
@@ -0,0 +1,604 @@
1
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import * as toml from "toml";
4
+ import * as yaml from "js-yaml";
5
+ // ---- Detection tables ----
6
+ const MANIFEST_RULES = [
7
+ { file: "tsconfig.json", language: "TypeScript" },
8
+ { file: "pyproject.toml", language: "Python" },
9
+ { file: "go.mod", language: "Go" },
10
+ { file: "Cargo.toml", language: "Rust" },
11
+ { file: "Gemfile", language: "Ruby" },
12
+ { file: "composer.json", language: "PHP" },
13
+ { file: "pom.xml", language: "Java" },
14
+ { file: "build.gradle", language: "Java" },
15
+ { file: "build.gradle.kts", language: "Kotlin" },
16
+ { file: "pubspec.yaml", language: "Dart" },
17
+ { file: "mix.exs", language: "Elixir" },
18
+ { file: "deno.json", language: "TypeScript", framework: "Deno" },
19
+ { file: "deno.jsonc", language: "TypeScript", framework: "Deno" },
20
+ { file: "*.csproj", language: "C#" },
21
+ { file: "CMakeLists.txt", language: "C/C++" },
22
+ { file: "Package.swift", language: "Swift" },
23
+ ];
24
+ const DEPENDENCY_RULES = [
25
+ // Frontend frameworks
26
+ { depKey: "react", language: "TypeScript", framework: "React" },
27
+ { depKey: "next", language: "TypeScript", framework: "Next.js" },
28
+ { depKey: "vue", language: "TypeScript", framework: "Vue" },
29
+ { depKey: "@angular/core", language: "TypeScript", framework: "Angular" },
30
+ { depKey: "svelte", language: "TypeScript", framework: "Svelte" },
31
+ { depKey: "solid-js", language: "TypeScript", framework: "SolidJS" },
32
+ { depKey: "astro", language: "TypeScript", framework: "Astro" },
33
+ { depKey: "nuxt", language: "TypeScript", framework: "Nuxt" },
34
+ // Backend frameworks (Node)
35
+ { depKey: "express", language: "TypeScript", framework: "Express" },
36
+ { depKey: "fastify", language: "TypeScript", framework: "Fastify" },
37
+ { depKey: "hono", language: "TypeScript", framework: "Hono" },
38
+ { depKey: "koa", language: "TypeScript", framework: "Koa" },
39
+ { depKey: "nestjs", language: "TypeScript", framework: "NestJS" },
40
+ { depKey: "@nestjs/core", language: "TypeScript", framework: "NestJS" },
41
+ { depKey: "elysia", language: "TypeScript", framework: "Elysia" },
42
+ ];
43
+ const MONOREPO_FILES = [
44
+ ["pnpm-workspace.yaml", "pnpm Workspaces"],
45
+ ["turbo.json", "Turborepo"],
46
+ ["nx.json", "Nx"],
47
+ ["lerna.json", "Lerna"],
48
+ ["rush.json", "Rush"],
49
+ ];
50
+ const PACKAGE_MANAGER_LOCKFILES = [
51
+ ["bun.lockb", "bun"],
52
+ ["pnpm-lock.yaml", "pnpm"],
53
+ ["yarn.lock", "yarn"],
54
+ ["package-lock.json", "npm"],
55
+ ["Cargo.lock", "cargo"],
56
+ ["composer.lock", "composer"],
57
+ ["Gemfile.lock", "bundler"],
58
+ ["poetry.lock", "poetry"],
59
+ ["uv.lock", "uv"],
60
+ ["Pipfile.lock", "pipenv"],
61
+ ["go.sum", "go modules"],
62
+ ];
63
+ const CI_CD_FILES = [
64
+ [".github/workflows", "GitHub Actions"],
65
+ [".gitlab-ci.yml", "GitLab CI"],
66
+ ["Jenkinsfile", "Jenkins"],
67
+ [".circleci/config.yml", "CircleCI"],
68
+ [".travis.yml", "Travis CI"],
69
+ ["bitbucket-pipelines.yml", "Bitbucket Pipelines"],
70
+ [".drone.yml", "Drone CI"],
71
+ ["azure-pipelines.yml", "Azure Pipelines"],
72
+ ];
73
+ // ---- Manifest parsers ----
74
+ function tryParseJson(path) {
75
+ try {
76
+ return JSON.parse(readFileSync(path, "utf-8"));
77
+ }
78
+ catch {
79
+ return null;
80
+ }
81
+ }
82
+ function tryParseToml(path) {
83
+ try {
84
+ return toml.parse(readFileSync(path, "utf-8"));
85
+ }
86
+ catch {
87
+ return null;
88
+ }
89
+ }
90
+ function tryParseYaml(path) {
91
+ try {
92
+ return yaml.load(readFileSync(path, "utf-8"));
93
+ }
94
+ catch {
95
+ return null;
96
+ }
97
+ }
98
+ function parseGoMod(path) {
99
+ try {
100
+ const content = readFileSync(path, "utf-8");
101
+ const lines = content.split("\n");
102
+ let moduleName;
103
+ let goVersion;
104
+ const deps = [];
105
+ let inRequireBlock = false;
106
+ for (const line of lines) {
107
+ const trimmed = line.trim();
108
+ if (trimmed.startsWith("module ")) {
109
+ moduleName = trimmed.slice(7).trim();
110
+ }
111
+ else if (trimmed.startsWith("go ")) {
112
+ goVersion = trimmed.slice(3).trim();
113
+ }
114
+ else if (trimmed === "require (") {
115
+ inRequireBlock = true;
116
+ }
117
+ else if (trimmed === ")") {
118
+ inRequireBlock = false;
119
+ }
120
+ else if (inRequireBlock && trimmed && !trimmed.startsWith("//")) {
121
+ const parts = trimmed.split(/\s+/);
122
+ if (parts[0])
123
+ deps.push(parts[0]);
124
+ }
125
+ else if (trimmed.startsWith("require ") && !trimmed.includes("(")) {
126
+ const parts = trimmed.slice(8).trim().split(/\s+/);
127
+ if (parts[0])
128
+ deps.push(parts[0]);
129
+ }
130
+ }
131
+ return { module: moduleName, goVersion, deps };
132
+ }
133
+ catch {
134
+ return { deps: [] };
135
+ }
136
+ }
137
+ function parseGemfile(path) {
138
+ try {
139
+ const content = readFileSync(path, "utf-8");
140
+ const lines = content.split("\n");
141
+ let rubyVersion;
142
+ const gems = [];
143
+ for (const line of lines) {
144
+ const trimmed = line.trim();
145
+ const rubyMatch = trimmed.match(/^ruby\s+['"](.+)['"]/);
146
+ if (rubyMatch) {
147
+ rubyVersion = rubyMatch[1];
148
+ continue;
149
+ }
150
+ const gemMatch = trimmed.match(/^gem\s+['"]([^'"]+)['"]/);
151
+ if (gemMatch) {
152
+ gems.push(gemMatch[1]);
153
+ }
154
+ }
155
+ return { rubyVersion, gems };
156
+ }
157
+ catch {
158
+ return { gems: [] };
159
+ }
160
+ }
161
+ function detectCsprojFiles(cwd) {
162
+ try {
163
+ return readdirSync(cwd, { withFileTypes: true })
164
+ .filter((entry) => entry.isFile() && entry.name.endsWith(".csproj"))
165
+ .map((entry) => entry.name);
166
+ }
167
+ catch {
168
+ return [];
169
+ }
170
+ }
171
+ function normalizeDependencyName(dep) {
172
+ return dep.split(/[><=!;\s\[]/)[0].trim().toLowerCase();
173
+ }
174
+ // ---- Detector helpers ----
175
+ function detectFromManifests(cwd) {
176
+ const languages = new Set();
177
+ const frameworks = new Set();
178
+ for (const rule of MANIFEST_RULES) {
179
+ if (rule.file === "*.csproj") {
180
+ for (const file of detectCsprojFiles(cwd)) {
181
+ if (file.endsWith(".csproj")) {
182
+ if (rule.language)
183
+ languages.add(rule.language);
184
+ if (rule.framework)
185
+ frameworks.add(rule.framework);
186
+ break;
187
+ }
188
+ }
189
+ continue;
190
+ }
191
+ if (existsSync(join(cwd, rule.file))) {
192
+ if (rule.language)
193
+ languages.add(rule.language);
194
+ if (rule.framework)
195
+ frameworks.add(rule.framework);
196
+ }
197
+ }
198
+ return { languages: [...languages], frameworks: [...frameworks] };
199
+ }
200
+ function detectFromPackageJson(cwd) {
201
+ const pkgPath = join(cwd, "package.json");
202
+ const pkg = tryParseJson(pkgPath);
203
+ if (!pkg)
204
+ return null;
205
+ const extraLanguages = new Set();
206
+ const extraFrameworks = new Set();
207
+ const allDeps = {
208
+ ...(pkg.dependencies ?? {}),
209
+ ...(pkg.devDependencies ?? {}),
210
+ ...(pkg.peerDependencies ?? {}),
211
+ ...(pkg.optionalDependencies ?? {}),
212
+ };
213
+ for (const rule of DEPENDENCY_RULES) {
214
+ if (rule.depKey in allDeps) {
215
+ extraLanguages.add(rule.language);
216
+ if (rule.framework)
217
+ extraFrameworks.add(rule.framework);
218
+ }
219
+ }
220
+ const depKeys = [
221
+ ...Object.keys(pkg.dependencies ?? {}),
222
+ ...Object.keys(pkg.devDependencies ?? {}),
223
+ ...Object.keys(pkg.peerDependencies ?? {}),
224
+ ...Object.keys(pkg.optionalDependencies ?? {}),
225
+ ];
226
+ return {
227
+ projectName: typeof pkg.name === "string" ? pkg.name : undefined,
228
+ description: typeof pkg.description === "string" ? pkg.description : undefined,
229
+ scripts: pkg.scripts && typeof pkg.scripts === "object"
230
+ ? pkg.scripts
231
+ : undefined,
232
+ dependencies: depKeys.slice(0, 10),
233
+ extraLanguages: [...extraLanguages],
234
+ extraFrameworks: [...extraFrameworks],
235
+ hasWorkspaces: Boolean(pkg.workspaces),
236
+ packageManager: typeof pkg.packageManager === "string" ? pkg.packageManager.split("@")[0] : undefined,
237
+ };
238
+ }
239
+ function detectFromPyproject(cwd) {
240
+ const path = join(cwd, "pyproject.toml");
241
+ if (!existsSync(path))
242
+ return null;
243
+ const data = tryParseToml(path);
244
+ if (!data)
245
+ return null;
246
+ const project = data.project;
247
+ const frameworks = [];
248
+ const deps = [];
249
+ if (project?.dependencies && Array.isArray(project.dependencies)) {
250
+ for (const dep of project.dependencies) {
251
+ const name = normalizeDependencyName(dep);
252
+ if (name)
253
+ deps.push(name);
254
+ if (name === "django")
255
+ frameworks.push("Django");
256
+ else if (name === "flask")
257
+ frameworks.push("Flask");
258
+ else if (name === "fastapi")
259
+ frameworks.push("FastAPI");
260
+ else if (name === "starlette")
261
+ frameworks.push("Starlette");
262
+ else if (name === "litestar")
263
+ frameworks.push("Litestar");
264
+ else if (name === "tornado")
265
+ frameworks.push("Tornado");
266
+ }
267
+ }
268
+ return {
269
+ projectName: typeof project?.name === "string" ? project.name : undefined,
270
+ description: typeof project?.description === "string" ? project.description : undefined,
271
+ deps,
272
+ frameworks,
273
+ };
274
+ }
275
+ function detectFromCargoToml(cwd) {
276
+ const path = join(cwd, "Cargo.toml");
277
+ if (!existsSync(path))
278
+ return null;
279
+ const data = tryParseToml(path);
280
+ if (!data)
281
+ return null;
282
+ const pkg = data.package;
283
+ const rawDeps = data.dependencies ?? {};
284
+ const deps = Object.keys(rawDeps).slice(0, 10);
285
+ const frameworks = [];
286
+ const depLower = deps.map((d) => d.toLowerCase());
287
+ if (depLower.includes("axum"))
288
+ frameworks.push("Axum");
289
+ if (depLower.includes("actix-web") || depLower.includes("actix_web"))
290
+ frameworks.push("Actix-web");
291
+ if (depLower.includes("rocket"))
292
+ frameworks.push("Rocket");
293
+ if (depLower.includes("warp"))
294
+ frameworks.push("Warp");
295
+ if (depLower.includes("tokio"))
296
+ frameworks.push("Tokio");
297
+ return {
298
+ projectName: typeof pkg?.name === "string" ? pkg.name : undefined,
299
+ description: typeof pkg?.description === "string" ? pkg.description : undefined,
300
+ deps,
301
+ frameworks,
302
+ };
303
+ }
304
+ function detectFromGoMod(cwd) {
305
+ const path = join(cwd, "go.mod");
306
+ if (!existsSync(path))
307
+ return null;
308
+ const parsed = parseGoMod(path);
309
+ const frameworks = [];
310
+ for (const dep of parsed.deps) {
311
+ if (dep.includes("gin-gonic/gin"))
312
+ frameworks.push("Gin");
313
+ else if (dep.includes("labstack/echo"))
314
+ frameworks.push("Echo");
315
+ else if (dep.includes("gofiber/fiber"))
316
+ frameworks.push("Fiber");
317
+ else if (dep.includes("go-chi/chi"))
318
+ frameworks.push("Chi");
319
+ }
320
+ return { moduleName: parsed.module, goVersion: parsed.goVersion, deps: parsed.deps.slice(0, 10), frameworks };
321
+ }
322
+ function detectFromGemfile(cwd) {
323
+ const path = join(cwd, "Gemfile");
324
+ if (!existsSync(path))
325
+ return null;
326
+ const parsed = parseGemfile(path);
327
+ const frameworks = [];
328
+ if (parsed.gems.includes("rails"))
329
+ frameworks.push("Rails");
330
+ if (parsed.gems.includes("sinatra"))
331
+ frameworks.push("Sinatra");
332
+ if (parsed.gems.includes("hanami"))
333
+ frameworks.push("Hanami");
334
+ return { rubyVersion: parsed.rubyVersion, gems: parsed.gems.slice(0, 10), frameworks };
335
+ }
336
+ function detectFromComposerJson(cwd) {
337
+ const path = join(cwd, "composer.json");
338
+ const data = tryParseJson(path);
339
+ if (!data)
340
+ return null;
341
+ const require = data.require ?? {};
342
+ const deps = Object.keys(require).filter((d) => !d.startsWith("php")).slice(0, 10);
343
+ const frameworks = [];
344
+ if (deps.some((d) => d.includes("laravel/framework")))
345
+ frameworks.push("Laravel");
346
+ if (deps.some((d) => d.includes("symfony/")))
347
+ frameworks.push("Symfony");
348
+ return {
349
+ projectName: typeof data.name === "string" ? data.name : undefined,
350
+ description: typeof data.description === "string" ? data.description : undefined,
351
+ deps,
352
+ frameworks,
353
+ };
354
+ }
355
+ function detectFromPubspec(cwd) {
356
+ const path = join(cwd, "pubspec.yaml");
357
+ if (!existsSync(path))
358
+ return null;
359
+ const data = tryParseYaml(path);
360
+ if (!data)
361
+ return null;
362
+ const rawDeps = data.dependencies ?? {};
363
+ const deps = Object.keys(rawDeps).filter((d) => d !== "flutter" && d !== "sdk").slice(0, 10);
364
+ const frameworks = [];
365
+ if ("flutter" in rawDeps)
366
+ frameworks.push("Flutter");
367
+ return {
368
+ projectName: typeof data.name === "string" ? data.name : undefined,
369
+ description: typeof data.description === "string" ? data.description : undefined,
370
+ deps,
371
+ frameworks,
372
+ };
373
+ }
374
+ function detectPackageManagers(cwd) {
375
+ const found = [];
376
+ const pkg = tryParseJson(join(cwd, "package.json"));
377
+ if (typeof pkg?.packageManager === "string") {
378
+ found.push(pkg.packageManager.split("@")[0]);
379
+ }
380
+ for (const [lockfile, name] of PACKAGE_MANAGER_LOCKFILES) {
381
+ if (existsSync(join(cwd, lockfile))) {
382
+ found.push(name);
383
+ }
384
+ }
385
+ return [...new Set(found)];
386
+ }
387
+ function detectMonorepo(cwd, pkgHasWorkspaces) {
388
+ for (const [file, tool] of MONOREPO_FILES) {
389
+ if (existsSync(join(cwd, file)))
390
+ return tool;
391
+ }
392
+ if (pkgHasWorkspaces)
393
+ return "npm/yarn workspaces";
394
+ return undefined;
395
+ }
396
+ function detectCiCd(cwd) {
397
+ const found = [];
398
+ for (const [file, name] of CI_CD_FILES) {
399
+ if (existsSync(join(cwd, file)))
400
+ found.push(name);
401
+ }
402
+ return found;
403
+ }
404
+ function detectReadme(cwd) {
405
+ for (const name of ["README.md", "README.rst", "README.txt", "README"]) {
406
+ try {
407
+ const content = readFileSync(join(cwd, name), "utf-8");
408
+ const lines = content.split("\n").slice(0, 50).join("\n").trim();
409
+ return lines.length > 0 ? lines : undefined;
410
+ }
411
+ catch {
412
+ // try next
413
+ }
414
+ }
415
+ return undefined;
416
+ }
417
+ // ---- ProjectDetector ----
418
+ export class ProjectDetector {
419
+ static detect(cwd, opts = {}) {
420
+ const languages = new Set();
421
+ const frameworks = new Set();
422
+ const packageManagers = detectPackageManagers(cwd);
423
+ const ciCd = detectCiCd(cwd);
424
+ const hasDocker = existsSync(join(cwd, "Dockerfile")) ||
425
+ existsSync(join(cwd, "docker-compose.yml")) ||
426
+ existsSync(join(cwd, "docker-compose.yaml"));
427
+ const hasGitignore = existsSync(join(cwd, ".gitignore"));
428
+ const readme = detectReadme(cwd);
429
+ let projectName;
430
+ let description;
431
+ let scripts;
432
+ const dependenciesSet = new Set();
433
+ // Manifest-based language/framework detection
434
+ const manifestResult = detectFromManifests(cwd);
435
+ for (const lang of manifestResult.languages)
436
+ languages.add(lang);
437
+ for (const fw of manifestResult.frameworks)
438
+ frameworks.add(fw);
439
+ // package.json
440
+ const pkg = detectFromPackageJson(cwd);
441
+ if (pkg) {
442
+ if (pkg.projectName)
443
+ projectName = pkg.projectName;
444
+ if (pkg.description)
445
+ description = pkg.description;
446
+ if (pkg.scripts)
447
+ scripts = pkg.scripts;
448
+ if (pkg.dependencies)
449
+ for (const dep of pkg.dependencies)
450
+ dependenciesSet.add(dep);
451
+ for (const lang of pkg.extraLanguages)
452
+ languages.add(lang);
453
+ for (const fw of pkg.extraFrameworks)
454
+ frameworks.add(fw);
455
+ if (existsSync(join(cwd, "tsconfig.json"))) {
456
+ languages.add("TypeScript");
457
+ }
458
+ else {
459
+ languages.add("JavaScript");
460
+ }
461
+ }
462
+ // pyproject.toml
463
+ const py = detectFromPyproject(cwd);
464
+ if (py) {
465
+ if (!projectName && py.projectName)
466
+ projectName = py.projectName;
467
+ if (!description && py.description)
468
+ description = py.description;
469
+ if (py.deps)
470
+ for (const dep of py.deps)
471
+ dependenciesSet.add(dep);
472
+ for (const fw of py.frameworks)
473
+ frameworks.add(fw);
474
+ }
475
+ // Cargo.toml
476
+ const cargo = detectFromCargoToml(cwd);
477
+ if (cargo) {
478
+ if (!projectName && cargo.projectName)
479
+ projectName = cargo.projectName;
480
+ if (!description && cargo.description)
481
+ description = cargo.description;
482
+ for (const dep of cargo.deps)
483
+ dependenciesSet.add(dep);
484
+ for (const fw of cargo.frameworks)
485
+ frameworks.add(fw);
486
+ }
487
+ // go.mod
488
+ const goMod = detectFromGoMod(cwd);
489
+ if (goMod) {
490
+ if (!projectName && goMod.moduleName)
491
+ projectName = goMod.moduleName;
492
+ for (const fw of goMod.frameworks)
493
+ frameworks.add(fw);
494
+ }
495
+ // Gemfile
496
+ const gem = detectFromGemfile(cwd);
497
+ if (gem) {
498
+ for (const fw of gem.frameworks)
499
+ frameworks.add(fw);
500
+ }
501
+ // composer.json
502
+ const composer = detectFromComposerJson(cwd);
503
+ if (composer) {
504
+ if (!projectName && composer.projectName)
505
+ projectName = composer.projectName;
506
+ if (!description && composer.description)
507
+ description = composer.description;
508
+ for (const fw of composer.frameworks)
509
+ frameworks.add(fw);
510
+ }
511
+ // pubspec.yaml
512
+ const pubspec = detectFromPubspec(cwd);
513
+ if (pubspec) {
514
+ if (!projectName && pubspec.projectName)
515
+ projectName = pubspec.projectName;
516
+ if (!description && pubspec.description)
517
+ description = pubspec.description;
518
+ for (const fw of pubspec.frameworks)
519
+ frameworks.add(fw);
520
+ }
521
+ const monorepoTool = detectMonorepo(cwd, pkg?.hasWorkspaces ?? false);
522
+ const detectedLanguages = [...languages];
523
+ const detectedFrameworks = [...frameworks];
524
+ const finalLanguages = opts.languages?.length ? [...opts.languages] : detectedLanguages;
525
+ const finalFrameworks = opts.frameworks?.length ? [...opts.frameworks] : detectedFrameworks;
526
+ const finalDependencies = dependenciesSet.size > 0 ? [...dependenciesSet] : undefined;
527
+ // Empty project — nothing detected
528
+ if (languages.size === 0 &&
529
+ frameworks.size === 0 &&
530
+ packageManagers.length === 0 &&
531
+ !projectName &&
532
+ !hasDocker &&
533
+ !hasGitignore &&
534
+ ciCd.length === 0 &&
535
+ !readme) {
536
+ return null;
537
+ }
538
+ return {
539
+ projectName,
540
+ description,
541
+ languages: finalLanguages,
542
+ frameworks: finalFrameworks,
543
+ packageManagers,
544
+ scripts,
545
+ dependencies: finalDependencies,
546
+ monorepoTool,
547
+ ciCd,
548
+ hasDocker,
549
+ hasGitignore,
550
+ readme,
551
+ };
552
+ }
553
+ }
554
+ // ---- Formatter ----
555
+ /**
556
+ * Convert a ProjectDetectionResult into the compact string used by the prompt
557
+ * compiler's ## Project Stack section. Returns null when result is null.
558
+ */
559
+ export function formatProjectContext(result, opts = {}) {
560
+ const { maxChars = 1200 } = opts;
561
+ const parts = [];
562
+ if (result.projectName)
563
+ parts.push(`Project: ${result.projectName}`);
564
+ if (result.description)
565
+ parts.push(`Description: ${result.description}`);
566
+ if (result.languages.length > 0)
567
+ parts.push(`Languages: ${result.languages.join(", ")}`);
568
+ if (result.frameworks.length > 0)
569
+ parts.push(`Frameworks: ${result.frameworks.join(", ")}`);
570
+ if (result.packageManagers.length > 0)
571
+ parts.push(`Package Manager: ${result.packageManagers[0]}`);
572
+ if (result.monorepoTool)
573
+ parts.push(`Monorepo: ${result.monorepoTool}`);
574
+ if (result.scripts) {
575
+ const scriptKeys = Object.keys(result.scripts).join(", ");
576
+ if (scriptKeys)
577
+ parts.push(`Scripts: ${scriptKeys}`);
578
+ }
579
+ if (result.dependencies && result.dependencies.length > 0) {
580
+ const more = result.dependencies.length > 10 ? "..." : "";
581
+ parts.push(`Dependencies: ${result.dependencies.join(", ")}${more}`);
582
+ }
583
+ if (result.ciCd.length > 0)
584
+ parts.push(`CI/CD: ${result.ciCd.join(", ")}`);
585
+ if (result.hasDocker)
586
+ parts.push("Docker: yes");
587
+ if (result.hasGitignore)
588
+ parts.push("Has .gitignore");
589
+ if (result.readme)
590
+ parts.push(`README (untrusted): ${result.readme}`);
591
+ const summary = parts.join("\n");
592
+ return summary.length > maxChars ? summary.slice(0, maxChars) + "..." : summary;
593
+ }
594
+ /**
595
+ * Detect project context and return a formatted string summary.
596
+ * Returns null when no meaningful signals are found.
597
+ * This is the main entry point consumed by session.ts.
598
+ */
599
+ export function buildProjectContext(cwd, opts = {}) {
600
+ const result = ProjectDetector.detect(cwd, opts);
601
+ if (!result)
602
+ return null;
603
+ return formatProjectContext(result);
604
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Markdown rendering for the terminal.
3
+ * Uses marked + marked-terminal to convert Markdown to styled terminal output.
4
+ *
5
+ * Only active when stdout/stderr is a TTY (chalk auto-detects).
6
+ */
7
+ /**
8
+ * Render Markdown text to a terminal-formatted string with ANSI styles.
9
+ * Returns plain text when not in a TTY (chalk strips colors automatically).
10
+ */
11
+ export declare function renderMarkdown(text: string): string;
12
+ /**
13
+ * Render Markdown to a writable stream (stdout/stderr).
14
+ */
15
+ export declare function writeMarkdown(text: string, stream?: NodeJS.WriteStream): void;
package/dist/render.js ADDED
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Markdown rendering for the terminal.
3
+ * Uses marked + marked-terminal to convert Markdown to styled terminal output.
4
+ *
5
+ * Only active when stdout/stderr is a TTY (chalk auto-detects).
6
+ */
7
+ import { marked } from "marked";
8
+ import { markedTerminal } from "marked-terminal";
9
+ let initialized = false;
10
+ function ensureInit() {
11
+ if (initialized)
12
+ return;
13
+ marked.use(new markedTerminal({
14
+ // Disable reflow — we want to preserve the original line breaks
15
+ reflowText: false,
16
+ // Don't add section prefixes like "h1. "
17
+ showSectionPrefix: false,
18
+ // Unescape HTML entities
19
+ unescape: true,
20
+ // Use a dark theme for code blocks
21
+ code: { theme: "solarized-dark" },
22
+ }));
23
+ initialized = true;
24
+ }
25
+ /**
26
+ * Render Markdown text to a terminal-formatted string with ANSI styles.
27
+ * Returns plain text when not in a TTY (chalk strips colors automatically).
28
+ */
29
+ export function renderMarkdown(text) {
30
+ if (!text)
31
+ return "";
32
+ ensureInit();
33
+ // marked.parse returns string when used with synchronous renderer
34
+ return marked.parse(text);
35
+ }
36
+ /**
37
+ * Render Markdown to a writable stream (stdout/stderr).
38
+ */
39
+ export function writeMarkdown(text, stream = process.stdout) {
40
+ if (!text)
41
+ return;
42
+ const rendered = renderMarkdown(text);
43
+ stream.write(rendered);
44
+ if (!rendered.endsWith("\n"))
45
+ stream.write("\n");
46
+ }