chati-dev 4.3.1 → 4.4.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 (158) hide show
  1. package/README.md +18 -13
  2. package/bin/chati.js +28 -4
  3. package/framework/agents/build/dev.md +5 -1
  4. package/framework/agents/discover/greenfield-wu.md +16 -1
  5. package/framework/agents/plan/tasks.md +31 -0
  6. package/framework/agents/plan/ux-brand-architect.md +21 -2
  7. package/framework/agents/plan/ux-component-engineer.md +10 -0
  8. package/framework/agents/quality/qa-implementation.md +1 -1
  9. package/framework/agents/quality/qa-planning.md +1 -1
  10. package/framework/agents/quality/qa-visual.md +24 -21
  11. package/framework/config.yaml +5 -4
  12. package/framework/constitution.md +60 -11
  13. package/framework/context/governance.md +12 -10
  14. package/framework/context/root.md +2 -2
  15. package/framework/data/entity-registry.yaml +12 -4
  16. package/framework/data/qa-rubrics.yaml +335 -0
  17. package/framework/domains/constitution.yaml +3 -3
  18. package/framework/executors/json-validate.js +80 -0
  19. package/framework/executors/npm-script.js +72 -0
  20. package/framework/executors/path-check.js +67 -0
  21. package/framework/executors/registry.yaml +36 -0
  22. package/framework/hooks/advance-trigger.js +47 -3
  23. package/framework/hooks/constitution-guard.js +9 -4
  24. package/framework/hooks/git-push-authority.js +113 -0
  25. package/framework/hooks/license-guard.js +59 -9
  26. package/framework/hooks/model-governance.js +40 -17
  27. package/framework/hooks/prism-engine.js +78 -11
  28. package/framework/hooks/session-digest.js +12 -3
  29. package/framework/hooks/settings.json +7 -3
  30. package/framework/hooks/style-guard.js +52 -3
  31. package/framework/hooks/team-quality-gate.js +43 -5
  32. package/framework/i18n/en.yaml +3 -3
  33. package/framework/i18n/es.yaml +3 -3
  34. package/framework/i18n/fr.yaml +3 -3
  35. package/framework/i18n/pt.yaml +3 -3
  36. package/framework/intelligence/confidence.yaml +85 -6
  37. package/framework/intelligence/context-engine.md +9 -5
  38. package/framework/intelligence/decision-engine.md +1 -1
  39. package/framework/manifest.json +1351 -0
  40. package/framework/manifest.sig +1 -0
  41. package/framework/orchestrator/chati-router.js +1 -1
  42. package/framework/orchestrator/chati-update.md +19 -3
  43. package/framework/orchestrator/chati.md +24 -3
  44. package/framework/quality-gates/planning-gate.md +2 -0
  45. package/framework/scaffold/motion-premium/scaffold.yaml +2 -1
  46. package/framework/scaffold/motion-premium-3d/components/webgl/WebGLContext.tsx.template +1 -1
  47. package/framework/scaffold/saas-dashboard/README.md +58 -0
  48. package/framework/scaffold/saas-dashboard/app/dashboard/page.tsx.template +546 -0
  49. package/framework/scaffold/saas-dashboard/app/globals.css.template +746 -0
  50. package/framework/scaffold/saas-dashboard/app/layout.tsx.template +192 -0
  51. package/framework/scaffold/saas-dashboard/app/shell.css.template +1070 -0
  52. package/framework/scaffold/saas-dashboard/components/data/Chart.tsx.template +725 -0
  53. package/framework/scaffold/saas-dashboard/components/data/DataTable.tsx.template +623 -0
  54. package/framework/scaffold/saas-dashboard/components/data/DataTable.types.ts.template +104 -0
  55. package/framework/scaffold/saas-dashboard/components/data/DataTablePagination.tsx.template +234 -0
  56. package/framework/scaffold/saas-dashboard/components/data/SkeletonBlock.tsx.template +111 -0
  57. package/framework/scaffold/saas-dashboard/components/data/StatCard.tsx.template +393 -0
  58. package/framework/scaffold/saas-dashboard/components/data/StatCardGrid.tsx.template +121 -0
  59. package/framework/scaffold/saas-dashboard/components/feedback/EmptyState.tsx.template +281 -0
  60. package/framework/scaffold/saas-dashboard/components/interaction/CommandPalette.tsx.template +767 -0
  61. package/framework/scaffold/saas-dashboard/components/interaction/Field.tsx.template +351 -0
  62. package/framework/scaffold/saas-dashboard/components/interaction/Form.tsx.template +424 -0
  63. package/framework/scaffold/saas-dashboard/components/primitives/Badge.tsx.template +193 -0
  64. package/framework/scaffold/saas-dashboard/components/primitives/Button.tsx.template +261 -0
  65. package/framework/scaffold/saas-dashboard/components/primitives/Card.tsx.template +287 -0
  66. package/framework/scaffold/saas-dashboard/components/primitives/Input.tsx.template +310 -0
  67. package/framework/scaffold/saas-dashboard/components/primitives/Spinner.tsx.template +135 -0
  68. package/framework/scaffold/saas-dashboard/components/primitives/index.ts.template +60 -0
  69. package/framework/scaffold/saas-dashboard/components/shell/AppShell.tsx.template +174 -0
  70. package/framework/scaffold/saas-dashboard/components/shell/Breadcrumb.tsx.template +186 -0
  71. package/framework/scaffold/saas-dashboard/components/shell/Container.tsx.template +86 -0
  72. package/framework/scaffold/saas-dashboard/components/shell/NavItem.tsx.template +147 -0
  73. package/framework/scaffold/saas-dashboard/components/shell/Sidebar.tsx.template +378 -0
  74. package/framework/scaffold/saas-dashboard/components/shell/SidebarContext.tsx.template +169 -0
  75. package/framework/scaffold/saas-dashboard/components/shell/ThemeToggle.tsx.template +128 -0
  76. package/framework/scaffold/saas-dashboard/components/shell/Topbar.tsx.template +301 -0
  77. package/framework/scaffold/saas-dashboard/lib/brand.ts.template +324 -0
  78. package/framework/scaffold/saas-dashboard/scaffold.yaml +225 -0
  79. package/framework/schemas/session.schema.json +5 -0
  80. package/framework/schemas/task.schema.json +9 -0
  81. package/framework/scripts/visual-qa.js +87 -1
  82. package/framework/tasks/orchestrator-health.md +4 -4
  83. package/framework/tasks/qa-impl-verdict.md +11 -5
  84. package/framework/templates/qa-gate-tmpl.yaml +24 -11
  85. package/package.json +4 -4
  86. package/src/config/claude-settings-generator.js +9 -6
  87. package/src/config/context-file-generator.js +0 -6
  88. package/src/dashboard/renderer.js +0 -36
  89. package/src/executors/runner.js +204 -0
  90. package/src/installer/core.js +35 -4
  91. package/src/installer/manifest.js +30 -4
  92. package/src/installer/signing-public-key.pem +1 -1
  93. package/src/installer/templates.js +5 -6
  94. package/src/installer/validator.js +2 -1
  95. package/src/intelligence/registry-manager.js +3 -2
  96. package/src/license/client.js +9 -3
  97. package/src/license/commands.js +12 -2
  98. package/src/license/machine-id.js +42 -1
  99. package/src/memory/gotchas.js +58 -40
  100. package/src/memory/magic-docs.js +7 -2
  101. package/src/memory/session-digest.js +9 -4
  102. package/src/orchestrator/cli.js +195 -23
  103. package/src/orchestrator/deviation-handler.js +5 -3
  104. package/src/orchestrator/doctor.js +98 -16
  105. package/src/orchestrator/pipeline-manager.js +44 -22
  106. package/src/orchestrator/session-manager.js +56 -8
  107. package/src/telemetry/sender.js +7 -19
  108. package/src/terminal/run-parallel.js +14 -14
  109. package/src/terminal/run-team.js +3 -3
  110. package/src/upgrade/backup.js +22 -44
  111. package/src/upgrade/migrator.js +51 -12
  112. package/src/upgrade/tracked-files-detector.js +34 -16
  113. package/src/utils/constitution-meta.js +12 -0
  114. package/src/utils/feature-flags.js +26 -2
  115. package/src/utils/flatten-entities.js +4 -40
  116. package/src/utils/schema-validator.js +0 -14
  117. package/src/wizard/i18n.js +3 -3
  118. package/src/api/index.js +0 -120
  119. package/src/autonomy/autonomous-gate.js +0 -294
  120. package/src/autonomy/build-loop.js +0 -281
  121. package/src/autonomy/build-state.js +0 -286
  122. package/src/autonomy/cause-analyzer.js +0 -177
  123. package/src/autonomy/escalation.js +0 -214
  124. package/src/autonomy/index.js +0 -51
  125. package/src/autonomy/mode-manager.js +0 -225
  126. package/src/autonomy/mode-suggester.js +0 -283
  127. package/src/autonomy/progress-reporter.js +0 -275
  128. package/src/autonomy/safety-net.js +0 -370
  129. package/src/config/agent-customizer.js +0 -231
  130. package/src/decision/analyzer.js +0 -291
  131. package/src/decision/engine.js +0 -250
  132. package/src/decision/index.js +0 -38
  133. package/src/decision/registry-healer.js +0 -468
  134. package/src/decision/registry-updater.js +0 -339
  135. package/src/extensions/loader.js +0 -145
  136. package/src/extensions/registry.js +0 -134
  137. package/src/gates/circuit-breaker.js +0 -151
  138. package/src/gates/g1-planning-complete.js +0 -154
  139. package/src/gates/g2-qa-planning.js +0 -156
  140. package/src/gates/g3-implementation.js +0 -215
  141. package/src/gates/g4-qa-implementation.js +0 -240
  142. package/src/gates/g5-deploy-ready.js +0 -181
  143. package/src/gates/gate-base.js +0 -185
  144. package/src/gates/index.js +0 -46
  145. package/src/health/auto-fix.js +0 -216
  146. package/src/health/engine.js +0 -246
  147. package/src/merger/semantic-merger.js +0 -292
  148. package/src/preview/detector.js +0 -238
  149. package/src/preview/index.js +0 -20
  150. package/src/preview/launcher.js +0 -235
  151. package/src/preview/log-buffer.js +0 -103
  152. package/src/quality/metrics-collector.js +0 -281
  153. package/src/quality/test-runner.js +0 -366
  154. package/src/tasks/executor.js +0 -195
  155. package/src/tasks/index.js +0 -4
  156. package/src/tasks/loader.js +0 -210
  157. package/src/tasks/router.js +0 -182
  158. package/src/utils/event-bus.js +0 -126
@@ -1,291 +0,0 @@
1
- /**
2
- * Impact Analyzer - Dependency analysis for chati.dev
3
- * Analyzes impact of changes via dependency graph traversal
4
- *
5
- * @module decision/analyzer
6
- */
7
-
8
- import { readFileSync, existsSync } from 'fs';
9
- import { join } from 'path';
10
- import yaml from 'js-yaml';
11
- import { resolveFrameworkDir } from '../utils/framework-dir.js';
12
- import { flattenEntitiesFromRegistry } from '../utils/flatten-entities.js';
13
-
14
- /**
15
- * Analyze impact of changing an entity.
16
- * Builds dependency graph from entity-registry and calculates affected entities.
17
- *
18
- * @param {string} projectDir
19
- * @param {string} entityPath - Path of entity being changed
20
- * @returns {{ impactLevel: 'low'|'medium'|'high'|'critical', affectedEntities: object[], dependencyChain: string[] }}
21
- */
22
- export function analyzeImpact(projectDir, entityPath) {
23
- const graph = buildDependencyGraph(projectDir);
24
- const affectedPaths = getTransitiveDependents(graph, entityPath);
25
-
26
- // Determine impact level based on affected entity count
27
- let impactLevel;
28
- const affectedCount = affectedPaths.length;
29
-
30
- if (affectedCount === 0) {
31
- impactLevel = 'low';
32
- } else if (affectedCount <= 2) {
33
- impactLevel = 'low';
34
- } else if (affectedCount <= 5) {
35
- impactLevel = 'medium';
36
- } else if (affectedCount <= 10) {
37
- impactLevel = 'high';
38
- } else {
39
- impactLevel = 'critical';
40
- }
41
-
42
- // Load entity details
43
- const registryPath = join(projectDir, resolveFrameworkDir(projectDir), 'entity-registry.yaml');
44
- const registry = existsSync(registryPath)
45
- ? yaml.load(readFileSync(registryPath, 'utf8'))
46
- : { entities: {} };
47
-
48
- const allEntities = flattenEntitiesFromRegistry(registry);
49
- const entityMap = new Map(allEntities.map(e => [e.path, e]));
50
-
51
- const affectedEntities = affectedPaths.map(path => {
52
- const entity = entityMap.get(path);
53
- return {
54
- path,
55
- type: entity?.type || 'unknown',
56
- name: entity?.name || path.split('/').pop()
57
- };
58
- });
59
-
60
- return {
61
- impactLevel,
62
- affectedEntities,
63
- dependencyChain: affectedPaths
64
- };
65
- }
66
-
67
- /**
68
- * Build dependency graph from entity registry.
69
- * @param {string} projectDir
70
- * @returns {Map<string, string[]>} Map of entityPath -> [dependentPaths]
71
- */
72
- export function buildDependencyGraph(projectDir) {
73
- const registryPath = join(projectDir, resolveFrameworkDir(projectDir), 'entity-registry.yaml');
74
- const graph = new Map();
75
-
76
- if (!existsSync(registryPath)) {
77
- return graph;
78
- }
79
-
80
- const registry = yaml.load(readFileSync(registryPath, 'utf8'));
81
- const entities = flattenEntitiesFromRegistry(registry);
82
-
83
- // Initialize graph with all entities
84
- entities.forEach(entity => {
85
- if (entity.path) {
86
- graph.set(entity.path, []);
87
- }
88
- });
89
-
90
- // Build reverse dependency map (who depends on whom)
91
- entities.forEach(entity => {
92
- if (entity.dependencies && Array.isArray(entity.dependencies)) {
93
- entity.dependencies.forEach(depPath => {
94
- if (!graph.has(depPath)) {
95
- graph.set(depPath, []);
96
- }
97
- // Add entity.path as dependent of depPath
98
- const dependents = graph.get(depPath);
99
- if (!dependents.includes(entity.path)) {
100
- dependents.push(entity.path);
101
- }
102
- });
103
- }
104
- });
105
-
106
- return graph;
107
- }
108
-
109
- /**
110
- * Get all entities that depend on a given entity (transitive).
111
- * Uses BFS traversal.
112
- * @param {Map<string, string[]>} graph
113
- * @param {string} entityPath
114
- * @returns {string[]} All affected paths
115
- */
116
- export function getTransitiveDependents(graph, entityPath) {
117
- const visited = new Set();
118
- const queue = [entityPath];
119
- const result = [];
120
-
121
- while (queue.length > 0) {
122
- const current = queue.shift();
123
-
124
- if (visited.has(current)) {
125
- continue;
126
- }
127
-
128
- visited.add(current);
129
-
130
- // Get direct dependents
131
- const dependents = graph.get(current) || [];
132
-
133
- dependents.forEach(dependent => {
134
- if (!visited.has(dependent)) {
135
- queue.push(dependent);
136
- result.push(dependent);
137
- }
138
- });
139
- }
140
-
141
- return result;
142
- }
143
-
144
- /**
145
- * Score a decision option based on multiple criteria using TF-IDF-inspired
146
- * keyword relevance scoring.
147
- *
148
- * @param {{ name: string, description?: string, keywords?: string[] }} option
149
- * @param {{ requirements?: string[], constraints?: string[], existingEntities?: string[] }} context
150
- * @returns {{ score: number, alignment: number, complexity: number, reuse: number, breakdown: object }}
151
- */
152
- export function scoreDecision(option, context = {}) {
153
- if (!option || !option.name) {
154
- return { score: 0, alignment: 0, complexity: 0, reuse: 0, breakdown: {} };
155
- }
156
-
157
- const requirements = context.requirements || [];
158
- const constraints = context.constraints || [];
159
- const existingEntities = context.existingEntities || [];
160
-
161
- // 1. Alignment score — how well does option match requirements?
162
- const optionText = `${option.name} ${option.description || ''} ${(option.keywords || []).join(' ')}`.toLowerCase();
163
- const alignment = computeRelevance(optionText, requirements);
164
-
165
- // 2. Complexity score (inverse — lower complexity = higher score)
166
- const complexitySignals = ['microservice', 'distributed', 'multi-tenant', 'real-time', 'concurrent', 'migration'];
167
- let complexityCount = 0;
168
- for (const signal of complexitySignals) {
169
- if (optionText.includes(signal)) {
170
- complexityCount++;
171
- }
172
- }
173
- const complexity = Math.max(0, 1 - complexityCount * 0.15);
174
-
175
- // 3. Reuse score — overlap with existing entities (decision priority: REUSE > ADAPT > CREATE)
176
- let reuse = 0;
177
- if (existingEntities.length > 0) {
178
- const optionWords = new Set(optionText.split(/\s+/));
179
- let matchCount = 0;
180
- for (const entity of existingEntities) {
181
- const entityWords = entity.toLowerCase().split(/[\s/.-]+/);
182
- for (const word of entityWords) {
183
- if (word.length > 3 && optionWords.has(word)) {
184
- matchCount++;
185
- break;
186
- }
187
- }
188
- }
189
- reuse = existingEntities.length > 0 ? matchCount / existingEntities.length : 0;
190
- }
191
-
192
- // 4. Constraint satisfaction
193
- let constraintScore = 1;
194
- if (constraints.length > 0) {
195
- let satisfied = 0;
196
- for (const constraint of constraints) {
197
- const constraintWords = constraint.toLowerCase().split(/\s+/);
198
- const hasMatch = constraintWords.some(w => w.length > 3 && optionText.includes(w));
199
- if (hasMatch) satisfied++;
200
- }
201
- constraintScore = constraints.length > 0 ? satisfied / constraints.length : 1;
202
- }
203
-
204
- // Weighted final score
205
- const score = Math.round(
206
- (alignment * 0.4 + complexity * 0.2 + reuse * 0.25 + constraintScore * 0.15) * 100
207
- ) / 100;
208
-
209
- return {
210
- score,
211
- alignment: Math.round(alignment * 100) / 100,
212
- complexity: Math.round(complexity * 100) / 100,
213
- reuse: Math.round(reuse * 100) / 100,
214
- breakdown: {
215
- alignment: { weight: 0.4, raw: alignment },
216
- complexity: { weight: 0.2, raw: complexity },
217
- reuse: { weight: 0.25, raw: reuse },
218
- constraints: { weight: 0.15, raw: constraintScore },
219
- },
220
- };
221
- }
222
-
223
- // ---------------------------------------------------------------------------
224
- // TF-IDF Helpers
225
- // ---------------------------------------------------------------------------
226
-
227
- /**
228
- * Compute TF-IDF-inspired relevance between text and a set of documents.
229
- *
230
- * @param {string} text - Source text
231
- * @param {string[]} documents - Reference documents (requirements, etc.)
232
- * @returns {number} Relevance score (0-1)
233
- */
234
- function computeRelevance(text, documents) {
235
- if (!text || documents.length === 0) return 0;
236
-
237
- const textWords = tokenize(text);
238
- if (textWords.length === 0) return 0;
239
-
240
- // Build document frequency map
241
- const docCount = documents.length;
242
- const df = new Map();
243
-
244
- for (const doc of documents) {
245
- const words = new Set(tokenize(doc.toLowerCase()));
246
- for (const word of words) {
247
- df.set(word, (df.get(word) || 0) + 1);
248
- }
249
- }
250
-
251
- // Calculate TF-IDF score for each text word
252
- const textWordSet = new Set(textWords);
253
- let totalScore = 0;
254
-
255
- for (const word of textWordSet) {
256
- const termFreq = textWords.filter(w => w === word).length / textWords.length;
257
- const docFreq = df.get(word) || 0;
258
-
259
- if (docFreq > 0) {
260
- // IDF = log(N / df) — boost rare terms
261
- const idf = Math.log((docCount + 1) / (docFreq + 1)) + 1;
262
- totalScore += termFreq * idf;
263
- }
264
- }
265
-
266
- // Normalize to 0-1 range
267
- const maxPossible = Math.log(docCount + 1) + 1;
268
- return maxPossible > 0 ? Math.min(totalScore / maxPossible, 1) : 0;
269
- }
270
-
271
- /**
272
- * Tokenize text into meaningful words (skip stopwords and short tokens).
273
- *
274
- * @param {string} text
275
- * @returns {string[]}
276
- */
277
- function tokenize(text) {
278
- const stopwords = new Set([
279
- 'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for',
280
- 'of', 'with', 'by', 'is', 'are', 'was', 'were', 'be', 'been', 'being',
281
- 'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'could',
282
- 'should', 'may', 'might', 'shall', 'can', 'this', 'that', 'these',
283
- 'those', 'it', 'its', 'not', 'no', 'from', 'as', 'into', 'all',
284
- ]);
285
-
286
- return text
287
- .toLowerCase()
288
- .split(/[\s,.;:!?()[\]{}"'`/\\-]+/)
289
- .filter(w => w.length > 2 && !stopwords.has(w));
290
- }
291
-
@@ -1,250 +0,0 @@
1
- /**
2
- * Decision Engine - Core COMPASS module for chati.dev
3
- * Analyzes intent and recommends: REUSE (>=90%), ADAPT (60-89%), CREATE (<60%)
4
- *
5
- * NOTE: This module handles entity-level routing (registry lookup).
6
- * For intent-level similarity scoring (weighted keyword + purpose),
7
- * see `intelligence/decision-engine.js` (calculateIntentSimilarity).
8
- *
9
- * @module decision/engine
10
- */
11
-
12
- import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
13
- import { join } from 'path';
14
- import yaml from 'js-yaml';
15
- import { resolveFrameworkDir } from '../utils/framework-dir.js';
16
- import { flattenEntitiesFromRegistry } from '../utils/flatten-entities.js';
17
-
18
- /**
19
- * Analyze a request against existing entities and recommend action.
20
- * Uses keyword matching to find similar existing artifacts.
21
- *
22
- * @param {string} projectDir
23
- * @param {object} request - { description, type, keywords, agent }
24
- * @returns {{ recommendation: 'REUSE'|'ADAPT'|'CREATE', score: number, matches: object[], reasoning: string }}
25
- */
26
- export function analyzeRequest(projectDir, request) {
27
- const registryPath = join(projectDir, resolveFrameworkDir(projectDir), 'entity-registry.yaml');
28
-
29
- if (!existsSync(registryPath)) {
30
- return {
31
- recommendation: 'CREATE',
32
- score: 0,
33
- matches: [],
34
- reasoning: 'No entity registry found - first artifact in project'
35
- };
36
- }
37
-
38
- const registry = yaml.load(readFileSync(registryPath, 'utf8'));
39
- const entities = flattenEntitiesFromRegistry(registry);
40
-
41
- // Calculate similarity scores for all entities
42
- const scoredMatches = entities
43
- .map(entity => ({
44
- entity,
45
- score: calculateSimilarity(request, entity)
46
- }))
47
- .filter(match => match.score > 0)
48
- .sort((a, b) => b.score - a.score);
49
-
50
- const topScore = scoredMatches.length > 0 ? scoredMatches[0].score : 0;
51
- const topMatches = scoredMatches.slice(0, 5).map(m => ({
52
- path: m.entity.path,
53
- type: m.entity.type,
54
- score: m.score,
55
- purpose: m.entity.purpose || 'No purpose defined'
56
- }));
57
-
58
- let recommendation;
59
- let reasoning;
60
-
61
- if (topScore >= 90) {
62
- recommendation = 'REUSE';
63
- reasoning = `High similarity (${topScore}%) - existing artifact meets requirements`;
64
- } else if (topScore >= 60) {
65
- recommendation = 'ADAPT';
66
- reasoning = `Moderate similarity (${topScore}%) - adapt existing artifact`;
67
- } else {
68
- recommendation = 'CREATE';
69
- reasoning = topScore > 0
70
- ? `Low similarity (${topScore}%) - create new artifact`
71
- : 'No similar artifacts found - create new';
72
- }
73
-
74
- const decision = {
75
- timestamp: new Date().toISOString(),
76
- request: {
77
- description: request.description,
78
- type: request.type,
79
- keywords: request.keywords,
80
- agent: request.agent
81
- },
82
- recommendation,
83
- score: topScore,
84
- reasoning,
85
- topMatches: topMatches.slice(0, 3)
86
- };
87
-
88
- recordDecision(projectDir, decision);
89
-
90
- return {
91
- recommendation,
92
- score: topScore,
93
- matches: topMatches,
94
- reasoning
95
- };
96
- }
97
-
98
- /**
99
- * Calculate similarity between a request and an entity.
100
- * Uses keyword overlap (Jaccard similarity) + type matching.
101
- *
102
- * @param {object} request - { keywords, type }
103
- * @param {object} entity - { keywords, type, purpose }
104
- * @returns {number} Score 0-100
105
- */
106
- export function calculateSimilarity(request, entity) {
107
- // Normalize and collect keywords
108
- const requestKeywords = new Set(
109
- (request.keywords || []).map(k => k.toLowerCase())
110
- );
111
-
112
- const entityKeywords = new Set();
113
-
114
- // Extract from entity keywords (primary source)
115
- if (entity.keywords) {
116
- entity.keywords.forEach(k => entityKeywords.add(k.toLowerCase()));
117
- }
118
-
119
- // Extract from entity name (secondary source - only key terms)
120
- if (entity.name) {
121
- const nameWords = entity.name
122
- .toLowerCase()
123
- .replace(/[._-]/g, ' ')
124
- .split(/\W+/)
125
- .filter(w => w.length > 3);
126
- nameWords.forEach(w => entityKeywords.add(w));
127
- }
128
-
129
- if (requestKeywords.size === 0 || entityKeywords.size === 0) {
130
- return 0;
131
- }
132
-
133
- // Calculate Jaccard similarity
134
- const intersection = new Set(
135
- [...requestKeywords].filter(k => entityKeywords.has(k))
136
- );
137
- const union = new Set([...requestKeywords, ...entityKeywords]);
138
-
139
- let score = (intersection.size / union.size) * 100;
140
-
141
- // Add bonus points for type match (additive, not multiplicative)
142
- if (request.type && entity.type && request.type === entity.type) {
143
- score = Math.min(100, score + 25);
144
- }
145
-
146
- // Add bonus for high intersection ratio (more matches = better)
147
- const intersectionRatio = intersection.size / requestKeywords.size;
148
- if (intersectionRatio >= 0.8) {
149
- score = Math.min(100, score + 15);
150
- } else if (intersectionRatio >= 0.5) {
151
- score = Math.min(100, score + 10);
152
- }
153
-
154
- return Math.round(score);
155
- }
156
-
157
- /**
158
- * Get decision history for auditing.
159
- * @param {string} projectDir
160
- * @returns {object[]} Recent decisions
161
- */
162
- export function getDecisionHistory(projectDir) {
163
- const historyPath = join(projectDir, '.chati', 'decisions', 'history.json');
164
-
165
- if (!existsSync(historyPath)) {
166
- return [];
167
- }
168
-
169
- try {
170
- const content = readFileSync(historyPath, 'utf8');
171
- const data = JSON.parse(content);
172
- return data.decisions || [];
173
- } catch { /* expected: file may not exist */
174
- return [];
175
- }
176
- }
177
-
178
- /**
179
- * Record a decision for audit trail.
180
- * @param {string} projectDir
181
- * @param {object} decision
182
- */
183
- export function recordDecision(projectDir, decision) {
184
- const decisionsDir = join(projectDir, '.chati', 'decisions');
185
- const historyPath = join(decisionsDir, 'history.json');
186
-
187
- // Ensure directory exists
188
- if (!existsSync(decisionsDir)) {
189
- mkdirSync(decisionsDir, { recursive: true });
190
- }
191
-
192
- let history = { decisions: [] };
193
-
194
- if (existsSync(historyPath)) {
195
- try {
196
- const content = readFileSync(historyPath, 'utf8');
197
- history = JSON.parse(content);
198
- } catch { /* expected: file may not exist */
199
- // Keep empty history if parse fails
200
- }
201
- }
202
-
203
- history.decisions = history.decisions || [];
204
- history.decisions.push(decision);
205
-
206
- // Keep last 100 decisions
207
- if (history.decisions.length > 100) {
208
- history.decisions = history.decisions.slice(-100);
209
- }
210
-
211
- writeFileSync(historyPath, JSON.stringify(history, null, 2), 'utf8');
212
- }
213
-
214
- /**
215
- * Get engine statistics.
216
- * @param {string} projectDir
217
- * @returns {object} { totalDecisions, byRecommendation: { REUSE, ADAPT, CREATE }, avgScore }
218
- */
219
- export function getEngineStats(projectDir) {
220
- const history = getDecisionHistory(projectDir);
221
-
222
- const stats = {
223
- totalDecisions: history.length,
224
- byRecommendation: {
225
- REUSE: 0,
226
- ADAPT: 0,
227
- CREATE: 0
228
- },
229
- avgScore: 0
230
- };
231
-
232
- if (history.length === 0) {
233
- return stats;
234
- }
235
-
236
- let totalScore = 0;
237
-
238
- history.forEach(decision => {
239
- if (decision.recommendation) {
240
- stats.byRecommendation[decision.recommendation] =
241
- (stats.byRecommendation[decision.recommendation] || 0) + 1;
242
- }
243
- totalScore += decision.score || 0;
244
- });
245
-
246
- stats.avgScore = Math.round(totalScore / history.length);
247
-
248
- return stats;
249
- }
250
-
@@ -1,38 +0,0 @@
1
- /**
2
- * Decision Engine - COMPASS for chati.dev
3
- * Barrel exports for decision engine modules
4
- *
5
- * @module decision
6
- */
7
-
8
- export {
9
- analyzeRequest,
10
- calculateSimilarity,
11
- getDecisionHistory,
12
- recordDecision,
13
- getEngineStats
14
- } from './engine.js';
15
-
16
- export {
17
- analyzeImpact,
18
- buildDependencyGraph,
19
- getTransitiveDependents
20
- } from './analyzer.js';
21
-
22
- export {
23
- updateRegistry,
24
- detectNewEntities,
25
- detectRemovedEntities,
26
- generateEntityMeta
27
- } from './registry-updater.js';
28
-
29
- export {
30
- healRegistry,
31
- detectMissingEntities,
32
- detectOrphanedEntries,
33
- detectStaleMetadata,
34
- detectDuplicates,
35
- detectInvalidPaths,
36
- detectCountMismatch,
37
- applyFixes
38
- } from './registry-healer.js';