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,292 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Semantic merge engine for parallel agent outputs.
|
|
3
|
-
*
|
|
4
|
-
* Detects file-level, import, and naming conflicts between
|
|
5
|
-
* changes produced by concurrent agents, and applies merge
|
|
6
|
-
* strategies (conservative or latest-wins).
|
|
7
|
-
*
|
|
8
|
-
* Constitution Protocol 5 — Two-Layer Handoff.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
// ---------------------------------------------------------------------------
|
|
12
|
-
// Constants
|
|
13
|
-
// ---------------------------------------------------------------------------
|
|
14
|
-
|
|
15
|
-
/** Conflict type identifiers. */
|
|
16
|
-
export const CONFLICT_TYPES = {
|
|
17
|
-
FILE_OVERLAP: 'file_overlap',
|
|
18
|
-
IMPORT_CONFLICT: 'import_conflict',
|
|
19
|
-
NAMING_CONFLICT: 'naming_conflict',
|
|
20
|
-
STRUCTURAL_CONFLICT: 'structural_conflict',
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
/** Merge strategy options. */
|
|
24
|
-
export const MERGE_STRATEGIES = {
|
|
25
|
-
CONSERVATIVE: 'conservative',
|
|
26
|
-
LATEST: 'latest',
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
// ---------------------------------------------------------------------------
|
|
30
|
-
// Public API
|
|
31
|
-
// ---------------------------------------------------------------------------
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* @typedef {object} Change
|
|
35
|
-
* @property {string} agent - Agent name that produced the change
|
|
36
|
-
* @property {string} file - File path affected
|
|
37
|
-
* @property {string} [content] - New content (for full-file changes)
|
|
38
|
-
* @property {string[]} [imports] - Import statements added
|
|
39
|
-
* @property {string[]} [exports] - Export names added
|
|
40
|
-
* @property {string} [timestamp] - ISO timestamp of the change
|
|
41
|
-
*/
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* @typedef {object} Conflict
|
|
45
|
-
* @property {string} type - Conflict type (from CONFLICT_TYPES)
|
|
46
|
-
* @property {string} file - File involved
|
|
47
|
-
* @property {string[]} agents - Agents that conflict
|
|
48
|
-
* @property {string} description - Human-readable description
|
|
49
|
-
* @property {string} severity - 'error' | 'warning'
|
|
50
|
-
*/
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Detect conflicts between a set of changes from different agents.
|
|
54
|
-
*
|
|
55
|
-
* @param {Change[]} changes
|
|
56
|
-
* @returns {{ hasConflicts: boolean, conflicts: Conflict[] }}
|
|
57
|
-
*/
|
|
58
|
-
export function detectConflicts(changes) {
|
|
59
|
-
if (!Array.isArray(changes) || changes.length < 2) {
|
|
60
|
-
return { hasConflicts: false, conflicts: [] };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const conflicts = [];
|
|
64
|
-
|
|
65
|
-
// 1. File overlap detection — multiple agents changing same file
|
|
66
|
-
const fileAgentMap = groupByFile(changes);
|
|
67
|
-
for (const [file, agentChanges] of fileAgentMap.entries()) {
|
|
68
|
-
const agents = [...new Set(agentChanges.map(c => c.agent))];
|
|
69
|
-
if (agents.length > 1) {
|
|
70
|
-
conflicts.push({
|
|
71
|
-
type: CONFLICT_TYPES.FILE_OVERLAP,
|
|
72
|
-
file,
|
|
73
|
-
agents,
|
|
74
|
-
description: `Multiple agents (${agents.join(', ')}) modified ${file}`,
|
|
75
|
-
severity: 'error',
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// 2. Import conflict detection — duplicate or contradictory imports
|
|
81
|
-
const importsByFile = new Map();
|
|
82
|
-
for (const change of changes) {
|
|
83
|
-
if (!change.imports || change.imports.length === 0) continue;
|
|
84
|
-
if (!importsByFile.has(change.file)) {
|
|
85
|
-
importsByFile.set(change.file, []);
|
|
86
|
-
}
|
|
87
|
-
importsByFile.get(change.file).push({ agent: change.agent, imports: change.imports });
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
for (const [file, importSets] of importsByFile.entries()) {
|
|
91
|
-
if (importSets.length < 2) continue;
|
|
92
|
-
|
|
93
|
-
// Check for duplicate imports from different agents
|
|
94
|
-
const allImports = new Map();
|
|
95
|
-
for (const { agent, imports } of importSets) {
|
|
96
|
-
for (const imp of imports) {
|
|
97
|
-
const normalized = normalizeImport(imp);
|
|
98
|
-
if (allImports.has(normalized)) {
|
|
99
|
-
const existing = allImports.get(normalized);
|
|
100
|
-
if (existing.agent !== agent) {
|
|
101
|
-
conflicts.push({
|
|
102
|
-
type: CONFLICT_TYPES.IMPORT_CONFLICT,
|
|
103
|
-
file,
|
|
104
|
-
agents: [existing.agent, agent],
|
|
105
|
-
description: `Duplicate import "${normalized}" from agents ${existing.agent} and ${agent}`,
|
|
106
|
-
severity: 'warning',
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
} else {
|
|
110
|
-
allImports.set(normalized, { agent, raw: imp });
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// 3. Naming conflict detection — same export name from different agents
|
|
117
|
-
const exportsByName = new Map();
|
|
118
|
-
for (const change of changes) {
|
|
119
|
-
if (!change.exports || change.exports.length === 0) continue;
|
|
120
|
-
for (const exp of change.exports) {
|
|
121
|
-
if (!exportsByName.has(exp)) {
|
|
122
|
-
exportsByName.set(exp, []);
|
|
123
|
-
}
|
|
124
|
-
exportsByName.get(exp).push({ agent: change.agent, file: change.file });
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
for (const [name, sources] of exportsByName.entries()) {
|
|
129
|
-
const uniqueAgents = [...new Set(sources.map(s => s.agent))];
|
|
130
|
-
if (uniqueAgents.length > 1) {
|
|
131
|
-
const files = [...new Set(sources.map(s => s.file))];
|
|
132
|
-
conflicts.push({
|
|
133
|
-
type: CONFLICT_TYPES.NAMING_CONFLICT,
|
|
134
|
-
file: files[0],
|
|
135
|
-
agents: uniqueAgents,
|
|
136
|
-
description: `Export name "${name}" defined by multiple agents: ${uniqueAgents.join(', ')}`,
|
|
137
|
-
severity: 'error',
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return {
|
|
143
|
-
hasConflicts: conflicts.length > 0,
|
|
144
|
-
conflicts,
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Merge changes from multiple agents using a strategy.
|
|
150
|
-
*
|
|
151
|
-
* @param {Change[]} changes
|
|
152
|
-
* @param {string} [strategy='conservative'] - Merge strategy
|
|
153
|
-
* @returns {{ merged: Change[], skipped: Change[], strategy: string }}
|
|
154
|
-
*/
|
|
155
|
-
export function mergeChanges(changes, strategy = MERGE_STRATEGIES.CONSERVATIVE) {
|
|
156
|
-
if (!Array.isArray(changes) || changes.length === 0) {
|
|
157
|
-
return { merged: [], skipped: [], strategy };
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (changes.length === 1) {
|
|
161
|
-
return { merged: [...changes], skipped: [], strategy };
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const fileAgentMap = groupByFile(changes);
|
|
165
|
-
const merged = [];
|
|
166
|
-
const skipped = [];
|
|
167
|
-
|
|
168
|
-
for (const [, agentChanges] of fileAgentMap.entries()) {
|
|
169
|
-
const agents = [...new Set(agentChanges.map(c => c.agent))];
|
|
170
|
-
|
|
171
|
-
if (agents.length === 1) {
|
|
172
|
-
// No conflict — include all changes for this file
|
|
173
|
-
merged.push(...agentChanges);
|
|
174
|
-
continue;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Conflict resolution based on strategy
|
|
178
|
-
if (strategy === MERGE_STRATEGIES.LATEST) {
|
|
179
|
-
// Latest wins — sort by timestamp, take the most recent
|
|
180
|
-
const sorted = [...agentChanges].sort((a, b) => {
|
|
181
|
-
const tA = a.timestamp ? new Date(a.timestamp).getTime() : 0;
|
|
182
|
-
const tB = b.timestamp ? new Date(b.timestamp).getTime() : 0;
|
|
183
|
-
return tB - tA;
|
|
184
|
-
});
|
|
185
|
-
merged.push(sorted[0]);
|
|
186
|
-
skipped.push(...sorted.slice(1));
|
|
187
|
-
} else {
|
|
188
|
-
// Conservative — keep the first agent's changes, skip later ones
|
|
189
|
-
const sorted = [...agentChanges].sort((a, b) => {
|
|
190
|
-
const tA = a.timestamp ? new Date(a.timestamp).getTime() : 0;
|
|
191
|
-
const tB = b.timestamp ? new Date(b.timestamp).getTime() : 0;
|
|
192
|
-
return tA - tB;
|
|
193
|
-
});
|
|
194
|
-
merged.push(sorted[0]);
|
|
195
|
-
skipped.push(...sorted.slice(1));
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
return { merged, skipped, strategy };
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Validate that a merged result is structurally sound.
|
|
204
|
-
*
|
|
205
|
-
* @param {string} original - Original file content
|
|
206
|
-
* @param {string} merged - Merged file content
|
|
207
|
-
* @returns {{ valid: boolean, issues: string[] }}
|
|
208
|
-
*/
|
|
209
|
-
export function validateMergedResult(original, merged) {
|
|
210
|
-
const issues = [];
|
|
211
|
-
|
|
212
|
-
if (!merged || typeof merged !== 'string') {
|
|
213
|
-
issues.push('Merged content is empty or not a string');
|
|
214
|
-
return { valid: false, issues };
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Check for obvious syntax issues
|
|
218
|
-
|
|
219
|
-
// 1. Balanced braces
|
|
220
|
-
const openBraces = (merged.match(/\{/g) || []).length;
|
|
221
|
-
const closeBraces = (merged.match(/\}/g) || []).length;
|
|
222
|
-
if (openBraces !== closeBraces) {
|
|
223
|
-
issues.push(`Unbalanced braces: ${openBraces} open, ${closeBraces} close`);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// 2. Balanced parentheses
|
|
227
|
-
const openParens = (merged.match(/\(/g) || []).length;
|
|
228
|
-
const closeParens = (merged.match(/\)/g) || []).length;
|
|
229
|
-
if (openParens !== closeParens) {
|
|
230
|
-
issues.push(`Unbalanced parentheses: ${openParens} open, ${closeParens} close`);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// 3. Balanced brackets
|
|
234
|
-
const openBrackets = (merged.match(/\[/g) || []).length;
|
|
235
|
-
const closeBrackets = (merged.match(/\]/g) || []).length;
|
|
236
|
-
if (openBrackets !== closeBrackets) {
|
|
237
|
-
issues.push(`Unbalanced brackets: ${openBrackets} open, ${closeBrackets} close`);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// 4. Check that imports haven't been duplicated
|
|
241
|
-
const importLines = merged.match(/^import\s+.+$/gm) || [];
|
|
242
|
-
const normalizedImports = importLines.map(normalizeImport);
|
|
243
|
-
const uniqueImports = new Set(normalizedImports);
|
|
244
|
-
if (uniqueImports.size < normalizedImports.length) {
|
|
245
|
-
issues.push(`Duplicate imports detected (${normalizedImports.length - uniqueImports.size} duplicates)`);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// 5. Check content wasn't completely destroyed
|
|
249
|
-
if (original && merged.length < original.length * 0.5) {
|
|
250
|
-
issues.push('Merged content is less than 50% of original size — possible data loss');
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
return {
|
|
254
|
-
valid: issues.length === 0,
|
|
255
|
-
issues,
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// ---------------------------------------------------------------------------
|
|
260
|
-
// Helpers
|
|
261
|
-
// ---------------------------------------------------------------------------
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* Group changes by file path.
|
|
265
|
-
*
|
|
266
|
-
* @param {Change[]} changes
|
|
267
|
-
* @returns {Map<string, Change[]>}
|
|
268
|
-
*/
|
|
269
|
-
function groupByFile(changes) {
|
|
270
|
-
const map = new Map();
|
|
271
|
-
for (const change of changes) {
|
|
272
|
-
if (!change.file) continue;
|
|
273
|
-
if (!map.has(change.file)) {
|
|
274
|
-
map.set(change.file, []);
|
|
275
|
-
}
|
|
276
|
-
map.get(change.file).push(change);
|
|
277
|
-
}
|
|
278
|
-
return map;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Normalize an import statement for comparison.
|
|
283
|
-
*
|
|
284
|
-
* @param {string} importStr
|
|
285
|
-
* @returns {string}
|
|
286
|
-
*/
|
|
287
|
-
function normalizeImport(importStr) {
|
|
288
|
-
return importStr
|
|
289
|
-
.replace(/['"]/g, "'")
|
|
290
|
-
.replace(/\s+/g, ' ')
|
|
291
|
-
.trim();
|
|
292
|
-
}
|
package/src/preview/detector.js
DELETED
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Framework and dev command detection for User Preview.
|
|
3
|
-
* Analyzes the project directory to determine what dev server to run.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { existsSync, readFileSync } from 'fs';
|
|
7
|
-
import { join } from 'path';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Framework detection rules (checked in priority order).
|
|
11
|
-
* Framework-specific config files take precedence over generic package.json scripts.
|
|
12
|
-
*/
|
|
13
|
-
const FRAMEWORK_DETECTORS = [
|
|
14
|
-
{
|
|
15
|
-
files: ['next.config.js', 'next.config.mjs', 'next.config.ts'],
|
|
16
|
-
framework: 'nextjs',
|
|
17
|
-
defaultPort: 3000,
|
|
18
|
-
kind: 'fullstack',
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
files: ['nuxt.config.js', 'nuxt.config.ts'],
|
|
22
|
-
framework: 'nuxt',
|
|
23
|
-
defaultPort: 3000,
|
|
24
|
-
kind: 'fullstack',
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
files: ['vite.config.js', 'vite.config.ts', 'vite.config.mjs'],
|
|
28
|
-
framework: 'vite',
|
|
29
|
-
defaultPort: 5173,
|
|
30
|
-
kind: 'frontend',
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
files: ['angular.json'],
|
|
34
|
-
framework: 'angular',
|
|
35
|
-
defaultPort: 4200,
|
|
36
|
-
kind: 'frontend',
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
files: ['svelte.config.js'],
|
|
40
|
-
framework: 'sveltekit',
|
|
41
|
-
defaultPort: 5173,
|
|
42
|
-
kind: 'frontend',
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
files: ['astro.config.mjs', 'astro.config.ts'],
|
|
46
|
-
framework: 'astro',
|
|
47
|
-
defaultPort: 4321,
|
|
48
|
-
kind: 'frontend',
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
files: ['manage.py'],
|
|
52
|
-
framework: 'django',
|
|
53
|
-
defaultPort: 8000,
|
|
54
|
-
kind: 'fullstack',
|
|
55
|
-
},
|
|
56
|
-
];
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* API framework packages — if found in dependencies, the project is an API.
|
|
60
|
-
*/
|
|
61
|
-
const API_PACKAGES = ['express', 'fastify', 'koa', 'hapi', 'nest', '@nestjs/core', 'restify'];
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Preferred npm script names in priority order.
|
|
65
|
-
*/
|
|
66
|
-
const SCRIPT_PRIORITY = ['dev', 'start', 'serve', 'preview'];
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Detect the dev command for a project.
|
|
70
|
-
*
|
|
71
|
-
* Detection priority:
|
|
72
|
-
* 1. Framework-specific config files (next.config.*, vite.config.*, etc.)
|
|
73
|
-
* 2. package.json scripts (dev > start > serve > preview)
|
|
74
|
-
* 3. Static index.html fallback (npx serve .)
|
|
75
|
-
* 4. null if nothing detected (CLI tools, libraries)
|
|
76
|
-
*
|
|
77
|
-
* @param {string} projectDir - Absolute path to the project root
|
|
78
|
-
* @returns {{ command: string, args: string[], description: string, framework: string, defaultPort: number } | null}
|
|
79
|
-
*/
|
|
80
|
-
export function detectDevCommand(projectDir) {
|
|
81
|
-
// 1. Framework-specific config files
|
|
82
|
-
for (const detector of FRAMEWORK_DETECTORS) {
|
|
83
|
-
if (detector.files.some((f) => existsSync(join(projectDir, f)))) {
|
|
84
|
-
return resolveFrameworkCommand(projectDir, detector);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// 2. package.json scripts
|
|
89
|
-
const pkgPath = join(projectDir, 'package.json');
|
|
90
|
-
if (existsSync(pkgPath)) {
|
|
91
|
-
try {
|
|
92
|
-
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
93
|
-
const scripts = pkg.scripts || {};
|
|
94
|
-
|
|
95
|
-
for (const script of SCRIPT_PRIORITY) {
|
|
96
|
-
if (scripts[script]) {
|
|
97
|
-
const defaultPort = 3000;
|
|
98
|
-
const command = script === 'start' ? 'npm' : 'npm';
|
|
99
|
-
const args = script === 'start' ? ['start'] : ['run', script];
|
|
100
|
-
|
|
101
|
-
return {
|
|
102
|
-
command,
|
|
103
|
-
args,
|
|
104
|
-
description: script === 'start' ? 'npm start' : `npm run ${script}`,
|
|
105
|
-
framework: 'node',
|
|
106
|
-
defaultPort,
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
} catch { /* expected: operation may fail gracefully */
|
|
111
|
-
// Invalid package.json — skip
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// 3. Static index.html fallback
|
|
116
|
-
if (existsSync(join(projectDir, 'index.html'))) {
|
|
117
|
-
return {
|
|
118
|
-
command: 'npx',
|
|
119
|
-
args: ['serve', '.'],
|
|
120
|
-
description: 'Static file server (npx serve)',
|
|
121
|
-
framework: 'static',
|
|
122
|
-
defaultPort: 3000,
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// 4. Nothing detected
|
|
127
|
-
return null;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Detect the kind of project for UX messaging.
|
|
132
|
-
*
|
|
133
|
-
* @param {string} projectDir - Absolute path to the project root
|
|
134
|
-
* @returns {'frontend'|'api'|'fullstack'|'cli'|'library'|'static'}
|
|
135
|
-
*/
|
|
136
|
-
export function detectProjectKind(projectDir) {
|
|
137
|
-
// Check framework-specific configs first
|
|
138
|
-
for (const detector of FRAMEWORK_DETECTORS) {
|
|
139
|
-
if (detector.files.some((f) => existsSync(join(projectDir, f)))) {
|
|
140
|
-
return detector.kind;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Check package.json for API framework deps
|
|
145
|
-
const pkgPath = join(projectDir, 'package.json');
|
|
146
|
-
if (existsSync(pkgPath)) {
|
|
147
|
-
try {
|
|
148
|
-
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
149
|
-
const allDeps = {
|
|
150
|
-
...pkg.dependencies,
|
|
151
|
-
...pkg.devDependencies,
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
if (API_PACKAGES.some((p) => allDeps[p])) {
|
|
155
|
-
return 'api';
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Has bin field → CLI tool
|
|
159
|
-
if (pkg.bin) {
|
|
160
|
-
return 'cli';
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Has main/exports but no scripts → library
|
|
164
|
-
if ((pkg.main || pkg.exports) && !pkg.scripts?.dev && !pkg.scripts?.start) {
|
|
165
|
-
return 'library';
|
|
166
|
-
}
|
|
167
|
-
} catch { /* expected: operation may fail gracefully */
|
|
168
|
-
// Invalid package.json
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Static HTML
|
|
173
|
-
if (existsSync(join(projectDir, 'index.html'))) {
|
|
174
|
-
return 'static';
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Python projects
|
|
178
|
-
if (existsSync(join(projectDir, 'manage.py'))) {
|
|
179
|
-
return 'fullstack';
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Default: assume library/CLI (no preview server)
|
|
183
|
-
return 'library';
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Resolve the dev command for a known framework.
|
|
188
|
-
*
|
|
189
|
-
* @param {string} projectDir
|
|
190
|
-
* @param {object} detector - Framework detector entry
|
|
191
|
-
* @returns {{ command: string, args: string[], description: string, framework: string, defaultPort: number }}
|
|
192
|
-
*/
|
|
193
|
-
function resolveFrameworkCommand(projectDir, detector) {
|
|
194
|
-
const { framework, defaultPort } = detector;
|
|
195
|
-
|
|
196
|
-
// Django has its own command
|
|
197
|
-
if (framework === 'django') {
|
|
198
|
-
return {
|
|
199
|
-
command: 'python',
|
|
200
|
-
args: ['manage.py', 'runserver'],
|
|
201
|
-
description: 'Django dev server',
|
|
202
|
-
framework,
|
|
203
|
-
defaultPort,
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Node-based frameworks: prefer package.json scripts
|
|
208
|
-
const pkgPath = join(projectDir, 'package.json');
|
|
209
|
-
if (existsSync(pkgPath)) {
|
|
210
|
-
try {
|
|
211
|
-
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
212
|
-
const scripts = pkg.scripts || {};
|
|
213
|
-
|
|
214
|
-
for (const script of SCRIPT_PRIORITY) {
|
|
215
|
-
if (scripts[script]) {
|
|
216
|
-
return {
|
|
217
|
-
command: 'npm',
|
|
218
|
-
args: script === 'start' ? ['start'] : ['run', script],
|
|
219
|
-
description: `${framework} (npm ${script === 'start' ? 'start' : `run ${script}`})`,
|
|
220
|
-
framework,
|
|
221
|
-
defaultPort,
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
} catch { /* expected: operation may fail gracefully */
|
|
226
|
-
// Fall through to default
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// Fallback for Node frameworks without scripts
|
|
231
|
-
return {
|
|
232
|
-
command: 'npm',
|
|
233
|
-
args: ['run', 'dev'],
|
|
234
|
-
description: `${framework} dev server`,
|
|
235
|
-
framework,
|
|
236
|
-
defaultPort,
|
|
237
|
-
};
|
|
238
|
-
}
|
package/src/preview/index.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview User Preview module — local server for pre-deploy validation.
|
|
3
|
-
*
|
|
4
|
-
* Detects the project's framework, spawns a dev server on an available port,
|
|
5
|
-
* captures logs for Dev agent context, and manages server lifecycle.
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* import { detectDevCommand, findAvailablePort, launchPreview } from './preview/index.js';
|
|
9
|
-
*
|
|
10
|
-
* const devCmd = detectDevCommand(projectDir);
|
|
11
|
-
* const port = await findAvailablePort(devCmd.defaultPort);
|
|
12
|
-
* const handle = await launchPreview(projectDir, { ...devCmd, port });
|
|
13
|
-
* // ... user validates ...
|
|
14
|
-
* // handle.logs.toContext() → send to Dev agent
|
|
15
|
-
* // await handle.kill() → when user decides to stop
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
export { detectDevCommand, detectProjectKind } from './detector.js';
|
|
19
|
-
export { findAvailablePort, launchPreview, waitForServer, openBrowser } from './launcher.js';
|
|
20
|
-
export { LogBuffer } from './log-buffer.js';
|