chati-dev 4.3.0 → 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -13
- package/bin/chati.js +32 -4
- package/framework/agents/build/dev.md +9 -5
- package/framework/agents/discover/brief.md +2 -0
- package/framework/agents/discover/brownfield-wu.md +2 -0
- package/framework/agents/discover/greenfield-wu.md +18 -1
- package/framework/agents/plan/detail.md +2 -0
- 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 +33 -21
- package/framework/config.yaml +5 -4
- package/framework/constitution.md +58 -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/orchestrator/chati-router.js +59 -5
- package/framework/orchestrator/chati-update.md +19 -3
- package/framework/orchestrator/chati.md +20 -1
- package/framework/quality-gates/planning-gate.md +2 -0
- package/framework/rules/human-writing-style.md +47 -0
- package/framework/scaffold/motion-premium/README.md +12 -2
- package/framework/scaffold/motion-premium/lib/animations/tokens.ts.template +1 -1
- package/framework/scaffold/motion-premium/scaffold.yaml +6 -1
- package/framework/scaffold/motion-premium-3d/README.md +10 -0
- package/framework/scaffold/motion-premium-3d/components/webgl/WebGLContext.tsx.template +1 -1
- package/framework/scaffold/motion-premium-3d/scaffold.yaml +3 -0
- 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 +101 -3
- package/framework/tasks/orchestrator-health.md +4 -4
- package/framework/tasks/qa-impl-verdict.md +11 -5
- package/framework/templates/brandbook-html-tmpl.md +1 -1
- package/framework/templates/qa-gate-tmpl.yaml +24 -11
- package/package.json +4 -3
- 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 +13 -3
- package/src/installer/templates.js +5 -6
- package/src/intelligence/registry-manager.js +1 -1
- 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 +1 -1
- package/src/memory/session-digest.js +9 -4
- package/src/orchestrator/cli.js +186 -15
- package/src/orchestrator/doctor.js +98 -16
- package/src/orchestrator/pipeline-manager.js +44 -22
- package/src/orchestrator/session-manager.js +64 -8
- package/src/telemetry/sender.js +7 -19
- package/src/terminal/run-team.js +3 -3
- package/src/upgrade/tracked-files-detector.js +34 -16
- package/src/utils/feature-flags.js +1 -1
- 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
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Agent customization via per-project override files.
|
|
3
|
-
*
|
|
4
|
-
* Loads `.customize.yaml` files from `chati.dev/customize/<agent>.yaml`
|
|
5
|
-
* and applies overrides to agent prompts.
|
|
6
|
-
*
|
|
7
|
-
* Constitution Article XVI — Model Governance.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { existsSync, readFileSync } from 'fs';
|
|
11
|
-
import { join } from 'path';
|
|
12
|
-
import yaml from 'js-yaml';
|
|
13
|
-
import { resolveFrameworkDir } from '../utils/framework-dir.js';
|
|
14
|
-
|
|
15
|
-
// ---------------------------------------------------------------------------
|
|
16
|
-
// Constants
|
|
17
|
-
// ---------------------------------------------------------------------------
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Get the customize directory for a given project.
|
|
21
|
-
* Supports both .chati.dev/ and chati.dev/ structures.
|
|
22
|
-
* @param {string} projectDir
|
|
23
|
-
* @returns {string} Relative path from project root
|
|
24
|
-
*/
|
|
25
|
-
function getCustomizeDir(projectDir) {
|
|
26
|
-
return `${resolveFrameworkDir(projectDir)}/customize`;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** Valid override fields that customizations can set. */
|
|
30
|
-
export const VALID_OVERRIDE_FIELDS = [
|
|
31
|
-
'model',
|
|
32
|
-
'extra_context',
|
|
33
|
-
'skip_sections',
|
|
34
|
-
'add_sections',
|
|
35
|
-
'provider',
|
|
36
|
-
'timeout',
|
|
37
|
-
'max_iterations',
|
|
38
|
-
];
|
|
39
|
-
|
|
40
|
-
// ---------------------------------------------------------------------------
|
|
41
|
-
// Public API
|
|
42
|
-
// ---------------------------------------------------------------------------
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* @typedef {object} Customization
|
|
46
|
-
* @property {boolean} found - Whether a customization file was found
|
|
47
|
-
* @property {string} [model] - Override model name
|
|
48
|
-
* @property {string} [extra_context] - Additional context to inject
|
|
49
|
-
* @property {string[]} [skip_sections] - Sections to omit from prompt
|
|
50
|
-
* @property {{ name: string, content: string }[]} [add_sections] - Sections to append
|
|
51
|
-
* @property {string} [provider] - Override provider name
|
|
52
|
-
* @property {number} [timeout] - Override timeout in ms
|
|
53
|
-
* @property {number} [max_iterations] - Override max iterations
|
|
54
|
-
*/
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Load customization for a specific agent.
|
|
58
|
-
*
|
|
59
|
-
* @param {string} projectDir - Project root directory
|
|
60
|
-
* @param {string} agentName - Agent name (e.g., 'brief', 'dev')
|
|
61
|
-
* @returns {{ found: boolean, overrides: Customization }}
|
|
62
|
-
*/
|
|
63
|
-
export function loadCustomization(projectDir, agentName) {
|
|
64
|
-
if (!projectDir || !agentName) {
|
|
65
|
-
return { found: false, overrides: {} };
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const customizePath = join(projectDir, getCustomizeDir(projectDir), `${agentName}.yaml`);
|
|
69
|
-
|
|
70
|
-
if (!existsSync(customizePath)) {
|
|
71
|
-
return { found: false, overrides: {} };
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
const raw = readFileSync(customizePath, 'utf-8');
|
|
76
|
-
const parsed = yaml.load(raw);
|
|
77
|
-
|
|
78
|
-
if (!parsed || typeof parsed !== 'object') {
|
|
79
|
-
return { found: true, overrides: {} };
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Filter to valid override fields only
|
|
83
|
-
const overrides = {};
|
|
84
|
-
for (const field of VALID_OVERRIDE_FIELDS) {
|
|
85
|
-
if (parsed[field] !== undefined) {
|
|
86
|
-
overrides[field] = parsed[field];
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return { found: true, overrides };
|
|
91
|
-
} catch { /* expected: content may be malformed */
|
|
92
|
-
return { found: false, overrides: {} };
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Apply customization overrides to an agent prompt.
|
|
98
|
-
*
|
|
99
|
-
* @param {string} agentPrompt - Original agent prompt
|
|
100
|
-
* @param {Customization} customization - Customization overrides
|
|
101
|
-
* @returns {string} Modified prompt
|
|
102
|
-
*/
|
|
103
|
-
export function applyCustomization(agentPrompt, customization) {
|
|
104
|
-
if (!agentPrompt || !customization) {
|
|
105
|
-
return agentPrompt || '';
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
let prompt = agentPrompt;
|
|
109
|
-
|
|
110
|
-
// Skip sections
|
|
111
|
-
if (customization.skip_sections && Array.isArray(customization.skip_sections)) {
|
|
112
|
-
for (const sectionName of customization.skip_sections) {
|
|
113
|
-
// Match section heading + all content until the next heading of same or higher level, or end of text
|
|
114
|
-
// Two-pass approach: try lookahead for next heading, then fallback to end
|
|
115
|
-
const headingPattern = new RegExp(
|
|
116
|
-
`^(#{1,3})\\s+${escapeRegex(sectionName)}[^\\n]*\\n([\\s\\S]*?)(?=^#{1,3}\\s)`,
|
|
117
|
-
'gm'
|
|
118
|
-
);
|
|
119
|
-
let replaced = prompt.replace(headingPattern, '');
|
|
120
|
-
if (replaced === prompt) {
|
|
121
|
-
// Section is at the end — no next heading follows
|
|
122
|
-
const endPattern = new RegExp(
|
|
123
|
-
`^(#{1,3})\\s+${escapeRegex(sectionName)}[^\\n]*\\n[\\s\\S]*$`,
|
|
124
|
-
'gm'
|
|
125
|
-
);
|
|
126
|
-
replaced = prompt.replace(endPattern, '');
|
|
127
|
-
}
|
|
128
|
-
prompt = replaced;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Add extra context at the end
|
|
133
|
-
if (customization.extra_context && typeof customization.extra_context === 'string') {
|
|
134
|
-
prompt += `\n\n---\n\n## Additional Context\n\n${customization.extra_context}\n`;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Add new sections
|
|
138
|
-
if (customization.add_sections && Array.isArray(customization.add_sections)) {
|
|
139
|
-
for (const section of customization.add_sections) {
|
|
140
|
-
if (section.name && section.content) {
|
|
141
|
-
prompt += `\n\n## ${section.name}\n\n${section.content}\n`;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return prompt;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Validate a customization object.
|
|
151
|
-
*
|
|
152
|
-
* @param {object} customization
|
|
153
|
-
* @returns {{ valid: boolean, errors: string[] }}
|
|
154
|
-
*/
|
|
155
|
-
export function validateCustomization(customization) {
|
|
156
|
-
const errors = [];
|
|
157
|
-
|
|
158
|
-
if (!customization || typeof customization !== 'object') {
|
|
159
|
-
errors.push('Customization must be a non-null object');
|
|
160
|
-
return { valid: false, errors };
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Check for unknown fields
|
|
164
|
-
for (const key of Object.keys(customization)) {
|
|
165
|
-
if (!VALID_OVERRIDE_FIELDS.includes(key)) {
|
|
166
|
-
errors.push(`Unknown override field: "${key}"`);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Validate model
|
|
171
|
-
if (customization.model !== undefined && typeof customization.model !== 'string') {
|
|
172
|
-
errors.push('model must be a string');
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Validate extra_context
|
|
176
|
-
if (customization.extra_context !== undefined && typeof customization.extra_context !== 'string') {
|
|
177
|
-
errors.push('extra_context must be a string');
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Validate skip_sections
|
|
181
|
-
if (customization.skip_sections !== undefined) {
|
|
182
|
-
if (!Array.isArray(customization.skip_sections)) {
|
|
183
|
-
errors.push('skip_sections must be an array');
|
|
184
|
-
} else if (customization.skip_sections.some(s => typeof s !== 'string')) {
|
|
185
|
-
errors.push('skip_sections must contain only strings');
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Validate add_sections
|
|
190
|
-
if (customization.add_sections !== undefined) {
|
|
191
|
-
if (!Array.isArray(customization.add_sections)) {
|
|
192
|
-
errors.push('add_sections must be an array');
|
|
193
|
-
} else {
|
|
194
|
-
for (const section of customization.add_sections) {
|
|
195
|
-
if (!section.name || !section.content) {
|
|
196
|
-
errors.push('Each add_section must have name and content');
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Validate timeout
|
|
203
|
-
if (customization.timeout !== undefined) {
|
|
204
|
-
if (typeof customization.timeout !== 'number' || customization.timeout <= 0) {
|
|
205
|
-
errors.push('timeout must be a positive number');
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Validate max_iterations
|
|
210
|
-
if (customization.max_iterations !== undefined) {
|
|
211
|
-
if (typeof customization.max_iterations !== 'number' || customization.max_iterations <= 0) {
|
|
212
|
-
errors.push('max_iterations must be a positive number');
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
return { valid: errors.length === 0, errors };
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// ---------------------------------------------------------------------------
|
|
220
|
-
// Helpers
|
|
221
|
-
// ---------------------------------------------------------------------------
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Escape special regex characters in a string.
|
|
225
|
-
*
|
|
226
|
-
* @param {string} str
|
|
227
|
-
* @returns {string}
|
|
228
|
-
*/
|
|
229
|
-
function escapeRegex(str) {
|
|
230
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
231
|
-
}
|
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
|
-
|