infernoflow 0.32.8 → 0.33.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 (81) hide show
  1. package/dist/bin/infernoflow.mjs +84 -255
  2. package/dist/lib/adopters/angular.mjs +1 -128
  3. package/dist/lib/adopters/css.mjs +1 -111
  4. package/dist/lib/adopters/react.mjs +1 -104
  5. package/dist/lib/ai/ideDetection.mjs +1 -31
  6. package/dist/lib/ai/localProvider.mjs +1 -88
  7. package/dist/lib/ai/providerRouter.mjs +2 -295
  8. package/dist/lib/commands/adopt.mjs +20 -869
  9. package/dist/lib/commands/adoptWizard.mjs +9 -320
  10. package/dist/lib/commands/agent.mjs +5 -191
  11. package/dist/lib/commands/ai.mjs +2 -407
  12. package/dist/lib/commands/audit.mjs +13 -300
  13. package/dist/lib/commands/changelog.mjs +26 -594
  14. package/dist/lib/commands/check.mjs +3 -184
  15. package/dist/lib/commands/ci.mjs +3 -208
  16. package/dist/lib/commands/claudeMd.mjs +25 -130
  17. package/dist/lib/commands/cloud.mjs +5 -521
  18. package/dist/lib/commands/context.mjs +34 -287
  19. package/dist/lib/commands/coverage.mjs +2 -282
  20. package/dist/lib/commands/dashboard.mjs +123 -635
  21. package/dist/lib/commands/demo.mjs +8 -465
  22. package/dist/lib/commands/diff.mjs +5 -274
  23. package/dist/lib/commands/docGate.mjs +2 -81
  24. package/dist/lib/commands/doctor.mjs +3 -321
  25. package/dist/lib/commands/explain.mjs +8 -438
  26. package/dist/lib/commands/export.mjs +10 -239
  27. package/dist/lib/commands/generateSkills.mjs +38 -163
  28. package/dist/lib/commands/graph.mjs +203 -321
  29. package/dist/lib/commands/health.mjs +2 -309
  30. package/dist/lib/commands/impact.mjs +2 -325
  31. package/dist/lib/commands/implement.mjs +7 -103
  32. package/dist/lib/commands/init.mjs +23 -475
  33. package/dist/lib/commands/installCursorHooks.mjs +1 -36
  34. package/dist/lib/commands/installVsCodeCopilotHooks.mjs +1 -37
  35. package/dist/lib/commands/link.mjs +2 -342
  36. package/dist/lib/commands/log.mjs +16 -0
  37. package/dist/lib/commands/monorepo.mjs +4 -428
  38. package/dist/lib/commands/notify.mjs +4 -258
  39. package/dist/lib/commands/onboard.mjs +4 -296
  40. package/dist/lib/commands/prComment.mjs +2 -361
  41. package/dist/lib/commands/prImpact.mjs +2 -157
  42. package/dist/lib/commands/publish.mjs +15 -316
  43. package/dist/lib/commands/report.mjs +28 -272
  44. package/dist/lib/commands/review.mjs +9 -223
  45. package/dist/lib/commands/run.mjs +8 -336
  46. package/dist/lib/commands/scaffold.mjs +54 -419
  47. package/dist/lib/commands/scan.mjs +5 -558
  48. package/dist/lib/commands/scout.mjs +2 -291
  49. package/dist/lib/commands/setup.mjs +5 -310
  50. package/dist/lib/commands/share.mjs +13 -196
  51. package/dist/lib/commands/snapshot.mjs +3 -383
  52. package/dist/lib/commands/stability.mjs +2 -293
  53. package/dist/lib/commands/status.mjs +4 -172
  54. package/dist/lib/commands/suggest.mjs +21 -563
  55. package/dist/lib/commands/syncAuto.mjs +1 -96
  56. package/dist/lib/commands/synthesize.mjs +10 -228
  57. package/dist/lib/commands/teamSync.mjs +2 -388
  58. package/dist/lib/commands/test.mjs +6 -363
  59. package/dist/lib/commands/theme.mjs +18 -0
  60. package/dist/lib/commands/version.mjs +2 -282
  61. package/dist/lib/commands/vibe.mjs +7 -357
  62. package/dist/lib/commands/watch.mjs +4 -203
  63. package/dist/lib/commands/why.mjs +4 -358
  64. package/dist/lib/cursorHooksInstall.mjs +1 -60
  65. package/dist/lib/draftToolingInstall.mjs +7 -68
  66. package/dist/lib/git/detect-drift.mjs +4 -208
  67. package/dist/lib/learning/adapt.mjs +6 -101
  68. package/dist/lib/learning/observe.mjs +1 -119
  69. package/dist/lib/learning/patternDetector.mjs +1 -298
  70. package/dist/lib/learning/profile.mjs +2 -279
  71. package/dist/lib/learning/skillSynthesizer.mjs +24 -145
  72. package/dist/lib/templates/index.mjs +1 -131
  73. package/dist/lib/theme/scanner.mjs +4 -0
  74. package/dist/lib/ui/errors.mjs +1 -142
  75. package/dist/lib/ui/output.mjs +6 -72
  76. package/dist/lib/ui/prompts.mjs +6 -147
  77. package/dist/lib/vsCodeCopilotHooksInstall.mjs +1 -42
  78. package/dist/templates/cursor/inferno-mcp-server.mjs +29 -0
  79. package/dist/templates/github-app/GITHUB_APP.md +67 -0
  80. package/dist/templates/github-app/app-manifest.json +20 -0
  81. package/package.json +1 -1
@@ -1,128 +1 @@
1
- /**
2
- * lib/adopters/angular.mjs
3
- * Angular-specific scanner for --adopt.
4
- * Detects components, routes, services, and UI capabilities from Angular projects.
5
- */
6
-
7
- import * as fs from "node:fs";
8
- import * as path from "node:path";
9
-
10
- function safeRead(filePath) {
11
- try { return fs.readFileSync(filePath, "utf8"); } catch { return ""; }
12
- }
13
-
14
- /**
15
- * Scan an Angular project's source files and return detected signals.
16
- *
17
- * Returns:
18
- * {
19
- * components: string[], // component class names
20
- * routes: string[], // route paths detected
21
- * services: string[], // service class names
22
- * lazyModules: string[], // lazy-loaded module paths
23
- * formFields: string[], // reactive form control names
24
- * capabilities: { id, title, reason, sourceFiles }[]
25
- * }
26
- */
27
- export function scanAngular(cwd, files) {
28
- const components = new Set();
29
- const routes = new Set();
30
- const services = new Set();
31
- const lazyModules = new Set();
32
- const formFields = new Set();
33
- const capabilityMap = new Map(); // capId → { id, title, reason, sourceFiles: Set }
34
-
35
- const addCap = (id, title, reason, filePath) => {
36
- if (!capabilityMap.has(id)) {
37
- capabilityMap.set(id, { id, title, reason, sourceFiles: new Set() });
38
- }
39
- capabilityMap.get(id).sourceFiles.add(path.relative(cwd, filePath));
40
- };
41
-
42
- for (const filePath of files) {
43
- const rel = path.relative(cwd, filePath).replace(/\\/g, "/");
44
- const text = safeRead(filePath);
45
- if (!text) continue;
46
-
47
- // ── Component detection ───────────────────────────────────────────────
48
- if (/\.(ts)$/.test(filePath)) {
49
- // @Component decorated classes
50
- const compMatches = text.matchAll(/@Component\s*\([^)]*\)[\s\S]*?class\s+([A-Z][A-Za-z0-9_]*Component)/g);
51
- for (const m of compMatches) {
52
- const name = m[1].replace(/Component$/, "");
53
- components.add(name);
54
- // Derive a capability from the component name
55
- const capId = name.endsWith("Page") || name.endsWith("View")
56
- ? `View${name.replace(/(Page|View)$/, "")}`
57
- : `View${name}`;
58
- addCap(capId, `View ${name.replace(/([A-Z])/g, " $1").trim()}`, `@Component class detected: ${m[1]}`, filePath);
59
- }
60
-
61
- // Service classes → often wrap API capabilities
62
- const svcMatches = text.matchAll(/@Injectable[\s\S]*?class\s+([A-Z][A-Za-z0-9_]*Service)/g);
63
- for (const m of svcMatches) {
64
- services.add(m[1]);
65
- }
66
-
67
- // Reactive form controls → hint at form-based capabilities
68
- const formGroups = text.matchAll(/FormBuilder|FormGroup|FormControl/g);
69
- if ([...formGroups].length > 0) {
70
- const controlNames = text.matchAll(/['"]([a-zA-Z][a-zA-Z0-9_]*)['"]:\s*(?:this\.\w+\.control|new FormControl|\[)/g);
71
- for (const m of controlNames) formFields.add(m[1]);
72
- }
73
- }
74
-
75
- // ── Route detection ───────────────────────────────────────────────────
76
- if (rel.includes("routing") || rel.includes("routes") || rel.endsWith("app.routes.ts")) {
77
- // path: 'some/route'
78
- const pathMatches = text.matchAll(/\bpath\s*:\s*['"`]([^'"`]+)['"`]/g);
79
- for (const m of pathMatches) {
80
- const routePath = m[1].trim();
81
- if (routePath && routePath !== "**" && !routePath.startsWith(":")) {
82
- routes.add(routePath);
83
- // Each top-level route = likely a view capability
84
- const parts = routePath.split("/").filter(Boolean);
85
- if (parts.length >= 1) {
86
- const name = parts[parts.length - 1];
87
- const capId = "View" + name.charAt(0).toUpperCase() + name.slice(1).replace(/-([a-z])/g, (_, c) => c.toUpperCase());
88
- const title = "View " + name.replace(/-/g, " ").replace(/\b\w/g, c => c.toUpperCase());
89
- addCap(capId, title, `Route detected: /${routePath}`, filePath);
90
- }
91
- }
92
- }
93
-
94
- // Lazy-loaded modules
95
- const lazyMatches = text.matchAll(/loadChildren\s*:\s*\(\s*\)\s*=>\s*import\s*\(['"`]([^'"`]+)['"`]\)/g);
96
- for (const m of lazyMatches) lazyModules.add(m[1]);
97
- }
98
-
99
- // ── Template-based capability detection (.html) ───────────────────────
100
- if (/\.html$/.test(filePath)) {
101
- // Router links hint at navigation capabilities
102
- const routerLinks = text.matchAll(/routerLink\s*=\s*['"`]([^'"`]+)['"`]/g);
103
- for (const m of routerLinks) routes.add(m[1].replace(/^\//, ""));
104
-
105
- // (click) event bindings → actions
106
- const clickHandlers = text.matchAll(/\(click\)\s*=\s*["']([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/g);
107
- for (const m of clickHandlers) {
108
- const handler = m[1];
109
- // Heuristic: delete/remove/create handlers → capabilities
110
- if (/delete|remove/i.test(handler)) addCap("DeleteItem", "Delete Item", `(click) handler: ${handler}`, filePath);
111
- if (/create|add|new/i.test(handler)) addCap("CreateItem", "Create Item", `(click) handler: ${handler}`, filePath);
112
- if (/submit|save/i.test(handler)) addCap("UpdateItem", "Update Item", `(click) handler: ${handler}`, filePath);
113
- }
114
- }
115
- }
116
-
117
- return {
118
- components: Array.from(components).sort(),
119
- routes: Array.from(routes).sort(),
120
- services: Array.from(services).sort(),
121
- lazyModules: Array.from(lazyModules).sort(),
122
- formFields: Array.from(formFields).sort(),
123
- capabilities: Array.from(capabilityMap.values()).map(c => ({
124
- ...c,
125
- sourceFiles: Array.from(c.sourceFiles),
126
- })),
127
- };
128
- }
1
+ import*as z from"node:fs";import*as w from"node:path";function S(m){try{return z.readFileSync(m,"utf8")}catch{return""}}function v(m,y){const d=new Set,p=new Set,h=new Set,u=new Set,A=new Set,f=new Map,c=(t,n,o,a)=>{f.has(t)||f.set(t,{id:t,title:n,reason:o,sourceFiles:new Set}),f.get(t).sourceFiles.add(w.relative(m,a))};for(const t of y){const n=w.relative(m,t).replace(/\\/g,"/"),o=S(t);if(o){if(/\.(ts)$/.test(t)){const a=o.matchAll(/@Component\s*\([^)]*\)[\s\S]*?class\s+([A-Z][A-Za-z0-9_]*Component)/g);for(const e of a){const s=e[1].replace(/Component$/,"");d.add(s);const i=s.endsWith("Page")||s.endsWith("View")?`View${s.replace(/(Page|View)$/,"")}`:`View${s}`;c(i,`View ${s.replace(/([A-Z])/g," $1").trim()}`,`@Component class detected: ${e[1]}`,t)}const l=o.matchAll(/@Injectable[\s\S]*?class\s+([A-Z][A-Za-z0-9_]*Service)/g);for(const e of l)h.add(e[1]);if([...o.matchAll(/FormBuilder|FormGroup|FormControl/g)].length>0){const e=o.matchAll(/['"]([a-zA-Z][a-zA-Z0-9_]*)['"]:\s*(?:this\.\w+\.control|new FormControl|\[)/g);for(const s of e)A.add(s[1])}}if(n.includes("routing")||n.includes("routes")||n.endsWith("app.routes.ts")){const a=o.matchAll(/\bpath\s*:\s*['"`]([^'"`]+)['"`]/g);for(const r of a){const e=r[1].trim();if(e&&e!=="**"&&!e.startsWith(":")){p.add(e);const s=e.split("/").filter(Boolean);if(s.length>=1){const i=s[s.length-1],C="View"+i.charAt(0).toUpperCase()+i.slice(1).replace(/-([a-z])/g,(g,F)=>F.toUpperCase()),$="View "+i.replace(/-/g," ").replace(/\b\w/g,g=>g.toUpperCase());c(C,$,`Route detected: /${e}`,t)}}}const l=o.matchAll(/loadChildren\s*:\s*\(\s*\)\s*=>\s*import\s*\(['"`]([^'"`]+)['"`]\)/g);for(const r of l)u.add(r[1])}if(/\.html$/.test(t)){const a=o.matchAll(/routerLink\s*=\s*['"`]([^'"`]+)['"`]/g);for(const r of a)p.add(r[1].replace(/^\//,""));const l=o.matchAll(/\(click\)\s*=\s*["']([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/g);for(const r of l){const e=r[1];/delete|remove/i.test(e)&&c("DeleteItem","Delete Item",`(click) handler: ${e}`,t),/create|add|new/i.test(e)&&c("CreateItem","Create Item",`(click) handler: ${e}`,t),/submit|save/i.test(e)&&c("UpdateItem","Update Item",`(click) handler: ${e}`,t)}}}}return{components:Array.from(d).sort(),routes:Array.from(p).sort(),services:Array.from(h).sort(),lazyModules:Array.from(u).sort(),formFields:Array.from(A).sort(),capabilities:Array.from(f.values()).map(t=>({...t,sourceFiles:Array.from(t.sourceFiles)}))}}export{v as scanAngular};
@@ -1,111 +1 @@
1
- /**
2
- * lib/adopters/css.mjs
3
- * CSS / SCSS / design token scanner for --adopt.
4
- * Extracts design tokens, component class names, and UI patterns.
5
- */
6
-
7
- import * as fs from "node:fs";
8
- import * as path from "node:path";
9
-
10
- function safeRead(filePath) {
11
- try { return fs.readFileSync(filePath, "utf8"); } catch { return ""; }
12
- }
13
-
14
- /**
15
- * Scan CSS/SCSS/style files for design tokens and UI signals.
16
- *
17
- * Returns:
18
- * {
19
- * designTokens: string[], // CSS custom properties (--var-name)
20
- * colorTokens: string[], // tokens that look like colors
21
- * spacingTokens: string[], // tokens that look like spacing
22
- * componentClasses: string[], // BEM-style or component-level class names
23
- * themeVars: string[], // theme-related variables
24
- * }
25
- */
26
- export function scanCSS(cwd, files) {
27
- const allTokens = new Set();
28
- const colorTokens = new Set();
29
- const spacingTokens = new Set();
30
- const componentClasses = new Set();
31
- const themeVars = new Set();
32
-
33
- const styleFiles = files.filter(f =>
34
- /\.(css|scss|sass|less|styl)$/.test(f) ||
35
- // Also scan JS/TS files for CSS-in-JS (styled-components, emotion)
36
- (/\.(ts|tsx|js|jsx)$/.test(f) && !f.includes("node_modules"))
37
- );
38
-
39
- for (const filePath of styleFiles) {
40
- const text = safeRead(filePath);
41
- if (!text) continue;
42
-
43
- // ── CSS custom properties (design tokens) ─────────────────────────────
44
- const tokenMatches = text.matchAll(/--([a-zA-Z][a-zA-Z0-9_-]*)\s*:/g);
45
- for (const m of tokenMatches) {
46
- const token = `--${m[1]}`;
47
- allTokens.add(token);
48
-
49
- // Classify by name
50
- if (/color|colour|bg|background|text|border|shadow|fill|stroke/i.test(m[1])) {
51
- colorTokens.add(token);
52
- } else if (/space|spacing|gap|padding|margin|size|radius|width|height/i.test(m[1])) {
53
- spacingTokens.add(token);
54
- } else if (/theme|primary|secondary|accent|brand|dark|light/i.test(m[1])) {
55
- themeVars.add(token);
56
- }
57
- }
58
-
59
- // ── CSS class names → component hints ────────────────────────────────
60
- if (/\.(css|scss|sass|less)$/.test(filePath)) {
61
- // BEM block names: .my-component { }
62
- const classMatches = text.matchAll(/^\s*\.([a-zA-Z][a-zA-Z0-9_-]*)[\s{,]/gm);
63
- for (const m of classMatches) {
64
- const cls = m[1];
65
- // Skip utility classes (short names, numbers, state classes)
66
- if (cls.length < 4) continue;
67
- if (/^(flex|grid|block|hidden|text|font|bg|border|p-|m-|w-|h-)/.test(cls)) continue;
68
- if (/^(active|disabled|hover|focus|error|success|warning)$/.test(cls)) continue;
69
- componentClasses.add(cls);
70
- }
71
- }
72
-
73
- // ── CSS-in-JS: styled-components / emotion ────────────────────────────
74
- if (/\.(ts|tsx|js|jsx)$/.test(filePath)) {
75
- // styled.div`...` or styled(Component)`...`
76
- const styledMatches = text.matchAll(/(?:styled|css)`[^`]*--([a-zA-Z][a-zA-Z0-9_-]*)\s*:/g);
77
- for (const m of styledMatches) allTokens.add(`--${m[1]}`);
78
-
79
- // Tailwind arbitrary values referencing CSS vars: bg-[--color-primary]
80
- const tailwindVars = text.matchAll(/\[--([a-zA-Z][a-zA-Z0-9_-]*)\]/g);
81
- for (const m of tailwindVars) allTokens.add(`--${m[1]}`);
82
- }
83
- }
84
-
85
- return {
86
- designTokens: Array.from(allTokens).sort().slice(0, 40),
87
- colorTokens: Array.from(colorTokens).sort().slice(0, 20),
88
- spacingTokens: Array.from(spacingTokens).sort().slice(0, 15),
89
- componentClasses: Array.from(componentClasses).sort().slice(0, 30),
90
- themeVars: Array.from(themeVars).sort().slice(0, 15),
91
- };
92
- }
93
-
94
- /**
95
- * Detect which CSS framework is in use from class names and package deps.
96
- */
97
- export function detectCSSFramework(text, externalLibraries = []) {
98
- const hasDep = (name) => externalLibraries.includes(name);
99
- const hasClass = (pattern) => pattern.test(text);
100
-
101
- if (hasDep("tailwindcss") || hasClass(/\b(?:flex|grid|px-\d|py-\d|text-\w+|bg-\w+|rounded)/)) return "tailwind";
102
- if (hasDep("bootstrap") || hasClass(/\b(?:container|row|col-|btn btn-|navbar|card)/)) return "bootstrap";
103
- if (externalLibraries.some(d => d.startsWith("@angular/material"))) return "angular-material";
104
- if (hasDep("antd") || hasClass(/\bant-/)) return "ant-design";
105
- if (hasDep("@mui/material") || hasDep("@material-ui/core")) return "mui";
106
- if (hasDep("styled-components")) return "styled-components";
107
- if (hasDep("@emotion/react") || hasDep("@emotion/styled")) return "emotion";
108
- if (hasDep("@chakra-ui/react")) return "chakra-ui";
109
- if (hasDep("@radix-ui/react-primitive")) return "radix-ui";
110
- return "unknown";
111
- }
1
+ import*as h from"node:fs";import"node:path";function g(c){try{return h.readFileSync(c,"utf8")}catch{return""}}function w(c,l){const t=new Set,a=new Set,s=new Set,d=new Set,f=new Set,m=l.filter(o=>/\.(css|scss|sass|less|styl)$/.test(o)||/\.(ts|tsx|js|jsx)$/.test(o)&&!o.includes("node_modules"));for(const o of m){const i=g(o);if(!i)continue;const u=i.matchAll(/--([a-zA-Z][a-zA-Z0-9_-]*)\s*:/g);for(const e of u){const r=`--${e[1]}`;t.add(r),/color|colour|bg|background|text|border|shadow|fill|stroke/i.test(e[1])?a.add(r):/space|spacing|gap|padding|margin|size|radius|width|height/i.test(e[1])?s.add(r):/theme|primary|secondary|accent|brand|dark|light/i.test(e[1])&&f.add(r)}if(/\.(css|scss|sass|less)$/.test(o)){const e=i.matchAll(/^\s*\.([a-zA-Z][a-zA-Z0-9_-]*)[\s{,]/gm);for(const r of e){const n=r[1];n.length<4||/^(flex|grid|block|hidden|text|font|bg|border|p-|m-|w-|h-)/.test(n)||/^(active|disabled|hover|focus|error|success|warning)$/.test(n)||d.add(n)}}if(/\.(ts|tsx|js|jsx)$/.test(o)){const e=i.matchAll(/(?:styled|css)`[^`]*--([a-zA-Z][a-zA-Z0-9_-]*)\s*:/g);for(const n of e)t.add(`--${n[1]}`);const r=i.matchAll(/\[--([a-zA-Z][a-zA-Z0-9_-]*)\]/g);for(const n of r)t.add(`--${n[1]}`)}}return{designTokens:Array.from(t).sort().slice(0,40),colorTokens:Array.from(a).sort().slice(0,20),spacingTokens:Array.from(s).sort().slice(0,15),componentClasses:Array.from(d).sort().slice(0,30),themeVars:Array.from(f).sort().slice(0,15)}}function y(c,l=[]){const t=s=>l.includes(s),a=s=>s.test(c);return t("tailwindcss")||a(/\b(?:flex|grid|px-\d|py-\d|text-\w+|bg-\w+|rounded)/)?"tailwind":t("bootstrap")||a(/\b(?:container|row|col-|btn btn-|navbar|card)/)?"bootstrap":l.some(s=>s.startsWith("@angular/material"))?"angular-material":t("antd")||a(/\bant-/)?"ant-design":t("@mui/material")||t("@material-ui/core")?"mui":t("styled-components")?"styled-components":t("@emotion/react")||t("@emotion/styled")?"emotion":t("@chakra-ui/react")?"chakra-ui":t("@radix-ui/react-primitive")?"radix-ui":"unknown"}export{y as detectCSSFramework,w as scanCSS};
@@ -1,104 +1 @@
1
- /**
2
- * lib/adopters/react.mjs
3
- * React-specific scanner for --adopt.
4
- * Detects components, hooks, routes, and UI capabilities from React projects.
5
- */
6
-
7
- import * as fs from "node:fs";
8
- import * as path from "node:path";
9
-
10
- function safeRead(filePath) {
11
- try { return fs.readFileSync(filePath, "utf8"); } catch { return ""; }
12
- }
13
-
14
- /**
15
- * Scan a React project's source files for UI signals.
16
- *
17
- * Returns:
18
- * {
19
- * components: string[],
20
- * customHooks: string[],
21
- * routes: string[],
22
- * capabilities: { id, title, reason, sourceFiles }[]
23
- * }
24
- */
25
- export function scanReact(cwd, files) {
26
- const components = new Set();
27
- const customHooks = new Set();
28
- const routes = new Set();
29
- const capabilityMap = new Map();
30
-
31
- const addCap = (id, title, reason, filePath) => {
32
- if (!capabilityMap.has(id)) {
33
- capabilityMap.set(id, { id, title, reason, sourceFiles: new Set() });
34
- }
35
- capabilityMap.get(id).sourceFiles.add(path.relative(cwd, filePath));
36
- };
37
-
38
- for (const filePath of files) {
39
- if (!/\.(tsx?|jsx?)$/.test(filePath)) continue;
40
- const text = safeRead(filePath);
41
- if (!text) continue;
42
-
43
- // ── Component detection ───────────────────────────────────────────────
44
- // export default function MyComponent / export function MyComponent
45
- const exportFn = text.matchAll(/export\s+(?:default\s+)?function\s+([A-Z][A-Za-z0-9_]*)\s*\(/g);
46
- for (const m of exportFn) {
47
- components.add(m[1]);
48
- // Page/View/Screen/Dashboard components → ViewXxx capability
49
- if (/Page|View|Screen|Dashboard|Panel|Modal|Dialog/i.test(m[1])) {
50
- const capId = "View" + m[1].replace(/(Page|View|Screen|Dashboard|Panel|Modal|Dialog)$/, "");
51
- addCap(capId, `View ${m[1].replace(/([A-Z])/g, " $1").trim()}`, `React component: ${m[1]}`, filePath);
52
- }
53
- }
54
-
55
- // Arrow function components: const MyComponent = () =>
56
- const arrowComp = text.matchAll(/(?:export\s+)?const\s+([A-Z][A-Za-z0-9_]*)\s*=\s*(?:React\.memo\()?(?:\([^)]*\)|[a-zA-Z_]\w*)\s*=>/g);
57
- for (const m of arrowComp) {
58
- components.add(m[1]);
59
- }
60
-
61
- // ── Custom hooks ─────────────────────────────────────────────────────
62
- const hookMatches = text.matchAll(/export\s+(?:default\s+)?function\s+(use[A-Z][A-Za-z0-9_]*)\s*\(/g);
63
- for (const m of hookMatches) customHooks.add(m[1]);
64
-
65
- const hookArrow = text.matchAll(/(?:export\s+)?const\s+(use[A-Z][A-Za-z0-9_]*)\s*=/g);
66
- for (const m of hookArrow) customHooks.add(m[1]);
67
-
68
- // ── Route detection (react-router) ────────────────────────────────────
69
- // <Route path="/some/path" or path: "/some/path"
70
- const routeJsx = text.matchAll(/<Route[^>]+path\s*=\s*["'`]([^"'`]+)["'`]/g);
71
- for (const m of routeJsx) {
72
- const p = m[1].replace(/^\//, "").replace(/:[\w]+/g, "{id}");
73
- if (p) routes.add(p);
74
- }
75
-
76
- const routeObj = text.matchAll(/path\s*:\s*["'`]([^"'`]+)["'`]/g);
77
- for (const m of routeObj) {
78
- const p = m[1].replace(/^\//, "").replace(/:[\w]+/g, "{id}");
79
- if (p && p !== "*" && p.length < 60) routes.add(p);
80
- }
81
-
82
- // ── Button / action detection ─────────────────────────────────────────
83
- const onClicks = text.matchAll(/onClick\s*=\s*\{(?:[^}]*\b(delete|remove|create|add|submit|save|search|filter|toggle|update|edit)\b[^}]*)\}/gi);
84
- for (const m of onClicks) {
85
- const action = m[1].toLowerCase();
86
- if (action === "delete" || action === "remove") addCap("DeleteItem", "Delete Item", `onClick handler contains "${action}"`, filePath);
87
- if (action === "create" || action === "add") addCap("CreateItem", "Create Item", `onClick handler contains "${action}"`, filePath);
88
- if (action === "submit" || action === "save" || action === "update" || action === "edit") addCap("UpdateItem", "Update Item", `onClick handler contains "${action}"`, filePath);
89
- if (action === "search") addCap("SearchItems", "Search Items", `onClick handler contains "search"`, filePath);
90
- if (action === "filter") addCap("FilterItems", "Filter Items", `onClick handler contains "filter"`, filePath);
91
- if (action === "toggle") addCap("ToggleComplete", "Toggle Complete", `onClick handler contains "toggle"`, filePath);
92
- }
93
- }
94
-
95
- return {
96
- components: Array.from(components).sort(),
97
- customHooks: Array.from(customHooks).sort(),
98
- routes: Array.from(routes).sort(),
99
- capabilities: Array.from(capabilityMap.values()).map(c => ({
100
- ...c,
101
- sourceFiles: Array.from(c.sourceFiles),
102
- })),
103
- };
104
- }
1
+ import*as w from"node:fs";import*as C from"node:path";function k(c){try{return w.readFileSync(c,"utf8")}catch{return""}}function I(c,d){const n=new Set,l=new Set,i=new Set,r=new Map,a=(t,s,m,f)=>{r.has(t)||r.set(t,{id:t,title:s,reason:m,sourceFiles:new Set}),r.get(t).sourceFiles.add(C.relative(c,f))};for(const t of d){if(!/\.(tsx?|jsx?)$/.test(t))continue;const s=k(t);if(!s)continue;const m=s.matchAll(/export\s+(?:default\s+)?function\s+([A-Z][A-Za-z0-9_]*)\s*\(/g);for(const o of m)if(n.add(o[1]),/Page|View|Screen|Dashboard|Panel|Modal|Dialog/i.test(o[1])){const e="View"+o[1].replace(/(Page|View|Screen|Dashboard|Panel|Modal|Dialog)$/,"");a(e,`View ${o[1].replace(/([A-Z])/g," $1").trim()}`,`React component: ${o[1]}`,t)}const f=s.matchAll(/(?:export\s+)?const\s+([A-Z][A-Za-z0-9_]*)\s*=\s*(?:React\.memo\()?(?:\([^)]*\)|[a-zA-Z_]\w*)\s*=>/g);for(const o of f)n.add(o[1]);const p=s.matchAll(/export\s+(?:default\s+)?function\s+(use[A-Z][A-Za-z0-9_]*)\s*\(/g);for(const o of p)l.add(o[1]);const h=s.matchAll(/(?:export\s+)?const\s+(use[A-Z][A-Za-z0-9_]*)\s*=/g);for(const o of h)l.add(o[1]);const u=s.matchAll(/<Route[^>]+path\s*=\s*["'`]([^"'`]+)["'`]/g);for(const o of u){const e=o[1].replace(/^\//,"").replace(/:[\w]+/g,"{id}");e&&i.add(e)}const g=s.matchAll(/path\s*:\s*["'`]([^"'`]+)["'`]/g);for(const o of g){const e=o[1].replace(/^\//,"").replace(/:[\w]+/g,"{id}");e&&e!=="*"&&e.length<60&&i.add(e)}const A=s.matchAll(/onClick\s*=\s*\{(?:[^}]*\b(delete|remove|create|add|submit|save|search|filter|toggle|update|edit)\b[^}]*)\}/gi);for(const o of A){const e=o[1].toLowerCase();(e==="delete"||e==="remove")&&a("DeleteItem","Delete Item",`onClick handler contains "${e}"`,t),(e==="create"||e==="add")&&a("CreateItem","Create Item",`onClick handler contains "${e}"`,t),(e==="submit"||e==="save"||e==="update"||e==="edit")&&a("UpdateItem","Update Item",`onClick handler contains "${e}"`,t),e==="search"&&a("SearchItems","Search Items",'onClick handler contains "search"',t),e==="filter"&&a("FilterItems","Filter Items",'onClick handler contains "filter"',t),e==="toggle"&&a("ToggleComplete","Toggle Complete",'onClick handler contains "toggle"',t)}}return{components:Array.from(n).sort(),customHooks:Array.from(l).sort(),routes:Array.from(i).sort(),capabilities:Array.from(r.values()).map(t=>({...t,sourceFiles:Array.from(t.sourceFiles)}))}}export{I as scanReact};
@@ -1,31 +1 @@
1
- export function detectIdeContext(preferredIde = "auto") {
2
- const env = process.env;
3
- const lowerPreferred = String(preferredIde || "auto").toLowerCase();
4
-
5
- const hasCursor = !!(env.CURSOR_TRACE_ID || env.CURSOR_AGENT || env.CURSOR_SESSION_ID || (env.VSCODE_GIT_ASKPASS_NODE || "").toLowerCase().includes("cursor") || (env.VSCODE_GIT_ASKPASS_MAIN || "").toLowerCase().includes("cursor"));
6
- const hasVscode = !!(env.VSCODE_PID || env.VSCODE_CWD || env.GITHUB_COPILOT_AGENT);
7
- const hasWindsurf = !!(env.WINDSURF || env.CODEIUM || env.WINDSURF_SESSION_ID);
8
-
9
- let ideDetected = "unknown";
10
- if (hasCursor) ideDetected = "cursor";
11
- else if (hasVscode) ideDetected = "vscode";
12
- else if (hasWindsurf) ideDetected = "windsurf";
13
-
14
- if (lowerPreferred !== "auto" && ["cursor", "vscode", "windsurf"].includes(lowerPreferred)) {
15
- ideDetected = lowerPreferred;
16
- }
17
-
18
- const explicitAgentAvailability = env.INFERNO_AGENT_AVAILABLE;
19
- const agentAvailable = explicitAgentAvailability != null
20
- ? explicitAgentAvailability === "1" || explicitAgentAvailability === "true"
21
- : ideDetected !== "unknown";
22
-
23
- const reasonCodes = [];
24
- if (ideDetected !== "unknown") reasonCodes.push(`IDE_${ideDetected.toUpperCase()}_DETECTED`);
25
- else reasonCodes.push("IDE_UNKNOWN");
26
- if (agentAvailable) reasonCodes.push("IDE_AGENT_AVAILABLE");
27
- else reasonCodes.push("IDE_AGENT_UNAVAILABLE");
28
-
29
- return { ideDetected, agentAvailable, reasonCodes };
30
- }
31
-
1
+ function A(_="auto"){const e=process.env,n=String(_||"auto").toLowerCase(),u=!!(e.CURSOR_TRACE_ID||e.CURSOR_AGENT||e.CURSOR_SESSION_ID||(e.VSCODE_GIT_ASKPASS_NODE||"").toLowerCase().includes("cursor")||(e.VSCODE_GIT_ASKPASS_MAIN||"").toLowerCase().includes("cursor")),E=!!(e.VSCODE_PID||e.VSCODE_CWD||e.GITHUB_COPILOT_AGENT),c=!!(e.WINDSURF||e.CODEIUM||e.WINDSURF_SESSION_ID);let s="unknown";u?s="cursor":E?s="vscode":c&&(s="windsurf"),n!=="auto"&&["cursor","vscode","windsurf"].includes(n)&&(s=n);const t=e.INFERNO_AGENT_AVAILABLE,r=t!=null?t==="1"||t==="true":s!=="unknown",o=[];return s!=="unknown"?o.push(`IDE_${s.toUpperCase()}_DETECTED`):o.push("IDE_UNKNOWN"),r?o.push("IDE_AGENT_AVAILABLE"):o.push("IDE_AGENT_UNAVAILABLE"),{ideDetected:s,agentAvailable:r,reasonCodes:o}}export{A as detectIdeContext};
@@ -1,88 +1 @@
1
- const DEFAULT_TIMEOUT_MS = 45000;
2
-
3
- function withTimeout(ms) {
4
- const controller = new AbortController();
5
- const timer = setTimeout(() => controller.abort(), ms);
6
- return { controller, timer };
7
- }
8
-
9
- async function callOllama(prompt, timeoutMs) {
10
- const endpoint = process.env.INFERNO_LOCAL_ENDPOINT || "http://127.0.0.1:11434/api/generate";
11
- const model = process.env.INFERNO_LOCAL_MODEL || "llama3.1:8b";
12
- const { controller, timer } = withTimeout(timeoutMs);
13
- try {
14
- const res = await fetch(endpoint, {
15
- method: "POST",
16
- headers: { "Content-Type": "application/json" },
17
- signal: controller.signal,
18
- body: JSON.stringify({
19
- model,
20
- prompt,
21
- stream: false,
22
- }),
23
- });
24
- if (!res.ok) {
25
- const body = await res.text();
26
- throw new Error(`local_model_http_${res.status}: ${body.slice(0, 240)}`);
27
- }
28
- const data = await res.json();
29
- if (!data?.response || typeof data.response !== "string") {
30
- throw new Error("local_model_invalid_response");
31
- }
32
- return data.response.trim();
33
- } finally {
34
- clearTimeout(timer);
35
- }
36
- }
37
-
38
- async function callOpenAICompat(prompt, timeoutMs) {
39
- const endpoint = process.env.INFERNO_LOCAL_ENDPOINT || "http://127.0.0.1:1234/v1/chat/completions";
40
- const model = process.env.INFERNO_LOCAL_MODEL || "local-model";
41
- const apiKey = process.env.INFERNO_LOCAL_API_KEY || "local";
42
- const { controller, timer } = withTimeout(timeoutMs);
43
- try {
44
- const res = await fetch(endpoint, {
45
- method: "POST",
46
- headers: {
47
- "Content-Type": "application/json",
48
- Authorization: `Bearer ${apiKey}`,
49
- },
50
- signal: controller.signal,
51
- body: JSON.stringify({
52
- model,
53
- temperature: 0.1,
54
- messages: [
55
- { role: "system", content: "Return JSON only." },
56
- { role: "user", content: prompt },
57
- ],
58
- }),
59
- });
60
- if (!res.ok) {
61
- const body = await res.text();
62
- throw new Error(`local_model_http_${res.status}: ${body.slice(0, 240)}`);
63
- }
64
- const data = await res.json();
65
- const text = data?.choices?.[0]?.message?.content;
66
- if (!text || typeof text !== "string") {
67
- throw new Error("local_model_invalid_response");
68
- }
69
- return text.trim();
70
- } finally {
71
- clearTimeout(timer);
72
- }
73
- }
74
-
75
- export async function generateWithLocalModel(prompt, options = {}) {
76
- if (process.env.INFERNO_LOCAL_MOCK_RESPONSE) {
77
- return process.env.INFERNO_LOCAL_MOCK_RESPONSE;
78
- }
79
-
80
- const provider = (process.env.INFERNO_LOCAL_PROVIDER || "ollama").toLowerCase();
81
- const timeoutMs = Number(options.timeoutMs || process.env.INFERNO_LOCAL_TIMEOUT_MS || DEFAULT_TIMEOUT_MS);
82
-
83
- if (provider === "openai") {
84
- return callOpenAICompat(prompt, timeoutMs);
85
- }
86
- return callOllama(prompt, timeoutMs);
87
- }
88
-
1
+ const E=45e3;function _(e){const t=new AbortController,n=setTimeout(()=>t.abort(),e);return{controller:t,timer:n}}async function p(e,t){const n=process.env.INFERNO_LOCAL_ENDPOINT||"http://127.0.0.1:11434/api/generate",s=process.env.INFERNO_LOCAL_MODEL||"llama3.1:8b",{controller:a,timer:c}=_(t);try{const r=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json"},signal:a.signal,body:JSON.stringify({model:s,prompt:e,stream:!1})});if(!r.ok){const l=await r.text();throw new Error(`local_model_http_${r.status}: ${l.slice(0,240)}`)}const o=await r.json();if(!o?.response||typeof o.response!="string")throw new Error("local_model_invalid_response");return o.response.trim()}finally{clearTimeout(c)}}async function m(e,t){const n=process.env.INFERNO_LOCAL_ENDPOINT||"http://127.0.0.1:1234/v1/chat/completions",s=process.env.INFERNO_LOCAL_MODEL||"local-model",a=process.env.INFERNO_LOCAL_API_KEY||"local",{controller:c,timer:r}=_(t);try{const o=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a}`},signal:c.signal,body:JSON.stringify({model:s,temperature:.1,messages:[{role:"system",content:"Return JSON only."},{role:"user",content:e}]})});if(!o.ok){const O=await o.text();throw new Error(`local_model_http_${o.status}: ${O.slice(0,240)}`)}const i=(await o.json())?.choices?.[0]?.message?.content;if(!i||typeof i!="string")throw new Error("local_model_invalid_response");return i.trim()}finally{clearTimeout(r)}}async function N(e,t={}){if(process.env.INFERNO_LOCAL_MOCK_RESPONSE)return process.env.INFERNO_LOCAL_MOCK_RESPONSE;const n=(process.env.INFERNO_LOCAL_PROVIDER||"ollama").toLowerCase(),s=Number(t.timeoutMs||process.env.INFERNO_LOCAL_TIMEOUT_MS||45e3);return n==="openai"?m(e,s):p(e,s)}export{N as generateWithLocalModel};