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.
- package/README.md +18 -13
- package/bin/chati.js +28 -4
- package/framework/agents/build/dev.md +5 -1
- package/framework/agents/discover/greenfield-wu.md +16 -1
- package/framework/agents/plan/tasks.md +31 -0
- package/framework/agents/plan/ux-brand-architect.md +21 -2
- package/framework/agents/plan/ux-component-engineer.md +10 -0
- package/framework/agents/quality/qa-implementation.md +1 -1
- package/framework/agents/quality/qa-planning.md +1 -1
- package/framework/agents/quality/qa-visual.md +24 -21
- package/framework/config.yaml +5 -4
- package/framework/constitution.md +60 -11
- package/framework/context/governance.md +12 -10
- package/framework/context/root.md +2 -2
- package/framework/data/entity-registry.yaml +12 -4
- package/framework/data/qa-rubrics.yaml +335 -0
- package/framework/domains/constitution.yaml +3 -3
- package/framework/executors/json-validate.js +80 -0
- package/framework/executors/npm-script.js +72 -0
- package/framework/executors/path-check.js +67 -0
- package/framework/executors/registry.yaml +36 -0
- package/framework/hooks/advance-trigger.js +47 -3
- package/framework/hooks/constitution-guard.js +9 -4
- package/framework/hooks/git-push-authority.js +113 -0
- package/framework/hooks/license-guard.js +59 -9
- package/framework/hooks/model-governance.js +40 -17
- package/framework/hooks/prism-engine.js +78 -11
- package/framework/hooks/session-digest.js +12 -3
- package/framework/hooks/settings.json +7 -3
- package/framework/hooks/style-guard.js +52 -3
- package/framework/hooks/team-quality-gate.js +43 -5
- package/framework/i18n/en.yaml +3 -3
- package/framework/i18n/es.yaml +3 -3
- package/framework/i18n/fr.yaml +3 -3
- package/framework/i18n/pt.yaml +3 -3
- package/framework/intelligence/confidence.yaml +85 -6
- package/framework/intelligence/context-engine.md +9 -5
- package/framework/intelligence/decision-engine.md +1 -1
- package/framework/manifest.json +1351 -0
- package/framework/manifest.sig +1 -0
- package/framework/orchestrator/chati-router.js +1 -1
- package/framework/orchestrator/chati-update.md +19 -3
- package/framework/orchestrator/chati.md +24 -3
- package/framework/quality-gates/planning-gate.md +2 -0
- package/framework/scaffold/motion-premium/scaffold.yaml +2 -1
- package/framework/scaffold/motion-premium-3d/components/webgl/WebGLContext.tsx.template +1 -1
- package/framework/scaffold/saas-dashboard/README.md +58 -0
- package/framework/scaffold/saas-dashboard/app/dashboard/page.tsx.template +546 -0
- package/framework/scaffold/saas-dashboard/app/globals.css.template +746 -0
- package/framework/scaffold/saas-dashboard/app/layout.tsx.template +192 -0
- package/framework/scaffold/saas-dashboard/app/shell.css.template +1070 -0
- package/framework/scaffold/saas-dashboard/components/data/Chart.tsx.template +725 -0
- package/framework/scaffold/saas-dashboard/components/data/DataTable.tsx.template +623 -0
- package/framework/scaffold/saas-dashboard/components/data/DataTable.types.ts.template +104 -0
- package/framework/scaffold/saas-dashboard/components/data/DataTablePagination.tsx.template +234 -0
- package/framework/scaffold/saas-dashboard/components/data/SkeletonBlock.tsx.template +111 -0
- package/framework/scaffold/saas-dashboard/components/data/StatCard.tsx.template +393 -0
- package/framework/scaffold/saas-dashboard/components/data/StatCardGrid.tsx.template +121 -0
- package/framework/scaffold/saas-dashboard/components/feedback/EmptyState.tsx.template +281 -0
- package/framework/scaffold/saas-dashboard/components/interaction/CommandPalette.tsx.template +767 -0
- package/framework/scaffold/saas-dashboard/components/interaction/Field.tsx.template +351 -0
- package/framework/scaffold/saas-dashboard/components/interaction/Form.tsx.template +424 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Badge.tsx.template +193 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Button.tsx.template +261 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Card.tsx.template +287 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Input.tsx.template +310 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Spinner.tsx.template +135 -0
- package/framework/scaffold/saas-dashboard/components/primitives/index.ts.template +60 -0
- package/framework/scaffold/saas-dashboard/components/shell/AppShell.tsx.template +174 -0
- package/framework/scaffold/saas-dashboard/components/shell/Breadcrumb.tsx.template +186 -0
- package/framework/scaffold/saas-dashboard/components/shell/Container.tsx.template +86 -0
- package/framework/scaffold/saas-dashboard/components/shell/NavItem.tsx.template +147 -0
- package/framework/scaffold/saas-dashboard/components/shell/Sidebar.tsx.template +378 -0
- package/framework/scaffold/saas-dashboard/components/shell/SidebarContext.tsx.template +169 -0
- package/framework/scaffold/saas-dashboard/components/shell/ThemeToggle.tsx.template +128 -0
- package/framework/scaffold/saas-dashboard/components/shell/Topbar.tsx.template +301 -0
- package/framework/scaffold/saas-dashboard/lib/brand.ts.template +324 -0
- package/framework/scaffold/saas-dashboard/scaffold.yaml +225 -0
- package/framework/schemas/session.schema.json +5 -0
- package/framework/schemas/task.schema.json +9 -0
- package/framework/scripts/visual-qa.js +87 -1
- package/framework/tasks/orchestrator-health.md +4 -4
- package/framework/tasks/qa-impl-verdict.md +11 -5
- package/framework/templates/qa-gate-tmpl.yaml +24 -11
- package/package.json +4 -4
- package/src/config/claude-settings-generator.js +9 -6
- package/src/config/context-file-generator.js +0 -6
- package/src/dashboard/renderer.js +0 -36
- package/src/executors/runner.js +204 -0
- package/src/installer/core.js +35 -4
- package/src/installer/manifest.js +30 -4
- package/src/installer/signing-public-key.pem +1 -1
- package/src/installer/templates.js +5 -6
- package/src/installer/validator.js +2 -1
- package/src/intelligence/registry-manager.js +3 -2
- package/src/license/client.js +9 -3
- package/src/license/commands.js +12 -2
- package/src/license/machine-id.js +42 -1
- package/src/memory/gotchas.js +58 -40
- package/src/memory/magic-docs.js +7 -2
- package/src/memory/session-digest.js +9 -4
- package/src/orchestrator/cli.js +195 -23
- package/src/orchestrator/deviation-handler.js +5 -3
- package/src/orchestrator/doctor.js +98 -16
- package/src/orchestrator/pipeline-manager.js +44 -22
- package/src/orchestrator/session-manager.js +56 -8
- package/src/telemetry/sender.js +7 -19
- package/src/terminal/run-parallel.js +14 -14
- package/src/terminal/run-team.js +3 -3
- package/src/upgrade/backup.js +22 -44
- package/src/upgrade/migrator.js +51 -12
- package/src/upgrade/tracked-files-detector.js +34 -16
- package/src/utils/constitution-meta.js +12 -0
- package/src/utils/feature-flags.js +26 -2
- package/src/utils/flatten-entities.js +4 -40
- package/src/utils/schema-validator.js +0 -14
- package/src/wizard/i18n.js +3 -3
- package/src/api/index.js +0 -120
- package/src/autonomy/autonomous-gate.js +0 -294
- package/src/autonomy/build-loop.js +0 -281
- package/src/autonomy/build-state.js +0 -286
- package/src/autonomy/cause-analyzer.js +0 -177
- package/src/autonomy/escalation.js +0 -214
- package/src/autonomy/index.js +0 -51
- package/src/autonomy/mode-manager.js +0 -225
- package/src/autonomy/mode-suggester.js +0 -283
- package/src/autonomy/progress-reporter.js +0 -275
- package/src/autonomy/safety-net.js +0 -370
- package/src/config/agent-customizer.js +0 -231
- package/src/decision/analyzer.js +0 -291
- package/src/decision/engine.js +0 -250
- package/src/decision/index.js +0 -38
- package/src/decision/registry-healer.js +0 -468
- package/src/decision/registry-updater.js +0 -339
- package/src/extensions/loader.js +0 -145
- package/src/extensions/registry.js +0 -134
- package/src/gates/circuit-breaker.js +0 -151
- package/src/gates/g1-planning-complete.js +0 -154
- package/src/gates/g2-qa-planning.js +0 -156
- package/src/gates/g3-implementation.js +0 -215
- package/src/gates/g4-qa-implementation.js +0 -240
- package/src/gates/g5-deploy-ready.js +0 -181
- package/src/gates/gate-base.js +0 -185
- package/src/gates/index.js +0 -46
- package/src/health/auto-fix.js +0 -216
- package/src/health/engine.js +0 -246
- package/src/merger/semantic-merger.js +0 -292
- package/src/preview/detector.js +0 -238
- package/src/preview/index.js +0 -20
- package/src/preview/launcher.js +0 -235
- package/src/preview/log-buffer.js +0 -103
- package/src/quality/metrics-collector.js +0 -281
- package/src/quality/test-runner.js +0 -366
- package/src/tasks/executor.js +0 -195
- package/src/tasks/index.js +0 -4
- package/src/tasks/loader.js +0 -210
- package/src/tasks/router.js +0 -182
- package/src/utils/event-bus.js +0 -126
package/src/decision/analyzer.js
DELETED
|
@@ -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
|
-
|
package/src/decision/engine.js
DELETED
|
@@ -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
|
-
|
package/src/decision/index.js
DELETED
|
@@ -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';
|