@chemx/starter-kit 26.9.21-8 → 26.9.24-393

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 (138) hide show
  1. package/cli/audit/ast-visitors.js +10 -17
  2. package/cli/audit/autofix.js +17 -2
  3. package/cli/audit/clone-detector.js +64 -0
  4. package/cli/audit/clone-detector.spec.js +46 -0
  5. package/cli/audit/history.js +22 -6
  6. package/cli/audit/hook-shape-validator.js +312 -0
  7. package/cli/audit/prompts.js +13 -10
  8. package/cli/audit/prompts.spec.js +52 -1
  9. package/cli/audit/reporter-clones.js +33 -0
  10. package/cli/audit/reporter-hotspot-graph.js +34 -0
  11. package/cli/audit/rules-registry.js +6 -1
  12. package/cli/audit/rules.js +10 -2
  13. package/cli/audit/rules.spec.js +129 -0
  14. package/cli/audit-stages.spec.js +30 -0
  15. package/cli/audit.js +12 -3
  16. package/cli/commands/cmd-audit.js +56 -12
  17. package/cli/commands/cmd-project.js +100 -0
  18. package/cli/commands/cmd-router.js +52 -3
  19. package/cli/commands-schema.js +28 -0
  20. package/cli/create.spec.js +2 -2
  21. package/cli/exploder.js +258 -0
  22. package/cli/exploder.spec.js +99 -0
  23. package/cli/generator-compact.spec.js +104 -0
  24. package/cli/generator-framework.spec.js +72 -0
  25. package/cli/generator-templates/archetypes/collection-archetypes.js +9 -9
  26. package/cli/generator-templates/archetypes/index.js +34 -4
  27. package/cli/generator-templates/archetypes/input-archetypes.js +2 -2
  28. package/cli/generator-templates/archetypes/layout-archetypes.js +2 -2
  29. package/cli/generator-templates/archetypes/minimal-archetypes.js +131 -0
  30. package/cli/generator-templates/archetypes/status-archetypes.js +4 -4
  31. package/cli/generator-templates/archetypes/vector-matcher.js +50 -0
  32. package/cli/generator-templates/archetypes/vector-matcher.spec.js +25 -0
  33. package/cli/generator-templates/compact.js +130 -0
  34. package/cli/generator-templates/controller-converters.js +93 -0
  35. package/cli/generator-templates/controller.js +2 -64
  36. package/cli/generator-templates/react.js +1 -1
  37. package/cli/generator-templates/svelte.js +1 -1
  38. package/cli/generator-templates/types.js +2 -2
  39. package/cli/generator-templates/vue.js +1 -1
  40. package/cli/generator-templates.js +1 -0
  41. package/cli/generator-templates.spec.js +6 -1
  42. package/cli/generator.js +94 -7
  43. package/cli/help.js +1 -1
  44. package/cli/index.js +23 -8
  45. package/cli/installer-templates.js +1 -1
  46. package/cli/mcp/antigravity.js +5 -2
  47. package/cli/mcp/installer.js +7 -7
  48. package/cli/mcp/installer.spec.js +3 -3
  49. package/cli/mcp/manifests.js +4 -2
  50. package/cli/mcp/prompts.js +1 -1
  51. package/cli/mcp/server.js +2 -0
  52. package/cli/mcp/tools-audit.js +97 -0
  53. package/cli/mcp/tools-generate.js +62 -0
  54. package/cli/mcp/tools-patch.js +72 -0
  55. package/cli/mcp/tools-patterns.js +71 -0
  56. package/cli/mcp/tools-project.js +75 -0
  57. package/cli/mcp/tools-project.spec.js +51 -0
  58. package/cli/mcp/tools-q-modes.js +69 -0
  59. package/cli/mcp/tools-q.js +58 -0
  60. package/cli/mcp/tools-read.js +69 -0
  61. package/cli/mcp/tools-search-util.js +35 -0
  62. package/cli/mcp/tools-search.js +15 -294
  63. package/cli/mcp/tools-team-feed.js +57 -0
  64. package/cli/mcp/tools-team-locks.js +63 -0
  65. package/cli/mcp/tools-team-mailbox.js +32 -0
  66. package/cli/mcp/tools-team-tasks.js +85 -0
  67. package/cli/mcp/tools-team.js +37 -183
  68. package/cli/mcp/tools-verify.js +47 -0
  69. package/cli/mcp/tools-write.js +33 -0
  70. package/cli/mcp/tools.js +79 -372
  71. package/cli/mutators.js +10 -23
  72. package/cli/pillars-schema.js +3 -3
  73. package/cli/project-detector.js +47 -0
  74. package/cli/reader-shorthand.spec.js +60 -0
  75. package/cli/reader.js +67 -24
  76. package/cli/search-bulletproof.spec.js +104 -0
  77. package/cli/search-db.js +33 -5
  78. package/cli/search-queries-hotspot-graph.js +65 -0
  79. package/cli/search-queries-hotspot-graph.spec.js +66 -0
  80. package/cli/search-queries-similar.js +84 -0
  81. package/cli/search-queries-similar.spec.js +43 -0
  82. package/cli/search-schema.js +79 -11
  83. package/cli/search.js +13 -2
  84. package/cli/team/index.js +9 -0
  85. package/cli/team/team-commands.js +106 -64
  86. package/cli/team/team-concurrency-stress.spec.js +95 -0
  87. package/cli/team/team-db-lock-promotion.js +15 -11
  88. package/cli/team/team-db-mailbox.js +86 -0
  89. package/cli/team/team-db-task-helpers.js +80 -28
  90. package/cli/team/team-db-tasks.js +13 -5
  91. package/cli/team/team-flags.js +72 -0
  92. package/cli/team/team-format.js +87 -46
  93. package/cli/team/team-mailbox-empirical.spec.js +191 -0
  94. package/cli/team/team-mailbox.spec.js +93 -0
  95. package/cli/team/team-memory.js +84 -0
  96. package/cli/team/team-memory.spec.js +74 -0
  97. package/cli/team/team-projects-coordinator.js +100 -0
  98. package/cli/team/team-projects-format.js +45 -0
  99. package/cli/team/team-projects-memory.js +70 -0
  100. package/cli/team/team-projects.js +94 -0
  101. package/cli/team/team-projects.spec.js +116 -0
  102. package/cli/team/team-schema-memory.js +25 -0
  103. package/cli/team/team-schema-projects.js +50 -0
  104. package/cli/team/team-schema.js +33 -14
  105. package/cli/team/team-tasks-hierarchy.spec.js +176 -0
  106. package/cli/team/team-tokens.js +95 -0
  107. package/cli/team/team-triage.js +274 -52
  108. package/cli/team/team-verification-gate.spec.js +253 -0
  109. package/cli/team/team.spec.js +1 -1
  110. package/cli/tesseract-hud.js +59 -0
  111. package/cli/tesseract-manifesto.js +61 -0
  112. package/cli/tesseract-state.js +58 -0
  113. package/cli/tesseract.js +81 -0
  114. package/cli/tesseract.spec.js +55 -0
  115. package/cli/trend.js +84 -0
  116. package/cli/trend.spec.js +51 -0
  117. package/cli/ui-actions-tasks.js +13 -5
  118. package/cli/ui-actions.js +10 -2
  119. package/cli/ui-client-api.js +2 -2
  120. package/cli/ui-client-script.js +12 -15
  121. package/cli/ui-e2e-verification.spec.js +13 -3
  122. package/cli/ui-filetree.spec.js +5 -5
  123. package/cli/ui-handlers.js +10 -2
  124. package/cli/ui-html.js +26 -34
  125. package/cli/ui-index.html +905 -0
  126. package/cli/ui-kanban.spec.js +12 -12
  127. package/cli/ui-server.js +19 -3
  128. package/cli/ui-sse.js +57 -0
  129. package/cli/ui-sse.spec.js +74 -0
  130. package/cli/ui-styles.js +195 -81
  131. package/cli/ui-template-kanban.js +15 -1
  132. package/cli/ui-vbulletin.spec.js +12 -13
  133. package/cli/ui.spec.js +12 -5
  134. package/cli/verify.js +19 -8
  135. package/package.json +3 -1
  136. package/scripts/check-framework-generation.mjs +195 -0
  137. package/scripts/pre-commit.sh +2 -7
  138. package/scripts/publish-both.mjs +4 -0
@@ -7,6 +7,10 @@ import { INPUT_ARCHETYPES } from './input-archetypes.js';
7
7
  import { LAYOUT_ARCHETYPES } from './layout-archetypes.js';
8
8
  import { DOMAIN_ARCHETYPES } from './domain-archetypes.js';
9
9
  import { STATUS_ARCHETYPES } from './status-archetypes.js';
10
+ import { MINIMAL_ARCHETYPES } from './minimal-archetypes.js';
11
+ import { matchArchetypeByVector } from './vector-matcher.js';
12
+
13
+ export { matchArchetypeByVector };
10
14
 
11
15
  export const ALL_ARCHETYPES = [
12
16
  ...COLLECTION_ARCHETYPES,
@@ -16,7 +20,24 @@ export const ALL_ARCHETYPES = [
16
20
  ...STATUS_ARCHETYPES
17
21
  ];
18
22
 
19
- export const resolveArchetype = (slug = '', description = '') => {
23
+ export const EXTENDED_ARCHETYPES = [
24
+ ...MINIMAL_ARCHETYPES,
25
+ ...ALL_ARCHETYPES
26
+ ];
27
+
28
+ const isPartialMatch = (token, kw) => {
29
+ const isMinLength = token.length >= 3;
30
+ const isAffixMatch = token.startsWith(kw) || kw.startsWith(token);
31
+ return isMinLength && isAffixMatch;
32
+ };
33
+
34
+ export const resolveArchetype = (slug = '', description = '', explicitTemplate = '') => {
35
+ if (explicitTemplate) {
36
+ const templateNorm = String(explicitTemplate).toLowerCase().trim();
37
+ const exactMatch = EXTENDED_ARCHETYPES.find((a) => a.id === templateNorm || a.keywords.includes(templateNorm));
38
+ if (exactMatch) return exactMatch;
39
+ }
40
+
20
41
  const normalized = String(slug).toLowerCase().replace(/^(m-|a-|o-|t-|v-|use-)/, '');
21
42
  const slugTokens = normalized.split(/[-_]/).filter(Boolean);
22
43
  const descTokens = String(description)
@@ -40,7 +61,7 @@ export const resolveArchetype = (slug = '', description = '') => {
40
61
  for (const kw of archetype.keywords) {
41
62
  if (token === kw) {
42
63
  score += 12;
43
- } else if (token.length >= 3 && (token.startsWith(kw) || kw.startsWith(token))) {
64
+ } else if (isPartialMatch(token, kw)) {
44
65
  score += 8;
45
66
  }
46
67
  }
@@ -53,7 +74,7 @@ export const resolveArchetype = (slug = '', description = '') => {
53
74
  for (const kw of archetype.keywords) {
54
75
  if (token === kw) {
55
76
  score += 7;
56
- } else if (token.length >= 3 && (token.startsWith(kw) || kw.startsWith(token))) {
77
+ } else if (isPartialMatch(token, kw)) {
57
78
  score += 4;
58
79
  }
59
80
  }
@@ -65,10 +86,19 @@ export const resolveArchetype = (slug = '', description = '') => {
65
86
  }
66
87
  }
67
88
 
68
- if (bestArchetype && highestScore > 0) {
89
+ const hasKeywordWinner = Boolean(bestArchetype && highestScore > 0);
90
+ if (hasKeywordWinner) {
69
91
  return bestArchetype;
70
92
  }
71
93
 
94
+ const hasDescription = Boolean(description && String(description).trim());
95
+ if (hasDescription) {
96
+ const vectorMatch = matchArchetypeByVector(description, 0.3);
97
+ if (vectorMatch) {
98
+ return vectorMatch.archetype;
99
+ }
100
+ }
101
+
72
102
  // Fallback to State Boundary (Archetype #22 in LAYOUT_ARCHETYPES)
73
103
  return LAYOUT_ARCHETYPES.find((a) => a.id === 'state-boundary') || ALL_ARCHETYPES[0];
74
104
  };
@@ -86,7 +86,7 @@ export interface ${pascal}State {
86
86
  `,
87
87
  buildController: (name, pascal) => `import { useState } from 'react';
88
88
 
89
- export const use${pascal}Controller = (options: { onSelect?: (id: string) => void } = {}) => {
89
+ export const use${pascal}Controller = (options: { options?: readonly { id: string; label: string }[]; onSelect?: (id: string) => void } = {}) => {
90
90
  const [isOpen, setIsOpen] = useState<boolean>(false);
91
91
  const [selectedId, setSelectedId] = useState<string | null>(null);
92
92
 
@@ -97,7 +97,7 @@ export const use${pascal}Controller = (options: { onSelect?: (id: string) => voi
97
97
  options.onSelect?.(id);
98
98
  };
99
99
 
100
- return { isOpen, selectedId, toggle, select };
100
+ return { options: options.options || [], isOpen, selectedId, toggle, select };
101
101
  };
102
102
  `,
103
103
  buildReactBody: (name, pascal) => ` <div className="${name}__select" onClick={toggle}>
@@ -81,7 +81,7 @@ export interface ${pascal}State {
81
81
  `,
82
82
  buildController: (name, pascal) => `import { useState } from 'react';
83
83
 
84
- export const use${pascal}Controller = (options: { defaultTabId?: string; onTabChange?: (id: string) => void } = {}) => {
84
+ export const use${pascal}Controller = (options: { tabs?: readonly { id: string; label: string }[]; defaultTabId?: string; onTabChange?: (id: string) => void } = {}) => {
85
85
  const [activeTabId, setActiveTabId] = useState<string>(options.defaultTabId || 'overview');
86
86
 
87
87
  const selectTab = (id: string) => {
@@ -89,7 +89,7 @@ export const use${pascal}Controller = (options: { defaultTabId?: string; onTabCh
89
89
  options.onTabChange?.(id);
90
90
  };
91
91
 
92
- return { activeTabId, selectTab };
92
+ return { tabs: options.tabs || [{ id: 'overview', label: 'Overview' }], activeTabId, selectTab };
93
93
  };
94
94
  `,
95
95
  buildReactBody: (name, pascal) => ` <div className="${name}__tabs" role="tablist">
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Minimal & Prototyping Archetypes: Minimal Blank Slate, Controls Dock, Canvas Context
3
+ */
4
+
5
+ export const MINIMAL_ARCHETYPE = {
6
+ id: 'minimal',
7
+ name: 'Minimal Blank Slate',
8
+ keywords: ['minimal', 'bare', 'blank', 'clean', 'simple', 'empty'],
9
+ destructure: 'isActive, toggleActive',
10
+ buildState: (name, pascal) => `export interface ${pascal}State {
11
+ readonly isActive: boolean;
12
+ }
13
+ `,
14
+ buildProps: (name, pascal) => `import type { ReactNode } from 'react';
15
+
16
+ export interface ${pascal}Props {
17
+ readonly className?: string;
18
+ readonly children?: ReactNode;
19
+ readonly defaultActive?: boolean;
20
+ }
21
+ `,
22
+ buildController: (name, pascal) => `import { useState } from 'react';
23
+
24
+ export const use${pascal}Controller = (options: { defaultActive?: boolean } = {}) => {
25
+ const [isActive, setIsActive] = useState<boolean>(Boolean(options.defaultActive));
26
+
27
+ const toggleActive = () => {
28
+ setIsActive((prev) => !prev);
29
+ };
30
+
31
+ return { isActive, toggleActive };
32
+ };
33
+ `,
34
+ buildReactBody: (name) => ` <div className="${name}__content">
35
+ {children}
36
+ </div>`
37
+ };
38
+
39
+ export const CONTROLS_ARCHETYPE = {
40
+ id: 'controls',
41
+ name: 'Interactive Controls Dock',
42
+ keywords: ['controls', 'dock', 'toolbar', 'actions', 'slider', 'toggle', 'buttons'],
43
+ destructure: 'isEnabled, value, setEnabled, setValue, reset',
44
+ buildState: (name, pascal) => `export interface ${pascal}State {
45
+ readonly isEnabled: boolean;
46
+ readonly value: number;
47
+ }
48
+ `,
49
+ buildProps: (name, pascal) => `export interface ${pascal}Props {
50
+ readonly initialValue?: number;
51
+ readonly defaultEnabled?: boolean;
52
+ readonly onValueChange?: (value: number) => void;
53
+ readonly className?: string;
54
+ }
55
+ `,
56
+ buildController: (name, pascal) => `import { useState } from 'react';
57
+
58
+ export const use${pascal}Controller = (options: { initialValue?: number; defaultEnabled?: boolean; onValueChange?: (value: number) => void } = {}) => {
59
+ const [isEnabled, setEnabled] = useState<boolean>(options.defaultEnabled ?? true);
60
+ const [value, setValueState] = useState<number>(options.initialValue ?? 0);
61
+
62
+ const setValue = (next: number) => {
63
+ setValueState(next);
64
+ options.onValueChange?.(next);
65
+ };
66
+
67
+ const reset = () => {
68
+ setValue(options.initialValue ?? 0);
69
+ setEnabled(options.defaultEnabled ?? true);
70
+ };
71
+
72
+ return { isEnabled, value, setEnabled, setValue, reset };
73
+ };
74
+ `,
75
+ buildReactBody: (name) => ` <div className="${name}__controls">
76
+ <button type="button" onClick={() => setEnabled(!isEnabled)}>
77
+ {isEnabled ? 'Enabled' : 'Disabled'}
78
+ </button>
79
+ <input
80
+ type="range"
81
+ min="0"
82
+ max="100"
83
+ value={value}
84
+ onChange={(e) => setValue(Number(e.target.value))}
85
+ />
86
+ <button type="button" onClick={reset}>Reset</button>
87
+ </div>`
88
+ };
89
+
90
+ export const CANVAS_ARCHETYPE = {
91
+ id: 'canvas',
92
+ name: 'Canvas & Viewport Context',
93
+ keywords: ['canvas', 'webgl', 'three', 'render', 'stage', 'viewport', 'scene', 'graphics'],
94
+ destructure: 'canvasRef, isReady',
95
+ buildState: (name, pascal) => `export interface ${pascal}State {
96
+ readonly isReady: boolean;
97
+ }
98
+ `,
99
+ buildProps: (name, pascal) => `export interface ${pascal}Props {
100
+ readonly width?: number;
101
+ readonly height?: number;
102
+ readonly className?: string;
103
+ }
104
+ `,
105
+ buildController: (name, pascal) => `import { useState, useRef, useEffect } from 'react';
106
+
107
+ export const use${pascal}Controller = () => {
108
+ const canvasRef = useRef<HTMLCanvasElement | null>(null);
109
+ const [isReady, setIsReady] = useState<boolean>(false);
110
+
111
+ useEffect(() => {
112
+ const canvas = canvasRef.current;
113
+ if (!canvas) return;
114
+ const ctx = canvas.getContext('2d');
115
+ if (ctx) setIsReady(true);
116
+ }, []);
117
+
118
+ return { canvasRef, isReady };
119
+ };
120
+ `,
121
+ buildReactBody: (name) => ` <canvas
122
+ ref={canvasRef}
123
+ className="${name}__canvas"
124
+ />`
125
+ };
126
+
127
+ export const MINIMAL_ARCHETYPES = [
128
+ MINIMAL_ARCHETYPE,
129
+ CONTROLS_ARCHETYPE,
130
+ CANVAS_ARCHETYPE
131
+ ];
@@ -22,7 +22,7 @@ export const STATUS_ARCHETYPES = [
22
22
  `,
23
23
  buildController: (name, pascal) => `import { useState } from 'react';
24
24
 
25
- export const use${pascal}Controller = (options: { onSearch?: (q: string) => void; onFilterChange?: (f: readonly string[]) => void } = {}) => {
25
+ export const use${pascal}Controller = (options: { placeholder?: string; onSearch?: (q: string) => void; onFilterChange?: (f: readonly string[]) => void } = {}) => {
26
26
  const [query, setQuery] = useState<string>('');
27
27
  const [isSearching, setIsSearching] = useState<boolean>(false);
28
28
  const [activeFilters, setActiveFilters] = useState<readonly string[]>([]);
@@ -46,7 +46,7 @@ export const use${pascal}Controller = (options: { onSearch?: (q: string) => void
46
46
  options.onSearch?.('');
47
47
  };
48
48
 
49
- return { query, isSearching, activeFilters, handleQueryChange, toggleFilter, clear, setIsSearching };
49
+ return { query, isSearching, activeFilters, handleQueryChange, toggleFilter, clear, setIsSearching, placeholder: options.placeholder };
50
50
  };
51
51
  `,
52
52
  buildReactBody: (name, pascal) => ` <div className="${name}__search-bar">
@@ -74,7 +74,7 @@ export const use${pascal}Controller = (options: { onSearch?: (q: string) => void
74
74
  `,
75
75
  buildController: (name, pascal) => `import { useState } from 'react';
76
76
 
77
- export const use${pascal}Controller = (options: { value?: number | string; delta?: number; onRefresh?: () => void } = {}) => {
77
+ export const use${pascal}Controller = (options: { label?: string; value?: number | string; delta?: number; onRefresh?: () => void } = {}) => {
78
78
  const [value, setValue] = useState<number | string>(options.value ?? 1284);
79
79
  const [delta, setDelta] = useState<number>(options.delta ?? 12.5);
80
80
 
@@ -84,7 +84,7 @@ export const use${pascal}Controller = (options: { value?: number | string; delta
84
84
  options.onRefresh?.();
85
85
  };
86
86
 
87
- return { value, delta, trend, setValue, setDelta, refresh };
87
+ return { label: options.label ?? 'Total', value, delta, trend, setValue, setDelta, refresh };
88
88
  };
89
89
  `,
90
90
  buildReactBody: (name, pascal) => ` <div className="${name}__card">
@@ -0,0 +1,50 @@
1
+ import { generateEmbedding, cosineSimilarity } from '../../embeddings/vectorizer.js';
2
+ import { ALL_ARCHETYPES } from './index.js';
3
+
4
+ let cachedVectors = null;
5
+
6
+ const buildArchetypeText = (a) => {
7
+ const idWords = a.id.replace(/[-_]/g, ' ');
8
+ const kwWords = a.keywords.map((k) => k.replace(/[-_]/g, ' ')).join(' ');
9
+ const nameWords = (a.name || '').toLowerCase();
10
+ return `${a.id} ${idWords} ${nameWords} ${kwWords}`;
11
+ };
12
+
13
+ const getArchetypeVectors = () => {
14
+ if (cachedVectors) return cachedVectors;
15
+ cachedVectors = ALL_ARCHETYPES.map((a) => ({
16
+ archetype: a,
17
+ vector: generateEmbedding(buildArchetypeText(a))
18
+ }));
19
+ return cachedVectors;
20
+ };
21
+
22
+ export const matchArchetypeByVector = (descriptionText, minSimilarity = 0.25) => {
23
+ if (!descriptionText || typeof descriptionText !== 'string') return null;
24
+ const cleanDesc = descriptionText.trim();
25
+ if (cleanDesc.length === 0) return null;
26
+
27
+ const descVector = generateEmbedding(cleanDesc);
28
+ const archetypeVectors = getArchetypeVectors();
29
+
30
+ let bestMatch = null;
31
+ let highestSim = 0;
32
+
33
+ for (const item of archetypeVectors) {
34
+ const sim = cosineSimilarity(descVector, item.vector);
35
+ if (sim > highestSim) {
36
+ highestSim = sim;
37
+ bestMatch = item.archetype;
38
+ }
39
+ }
40
+
41
+ const isSimilaritySufficient = highestSim >= minSimilarity;
42
+ if (isSimilaritySufficient && bestMatch) {
43
+ return {
44
+ archetype: bestMatch,
45
+ similarity: Number(highestSim.toFixed(4))
46
+ };
47
+ }
48
+
49
+ return null;
50
+ };
@@ -0,0 +1,25 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { matchArchetypeByVector } from './vector-matcher.js';
4
+
5
+ test('vector-matcher: matches semantic descriptions to correct archetype', () => {
6
+ const chatMatch = matchArchetypeByVector('chat messages inbox thread conversation');
7
+ assert.ok(chatMatch);
8
+ assert.equal(chatMatch.archetype.id, 'chat-messaging');
9
+ assert.ok(chatMatch.similarity > 0.4);
10
+
11
+ const cartMatch = matchArchetypeByVector('shopping cart checkout billing payment invoice items');
12
+ assert.ok(cartMatch);
13
+ assert.equal(cartMatch.archetype.id, 'cart-billing');
14
+
15
+ const filterMatch = matchArchetypeByVector('search filter lookup query find records');
16
+ assert.ok(filterMatch);
17
+ assert.equal(filterMatch.archetype.id, 'search-filter');
18
+ });
19
+
20
+ test('vector-matcher: returns null for empty or irrelevant strings', () => {
21
+ assert.equal(matchArchetypeByVector(''), null);
22
+ assert.equal(matchArchetypeByVector(null), null);
23
+ const lowMatch = matchArchetypeByVector('qwerty asdfgh zxcvbn', 0.8);
24
+ assert.equal(lowMatch, null);
25
+ });
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Compact Single-File Capsule Builder ("Liquid Mode")
3
+ * Bundles types, controller hook, and component view in a single file (< 80 LOC).
4
+ */
5
+
6
+ import { toPascalCase } from './naming.js';
7
+
8
+ export const buildCompactReact = (name, pascalName, archetype, options = {}) => {
9
+ const atomsPackage = options.atomsPackage || null;
10
+ const atomImport = atomsPackage ? `import { AtomButton } from '${atomsPackage}';\n` : '';
11
+
12
+ const rawProps = archetype && typeof archetype.buildProps === 'function'
13
+ ? archetype.buildProps(name, pascalName)
14
+ : `export interface ${pascalName}Props {\n readonly className?: string;\n readonly children?: ReactNode;\n}\n`;
15
+
16
+ const rawState = archetype && typeof archetype.buildState === 'function'
17
+ ? archetype.buildState(name, pascalName)
18
+ : `export interface ${pascalName}State {\n readonly isActive: boolean;\n}\n`;
19
+
20
+ const rawController = archetype && typeof archetype.buildController === 'function'
21
+ ? archetype.buildController(name, pascalName)
22
+ : `export const use${pascalName}Controller = () => {\n const [isActive, setIsActive] = useState<boolean>(false);\n return { isActive };\n};\n`;
23
+
24
+ const destructure = archetype?.destructure || 'isActive';
25
+ let body = archetype && typeof archetype.buildReactBody === 'function'
26
+ ? archetype.buildReactBody(name, pascalName)
27
+ : ` <div className="${name}__content">\n {children}\n </div>`;
28
+
29
+ if (atomsPackage) {
30
+ body = body.replace(/<button\b/g, '<AtomButton').replace(/<\/button>/g, '</AtomButton>');
31
+ }
32
+
33
+ // Clean redundant imports from inlined controller, props, and state
34
+ const cleanedProps = rawProps
35
+ .replace(/^import\s+.*?;?\n/gm, '')
36
+ .trim();
37
+
38
+ const cleanedState = rawState
39
+ .replace(/^import\s+.*?;?\n/gm, '')
40
+ .trim();
41
+
42
+ const cleanedController = rawController
43
+ .replace(/^import\s+.*?;?\n/gm, '')
44
+ .trim();
45
+
46
+ return `import type { ReactNode } from 'react';
47
+ import { useState, useMemo } from 'react';
48
+ ${atomImport}
49
+ // --- Domain Types ---
50
+ ${cleanedProps}
51
+
52
+ ${cleanedState}
53
+
54
+ // --- Co-located Controller Hook ---
55
+ ${cleanedController}
56
+
57
+ // --- Component View ---
58
+ export const ${pascalName} = ({ className = '', children, ...props }: ${pascalName}Props) => {
59
+ const { ${destructure} } = use${pascalName}Controller(props as any);
60
+
61
+ return (
62
+ <div className={\`${name} \${className}\`.trim()}>
63
+ ${body}
64
+ </div>
65
+ );
66
+ };
67
+
68
+ export default ${pascalName};
69
+ `;
70
+ };
71
+
72
+ export const buildCompactVue = (name, pascalName, archetype) => {
73
+ const rawProps = archetype && typeof archetype.buildProps === 'function'
74
+ ? archetype.buildProps(name, pascalName)
75
+ : `export interface ${pascalName}Props {\n readonly className?: string;\n}\n`;
76
+
77
+ return `<script setup lang="ts">
78
+ import { ref } from 'vue';
79
+
80
+ ${rawProps.trim()}
81
+
82
+ const props = withDefaults(defineProps<${pascalName}Props>(), {
83
+ className: ''
84
+ });
85
+
86
+ const isActive = ref(false);
87
+ const toggle = () => {
88
+ isActive.value = !isActive.value;
89
+ };
90
+ </script>
91
+
92
+ <template>
93
+ <div :class="['${name}', props.className]">
94
+ <div class="${name}__content">
95
+ <slot />
96
+ </div>
97
+ </div>
98
+ </template>
99
+ `;
100
+ };
101
+
102
+ export const buildCompactSvelte = (name, pascalName, archetype) => {
103
+ return `<script lang="ts">
104
+ let { className = '', children } = $props<{ className?: string; children?: any }>();
105
+ let isActive = $state(false);
106
+ const toggle = () => { isActive = !isActive; };
107
+ </script>
108
+
109
+ <div class="${name} {className}">
110
+ {@render children?.()}
111
+ </div>
112
+ `;
113
+ };
114
+
115
+ export const buildCompactCapsule = ({
116
+ capsuleName,
117
+ pascalName,
118
+ framework = 'react',
119
+ archetype = null,
120
+ atomsPackage = null
121
+ }) => {
122
+ const fw = (framework || 'react').toLowerCase();
123
+ if (fw === 'vue') {
124
+ return buildCompactVue(capsuleName, pascalName, archetype);
125
+ }
126
+ if (fw === 'svelte') {
127
+ return buildCompactSvelte(capsuleName, pascalName, archetype);
128
+ }
129
+ return buildCompactReact(capsuleName, pascalName, archetype, { atomsPackage });
130
+ };
@@ -0,0 +1,93 @@
1
+ import { parse } from '@babel/parser';
2
+ import traverseModule from '@babel/traverse';
3
+
4
+ const traverse = traverseModule.default || traverseModule;
5
+
6
+ export const convertReactControllerToVue = (code) => {
7
+ let out = code.replace(/import\s*\{[^}]*\}\s*from\s*['"]react['"];?\n?/, "import { ref, computed } from 'vue';\n");
8
+
9
+ const stateVars = new Set();
10
+
11
+ out = out.replace(
12
+ /const\s*\[([a-zA-Z0-9_]+),\s*([a-zA-Z0-9_]+)\]\s*=\s*useState(?:<([^>]+)>)?\(([\s\S]*?)\);/g,
13
+ (match, varName, setterName, typeAnnotation, initVal) => {
14
+ stateVars.add(varName);
15
+ const typeStr = typeAnnotation ? `<${typeAnnotation}>` : '';
16
+ return `const ${varName} = ref${typeStr}(${initVal});\n const ${setterName} = (val: any) => { ${varName}.value = typeof val === 'function' ? val(${varName}.value) : val; };`;
17
+ }
18
+ );
19
+
20
+ out = out.replace(/useMemo\(\s*\(\)\s*=>\s*([\s\S]*?),\s*\[[^\]]*\]\s*\)/g, 'computed(() => $1)');
21
+
22
+ try {
23
+ const ast = parse(out, { sourceType: 'module', plugins: ['typescript'] });
24
+ const replaceOffsets = [];
25
+
26
+ traverse(ast, {
27
+ Identifier(path) {
28
+ if (!stateVars.has(path.node.name)) return;
29
+ if (!path.isReferencedIdentifier()) return;
30
+ if (path.findParent((p) => p.isTSType() || p.isTypeAnnotation())) return;
31
+ if (
32
+ path.parent.type === 'MemberExpression' &&
33
+ path.parent.object === path.node &&
34
+ path.parent.property.name === 'value'
35
+ ) {
36
+ return;
37
+ }
38
+ const fnParent = path.getFunctionParent();
39
+ const retParent = path.findParent((p) => p.isReturnStatement());
40
+ if (retParent && retParent.getFunctionParent() === fnParent && fnParent?.parent?.type === 'VariableDeclarator') {
41
+ const declaratorId = fnParent.parent.id;
42
+ if (declaratorId?.name?.startsWith('use') || declaratorId?.name?.startsWith('create')) {
43
+ return;
44
+ }
45
+ }
46
+
47
+ replaceOffsets.push({
48
+ name: path.node.name,
49
+ start: path.node.start,
50
+ end: path.node.end
51
+ });
52
+ }
53
+ });
54
+
55
+ replaceOffsets.sort((a, b) => b.start - a.start);
56
+ for (const o of replaceOffsets) {
57
+ out = out.slice(0, o.start) + `${o.name}.value` + out.slice(o.end);
58
+ }
59
+ } catch {
60
+ for (const v of stateVars) {
61
+ const varRegex = new RegExp(`(?<![.\\w$])\\b${v}\\b(?!\\.value)(?!:)`, 'g');
62
+ out = out.replace(varRegex, `${v}.value`);
63
+ }
64
+ }
65
+
66
+ return out;
67
+ };
68
+
69
+ export const convertReactControllerToSvelte = (code, name, pascalName) => {
70
+ let out = code.replace(/import\s*\{[^}]*\}\s*from\s*['"]react['"];?\n?/, '');
71
+
72
+ out = out.replace(
73
+ /const\s*\[([a-zA-Z0-9_]+),\s*([a-zA-Z0-9_]+)\]\s*=\s*useState(?:<([^>]+)>)?\(([\s\S]*?)\);/g,
74
+ (match, varName, setterName, typeAnnotation, initVal) => {
75
+ const typeStr = typeAnnotation ? `<${typeAnnotation}>` : '';
76
+ return `let ${varName} = $state${typeStr}(${initVal});\n const ${setterName} = (val: any) => { ${varName} = typeof val === 'function' ? val(${varName}) : val; };`;
77
+ }
78
+ );
79
+
80
+ out = out.replace(/useMemo\(\s*\(\)\s*=>\s*([\s\S]*?),\s*\[[^\]]*\]\s*\)/g, (match, body) => {
81
+ const trimmed = body.trim();
82
+ if (trimmed.startsWith('{')) {
83
+ return `$derived.by(() => ${trimmed})`;
84
+ }
85
+ return `$derived(${trimmed})`;
86
+ });
87
+
88
+ if (!out.includes(`create${pascalName}Controller`)) {
89
+ out += `\nexport const create${pascalName}Controller = use${pascalName}Controller;\n`;
90
+ }
91
+
92
+ return out;
93
+ };
@@ -1,71 +1,9 @@
1
1
  import { resolveArchetype } from './archetypes/index.js';
2
-
3
- const convertReactControllerToVue = (code) => {
4
- let out = code.replace(/import\s*\{[^}]*\}\s*from\s*['"]react['"];?\n?/, "import { ref, computed } from 'vue';\n");
5
-
6
- const stateVars = [];
7
- const declMap = new Map();
8
-
9
- out = out.replace(
10
- /const\s*\[([a-zA-Z0-9_]+),\s*([a-zA-Z0-9_]+)\]\s*=\s*useState(?:<([^>]+)>)?\(([\s\S]*?)\);/g,
11
- (match, varName, setterName, typeAnnotation, initVal) => {
12
- stateVars.push(varName);
13
- const placeholder = `__DECL_STATE_${varName}__`;
14
- const typeStr = typeAnnotation ? `<${typeAnnotation}>` : '';
15
- declMap.set(placeholder, `const ${varName} = ref${typeStr}(${initVal});\n const ${setterName} = (val: any) => { ${varName}.value = val; };`);
16
- return placeholder;
17
- }
18
- );
19
-
20
- out = out.replace(/useMemo\(\s*\(\)\s*=>\s*([\s\S]*?),\s*\[[^\]]*\]\s*\)/g, 'computed(() => $1)');
21
-
22
- const returnIdx = out.lastIndexOf('return {');
23
- if (returnIdx !== -1) {
24
- let beforeReturn = out.slice(0, returnIdx);
25
- const returnPart = out.slice(returnIdx);
26
-
27
- for (const v of stateVars) {
28
- const varRegex = new RegExp(`\\b${v}\\b(?!\\.value)(?!:)`, 'g');
29
- beforeReturn = beforeReturn.replace(varRegex, (m, offset, str) => {
30
- const prefix = str.slice(Math.max(0, offset - 13), offset);
31
- if (prefix.includes('__DECL_STATE_')) return m;
32
- return `${v}.value`;
33
- });
34
- }
35
-
36
- out = beforeReturn + returnPart;
37
- }
38
-
39
- for (const [placeholder, decl] of declMap.entries()) {
40
- out = out.replace(placeholder, decl);
41
- }
42
-
43
- return out;
44
- };
45
-
46
- const convertReactControllerToSvelte = (code, name, pascalName) => {
47
- let out = code.replace(/import\s*\{[^}]*\}\s*from\s*['"]react['"];?\n?/, '');
48
-
49
- out = out.replace(
50
- /const\s*\[([a-zA-Z0-9_]+),\s*([a-zA-Z0-9_]+)\]\s*=\s*useState(?:<([^>]+)>)?\(([\s\S]*?)\);/g,
51
- (match, varName, setterName, typeAnnotation, initVal) => {
52
- const typeStr = typeAnnotation ? `<${typeAnnotation}>` : '';
53
- return `let ${varName} = $state${typeStr}(${initVal});\n const ${setterName} = (val: any) => { ${varName} = val; };`;
54
- }
55
- );
56
-
57
- out = out.replace(/useMemo\(\s*\(\)\s*=>\s*([\s\S]*?),\s*\[[^\]]*\]\s*\)/g, '$derived($1)');
58
-
59
- if (!out.includes(`create${pascalName}Controller`)) {
60
- out += `\nexport const create${pascalName}Controller = use${pascalName}Controller;\n`;
61
- }
62
-
63
- return out;
64
- };
2
+ import { convertReactControllerToVue, convertReactControllerToSvelte } from './controller-converters.js';
65
3
 
66
4
  export const buildController = (name, pascalName, options = {}) => {
67
5
  const fw = options.framework ? options.framework.toLowerCase() : 'react';
68
- const archetype = resolveArchetype(name, options.description || options.desc);
6
+ const archetype = resolveArchetype(name, options.description || options.desc, options.template || options.archetype);
69
7
 
70
8
  if (archetype && typeof archetype.buildController === 'function') {
71
9
  const rawReactController = archetype.buildController(name, pascalName);
@@ -3,7 +3,7 @@ import { resolveArchetype } from './archetypes/index.js';
3
3
  export const buildReactComponent = (name, pascalName, options = {}) => {
4
4
  const atomsPackage = options.atomsPackage || null;
5
5
  const hasController = options.hasController !== false;
6
- const archetype = resolveArchetype(name, options.description || options.desc);
6
+ const archetype = resolveArchetype(name, options.description || options.desc, options.template || options.archetype);
7
7
 
8
8
  const atomImport = atomsPackage ? `import { AtomButton } from '${atomsPackage}';\n` : '';
9
9
  const controllerImport = hasController
@@ -3,7 +3,7 @@ import { resolveArchetype } from './archetypes/index.js';
3
3
  export const buildSvelteComponent = (name, pascalName, options = {}) => {
4
4
  const atomsPackage = options.atomsPackage || null;
5
5
  const hasController = options.hasController !== false;
6
- const archetype = resolveArchetype(name, options.description || options.desc);
6
+ const archetype = resolveArchetype(name, options.description || options.desc, options.template || options.archetype);
7
7
  const atomImport = atomsPackage ? `import { AtomButton } from '${atomsPackage}';\n` : '';
8
8
 
9
9
  const controllerImport = hasController