@su-record/vibe 2.13.0 → 2.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/CLAUDE.md +18 -15
  2. package/README.en.md +7 -5
  3. package/README.md +8 -6
  4. package/dist/cli/detect/matcher.d.ts +15 -0
  5. package/dist/cli/detect/matcher.d.ts.map +1 -0
  6. package/dist/cli/detect/matcher.js +278 -0
  7. package/dist/cli/detect/matcher.js.map +1 -0
  8. package/dist/cli/detect/signatures.d.ts +76 -0
  9. package/dist/cli/detect/signatures.d.ts.map +1 -0
  10. package/dist/cli/detect/signatures.js +175 -0
  11. package/dist/cli/detect/signatures.js.map +1 -0
  12. package/dist/cli/detect/workspace.d.ts +7 -0
  13. package/dist/cli/detect/workspace.d.ts.map +1 -0
  14. package/dist/cli/detect/workspace.js +112 -0
  15. package/dist/cli/detect/workspace.js.map +1 -0
  16. package/dist/cli/detect.characterization.test.d.ts +7 -0
  17. package/dist/cli/detect.characterization.test.d.ts.map +1 -0
  18. package/dist/cli/detect.characterization.test.js +294 -0
  19. package/dist/cli/detect.characterization.test.js.map +1 -0
  20. package/dist/cli/detect.d.ts.map +1 -1
  21. package/dist/cli/detect.js +64 -488
  22. package/dist/cli/detect.js.map +1 -1
  23. package/dist/cli/postinstall/constants.d.ts.map +1 -1
  24. package/dist/cli/postinstall/constants.js +1 -0
  25. package/dist/cli/postinstall/constants.js.map +1 -1
  26. package/dist/cli/setup/ProjectSetup.d.ts.map +1 -1
  27. package/dist/cli/setup/ProjectSetup.js +5 -4
  28. package/dist/cli/setup/ProjectSetup.js.map +1 -1
  29. package/dist/infra/lib/ui-ux/CsvDataLoader.d.ts +10 -1
  30. package/dist/infra/lib/ui-ux/CsvDataLoader.d.ts.map +1 -1
  31. package/dist/infra/lib/ui-ux/CsvDataLoader.js +11 -5
  32. package/dist/infra/lib/ui-ux/CsvDataLoader.js.map +1 -1
  33. package/dist/infra/lib/ui-ux/CsvDataLoader.test.js +8 -8
  34. package/dist/infra/lib/ui-ux/CsvDataLoader.test.js.map +1 -1
  35. package/dist/infra/lib/ui-ux/SearchService.test.js +1 -1
  36. package/dist/infra/lib/ui-ux/SearchService.test.js.map +1 -1
  37. package/dist/tools/index.d.ts +2 -0
  38. package/dist/tools/index.d.ts.map +1 -1
  39. package/dist/tools/index.js +2 -0
  40. package/dist/tools/index.js.map +1 -1
  41. package/dist/tools/loop/index.d.ts +6 -0
  42. package/dist/tools/loop/index.d.ts.map +1 -0
  43. package/dist/tools/loop/index.js +5 -0
  44. package/dist/tools/loop/index.js.map +1 -0
  45. package/dist/tools/loop/validateLoopDefinition.d.ts +38 -0
  46. package/dist/tools/loop/validateLoopDefinition.d.ts.map +1 -0
  47. package/dist/tools/loop/validateLoopDefinition.js +224 -0
  48. package/dist/tools/loop/validateLoopDefinition.js.map +1 -0
  49. package/dist/tools/loop/validateLoopDefinition.test.d.ts +14 -0
  50. package/dist/tools/loop/validateLoopDefinition.test.d.ts.map +1 -0
  51. package/dist/tools/loop/validateLoopDefinition.test.js +229 -0
  52. package/dist/tools/loop/validateLoopDefinition.test.js.map +1 -0
  53. package/hooks/scripts/__tests__/.vibe/command-log.txt +39 -0
  54. package/hooks/scripts/__tests__/.vibe/memories/memories.db-shm +0 -0
  55. package/hooks/scripts/__tests__/.vibe/memories/memories.db-wal +0 -0
  56. package/hooks/scripts/__tests__/keyword-detector.test.js +26 -18
  57. package/hooks/scripts/__tests__/loop-ledger.test.js +321 -0
  58. package/hooks/scripts/keyword-detector.js +22 -22
  59. package/hooks/scripts/lib/hook-context.js +31 -2
  60. package/hooks/scripts/lib/loop-ledger.js +118 -0
  61. package/hooks/scripts/loop-ledger.js +56 -0
  62. package/package.json +3 -2
  63. package/skills/vibe/SKILL.md +40 -23
  64. package/skills/vibe.loop/SKILL.md +116 -0
  65. package/skills/vibe.run/SKILL.md +22 -18
  66. package/skills/vibe.run/references/ralph-loop.md +18 -17
  67. package/skills/vibe.run/references/ultrawork-mode.md +36 -33
  68. package/vibe/rules/loop-contract.md +54 -0
  69. package/vibe/templates/loop-template.md +69 -0
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Declarative stack-detection signature table.
3
+ * Each entry describes what files/deps/content to look for, and what to emit.
4
+ */
5
+ import path from 'path';
6
+ import fs from 'fs';
7
+ // ── helpers ────────────────────────────────────────────────────────────────
8
+ function hasXcodeFiles(dir) {
9
+ try {
10
+ return fs.readdirSync(dir).some(f => f.endsWith('.xcodeproj') || f.endsWith('.xcworkspace'));
11
+ }
12
+ catch {
13
+ return false;
14
+ }
15
+ }
16
+ function hasCsharpFiles(dir) {
17
+ try {
18
+ return fs.readdirSync(dir).some(f => f.endsWith('.csproj') || f.endsWith('.sln'));
19
+ }
20
+ catch {
21
+ return false;
22
+ }
23
+ }
24
+ function hasGodotScripts(dir) {
25
+ try {
26
+ return fs.readdirSync(dir).some(f => f.endsWith('.gd'));
27
+ }
28
+ catch {
29
+ return false;
30
+ }
31
+ }
32
+ // ── NODE/TS STACK SIGNATURES ───────────────────────────────────────────────
33
+ // Order matters: first match wins within a manifestFile group.
34
+ export const NODE_STACK_SIGNATURES = [
35
+ // Desktop / Mobile (highest priority)
36
+ { type: 'typescript-tauri', manifestFile: 'package.json', packageDeps: ['@tauri-apps/cli', '@tauri-apps/api'] },
37
+ { type: 'typescript-electron', manifestFile: 'package.json', packageDeps: ['electron'] },
38
+ { type: 'typescript-react-native', manifestFile: 'package.json', packageDeps: ['react-native'] },
39
+ // Fullstack / SSR
40
+ { type: 'typescript-nextjs', manifestFile: 'package.json', packageDeps: ['next'] },
41
+ { type: 'typescript-nuxt', manifestFile: 'package.json', packageDeps: ['nuxt', 'nuxt3'] },
42
+ { type: 'typescript-astro', manifestFile: 'package.json', packageDeps: ['astro'] },
43
+ // Frontend
44
+ { type: 'typescript-angular', manifestFile: 'package.json', packageDeps: ['@angular/core'] },
45
+ { type: 'typescript-svelte', manifestFile: 'package.json', packageDeps: ['svelte'] },
46
+ { type: 'typescript-vue', manifestFile: 'package.json', packageDeps: ['vue'] },
47
+ { type: 'typescript-react', manifestFile: 'package.json', packageDeps: ['react'] },
48
+ // Backend
49
+ { type: 'typescript-nestjs', manifestFile: 'package.json', packageDeps: ['@nestjs/core'] },
50
+ { type: 'typescript-node', manifestFile: 'package.json', packageDeps: ['express', 'fastify', 'koa', 'hono'] },
51
+ ];
52
+ // ── PYTHON STACK SIGNATURES ────────────────────────────────────────────────
53
+ export const PYTHON_STACK_SIGNATURES = [
54
+ { type: 'python-fastapi', contentIncludes: ['fastapi'] },
55
+ { type: 'python-django', contentIncludes: ['django'] },
56
+ { type: 'python', contentIncludes: [] }, // fallback: always matches
57
+ ];
58
+ // ── OTHER LANGUAGE SIGNATURES ──────────────────────────────────────────────
59
+ /** Single-file manifests that map 1-to-1 to a stack type */
60
+ export const FILE_MANIFEST_STACKS = [
61
+ { type: 'dart-flutter', manifestFile: 'pubspec.yaml' },
62
+ { type: 'go', manifestFile: 'go.mod' },
63
+ { type: 'rust', manifestFile: 'Cargo.toml' },
64
+ ];
65
+ /** Gradle-based JVM stacks */
66
+ export const GRADLE_STACK_SIGNATURES = [
67
+ { type: 'kotlin-android', contentIncludes: ['com.android'] },
68
+ { type: 'kotlin', contentIncludes: ['kotlin'] },
69
+ { type: 'java-spring', contentIncludes: ['spring'] },
70
+ { type: 'java', contentIncludes: [] }, // fallback
71
+ ];
72
+ /** Maven pom.xml stacks */
73
+ export const MAVEN_STACK_SIGNATURES = [
74
+ { type: 'java-spring', contentIncludes: ['spring'] },
75
+ { type: 'java', contentIncludes: [] }, // fallback
76
+ ];
77
+ /** Swift/iOS — requires Package.swift OR Xcode project files */
78
+ export const SWIFT_STACK_SIGNATURES = [
79
+ {
80
+ type: 'swift-ios',
81
+ predicate: (dir) => fs.existsSync(path.join(dir, 'Package.swift')) || hasXcodeFiles(dir),
82
+ },
83
+ ];
84
+ /** Ruby — Gemfile with "rails" keyword */
85
+ export const RUBY_STACK_SIGNATURES = [
86
+ { type: 'ruby-rails', manifestFile: 'Gemfile', contentIncludes: ['rails'] },
87
+ ];
88
+ /** C# / Unity — requires .csproj/.sln AND Unity-specific indicators */
89
+ export const CSHARP_STACK_SIGNATURES = [
90
+ {
91
+ type: 'csharp-unity',
92
+ predicate: (dir) => hasCsharpFiles(dir) &&
93
+ (fs.existsSync(path.join(dir, 'ProjectSettings', 'ProjectVersion.txt')) ||
94
+ fs.existsSync(path.join(dir, 'Assets'))),
95
+ },
96
+ ];
97
+ /** GDScript / Godot */
98
+ export const GODOT_STACK_SIGNATURES = [
99
+ {
100
+ type: 'gdscript-godot',
101
+ predicate: (dir) => fs.existsSync(path.join(dir, 'project.godot')) || hasGodotScripts(dir),
102
+ },
103
+ ];
104
+ // ── DATABASE SIGNATURES ────────────────────────────────────────────────────
105
+ export const DATABASE_SIGNATURES = [
106
+ // contentIncludes covers: pyproject.toml, requirements.txt, go.mod, Cargo.toml, build.gradle, pom.xml, Gemfile
107
+ { name: 'PostgreSQL', packageDeps: ['pg', 'postgres', '@prisma/client'], contentIncludes: ['psycopg', 'asyncpg', 'pgx', 'pq', 'postgresql', 'sqlx', 'diesel', "'pg'", '"pg"'] },
108
+ { name: 'MySQL', packageDeps: ['mysql', 'mysql2'], contentIncludes: ['mysql'] },
109
+ { name: 'MongoDB', packageDeps: ['mongodb', 'mongoose'], contentIncludes: ['pymongo', 'mongo-driver', 'mongodb'] },
110
+ { name: 'Redis', packageDeps: ['redis', 'ioredis'], contentIncludes: ['go-redis'] },
111
+ { name: 'SQLite', packageDeps: ['sqlite3', 'better-sqlite3'], contentIncludes: ["'sqlite3'", '"sqlite3"'] },
112
+ { name: 'TypeORM', packageDeps: ['typeorm'], contentIncludes: [] },
113
+ { name: 'Prisma', packageDeps: ['prisma', '@prisma/client'], contentIncludes: ['prisma'] },
114
+ { name: 'Drizzle', packageDeps: ['drizzle-orm'], contentIncludes: [] },
115
+ { name: 'Sequelize', packageDeps: ['sequelize'], contentIncludes: [] },
116
+ { name: 'SQLAlchemy', packageDeps: [], contentIncludes: ['sqlalchemy'] },
117
+ { name: 'JPA/Hibernate', packageDeps: [], contentIncludes: ['jpa', 'hibernate'] },
118
+ ];
119
+ // ── STATE MANAGEMENT SIGNATURES ────────────────────────────────────────────
120
+ export const STATE_MANAGEMENT_SIGNATURES = [
121
+ { name: 'Redux', packageDeps: ['redux', '@reduxjs/toolkit'] },
122
+ { name: 'Zustand', packageDeps: ['zustand'] },
123
+ { name: 'Jotai', packageDeps: ['jotai'] },
124
+ { name: 'Recoil', packageDeps: ['recoil'] },
125
+ { name: 'MobX', packageDeps: ['mobx'] },
126
+ { name: 'React Query', packageDeps: ['@tanstack/react-query', 'react-query'] },
127
+ { name: 'SWR', packageDeps: ['swr'] },
128
+ { name: 'Pinia', packageDeps: ['pinia'] },
129
+ { name: 'Vuex', packageDeps: ['vuex'] },
130
+ { name: 'Riverpod', contentIncludes: ['flutter_riverpod', 'riverpod'] },
131
+ { name: 'Provider', contentIncludes: ['provider'] },
132
+ { name: 'BLoC', contentIncludes: ['bloc'] },
133
+ { name: 'GetX', contentIncludes: ['getx', 'get:'] },
134
+ ];
135
+ // ── CAPABILITY SIGNATURES ──────────────────────────────────────────────────
136
+ export const CAPABILITY_SIGNATURES = [
137
+ {
138
+ capability: 'commerce',
139
+ packageDeps: [
140
+ 'stripe', '@stripe/stripe-js', '@stripe/react-stripe-js',
141
+ '@shopify/shopify-api', 'shopify-api-node',
142
+ '@medusajs/medusa', '@paypal/checkout-server-sdk',
143
+ 'toss-payments', 'iamport-rest-client',
144
+ ],
145
+ contentIncludes: ['stripe', 'shopify', 'saleor'],
146
+ },
147
+ {
148
+ capability: 'video',
149
+ packageDeps: ['fluent-ffmpeg', '@ffmpeg/ffmpeg', 'ffmpeg-static', 'remotion', 'video.js', '@mux/mux-node'],
150
+ contentIncludes: ['moviepy', 'ffmpeg', 'opencv', 'vidgear'],
151
+ },
152
+ {
153
+ capability: 'event-automation',
154
+ packageDeps: ['@notionhq/client', 'aligo-smartsms', 'nodemailer'],
155
+ contentIncludes: ['notion-client', 'python-pptx', 'google-generativeai', 'aligo'],
156
+ requiresDirs: ['agents', 'schedules', '.event_state.json', 'prompts'],
157
+ },
158
+ ];
159
+ // ── HOSTING SIGNATURES ─────────────────────────────────────────────────────
160
+ export const HOSTING_SIGNATURES = [
161
+ { name: 'Vercel', files: ['vercel.json', '.vercel'] },
162
+ { name: 'Netlify', files: ['netlify.toml'] },
163
+ { name: 'Google Cloud', files: ['app.yaml', 'cloudbuild.yaml'] },
164
+ { name: 'Docker', files: ['Dockerfile', 'docker-compose.yml'] },
165
+ { name: 'Fly.io', files: ['fly.toml'] },
166
+ { name: 'Railway', files: ['railway.json'] },
167
+ ];
168
+ // ── CI/CD SIGNATURES ───────────────────────────────────────────────────────
169
+ export const CICD_SIGNATURES = [
170
+ { name: 'GitHub Actions', files: ['.github/workflows'] },
171
+ { name: 'GitLab CI', files: ['.gitlab-ci.yml'] },
172
+ { name: 'Jenkins', files: ['Jenkinsfile'] },
173
+ { name: 'CircleCI', files: ['.circleci'] },
174
+ ];
175
+ //# sourceMappingURL=signatures.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signatures.js","sourceRoot":"","sources":["../../../src/cli/detect/signatures.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Monorepo workspace path discovery.
3
+ * Supports: pnpm-workspace.yaml, package.json workspaces, lerna.json, nx.json, turbo.json.
4
+ */
5
+ /** Return relative paths of monorepo workspace package directories. */
6
+ export declare function detectWorkspacePaths(projectRoot: string): string[];
7
+ //# sourceMappingURL=workspace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../../src/cli/detect/workspace.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAwFH,uEAAuE;AACvE,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAQlE"}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Monorepo workspace path discovery.
3
+ * Supports: pnpm-workspace.yaml, package.json workspaces, lerna.json, nx.json, turbo.json.
4
+ */
5
+ import path from 'path';
6
+ import fs from 'fs';
7
+ // ── helpers ────────────────────────────────────────────────────────────────
8
+ function readText(filePath) {
9
+ try {
10
+ return fs.readFileSync(filePath, 'utf-8');
11
+ }
12
+ catch {
13
+ return '';
14
+ }
15
+ }
16
+ function expandGlobPattern(projectRoot, pattern, out) {
17
+ const clean = pattern.replace(/['"]/g, '').trim();
18
+ if (clean.startsWith('!'))
19
+ return;
20
+ if (!clean.includes('*')) {
21
+ const full = path.join(projectRoot, clean);
22
+ if (fs.existsSync(full) && fs.statSync(full).isDirectory())
23
+ out.add(clean);
24
+ return;
25
+ }
26
+ if (clean.endsWith('/*') || clean.endsWith('/*/')) {
27
+ const baseDir = clean.replace(/\/\*+\/?$/, '');
28
+ const basePath = path.join(projectRoot, baseDir);
29
+ if (!fs.existsSync(basePath) || !fs.statSync(basePath).isDirectory())
30
+ return;
31
+ try {
32
+ for (const entry of fs.readdirSync(basePath)) {
33
+ if (entry.startsWith('.'))
34
+ continue;
35
+ const ep = path.join(basePath, entry);
36
+ if (fs.statSync(ep).isDirectory())
37
+ out.add(`${baseDir}/${entry}`);
38
+ }
39
+ }
40
+ catch { /* ignore */ }
41
+ }
42
+ }
43
+ function expandPatterns(projectRoot, patterns, out) {
44
+ for (const p of patterns)
45
+ expandGlobPattern(projectRoot, p, out);
46
+ }
47
+ // ── readers ────────────────────────────────────────────────────────────────
48
+ function readPnpmWorkspace(projectRoot, out) {
49
+ const content = readText(path.join(projectRoot, 'pnpm-workspace.yaml'));
50
+ if (!content)
51
+ return;
52
+ const match = content.match(/packages:\s*\n((?:\s*-\s*.+\n?)+)/);
53
+ if (!match)
54
+ return;
55
+ for (const line of match[1].split('\n')) {
56
+ const m = line.match(/^\s*-\s*['"]?([^'"#\n]+)['"]?\s*$/);
57
+ if (m)
58
+ expandGlobPattern(projectRoot, m[1].trim(), out);
59
+ }
60
+ }
61
+ function readPackageJsonWorkspaces(projectRoot, out) {
62
+ try {
63
+ const raw = readText(path.join(projectRoot, 'package.json'));
64
+ if (!raw)
65
+ return;
66
+ const pkg = JSON.parse(raw);
67
+ const ws = pkg.workspaces;
68
+ if (Array.isArray(ws)) {
69
+ expandPatterns(projectRoot, ws, out);
70
+ return;
71
+ }
72
+ if (ws?.packages && Array.isArray(ws.packages))
73
+ expandPatterns(projectRoot, ws.packages, out);
74
+ }
75
+ catch { /* ignore */ }
76
+ }
77
+ function readLerna(projectRoot, out) {
78
+ try {
79
+ const raw = readText(path.join(projectRoot, 'lerna.json'));
80
+ if (!raw)
81
+ return;
82
+ const lerna = JSON.parse(raw);
83
+ expandPatterns(projectRoot, lerna.packages ?? ['packages/*'], out);
84
+ }
85
+ catch { /* ignore */ }
86
+ }
87
+ function readNx(projectRoot, out) {
88
+ if (!fs.existsSync(path.join(projectRoot, 'nx.json')))
89
+ return;
90
+ for (const dir of ['apps', 'libs', 'packages']) {
91
+ expandGlobPattern(projectRoot, `${dir}/*`, out);
92
+ }
93
+ }
94
+ function readTurbo(projectRoot, out) {
95
+ if (!fs.existsSync(path.join(projectRoot, 'turbo.json')))
96
+ return;
97
+ for (const dir of ['apps', 'packages']) {
98
+ expandGlobPattern(projectRoot, `${dir}/*`, out);
99
+ }
100
+ }
101
+ // ── public API ─────────────────────────────────────────────────────────────
102
+ /** Return relative paths of monorepo workspace package directories. */
103
+ export function detectWorkspacePaths(projectRoot) {
104
+ const out = new Set();
105
+ readPnpmWorkspace(projectRoot, out);
106
+ readPackageJsonWorkspaces(projectRoot, out);
107
+ readLerna(projectRoot, out);
108
+ readNx(projectRoot, out);
109
+ readTurbo(projectRoot, out);
110
+ return [...out];
111
+ }
112
+ //# sourceMappingURL=workspace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace.js","sourceRoot":"","sources":["../../../src/cli/detect/workspace.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Characterization tests for detectTechStacks — lock in current behavior before refactoring.
3
+ * These tests build real temp-dir fixtures and run against the actual implementation.
4
+ * They MUST NOT change during or after refactoring.
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=detect.characterization.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect.characterization.test.d.ts","sourceRoot":"","sources":["../../src/cli/detect.characterization.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
@@ -0,0 +1,294 @@
1
+ /**
2
+ * Characterization tests for detectTechStacks — lock in current behavior before refactoring.
3
+ * These tests build real temp-dir fixtures and run against the actual implementation.
4
+ * They MUST NOT change during or after refactoring.
5
+ */
6
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+ import os from 'os';
10
+ import { detectTechStacks } from './detect.js';
11
+ // ── helpers ────────────────────────────────────────────────────────────────
12
+ function makeTmpDir() {
13
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'vibe-detect-'));
14
+ }
15
+ function writeFile(dir, relPath, content) {
16
+ const full = path.join(dir, relPath);
17
+ fs.mkdirSync(path.dirname(full), { recursive: true });
18
+ fs.writeFileSync(full, content, 'utf-8');
19
+ }
20
+ function writePkg(dir, pkg) {
21
+ writeFile(dir, 'package.json', JSON.stringify(pkg));
22
+ }
23
+ function stackTypes(result) {
24
+ return result.stacks.map(s => s.type);
25
+ }
26
+ // ── suite ──────────────────────────────────────────────────────────────────
27
+ describe('detectTechStacks — characterization', () => {
28
+ let tmp;
29
+ beforeEach(() => { tmp = makeTmpDir(); });
30
+ afterEach(() => { fs.rmSync(tmp, { recursive: true, force: true }); });
31
+ // ── 1. Empty directory ───────────────────────────────────────────────────
32
+ it('empty dir → no stacks, empty details', () => {
33
+ const result = detectTechStacks(tmp);
34
+ expect(result.stacks).toHaveLength(0);
35
+ expect(result.details.databases).toHaveLength(0);
36
+ expect(result.details.stateManagement).toHaveLength(0);
37
+ expect(result.details.hosting).toHaveLength(0);
38
+ expect(result.details.cicd).toHaveLength(0);
39
+ expect(result.details.capabilities).toHaveLength(0);
40
+ });
41
+ // ── 2. Next.js ───────────────────────────────────────────────────────────
42
+ it('Next.js — detects typescript-nextjs', () => {
43
+ writePkg(tmp, { name: 'myapp', dependencies: { next: '^14.0.0', react: '^18.0.0' } });
44
+ const result = detectTechStacks(tmp);
45
+ expect(stackTypes(result)).toContain('typescript-nextjs');
46
+ // "react" is also present but next takes priority
47
+ expect(stackTypes(result)).not.toContain('typescript-react');
48
+ });
49
+ // ── 3. React + Vite ──────────────────────────────────────────────────────
50
+ it('React (vite) — detects typescript-react', () => {
51
+ writePkg(tmp, { name: 'myapp', dependencies: { react: '^18.0.0' } });
52
+ const result = detectTechStacks(tmp);
53
+ expect(stackTypes(result)).toContain('typescript-react');
54
+ });
55
+ // ── 4. Vue ───────────────────────────────────────────────────────────────
56
+ it('Vue — detects typescript-vue', () => {
57
+ writePkg(tmp, { name: 'myapp', dependencies: { vue: '^3.0.0' } });
58
+ const result = detectTechStacks(tmp);
59
+ expect(stackTypes(result)).toContain('typescript-vue');
60
+ });
61
+ // ── 5. Nuxt ──────────────────────────────────────────────────────────────
62
+ it('Nuxt — detects typescript-nuxt and not typescript-vue', () => {
63
+ writePkg(tmp, { name: 'myapp', dependencies: { nuxt: '^3.0.0', vue: '^3.0.0' } });
64
+ const result = detectTechStacks(tmp);
65
+ expect(stackTypes(result)).toContain('typescript-nuxt');
66
+ expect(stackTypes(result)).not.toContain('typescript-vue');
67
+ });
68
+ // ── 6. Django ────────────────────────────────────────────────────────────
69
+ it('Django — detects python-django via requirements.txt', () => {
70
+ writeFile(tmp, 'requirements.txt', 'django==4.2\npsycopg2-binary==2.9\n');
71
+ const result = detectTechStacks(tmp);
72
+ expect(stackTypes(result)).toContain('python-django');
73
+ expect(result.details.databases).toContain('PostgreSQL');
74
+ });
75
+ // ── 7. FastAPI via pyproject.toml ────────────────────────────────────────
76
+ it('FastAPI — detects python-fastapi via pyproject.toml', () => {
77
+ writeFile(tmp, 'pyproject.toml', '[tool.poetry.dependencies]\nfastapi = "^0.100"\nasyncpg = "^0.27"\n');
78
+ const result = detectTechStacks(tmp);
79
+ expect(stackTypes(result)).toContain('python-fastapi');
80
+ expect(result.details.databases).toContain('PostgreSQL');
81
+ });
82
+ // ── 8. Ruby on Rails ─────────────────────────────────────────────────────
83
+ it('Rails — detects ruby-rails via Gemfile', () => {
84
+ writeFile(tmp, 'Gemfile', "source 'https://rubygems.org'\ngem 'rails', '~> 7.1'\ngem 'pg'\n");
85
+ const result = detectTechStacks(tmp);
86
+ expect(stackTypes(result)).toContain('ruby-rails');
87
+ expect(result.details.databases).toContain('PostgreSQL');
88
+ });
89
+ // ── 9. Go ────────────────────────────────────────────────────────────────
90
+ it('Go — detects go via go.mod with Redis', () => {
91
+ writeFile(tmp, 'go.mod', 'module example.com/myapp\n\ngo 1.21\n\nrequire github.com/go-redis/redis/v9 v9.0.0\n');
92
+ const result = detectTechStacks(tmp);
93
+ expect(stackTypes(result)).toContain('go');
94
+ expect(result.details.databases).toContain('Redis');
95
+ });
96
+ // ── 10. Rust ─────────────────────────────────────────────────────────────
97
+ it('Rust — detects rust via Cargo.toml with sqlx', () => {
98
+ writeFile(tmp, 'Cargo.toml', '[package]\nname = "myapp"\nversion = "0.1.0"\n\n[dependencies]\nsqlx = "0.7"\n');
99
+ const result = detectTechStacks(tmp);
100
+ expect(stackTypes(result)).toContain('rust');
101
+ expect(result.details.databases).toContain('PostgreSQL');
102
+ });
103
+ // ── 11. Flutter / Dart ───────────────────────────────────────────────────
104
+ it('Flutter — detects dart-flutter via pubspec.yaml with Riverpod', () => {
105
+ writeFile(tmp, 'pubspec.yaml', 'name: myapp\ndependencies:\n flutter:\n sdk: flutter\n flutter_riverpod: ^2.0.0\n');
106
+ const result = detectTechStacks(tmp);
107
+ expect(stackTypes(result)).toContain('dart-flutter');
108
+ expect(result.details.stateManagement).toContain('Riverpod');
109
+ });
110
+ // ── 12. Plain Node/TS ────────────────────────────────────────────────────
111
+ it('plain Node.js — detects typescript-node via package.json with name only', () => {
112
+ writePkg(tmp, { name: 'mylib', version: '1.0.0' });
113
+ const result = detectTechStacks(tmp);
114
+ expect(stackTypes(result)).toContain('typescript-node');
115
+ });
116
+ // ── 13. Monorepo — nested detection via workspace subdirs ────────────────
117
+ it('monorepo — detects stacks in packages/* subdirectories', () => {
118
+ // root package.json with workspaces
119
+ writePkg(tmp, { name: 'monorepo', workspaces: ['packages/*'] });
120
+ // packages/web = Next.js
121
+ writePkg(path.join(tmp, 'packages/web'), { name: 'web', dependencies: { next: '^14.0.0' } });
122
+ // packages/api = NestJS
123
+ writePkg(path.join(tmp, 'packages/api'), { name: 'api', dependencies: { '@nestjs/core': '^10.0.0' } });
124
+ const result = detectTechStacks(tmp);
125
+ const types = stackTypes(result);
126
+ expect(types).toContain('typescript-nextjs');
127
+ expect(types).toContain('typescript-nestjs');
128
+ });
129
+ // ── 14. detectInDir via conventional subdirs ──────────────────────────────
130
+ it('conventional backend/ frontend/ subdirs are detected', () => {
131
+ // frontend
132
+ writePkg(path.join(tmp, 'frontend'), { name: 'fe', dependencies: { react: '^18.0.0' } });
133
+ // backend
134
+ writePkg(path.join(tmp, 'backend'), { name: 'be', dependencies: { '@nestjs/core': '^10.0.0' } });
135
+ const result = detectTechStacks(tmp);
136
+ const types = stackTypes(result);
137
+ expect(types).toContain('typescript-react');
138
+ expect(types).toContain('typescript-nestjs');
139
+ // paths should reflect the subdir prefix
140
+ const reactStack = result.stacks.find(s => s.type === 'typescript-react');
141
+ expect(reactStack?.path).toBe('frontend');
142
+ const nestStack = result.stacks.find(s => s.type === 'typescript-nestjs');
143
+ expect(nestStack?.path).toBe('backend');
144
+ });
145
+ // ── 15. Multiple stacks in one project (monorepo + no workspaces config) ─
146
+ it('monorepo fallback — detects stacks in apps/ without workspace config', () => {
147
+ // no root package.json
148
+ writePkg(path.join(tmp, 'apps/web'), { name: 'web', dependencies: { react: '^18.0.0' } });
149
+ writeFile(path.join(tmp, 'apps/service'), 'go.mod', 'module service\n\ngo 1.21\n');
150
+ const result = detectTechStacks(tmp);
151
+ const types = stackTypes(result);
152
+ expect(types).toContain('typescript-react');
153
+ expect(types).toContain('go');
154
+ });
155
+ // ── 16. DB detection — multiple DBs ──────────────────────────────────────
156
+ it('detects multiple databases from package.json deps', () => {
157
+ writePkg(tmp, {
158
+ name: 'myapp',
159
+ dependencies: {
160
+ react: '^18.0.0',
161
+ pg: '^8.0.0',
162
+ redis: '^4.0.0',
163
+ mongoose: '^7.0.0',
164
+ }
165
+ });
166
+ const result = detectTechStacks(tmp);
167
+ expect(result.details.databases).toContain('PostgreSQL');
168
+ expect(result.details.databases).toContain('Redis');
169
+ expect(result.details.databases).toContain('MongoDB');
170
+ // no duplicates
171
+ const pg = result.details.databases.filter(d => d === 'PostgreSQL');
172
+ expect(pg).toHaveLength(1);
173
+ });
174
+ // ── 17. State management detection ───────────────────────────────────────
175
+ it('detects state management libraries', () => {
176
+ writePkg(tmp, {
177
+ name: 'myapp',
178
+ dependencies: {
179
+ react: '^18.0.0',
180
+ zustand: '^4.0.0',
181
+ '@tanstack/react-query': '^5.0.0',
182
+ }
183
+ });
184
+ const result = detectTechStacks(tmp);
185
+ expect(result.details.stateManagement).toContain('Zustand');
186
+ expect(result.details.stateManagement).toContain('React Query');
187
+ });
188
+ // ── 18. Capability detection — commerce ──────────────────────────────────
189
+ it('detects commerce capability via stripe dep', () => {
190
+ writePkg(tmp, {
191
+ name: 'shop',
192
+ dependencies: { react: '^18.0.0', stripe: '^14.0.0' }
193
+ });
194
+ const result = detectTechStacks(tmp);
195
+ expect(result.details.capabilities).toContain('commerce');
196
+ });
197
+ // ── 19. Capability detection — video ─────────────────────────────────────
198
+ it('detects video capability via fluent-ffmpeg dep', () => {
199
+ writePkg(tmp, {
200
+ name: 'video-tool',
201
+ dependencies: { 'fluent-ffmpeg': '^2.0.0' }
202
+ });
203
+ const result = detectTechStacks(tmp);
204
+ expect(result.details.capabilities).toContain('video');
205
+ });
206
+ // ── 20. Capability detection — event-automation (with required dir) ───────
207
+ it('detects event-automation only when dir structure present', () => {
208
+ writePkg(tmp, {
209
+ name: 'event-tool',
210
+ dependencies: { nodemailer: '^6.0.0', '@notionhq/client': '^2.0.0' }
211
+ });
212
+ // Without required directories: no event-automation
213
+ const resultBefore = detectTechStacks(tmp);
214
+ expect(resultBefore.details.capabilities).not.toContain('event-automation');
215
+ // With required directory: event-automation detected
216
+ fs.mkdirSync(path.join(tmp, 'agents'), { recursive: true });
217
+ const resultAfter = detectTechStacks(tmp);
218
+ expect(resultAfter.details.capabilities).toContain('event-automation');
219
+ });
220
+ // ── 21. CI/CD detection ───────────────────────────────────────────────────
221
+ it('detects GitHub Actions CI/CD', () => {
222
+ fs.mkdirSync(path.join(tmp, '.github', 'workflows'), { recursive: true });
223
+ writeFile(tmp, '.github/workflows/ci.yml', 'name: CI\n');
224
+ const result = detectTechStacks(tmp);
225
+ expect(result.details.cicd).toContain('GitHub Actions');
226
+ });
227
+ // ── 22. Hosting detection ─────────────────────────────────────────────────
228
+ it('detects Vercel hosting via vercel.json', () => {
229
+ writeFile(tmp, 'vercel.json', '{}');
230
+ const result = detectTechStacks(tmp);
231
+ expect(result.details.hosting).toContain('Vercel');
232
+ });
233
+ it('detects Docker hosting', () => {
234
+ writeFile(tmp, 'Dockerfile', 'FROM node:20\n');
235
+ const result = detectTechStacks(tmp);
236
+ expect(result.details.hosting).toContain('Docker');
237
+ });
238
+ // ── 23. Priority: Tauri > React ───────────────────────────────────────────
239
+ it('Tauri takes priority over React', () => {
240
+ writePkg(tmp, {
241
+ name: 'desktop',
242
+ dependencies: { '@tauri-apps/api': '^1.0.0', react: '^18.0.0' }
243
+ });
244
+ const result = detectTechStacks(tmp);
245
+ expect(stackTypes(result)).toContain('typescript-tauri');
246
+ expect(stackTypes(result)).not.toContain('typescript-react');
247
+ });
248
+ // ── 24. Angular ───────────────────────────────────────────────────────────
249
+ it('Angular — detects typescript-angular', () => {
250
+ writePkg(tmp, { name: 'ng-app', dependencies: { '@angular/core': '^17.0.0' } });
251
+ const result = detectTechStacks(tmp);
252
+ expect(stackTypes(result)).toContain('typescript-angular');
253
+ });
254
+ // ── 25. Java Spring via pom.xml ───────────────────────────────────────────
255
+ it('Java Spring — detects java-spring via pom.xml', () => {
256
+ writeFile(tmp, 'pom.xml', '<project>\n<dependencies>\n<groupId>org.springframework</groupId>\n</dependencies>\n</project>');
257
+ const result = detectTechStacks(tmp);
258
+ expect(stackTypes(result)).toContain('java-spring');
259
+ });
260
+ // ── 26. pnpm workspace monorepo ───────────────────────────────────────────
261
+ it('pnpm-workspace.yaml — detects stacks in workspace packages', () => {
262
+ writeFile(tmp, 'pnpm-workspace.yaml', 'packages:\n - "apps/*"\n');
263
+ writePkg(path.join(tmp, 'apps/dashboard'), { name: 'dashboard', dependencies: { vue: '^3.0.0' } });
264
+ const result = detectTechStacks(tmp);
265
+ expect(stackTypes(result)).toContain('typescript-vue');
266
+ const vueStack = result.stacks.find(s => s.type === 'typescript-vue');
267
+ expect(vueStack?.path).toBe('apps/dashboard');
268
+ });
269
+ // ── 27. Electron ──────────────────────────────────────────────────────────
270
+ it('Electron — detects typescript-electron', () => {
271
+ writePkg(tmp, { name: 'desktop', dependencies: { electron: '^28.0.0' } });
272
+ const result = detectTechStacks(tmp);
273
+ expect(stackTypes(result)).toContain('typescript-electron');
274
+ });
275
+ // ── 28. NestJS ────────────────────────────────────────────────────────────
276
+ it('NestJS — detects typescript-nestjs', () => {
277
+ writePkg(tmp, { name: 'api', dependencies: { '@nestjs/core': '^10.0.0' } });
278
+ const result = detectTechStacks(tmp);
279
+ expect(stackTypes(result)).toContain('typescript-nestjs');
280
+ });
281
+ // ── 29. Astro ─────────────────────────────────────────────────────────────
282
+ it('Astro — detects typescript-astro', () => {
283
+ writePkg(tmp, { name: 'blog', dependencies: { astro: '^4.0.0' } });
284
+ const result = detectTechStacks(tmp);
285
+ expect(stackTypes(result)).toContain('typescript-astro');
286
+ });
287
+ // ── 30. React Native ──────────────────────────────────────────────────────
288
+ it('React Native — detects typescript-react-native', () => {
289
+ writePkg(tmp, { name: 'mobile', dependencies: { 'react-native': '^0.73.0' } });
290
+ const result = detectTechStacks(tmp);
291
+ expect(stackTypes(result)).toContain('typescript-react-native');
292
+ });
293
+ });
294
+ //# sourceMappingURL=detect.characterization.test.js.map