chati-dev 2.0.8 → 2.1.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 +32 -21
- package/assets/logo - co/314/201pia.png +0 -0
- package/assets/logo - co/314/201pia.svg" +42 -0
- package/bin/chati.js +3 -3
- package/framework/agents/{planning → discover}/brief.md +24 -2
- package/framework/agents/{planning → discover}/brownfield-wu.md +1 -1
- package/framework/agents/{planning → discover}/greenfield-wu.md +1 -1
- package/framework/agents/{planning → plan}/architect.md +1 -1
- package/framework/agents/{planning → plan}/detail.md +1 -1
- package/framework/agents/{planning → plan}/phases.md +1 -1
- package/framework/agents/{planning → plan}/tasks.md +1 -1
- package/framework/agents/{planning → plan}/ux.md +1 -1
- package/framework/agents/quality/qa-implementation.md +53 -4
- package/framework/agents/quality/qa-planning.md +53 -2
- package/framework/config.yaml +3 -3
- package/framework/constitution.md +4 -4
- package/framework/context/quality.md +1 -1
- package/framework/context/root.md +5 -4
- package/framework/data/entity-registry.yaml +35 -11
- package/framework/domains/agents/brief.yaml +14 -0
- package/framework/domains/agents/qa-implementation.yaml +16 -0
- package/framework/domains/agents/qa-planning.yaml +16 -0
- package/framework/domains/constitution.yaml +6 -2
- package/framework/domains/workflows/brownfield-discovery.yaml +5 -4
- package/framework/domains/workflows/quick-flow.yaml +23 -0
- package/framework/hooks/mode-governance.js +14 -2
- package/framework/hooks/model-governance.js +5 -1
- package/framework/hooks/prism-engine.js +1 -1
- package/framework/hooks/session-digest.js +6 -2
- package/framework/hooks/settings.json +11 -0
- 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/context-engine.md +2 -2
- package/framework/intelligence/decision-engine.md +2 -2
- package/framework/manifest.json +5 -0
- package/framework/manifest.sig +1 -0
- package/framework/orchestrator/chati.md +68 -20
- package/framework/patterns/elicitation-library.yaml +592 -0
- package/framework/patterns/elicitation.md +2 -0
- package/framework/quality-gates/planning-gate.md +1 -1
- package/framework/schemas/session.schema.json +36 -3
- package/framework/tasks/architect-api-design.md +1 -1
- package/framework/tasks/architect-consolidate.md +1 -1
- package/framework/tasks/architect-db-design.md +1 -1
- package/framework/tasks/architect-design.md +1 -1
- package/framework/tasks/architect-security-review.md +1 -1
- package/framework/tasks/architect-stack-selection.md +1 -1
- package/framework/tasks/brief-consolidate.md +1 -1
- package/framework/tasks/brief-constraint-identify.md +1 -1
- package/framework/tasks/brief-extract-requirements.md +1 -1
- package/framework/tasks/brief-stakeholder-map.md +1 -1
- package/framework/tasks/brief-validate-completeness.md +1 -1
- package/framework/tasks/brownfield-wu-architecture-map.md +1 -1
- package/framework/tasks/brownfield-wu-deep-discovery.md +1 -1
- package/framework/tasks/brownfield-wu-dependency-scan.md +1 -1
- package/framework/tasks/brownfield-wu-migration-plan.md +1 -1
- package/framework/tasks/brownfield-wu-report.md +1 -1
- package/framework/tasks/brownfield-wu-risk-assess.md +1 -1
- package/framework/tasks/detail-acceptance-criteria.md +1 -1
- package/framework/tasks/detail-consolidate.md +1 -1
- package/framework/tasks/detail-edge-case-analysis.md +1 -1
- package/framework/tasks/detail-expand-prd.md +1 -1
- package/framework/tasks/detail-nfr-extraction.md +1 -1
- package/framework/tasks/greenfield-wu-analyze-empty.md +1 -1
- package/framework/tasks/greenfield-wu-report.md +1 -1
- package/framework/tasks/greenfield-wu-scaffold-detection.md +1 -1
- package/framework/tasks/greenfield-wu-tech-stack-assess.md +1 -1
- package/framework/tasks/orchestrator-deviation.md +2 -2
- package/framework/tasks/orchestrator-escalate.md +1 -1
- package/framework/tasks/orchestrator-handoff.md +3 -3
- package/framework/tasks/orchestrator-mode-switch.md +16 -5
- package/framework/tasks/orchestrator-preview.md +181 -0
- package/framework/tasks/orchestrator-resume.md +2 -2
- package/framework/tasks/orchestrator-route.md +5 -4
- package/framework/tasks/orchestrator-status.md +4 -4
- package/framework/tasks/orchestrator-suggest-mode.md +7 -7
- package/framework/tasks/phases-breakdown.md +1 -1
- package/framework/tasks/phases-dependency-mapping.md +1 -1
- package/framework/tasks/phases-mvp-scoping.md +1 -1
- package/framework/tasks/qa-impl-consolidate.md +2 -2
- package/framework/tasks/qa-impl-verdict.md +2 -2
- package/framework/tasks/qa-planning-consolidate.md +1 -1
- package/framework/tasks/qa-planning-coverage-plan.md +2 -2
- package/framework/tasks/qa-planning-gate-define.md +1 -1
- package/framework/tasks/qa-planning-risk-matrix.md +1 -1
- package/framework/tasks/qa-planning-test-strategy.md +1 -1
- package/framework/tasks/tasks-acceptance-write.md +1 -1
- package/framework/tasks/tasks-consolidate.md +1 -1
- package/framework/tasks/tasks-decompose.md +1 -1
- package/framework/tasks/tasks-estimate.md +1 -1
- package/framework/tasks/ux-a11y-check.md +1 -1
- package/framework/tasks/ux-component-map.md +1 -1
- package/framework/tasks/ux-consolidate.md +1 -1
- package/framework/tasks/ux-user-flow.md +1 -1
- package/framework/tasks/ux-wireframe.md +1 -1
- package/framework/templates/quick-brief-tmpl.yaml +90 -0
- package/framework/workflows/brownfield-discovery.yaml +7 -7
- package/framework/workflows/brownfield-fullstack.yaml +24 -10
- package/framework/workflows/brownfield-service.yaml +41 -5
- package/framework/workflows/brownfield-ui.yaml +41 -5
- package/framework/workflows/greenfield-fullstack.yaml +24 -10
- package/framework/workflows/quick-flow.yaml +125 -0
- package/package.json +4 -4
- package/scripts/bundle-framework.js +1 -1
- package/scripts/populate-entity-registry.js +1 -1
- package/scripts/semantic-lint.js +2 -2
- package/scripts/validate-agents.js +2 -1
- package/scripts/validate-package.js +1 -1
- package/scripts/validate-tasks.js +1 -1
- package/src/autonomy/progress-reporter.js +26 -19
- package/src/context/engine.js +1 -1
- package/src/context/layers/l1-global.js +17 -5
- package/src/dashboard/layout.js +2 -1
- package/src/gates/g1-planning-complete.js +10 -10
- package/src/installer/core.js +14 -12
- package/src/installer/templates.js +1 -1
- package/src/installer/validator.js +8 -8
- package/src/intelligence/registry-manager.js +4 -4
- package/src/memory/session-digest.js +1 -1
- package/src/orchestrator/agent-selector.js +11 -11
- package/src/orchestrator/intent-classifier.js +12 -8
- package/src/orchestrator/pipeline-manager.js +100 -3
- package/src/orchestrator/pipeline-state.js +1 -1
- package/src/orchestrator/session-manager.js +13 -4
- package/src/preview/detector.js +238 -0
- package/src/preview/index.js +20 -0
- package/src/preview/launcher.js +235 -0
- package/src/preview/log-buffer.js +103 -0
- package/src/tasks/executor.js +1 -1
- package/src/tasks/loader.js +1 -1
- package/src/terminal/prompt-builder.js +10 -10
- package/src/wizard/i18n.js +3 -3
|
@@ -9,15 +9,15 @@ import { INTENT_TYPES } from './intent-classifier.js';
|
|
|
9
9
|
* The complete agent pipeline in execution order.
|
|
10
10
|
*/
|
|
11
11
|
export const AGENT_PIPELINE = [
|
|
12
|
-
{ name: 'greenfield-wu', phase: '
|
|
13
|
-
{ name: 'brownfield-wu', phase: '
|
|
14
|
-
{ name: 'brief', phase: '
|
|
15
|
-
{ name: 'detail', phase: '
|
|
16
|
-
{ name: 'architect', phase: '
|
|
17
|
-
{ name: 'ux', phase: '
|
|
18
|
-
{ name: 'phases', phase: '
|
|
19
|
-
{ name: 'tasks', phase: '
|
|
20
|
-
{ name: 'qa-planning', phase: '
|
|
12
|
+
{ name: 'greenfield-wu', phase: 'discover', group: 'wu', parallel: false },
|
|
13
|
+
{ name: 'brownfield-wu', phase: 'discover', group: 'wu', parallel: false },
|
|
14
|
+
{ name: 'brief', phase: 'discover', group: 'discover', parallel: false },
|
|
15
|
+
{ name: 'detail', phase: 'plan', group: 'plan-parallel', parallel: true },
|
|
16
|
+
{ name: 'architect', phase: 'plan', group: 'plan-parallel', parallel: true },
|
|
17
|
+
{ name: 'ux', phase: 'plan', group: 'plan-parallel', parallel: true },
|
|
18
|
+
{ name: 'phases', phase: 'plan', group: 'plan', parallel: false },
|
|
19
|
+
{ name: 'tasks', phase: 'plan', group: 'plan', parallel: false },
|
|
20
|
+
{ name: 'qa-planning', phase: 'plan', group: 'quality', parallel: false },
|
|
21
21
|
{ name: 'dev', phase: 'build', group: 'build', parallel: false },
|
|
22
22
|
{ name: 'qa-implementation', phase: 'build', group: 'quality', parallel: false },
|
|
23
23
|
{ name: 'devops', phase: 'deploy', group: 'deploy', parallel: false },
|
|
@@ -58,7 +58,7 @@ function getAgentsByGroup(group) {
|
|
|
58
58
|
*
|
|
59
59
|
* @param {object} context
|
|
60
60
|
* @param {string} context.intent - Classified intent
|
|
61
|
-
* @param {string} context.mode - Current mode (
|
|
61
|
+
* @param {string} context.mode - Current mode (discover/plan/build/deploy)
|
|
62
62
|
* @param {string} [context.currentAgent] - Currently active agent
|
|
63
63
|
* @param {string[]} [context.completedAgents] - Already completed agents
|
|
64
64
|
* @param {boolean} [context.isGreenfield] - true for new projects, false for existing
|
|
@@ -88,7 +88,7 @@ export function selectAgent(context) {
|
|
|
88
88
|
// If starting fresh with no completed agents
|
|
89
89
|
if (completedAgents.length === 0) {
|
|
90
90
|
// Planning intent starts with WU
|
|
91
|
-
if (intent === INTENT_TYPES.
|
|
91
|
+
if (intent === INTENT_TYPES.DISCOVER) {
|
|
92
92
|
const wuAgent = isGreenfield ? 'greenfield-wu' : 'brownfield-wu';
|
|
93
93
|
return {
|
|
94
94
|
agent: wuAgent,
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Intent categories the classifier can identify.
|
|
8
8
|
*/
|
|
9
9
|
export const INTENT_TYPES = {
|
|
10
|
-
|
|
10
|
+
DISCOVER: 'discover',
|
|
11
11
|
IMPLEMENTATION: 'implementation',
|
|
12
12
|
REVIEW: 'review',
|
|
13
13
|
DEPLOY: 'deploy',
|
|
@@ -23,7 +23,7 @@ export const INTENT_TYPES = {
|
|
|
23
23
|
* Higher weight = stronger signal for that intent.
|
|
24
24
|
*/
|
|
25
25
|
const INTENT_KEYWORDS = {
|
|
26
|
-
[INTENT_TYPES.
|
|
26
|
+
[INTENT_TYPES.DISCOVER]: {
|
|
27
27
|
high: ['plan', 'design', 'architecture', 'structure', 'requirements', 'spec', 'specification'],
|
|
28
28
|
medium: ['define', 'outline', 'organize', 'prepare', 'brief', 'draft'],
|
|
29
29
|
low: ['think', 'consider', 'analyze', 'brainstorm'],
|
|
@@ -83,14 +83,18 @@ const WEIGHTS = {
|
|
|
83
83
|
* Context boost multipliers based on current mode.
|
|
84
84
|
*/
|
|
85
85
|
const MODE_CONTEXT_BOOST = {
|
|
86
|
-
|
|
87
|
-
[INTENT_TYPES.
|
|
86
|
+
discover: {
|
|
87
|
+
[INTENT_TYPES.DISCOVER]: 1.5,
|
|
88
|
+
[INTENT_TYPES.IMPLEMENTATION]: 0.5,
|
|
89
|
+
},
|
|
90
|
+
plan: {
|
|
91
|
+
[INTENT_TYPES.DISCOVER]: 1.2,
|
|
88
92
|
[INTENT_TYPES.IMPLEMENTATION]: 0.5,
|
|
89
93
|
},
|
|
90
94
|
build: {
|
|
91
95
|
[INTENT_TYPES.IMPLEMENTATION]: 1.5,
|
|
92
96
|
[INTENT_TYPES.REVIEW]: 1.2,
|
|
93
|
-
[INTENT_TYPES.
|
|
97
|
+
[INTENT_TYPES.DISCOVER]: 0.5,
|
|
94
98
|
},
|
|
95
99
|
deploy: {
|
|
96
100
|
[INTENT_TYPES.DEPLOY]: 1.5,
|
|
@@ -198,11 +202,11 @@ function buildReasoning(intent, keywords, context) {
|
|
|
198
202
|
* Get intent-to-phase mapping.
|
|
199
203
|
*
|
|
200
204
|
* @param {string} intent
|
|
201
|
-
* @returns {string} Target phase (
|
|
205
|
+
* @returns {string} Target phase (discover, plan, build, deploy)
|
|
202
206
|
*/
|
|
203
207
|
export function getIntentPhase(intent) {
|
|
204
208
|
const phaseMap = {
|
|
205
|
-
[INTENT_TYPES.
|
|
209
|
+
[INTENT_TYPES.DISCOVER]: 'discover',
|
|
206
210
|
[INTENT_TYPES.IMPLEMENTATION]: 'build',
|
|
207
211
|
[INTENT_TYPES.REVIEW]: 'build',
|
|
208
212
|
[INTENT_TYPES.DEPLOY]: 'deploy',
|
|
@@ -243,7 +247,7 @@ export function checkModeAlignment(intent, currentMode) {
|
|
|
243
247
|
}
|
|
244
248
|
|
|
245
249
|
// Check if mode change is allowed
|
|
246
|
-
const modeOrder = ['
|
|
250
|
+
const modeOrder = ['discover', 'plan', 'build', 'deploy'];
|
|
247
251
|
const currentIndex = modeOrder.indexOf(currentMode);
|
|
248
252
|
const targetIndex = modeOrder.indexOf(targetPhase);
|
|
249
253
|
|
|
@@ -8,7 +8,7 @@ import { AGENT_PIPELINE, getNextAgent } from './agent-selector.js';
|
|
|
8
8
|
/**
|
|
9
9
|
* Pipeline phases in order.
|
|
10
10
|
*/
|
|
11
|
-
export const PIPELINE_PHASES = ['
|
|
11
|
+
export const PIPELINE_PHASES = ['discover', 'plan', 'build', 'deploy'];
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Agent status values.
|
|
@@ -35,7 +35,7 @@ const QA_IMPLEMENTATION_THRESHOLD = 95;
|
|
|
35
35
|
* @returns {object} Pipeline state
|
|
36
36
|
*/
|
|
37
37
|
export function initPipeline(options = {}) {
|
|
38
|
-
const { isGreenfield = true, mode = '
|
|
38
|
+
const { isGreenfield = true, mode = 'discover' } = options;
|
|
39
39
|
|
|
40
40
|
const agents = {};
|
|
41
41
|
for (const agentDef of AGENT_PIPELINE) {
|
|
@@ -112,6 +112,20 @@ export function advancePipeline(pipelineState, completedAgent, results = {}) {
|
|
|
112
112
|
const transitionCheck = checkPhaseTransition(newState);
|
|
113
113
|
|
|
114
114
|
if (transitionCheck.canAdvance) {
|
|
115
|
+
// User Preview gate: when QA-Implementation passes in build phase,
|
|
116
|
+
// don't advance to deploy yet — let user preview first.
|
|
117
|
+
if (completedAgent === 'qa-implementation' && newState.phase === 'build') {
|
|
118
|
+
return {
|
|
119
|
+
state: newState,
|
|
120
|
+
nextAction: 'user_preview',
|
|
121
|
+
nextAgent: null,
|
|
122
|
+
needsModeSwitch: false,
|
|
123
|
+
previewContext: {
|
|
124
|
+
qaScore: newState.agents['qa-implementation'].score,
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
115
129
|
// QA passed - advance to next phase
|
|
116
130
|
const currentPhaseIndex = PIPELINE_PHASES.indexOf(newState.phase);
|
|
117
131
|
if (currentPhaseIndex < PIPELINE_PHASES.length - 1) {
|
|
@@ -257,7 +271,17 @@ export function checkPhaseTransition(pipelineState) {
|
|
|
257
271
|
const { phase, agents } = pipelineState;
|
|
258
272
|
|
|
259
273
|
// Check based on current phase
|
|
260
|
-
if (phase === '
|
|
274
|
+
if (phase === 'discover') {
|
|
275
|
+
// DISCOVER -> PLAN transition: all discover agents must be completed
|
|
276
|
+
// (brief must be done to advance to plan)
|
|
277
|
+
return {
|
|
278
|
+
canAdvance: true,
|
|
279
|
+
reason: 'Discover phase agents completed',
|
|
280
|
+
requiredScore: null,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (phase === 'plan') {
|
|
261
285
|
// Need QA-Planning to be completed with score >= 95
|
|
262
286
|
const qaPlanning = agents['qa-planning'];
|
|
263
287
|
if (!qaPlanning) {
|
|
@@ -490,3 +514,76 @@ export function markAgentInProgress(pipelineState, agentName) {
|
|
|
490
514
|
|
|
491
515
|
return newState;
|
|
492
516
|
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Confirm user preview decision and advance pipeline accordingly.
|
|
520
|
+
* Called by the orchestrator after the user evaluates the local preview.
|
|
521
|
+
*
|
|
522
|
+
* @param {object} pipelineState - Current state (phase must be 'build')
|
|
523
|
+
* @param {'approve_keep'|'approve_kill'|'adjust'|'rethink'} decision
|
|
524
|
+
* @returns {{ state: object, nextAction: string, nextAgent: string|null, needsModeSwitch: boolean, serverAction?: string }}
|
|
525
|
+
*/
|
|
526
|
+
export function confirmPreview(pipelineState, decision) {
|
|
527
|
+
const newState = { ...pipelineState };
|
|
528
|
+
|
|
529
|
+
if (decision === 'approve_keep' || decision === 'approve_kill') {
|
|
530
|
+
const nextPhase = 'deploy';
|
|
531
|
+
|
|
532
|
+
newState.modeTransitions.push({
|
|
533
|
+
from: 'build',
|
|
534
|
+
to: nextPhase,
|
|
535
|
+
trigger: 'user_preview_approved',
|
|
536
|
+
timestamp: new Date().toISOString(),
|
|
537
|
+
reason: `User approved preview (server: ${decision === 'approve_keep' ? 'kept alive' : 'killed'})`,
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
newState.phase = nextPhase;
|
|
541
|
+
|
|
542
|
+
newState.history.push({
|
|
543
|
+
agent: 'orchestrator',
|
|
544
|
+
action: 'preview_approved',
|
|
545
|
+
timestamp: new Date().toISOString(),
|
|
546
|
+
decision,
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
return {
|
|
550
|
+
state: newState,
|
|
551
|
+
nextAction: 'advance_phase',
|
|
552
|
+
nextAgent: getFirstAgentInPhase(newState, nextPhase),
|
|
553
|
+
needsModeSwitch: true,
|
|
554
|
+
serverAction: decision === 'approve_kill' ? 'kill' : 'keep',
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
if (decision === 'adjust') {
|
|
559
|
+
newState.history.push({
|
|
560
|
+
agent: 'orchestrator',
|
|
561
|
+
action: 'preview_adjust',
|
|
562
|
+
timestamp: new Date().toISOString(),
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
return {
|
|
566
|
+
state: newState,
|
|
567
|
+
nextAction: 'continue',
|
|
568
|
+
nextAgent: 'dev',
|
|
569
|
+
needsModeSwitch: false,
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
if (decision === 'rethink') {
|
|
574
|
+
newState.history.push({
|
|
575
|
+
agent: 'orchestrator',
|
|
576
|
+
action: 'preview_rethink',
|
|
577
|
+
timestamp: new Date().toISOString(),
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
return {
|
|
581
|
+
state: newState,
|
|
582
|
+
nextAction: 'deviation',
|
|
583
|
+
nextAgent: null,
|
|
584
|
+
needsModeSwitch: false,
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
throw new Error(`Invalid preview decision: ${decision}`);
|
|
589
|
+
}
|
|
@@ -44,7 +44,7 @@ export function loadPipelineState(projectDir) {
|
|
|
44
44
|
|
|
45
45
|
// Extract pipeline-relevant fields
|
|
46
46
|
const state = {
|
|
47
|
-
phase: session.mode || '
|
|
47
|
+
phase: session.mode || 'discover',
|
|
48
48
|
isGreenfield: session.project_type === 'greenfield',
|
|
49
49
|
startedAt: session.started_at || null,
|
|
50
50
|
completedAt: session.completed_at || null,
|
|
@@ -16,7 +16,8 @@ const SESSION_FILE = '.chati/session.yaml';
|
|
|
16
16
|
*/
|
|
17
17
|
const DEFAULT_SESSION = {
|
|
18
18
|
version: '1.0',
|
|
19
|
-
mode: '
|
|
19
|
+
mode: 'discover',
|
|
20
|
+
project: { name: '', type: 'greenfield', state: 'discover' },
|
|
20
21
|
language: 'en',
|
|
21
22
|
project_type: 'greenfield',
|
|
22
23
|
started_at: null,
|
|
@@ -65,7 +66,12 @@ export function initSession(projectDir, options = {}) {
|
|
|
65
66
|
|
|
66
67
|
const session = {
|
|
67
68
|
...DEFAULT_SESSION,
|
|
68
|
-
mode: options.mode || '
|
|
69
|
+
mode: options.mode || 'discover',
|
|
70
|
+
project: {
|
|
71
|
+
name: options.projectName || '',
|
|
72
|
+
type: options.isGreenfield === false ? 'brownfield' : 'greenfield',
|
|
73
|
+
state: options.mode || 'discover',
|
|
74
|
+
},
|
|
69
75
|
language: options.language || 'en',
|
|
70
76
|
project_type: options.isGreenfield === false ? 'brownfield' : 'greenfield',
|
|
71
77
|
started_at: new Date().toISOString(),
|
|
@@ -220,8 +226,11 @@ export function recordModeTransition(projectDir, transition) {
|
|
|
220
226
|
timestamp: new Date().toISOString(),
|
|
221
227
|
});
|
|
222
228
|
|
|
223
|
-
// Update current mode
|
|
229
|
+
// Update current mode (both flat and nested for compatibility)
|
|
224
230
|
session.mode = transition.to;
|
|
231
|
+
if (session.project) {
|
|
232
|
+
session.project.state = transition.to;
|
|
233
|
+
}
|
|
225
234
|
|
|
226
235
|
return updateSession(projectDir, session);
|
|
227
236
|
}
|
|
@@ -383,7 +392,7 @@ export function validateSession(projectDir) {
|
|
|
383
392
|
}
|
|
384
393
|
|
|
385
394
|
// Validate mode
|
|
386
|
-
const validModes = ['
|
|
395
|
+
const validModes = ['discover', 'plan', 'build', 'validate', 'deploy', 'completed'];
|
|
387
396
|
if (!validModes.includes(session.mode)) {
|
|
388
397
|
return {
|
|
389
398
|
exists: true,
|
|
@@ -0,0 +1,238 @@
|
|
|
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 {
|
|
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 {
|
|
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, kind } = 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 {
|
|
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
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
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';
|