stealthos-cli 0.1.0-alpha.3 → 0.1.0-alpha.4

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 (176) hide show
  1. package/ai/CONTRACT.md +110 -0
  2. package/ai/INDEX.md +203 -0
  3. package/ai/README.md +434 -0
  4. package/ai/ROUTER.md +288 -0
  5. package/ai/agents/README.md +103 -0
  6. package/ai/agents/architect.md +59 -0
  7. package/ai/agents/backend-engineer.md +62 -0
  8. package/ai/agents/founder.md +45 -0
  9. package/ai/agents/frontend-engineer.md +61 -0
  10. package/ai/agents/product-manager.md +56 -0
  11. package/ai/agents/qa-engineer.md +53 -0
  12. package/ai/agents/researcher.md +74 -0
  13. package/ai/agents/reviewer.md +73 -0
  14. package/ai/agents/security-engineer.md +59 -0
  15. package/ai/agents/sre-engineer.md +70 -0
  16. package/ai/agents/tech-lead.md +70 -0
  17. package/ai/architecture/README.md +35 -0
  18. package/ai/architecture/components.md +24 -0
  19. package/ai/architecture/containers.md +30 -0
  20. package/ai/architecture/event-flows.md +36 -0
  21. package/ai/architecture/sequence-diagrams.md +38 -0
  22. package/ai/architecture/system-context.md +46 -0
  23. package/ai/architecture/threat-modeling.md +40 -0
  24. package/ai/blueprints/README.md +67 -0
  25. package/ai/blueprints/_schema.json +40 -0
  26. package/ai/blueprints/ai-platform.json +28 -0
  27. package/ai/blueprints/crm.json +22 -0
  28. package/ai/blueprints/game.json +25 -0
  29. package/ai/blueprints/mobile.json +24 -0
  30. package/ai/blueprints/realtime.json +22 -0
  31. package/ai/blueprints/saas.json +25 -0
  32. package/ai/blueprints/telemetry.json +30 -0
  33. package/ai/blueprints/web.json +23 -0
  34. package/ai/bootstrap/discovery-questions.md +117 -0
  35. package/ai/bootstrap/dispatcher.md +85 -0
  36. package/ai/bootstrap/existing-project.md +191 -0
  37. package/ai/bootstrap/new-project.md +127 -0
  38. package/ai/bootstrap/tech-mapping.md +164 -0
  39. package/ai/clients/README.md +114 -0
  40. package/ai/clients/antigravity.md +125 -0
  41. package/ai/clients/claude-code.md +65 -0
  42. package/ai/clients/cline.md +69 -0
  43. package/ai/clients/codex-aider-cli.md +82 -0
  44. package/ai/clients/continue.md +67 -0
  45. package/ai/clients/copilot.md +49 -0
  46. package/ai/clients/cursor.md +81 -0
  47. package/ai/clients/snippets/mcp-absolute-paths.json +9 -0
  48. package/ai/clients/snippets/mcp-http.json +7 -0
  49. package/ai/clients/snippets/mcp-stdio.json +9 -0
  50. package/ai/clients/trae.md +69 -0
  51. package/ai/clients/windsurf.md +71 -0
  52. package/ai/core/pipeline/execution-engine.md +157 -0
  53. package/ai/engineering/README.md +32 -0
  54. package/ai/engineering/observability/incident-response.md +82 -0
  55. package/ai/evals/protocol-tests.md +150 -0
  56. package/ai/evolution/agent-evolution.md +161 -0
  57. package/ai/evolution/improvements.md +91 -0
  58. package/ai/evolution/learnings.md +49 -0
  59. package/ai/evolution/patterns-discovered.md +48 -0
  60. package/ai/execution/README.md +33 -0
  61. package/ai/execution/backlog.md +27 -0
  62. package/ai/execution/milestones.md +26 -0
  63. package/ai/execution/roadmap.md +30 -0
  64. package/ai/execution/sprint.md +42 -0
  65. package/ai/governance/README.md +34 -0
  66. package/ai/governance/architecture-principles.md +99 -0
  67. package/ai/governance/definition-of-done.md +88 -0
  68. package/ai/governance/definition-of-ready.md +69 -0
  69. package/ai/governance/engineering-principles.md +70 -0
  70. package/ai/governance/quality-gates.md +85 -0
  71. package/ai/governance/security-policies.md +84 -0
  72. package/ai/hooks/enforce-audit.ps1 +41 -0
  73. package/ai/hooks/enforce-audit.sh +39 -0
  74. package/ai/hooks/guard-edit.ps1 +182 -0
  75. package/ai/hooks/guard-edit.sh +161 -0
  76. package/ai/hooks/inject-os-reminder.ps1 +40 -0
  77. package/ai/hooks/inject-os-reminder.sh +16 -0
  78. package/ai/manifest.json +238 -0
  79. package/ai/memory/_detected-stack.json +33 -0
  80. package/ai/memory/_summary.md +49 -0
  81. package/ai/memory/archive/.gitkeep +3 -0
  82. package/ai/memory/completed-tasks.md +156 -0
  83. package/ai/memory/decisions.md +257 -0
  84. package/ai/memory/errors-and-solutions.md +41 -0
  85. package/ai/memory/known-issues.md +40 -0
  86. package/ai/memory/pending-tasks.md +37 -0
  87. package/ai/memory/project-context.md +67 -0
  88. package/ai/operating-system/architecture.md +54 -0
  89. package/ai/operating-system/coding-standards.md +84 -0
  90. package/ai/operating-system/folder-structure.md +126 -0
  91. package/ai/operating-system/performance-rules.md +86 -0
  92. package/ai/operating-system/quality-control.md +81 -0
  93. package/ai/operating-system/security-rules.md +91 -0
  94. package/ai/operating-system/workflow.md +86 -0
  95. package/ai/product/README.md +24 -0
  96. package/ai/product/business-rules.md +26 -0
  97. package/ai/product/personas.md +29 -0
  98. package/ai/product/user-journeys.md +30 -0
  99. package/ai/product/vision.md +35 -0
  100. package/ai/rules/behavior.md +45 -0
  101. package/ai/rules/do.md +47 -0
  102. package/ai/rules/dont.md +46 -0
  103. package/ai/rules/execution-flow.md +125 -0
  104. package/ai/rules/structural-constraints.md +59 -0
  105. package/ai/rules/structure-canon.md +116 -0
  106. package/ai/runtime.md +179 -0
  107. package/ai/scripts/detect-stack.ps1 +166 -0
  108. package/ai/scripts/detect-stack.sh +172 -0
  109. package/ai/scripts/init-ai-os.ps1 +170 -0
  110. package/ai/scripts/init-ai-os.sh +99 -0
  111. package/ai/scripts/lint-os.ps1 +99 -0
  112. package/ai/scripts/lint-os.sh +85 -0
  113. package/ai/scripts/start-os.ps1 +151 -0
  114. package/ai/scripts/start-os.sh +141 -0
  115. package/ai/server/README.md +105 -0
  116. package/ai/server/aios-server.mjs +2134 -0
  117. package/ai/server/package-lock.json +802 -0
  118. package/ai/server/package.json +31 -0
  119. package/ai/server/src/analyzer/graph-builder.ts +92 -0
  120. package/ai/server/src/analyzer/index.ts +191 -0
  121. package/ai/server/src/analyzer/module-mapper.ts +171 -0
  122. package/ai/server/src/analyzer/smell-detector.ts +54 -0
  123. package/ai/server/src/analyzer/stack-detector.ts +70 -0
  124. package/ai/server/src/index.ts +16 -0
  125. package/ai/server/src/packager/context-builder.ts +217 -0
  126. package/ai/server/src/packager/index.ts +3 -0
  127. package/ai/server/src/packager/memory-injector.ts +128 -0
  128. package/ai/server/src/packager/module-summarizer.ts +60 -0
  129. package/ai/server/src/packager/token-estimator.ts +26 -0
  130. package/ai/server/src/snapshot/index.ts +3 -0
  131. package/ai/server/src/snapshot/snapshot-creator.ts +206 -0
  132. package/ai/server/src/snapshot/snapshot-diff.ts +86 -0
  133. package/ai/server/src/snapshot/snapshot-restore.ts +14 -0
  134. package/ai/server/src/types.ts +94 -0
  135. package/ai/server/tsconfig.json +26 -0
  136. package/ai/skills/architecture-design.md +82 -0
  137. package/ai/skills/backend-engineering.md +57 -0
  138. package/ai/skills/database-design.md +76 -0
  139. package/ai/skills/frontend-engineering.md +63 -0
  140. package/ai/skills/performance.md +73 -0
  141. package/ai/skills/scalability.md +84 -0
  142. package/ai/skills/security.md +71 -0
  143. package/ai/skills/testing.md +77 -0
  144. package/ai/specs/ADR/ADR-0002-typescript-runtime.md +103 -0
  145. package/ai/specs/ADR/ADR-0004-runtime-orchestrator.md +94 -0
  146. package/ai/specs/ADR/ADR-0005-workflow-engine.md +105 -0
  147. package/ai/specs/ADR/ADR-0006-runtime-state.md +104 -0
  148. package/ai/specs/ADR/ADR-0007-state-compiler-drift-context-layers-artifact-index.md +82 -0
  149. package/ai/specs/ADR/ADR-0008-intent-runtime-discovery-branching.md +93 -0
  150. package/ai/specs/ADR/ADR-0009-confidence-system-maturity-tracking.md +113 -0
  151. package/ai/specs/ADR/ADR-0010-structural-architecture-standards.md +121 -0
  152. package/ai/specs/ADR/ADR-0011-mcp-prompts.md +86 -0
  153. package/ai/specs/ADR/ADR-0012-stealthos-hybrid-architecture.md +174 -0
  154. package/ai/specs/ADR/_TEMPLATE.md +60 -0
  155. package/ai/specs/BRD/_TEMPLATE.md +50 -0
  156. package/ai/specs/PRD/_TEMPLATE.md +72 -0
  157. package/ai/specs/README.md +43 -0
  158. package/ai/specs/RFC/RFC-0001-runtime-orchestrator.md +149 -0
  159. package/ai/specs/RFC/RFC-0002-runtime-orchestrator-extended.md +134 -0
  160. package/ai/specs/RFC/_TEMPLATE.md +61 -0
  161. package/ai/specs/RUNBOOKS/_TEMPLATE.md +68 -0
  162. package/ai/specs/SDD/_TEMPLATE.md +104 -0
  163. package/ai/specs/TASKS/_TEMPLATE.md +52 -0
  164. package/ai/tools/debugging.md +64 -0
  165. package/ai/tools/dependency-analysis.md +46 -0
  166. package/ai/tools/internet-research.md +42 -0
  167. package/ai/tools/mcp-discovery.md +44 -0
  168. package/ai/workflows/_schema.json +81 -0
  169. package/ai/workflows/init.json +148 -0
  170. package/ai/workflows/sync.json +71 -0
  171. package/ai/workflows/work.json +91 -0
  172. package/package.json +7 -1
  173. package/scripts/bundle-ai.mjs +58 -0
  174. package/src/cli.mjs +1 -1
  175. package/src/commands/install.mjs +35 -11
  176. package/src/lib/resolve-source.mjs +27 -10
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "ai-os-server",
3
+ "version": "1.1.0",
4
+ "description": "AI Operating System — MCP daemon + Runtime v0.1 (Project State / Context Packager / Snapshot)",
5
+ "type": "module",
6
+ "main": "aios-server.mjs",
7
+ "bin": {
8
+ "aios-server": "./aios-server.mjs"
9
+ },
10
+ "scripts": {
11
+ "start": "node aios-server.mjs",
12
+ "http": "node aios-server.mjs --http",
13
+ "build": "tsc --noEmit && esbuild src/index.ts --bundle --platform=node --target=node18 --format=esm --outfile=dist/index.mjs --external:ts-morph --external:commander",
14
+ "build:standalone": "tsc --noEmit && esbuild src/index.ts --bundle --platform=node --target=node18 --format=esm --outfile=dist/index.mjs",
15
+ "dev": "tsc --watch",
16
+ "typecheck": "tsc --noEmit"
17
+ },
18
+ "engines": {
19
+ "node": ">=18.0.0"
20
+ },
21
+ "license": "MIT",
22
+ "dependencies": {
23
+ "commander": "^12.1.0",
24
+ "ts-morph": "^22.0.0"
25
+ },
26
+ "devDependencies": {
27
+ "@types/node": "^20.12.0",
28
+ "esbuild": "^0.21.0",
29
+ "typescript": "^5.4.0"
30
+ }
31
+ }
@@ -0,0 +1,92 @@
1
+ import { dirname, join, normalize } from "node:path";
2
+ import type { DependencyGraph, ModuleInfo } from "../types.js";
3
+
4
+ export function buildGraph(modules: ModuleInfo[]): DependencyGraph {
5
+ const nodeSet = new Set(modules.map((m) => m.id));
6
+ const edges: Array<{ from: string; to: string }> = [];
7
+
8
+ for (const m of modules) {
9
+ for (const imp of m.imports) {
10
+ const resolved = resolveImport(m.id, imp, nodeSet);
11
+ if (resolved && resolved !== m.id) {
12
+ edges.push({ from: m.id, to: resolved });
13
+ }
14
+ }
15
+ }
16
+
17
+ const cycles = detectCycles(Array.from(nodeSet), edges);
18
+ return { nodes: Array.from(nodeSet), edges, cycles };
19
+ }
20
+
21
+ function resolveImport(fromModule: string, spec: string, knownNodes: Set<string>): string | null {
22
+ if (!spec.startsWith(".")) return null;
23
+ const fromDir = dirname(fromModule);
24
+ const target = normalize(join(fromDir, spec)).replace(/\\/g, "/");
25
+ const candidates = [
26
+ target,
27
+ target + ".ts",
28
+ target + ".tsx",
29
+ target + ".mts",
30
+ target + ".js",
31
+ target + ".mjs",
32
+ target + ".jsx",
33
+ normalize(join(target, "index.ts")).replace(/\\/g, "/"),
34
+ normalize(join(target, "index.tsx")).replace(/\\/g, "/"),
35
+ normalize(join(target, "index.mts")).replace(/\\/g, "/"),
36
+ normalize(join(target, "index.js")).replace(/\\/g, "/"),
37
+ normalize(join(target, "index.mjs")).replace(/\\/g, "/"),
38
+ ];
39
+ for (const c of candidates) {
40
+ if (knownNodes.has(c)) return c;
41
+ }
42
+ // Try stripping .js extension (TS bundler convention)
43
+ if (spec.endsWith(".js")) {
44
+ const base = spec.slice(0, -3);
45
+ return resolveImport(fromModule, base, knownNodes);
46
+ }
47
+ return null;
48
+ }
49
+
50
+ function detectCycles(
51
+ nodes: string[],
52
+ edges: Array<{ from: string; to: string }>,
53
+ ): string[][] {
54
+ const adj = new Map<string, string[]>();
55
+ for (const n of nodes) adj.set(n, []);
56
+ for (const e of edges) adj.get(e.from)?.push(e.to);
57
+
58
+ const WHITE = 0;
59
+ const GRAY = 1;
60
+ const BLACK = 2;
61
+ const color = new Map<string, number>();
62
+ for (const n of nodes) color.set(n, WHITE);
63
+ const cycles: string[][] = [];
64
+ const seen = new Set<string>();
65
+
66
+ function dfs(node: string, stack: string[]): void {
67
+ color.set(node, GRAY);
68
+ stack.push(node);
69
+ for (const next of adj.get(node) ?? []) {
70
+ if (color.get(next) === GRAY) {
71
+ const idx = stack.indexOf(next);
72
+ if (idx >= 0) {
73
+ const cycle = stack.slice(idx);
74
+ const key = [...cycle].sort().join("|");
75
+ if (!seen.has(key)) {
76
+ seen.add(key);
77
+ cycles.push([...cycle, next]);
78
+ }
79
+ }
80
+ } else if (color.get(next) === WHITE) {
81
+ dfs(next, stack);
82
+ }
83
+ }
84
+ stack.pop();
85
+ color.set(node, BLACK);
86
+ }
87
+
88
+ for (const n of nodes) {
89
+ if (color.get(n) === WHITE) dfs(n, []);
90
+ }
91
+ return cycles;
92
+ }
@@ -0,0 +1,191 @@
1
+ import { mkdir, readdir, writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import type { ModuleInfo, ProjectState, ProjectStats } from "../types.js";
4
+ import { buildGraph } from "./graph-builder.js";
5
+ import { mapModules } from "./module-mapper.js";
6
+ import { detectSmells } from "./smell-detector.js";
7
+ import { detectStack } from "./stack-detector.js";
8
+
9
+ const RUNTIME_VERSION = "4.1.0";
10
+
11
+ export interface AnalyzeOptions {
12
+ write?: boolean;
13
+ scanDirs?: string[];
14
+ }
15
+
16
+ export async function analyzeProject(
17
+ projectRoot: string,
18
+ options: AnalyzeOptions = {},
19
+ ): Promise<ProjectState> {
20
+ const stack = await detectStack(projectRoot);
21
+ const modules = await mapModules(projectRoot, { scanDirs: options.scanDirs });
22
+ const graph = buildGraph(modules);
23
+ const smells = await detectSmells(modules, graph, projectRoot);
24
+ const stats = await computeStats(projectRoot, modules);
25
+
26
+ const state: ProjectState = {
27
+ version: RUNTIME_VERSION,
28
+ generated_at: new Date().toISOString(),
29
+ project_root: projectRoot,
30
+ stack,
31
+ modules,
32
+ graph,
33
+ smells,
34
+ stats,
35
+ };
36
+
37
+ if (options.write !== false) {
38
+ const ctxDir = join(projectRoot, ".ai", "context");
39
+ await mkdir(ctxDir, { recursive: true });
40
+ await writeFile(
41
+ join(ctxDir, "project-state.json"),
42
+ JSON.stringify(state, null, 2),
43
+ "utf8",
44
+ );
45
+ await writeFile(join(ctxDir, "project-state.md"), renderMarkdown(state), "utf8");
46
+ }
47
+
48
+ return state;
49
+ }
50
+
51
+ async function computeStats(
52
+ projectRoot: string,
53
+ modules: ModuleInfo[],
54
+ ): Promise<ProjectStats> {
55
+ const filesByLang: Record<string, number> = {};
56
+ let totalLines = 0;
57
+ for (const m of modules) {
58
+ filesByLang[m.language] = (filesByLang[m.language] ?? 0) + 1;
59
+ totalLines += m.size_lines;
60
+ }
61
+ const largest = [...modules]
62
+ .sort((a, b) => b.size_lines - a.size_lines)
63
+ .slice(0, 10)
64
+ .map((m) => ({ path: m.path, lines: m.size_lines }));
65
+ const totalFiles = await walkCount(projectRoot);
66
+ return {
67
+ total_files: totalFiles,
68
+ total_lines: totalLines,
69
+ files_by_language: filesByLang,
70
+ largest_files: largest,
71
+ };
72
+ }
73
+
74
+ const COUNT_IGNORED = new Set([
75
+ "node_modules",
76
+ ".git",
77
+ "dist",
78
+ "build",
79
+ ".runtime",
80
+ "archive",
81
+ ".turbo",
82
+ ".vercel",
83
+ ".next",
84
+ ".nuxt",
85
+ "coverage",
86
+ ]);
87
+
88
+ async function walkCount(dir: string): Promise<number> {
89
+ let count = 0;
90
+ let entries;
91
+ try {
92
+ entries = await readdir(dir, { withFileTypes: true });
93
+ } catch {
94
+ return 0;
95
+ }
96
+ for (const e of entries) {
97
+ if (COUNT_IGNORED.has(e.name)) continue;
98
+ if (e.name.startsWith(".") && e.name !== ".ai" && e.name !== ".claude") continue;
99
+ const p = join(dir, e.name);
100
+ if (e.isDirectory()) count += await walkCount(p);
101
+ else if (e.isFile()) count++;
102
+ }
103
+ return count;
104
+ }
105
+
106
+ function renderMarkdown(state: ProjectState): string {
107
+ const lines: string[] = [];
108
+ lines.push("# Project State", "");
109
+ lines.push(`**Generated:** ${state.generated_at}`);
110
+ lines.push(`**Runtime:** v${state.version}`);
111
+ lines.push(`**Root:** \`${state.project_root}\``, "");
112
+
113
+ lines.push("## Stack", "");
114
+ lines.push(`- **Languages:** ${state.stack.languages.join(", ") || "(none detected)"}`);
115
+ lines.push(`- **Frameworks:** ${state.stack.frameworks.join(", ") || "(none)"}`);
116
+ if (state.stack.package_manager) {
117
+ lines.push(`- **Package manager:** ${state.stack.package_manager}`);
118
+ }
119
+ if (state.stack.databases && state.stack.databases.length) {
120
+ lines.push(`- **Databases:** ${state.stack.databases.join(", ")}`);
121
+ }
122
+ if (state.stack.test_frameworks && state.stack.test_frameworks.length) {
123
+ lines.push(`- **Test frameworks:** ${state.stack.test_frameworks.join(", ")}`);
124
+ }
125
+ if (state.stack.linters && state.stack.linters.length) {
126
+ lines.push(`- **Linters:** ${state.stack.linters.join(", ")}`);
127
+ }
128
+ lines.push("");
129
+
130
+ lines.push("## Stats", "");
131
+ lines.push(`- **Total files (repo):** ${state.stats.total_files}`);
132
+ lines.push(`- **Modules tracked (AST/regex):** ${state.modules.length}`);
133
+ lines.push(`- **Total lines in modules:** ${state.stats.total_lines}`);
134
+ lines.push(`- **Dependency edges:** ${state.graph.edges.length}`);
135
+ lines.push(`- **Circular dependencies:** ${state.graph.cycles.length}`);
136
+ lines.push(`- **Smells found:** ${state.smells.length}`);
137
+ lines.push("");
138
+
139
+ lines.push("### Files by language", "");
140
+ const sorted = Object.entries(state.stats.files_by_language).sort((a, b) => b[1] - a[1]);
141
+ if (sorted.length === 0) {
142
+ lines.push("_(no language buckets — nothing scanned)_");
143
+ } else {
144
+ for (const [lang, n] of sorted) {
145
+ lines.push(`- \`${lang}\`: ${n}`);
146
+ }
147
+ }
148
+ lines.push("");
149
+
150
+ if (state.stats.largest_files.length) {
151
+ lines.push("### Largest files", "");
152
+ for (const f of state.stats.largest_files) {
153
+ lines.push(`- \`${f.path}\` — ${f.lines} lines`);
154
+ }
155
+ lines.push("");
156
+ }
157
+
158
+ if (state.smells.length) {
159
+ lines.push("## Smells (top 20)", "");
160
+ for (const s of state.smells.slice(0, 20)) {
161
+ const loc = `${s.location.file}${s.location.line ? ":" + s.location.line : ""}`;
162
+ lines.push(`- **${s.kind}** [${s.severity}] \`${loc}\` — ${s.message}`);
163
+ }
164
+ lines.push("");
165
+ }
166
+
167
+ if (state.graph.cycles.length) {
168
+ lines.push("## Cyclic dependencies", "");
169
+ for (const c of state.graph.cycles) {
170
+ lines.push(`- ${c.join(" → ")}`);
171
+ }
172
+ lines.push("");
173
+ }
174
+
175
+ if (state.modules.length) {
176
+ lines.push("## Modules", "");
177
+ for (const m of state.modules.slice(0, 50)) {
178
+ lines.push(
179
+ `- \`${m.path}\` (${m.language}, ${m.size_lines} lines, ${m.imports.length} imports, ${m.exports.length} exports)`,
180
+ );
181
+ }
182
+ if (state.modules.length > 50) {
183
+ lines.push(`- … and ${state.modules.length - 50} more`);
184
+ }
185
+ lines.push("");
186
+ }
187
+
188
+ return lines.join("\n");
189
+ }
190
+
191
+ export { detectStack, mapModules, buildGraph, detectSmells };
@@ -0,0 +1,171 @@
1
+ import { readFile, readdir } from "node:fs/promises";
2
+ import { existsSync } from "node:fs";
3
+ import { extname, join, relative } from "node:path";
4
+ import { Project } from "ts-morph";
5
+ import type { ModuleInfo } from "../types.js";
6
+
7
+ const IGNORED_DIRS = new Set([
8
+ "node_modules",
9
+ ".git",
10
+ "dist",
11
+ "build",
12
+ ".next",
13
+ ".nuxt",
14
+ "coverage",
15
+ ".cache",
16
+ ".runtime",
17
+ "archive",
18
+ ".turbo",
19
+ ".vercel",
20
+ ]);
21
+
22
+ const TS_EXTENSIONS = new Set([".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"]);
23
+ const TEXT_LANG_EXTENSIONS = new Set([
24
+ ".py",
25
+ ".rb",
26
+ ".go",
27
+ ".rs",
28
+ ".java",
29
+ ".kt",
30
+ ".php",
31
+ ".cs",
32
+ ".ex",
33
+ ".exs",
34
+ ".swift",
35
+ ]);
36
+
37
+ export interface MapOptions {
38
+ scanDirs?: string[];
39
+ }
40
+
41
+ export async function mapModules(
42
+ projectRoot: string,
43
+ options: MapOptions = {},
44
+ ): Promise<ModuleInfo[]> {
45
+ const scanDirs = options.scanDirs ?? [".ai/server/src", "src", "lib", "app", "packages"];
46
+ const allFiles: string[] = [];
47
+
48
+ for (const d of scanDirs) {
49
+ const abs = join(projectRoot, d);
50
+ if (!existsSync(abs)) continue;
51
+ await collectFiles(abs, allFiles);
52
+ }
53
+
54
+ const modules: ModuleInfo[] = [];
55
+
56
+ const tsFiles = allFiles.filter((f) => TS_EXTENSIONS.has(extname(f)));
57
+ if (tsFiles.length > 0) {
58
+ const project = new Project({
59
+ useInMemoryFileSystem: false,
60
+ skipAddingFilesFromTsConfig: true,
61
+ compilerOptions: { allowJs: true, noEmit: true },
62
+ });
63
+ for (const f of tsFiles) {
64
+ try {
65
+ project.addSourceFileAtPath(f);
66
+ } catch {
67
+ // ignore unreadable/odd file
68
+ }
69
+ }
70
+ for (const sf of project.getSourceFiles()) {
71
+ const path = sf.getFilePath();
72
+ const relPath = relative(projectRoot, path).replace(/\\/g, "/");
73
+ const text = sf.getFullText();
74
+ const lines = text.split("\n").length;
75
+ const imports: string[] = [];
76
+ for (const imp of sf.getImportDeclarations()) {
77
+ const v = imp.getModuleSpecifierValue();
78
+ if (v) imports.push(v);
79
+ }
80
+ for (const exp of sf.getExportDeclarations()) {
81
+ const v = exp.getModuleSpecifierValue();
82
+ if (v) imports.push(v);
83
+ }
84
+ const exportsList: string[] = [];
85
+ for (const [name] of sf.getExportedDeclarations()) {
86
+ exportsList.push(name);
87
+ }
88
+ modules.push({
89
+ id: relPath,
90
+ name: relPath,
91
+ path: relPath,
92
+ kind: "file",
93
+ language: extname(path) === ".tsx" || extname(path) === ".jsx" ? "tsx" : "typescript",
94
+ size_lines: lines,
95
+ imports,
96
+ exports: exportsList,
97
+ });
98
+ }
99
+ }
100
+
101
+ for (const f of allFiles) {
102
+ const ext = extname(f);
103
+ if (!TEXT_LANG_EXTENSIONS.has(ext)) continue;
104
+ const text = await readFile(f, "utf8").catch(() => "");
105
+ if (!text) continue;
106
+ const lines = text.split("\n").length;
107
+ const relPath = relative(projectRoot, f).replace(/\\/g, "/");
108
+ const lang = guessLanguage(ext);
109
+ modules.push({
110
+ id: relPath,
111
+ name: relPath,
112
+ path: relPath,
113
+ kind: "file",
114
+ language: lang,
115
+ size_lines: lines,
116
+ imports: extractImportsByLanguage(text, lang),
117
+ exports: [],
118
+ });
119
+ }
120
+
121
+ return modules;
122
+ }
123
+
124
+ async function collectFiles(dir: string, out: string[]): Promise<void> {
125
+ let entries;
126
+ try {
127
+ entries = await readdir(dir, { withFileTypes: true });
128
+ } catch {
129
+ return;
130
+ }
131
+ for (const e of entries) {
132
+ if (IGNORED_DIRS.has(e.name)) continue;
133
+ if (e.name.startsWith(".") && e.name !== ".ai" && e.name !== ".claude") continue;
134
+ const p = join(dir, e.name);
135
+ if (e.isDirectory()) {
136
+ await collectFiles(p, out);
137
+ } else if (e.isFile()) {
138
+ out.push(p);
139
+ }
140
+ }
141
+ }
142
+
143
+ function guessLanguage(ext: string): string {
144
+ const map: Record<string, string> = {
145
+ ".py": "python",
146
+ ".rb": "ruby",
147
+ ".go": "go",
148
+ ".rs": "rust",
149
+ ".java": "java",
150
+ ".kt": "kotlin",
151
+ ".php": "php",
152
+ ".cs": "csharp",
153
+ ".ex": "elixir",
154
+ ".exs": "elixir",
155
+ ".swift": "swift",
156
+ };
157
+ return map[ext] ?? "unknown";
158
+ }
159
+
160
+ function extractImportsByLanguage(text: string, lang: string): string[] {
161
+ const imports: string[] = [];
162
+ let re: RegExp | null = null;
163
+ if (lang === "python") re = /^(?:from|import)\s+([\w.]+)/gm;
164
+ else if (lang === "go") re = /import\s+"([^"]+)"/g;
165
+ else if (lang === "rust") re = /^use\s+([\w:]+)/gm;
166
+ else if (lang === "java" || lang === "kotlin") re = /^import\s+([\w.]+)/gm;
167
+ if (!re) return imports;
168
+ let m: RegExpExecArray | null;
169
+ while ((m = re.exec(text))) imports.push(m[1]);
170
+ return imports;
171
+ }
@@ -0,0 +1,54 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import type { DependencyGraph, ModuleInfo, Smell } from "../types.js";
4
+
5
+ const LARGE_FILE_THRESHOLD = 300;
6
+
7
+ export async function detectSmells(
8
+ modules: ModuleInfo[],
9
+ graph: DependencyGraph,
10
+ projectRoot: string,
11
+ ): Promise<Smell[]> {
12
+ const smells: Smell[] = [];
13
+
14
+ for (const cycle of graph.cycles) {
15
+ smells.push({
16
+ kind: "circular_dependency",
17
+ severity: "high",
18
+ location: { file: cycle[0] },
19
+ message: `Circular dependency: ${cycle.join(" → ")}`,
20
+ });
21
+ }
22
+
23
+ for (const m of modules) {
24
+ if (m.size_lines > LARGE_FILE_THRESHOLD) {
25
+ smells.push({
26
+ kind: "large_file",
27
+ severity: m.size_lines > 1000 ? "high" : "medium",
28
+ location: { file: m.path },
29
+ message: `File has ${m.size_lines} lines (soft limit: ${LARGE_FILE_THRESHOLD})`,
30
+ });
31
+ }
32
+ }
33
+
34
+ for (const m of modules) {
35
+ if (m.language === "unknown" || m.language === "markdown") continue;
36
+ const abs = join(projectRoot, m.path);
37
+ const text = await readFile(abs, "utf8").catch(() => "");
38
+ if (!text) continue;
39
+ const lines = text.split("\n");
40
+ lines.forEach((line, idx) => {
41
+ const isFixme = /\bFIXME\b/i.test(line);
42
+ const isTodo = /\bTODO\b/i.test(line);
43
+ if (!isFixme && !isTodo) return;
44
+ smells.push({
45
+ kind: isFixme ? "fixme_comment" : "todo_comment",
46
+ severity: isFixme ? "medium" : "low",
47
+ location: { file: m.path, line: idx + 1 },
48
+ message: line.trim().slice(0, 140),
49
+ });
50
+ });
51
+ }
52
+
53
+ return smells;
54
+ }
@@ -0,0 +1,70 @@
1
+ import { spawn } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import type { StackInfo } from "../types.js";
5
+
6
+ export async function detectStack(projectRoot: string): Promise<StackInfo> {
7
+ const fallback: StackInfo = { languages: [], frameworks: [] };
8
+
9
+ return new Promise((resolve) => {
10
+ const isWin = process.platform === "win32";
11
+ const aiDir = join(projectRoot, ".ai");
12
+ const scriptPs = join(aiDir, "scripts", "detect-stack.ps1");
13
+ const scriptSh = join(aiDir, "scripts", "detect-stack.sh");
14
+ const useScript = isWin ? scriptPs : existsSync(scriptSh) ? scriptSh : scriptPs;
15
+
16
+ if (!existsSync(useScript)) {
17
+ resolve(fallback);
18
+ return;
19
+ }
20
+
21
+ const cmd = isWin ? "powershell" : "bash";
22
+ const args = isWin
23
+ ? [
24
+ "-NoProfile",
25
+ "-ExecutionPolicy",
26
+ "Bypass",
27
+ "-File",
28
+ useScript,
29
+ "-TargetPath",
30
+ projectRoot,
31
+ ]
32
+ : [useScript, "--target", projectRoot];
33
+
34
+ const child = spawn(cmd, args, { cwd: projectRoot });
35
+ let out = "";
36
+ child.stdout.on("data", (d) => (out += d));
37
+ child.stderr.on("data", () => {});
38
+
39
+ child.on("close", () => {
40
+ try {
41
+ const jsonStart = out.indexOf("{");
42
+ const jsonEnd = out.lastIndexOf("}");
43
+ if (jsonStart >= 0 && jsonEnd > jsonStart) {
44
+ const parsed = JSON.parse(out.slice(jsonStart, jsonEnd + 1)) as Record<string, unknown>;
45
+ resolve(normalize(parsed));
46
+ return;
47
+ }
48
+ } catch {
49
+ // fall through
50
+ }
51
+ resolve(fallback);
52
+ });
53
+
54
+ child.on("error", () => resolve(fallback));
55
+ });
56
+ }
57
+
58
+ function normalize(raw: Record<string, unknown>): StackInfo {
59
+ const arr = (v: unknown): string[] | undefined =>
60
+ Array.isArray(v) ? (v as unknown[]).map(String) : undefined;
61
+ return {
62
+ languages: arr(raw.languages) ?? [],
63
+ frameworks: arr(raw.frameworks) ?? [],
64
+ package_manager: typeof raw.package_manager === "string" ? raw.package_manager : undefined,
65
+ databases: arr(raw.databases),
66
+ test_frameworks: arr(raw.test_frameworks),
67
+ linters: arr(raw.linters),
68
+ ci_cd: arr(raw.ci_cd),
69
+ };
70
+ }
@@ -0,0 +1,16 @@
1
+ export * as analyzer from "./analyzer/index.js";
2
+ export * as packager from "./packager/index.js";
3
+ export * as snapshot from "./snapshot/index.js";
4
+ export type {
5
+ ProjectState,
6
+ StackInfo,
7
+ ModuleInfo,
8
+ DependencyGraph,
9
+ Smell,
10
+ ProjectStats,
11
+ ContextPackage,
12
+ SnapshotManifest,
13
+ SnapshotDiff,
14
+ } from "./types.js";
15
+
16
+ export const RUNTIME_VERSION = "4.1.0";