aios-core 2.2.1 → 2.3.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/.aios-core/.session/current-session.json +14 -14
- package/.aios-core/cli/commands/migrate/validate.js +1 -1
- package/.aios-core/core/docs/session-update-pattern.md +17 -10
- package/.aios-core/core/elicitation/elicitation-engine.js +11 -6
- package/.aios-core/core/elicitation/session-manager.js +2 -1
- package/.aios-core/core/registry/registry-schema.json +166 -166
- package/.aios-core/core/registry/service-registry.json +6585 -6585
- package/.aios-core/core-config.yaml +12 -1
- package/.aios-core/data/agent-config-requirements.yaml +5 -5
- package/.aios-core/development/agents/devops.md +12 -0
- package/.aios-core/development/scripts/squad/README.md +112 -0
- package/.aios-core/development/scripts/squad/index.js +41 -0
- package/.aios-core/development/scripts/squad/squad-loader.js +359 -0
- package/.aios-core/development/scripts/squad/squad-validator.js +685 -0
- package/.aios-core/development/tasks/add-mcp.md +11 -5
- package/.aios-core/development/tasks/search-mcp.md +309 -0
- package/.aios-core/development/tasks/setup-mcp-docker.md +11 -8
- package/.aios-core/development/tasks/squad-creator-validate.md +151 -0
- package/.aios-core/docs/standards/AGENT-PERSONALIZATION-STANDARD-V1.md +3 -3
- package/.aios-core/index.d.ts +7 -7
- package/.aios-core/index.js +1 -1
- package/.aios-core/infrastructure/scripts/batch-creator.js +1 -1
- package/.aios-core/infrastructure/scripts/component-generator.js +1 -1
- package/.aios-core/infrastructure/templates/coderabbit.yaml.template +279 -279
- package/.aios-core/infrastructure/templates/github-workflows/ci.yml.template +169 -169
- package/.aios-core/infrastructure/templates/github-workflows/pr-automation.yml.template +330 -330
- package/.aios-core/infrastructure/templates/github-workflows/release.yml.template +196 -196
- package/.aios-core/infrastructure/templates/gitignore/gitignore-aios-base.tmpl +63 -63
- package/.aios-core/infrastructure/templates/gitignore/gitignore-brownfield-merge.tmpl +18 -18
- package/.aios-core/infrastructure/templates/gitignore/gitignore-node.tmpl +85 -85
- package/.aios-core/infrastructure/templates/gitignore/gitignore-python.tmpl +145 -145
- package/.aios-core/infrastructure/tests/utilities-audit-results.json +500 -500
- package/.aios-core/infrastructure/tools/README.md +1 -1
- package/.aios-core/install-manifest.yaml +4 -1
- package/.aios-core/manifests/schema/manifest-schema.json +190 -190
- package/.aios-core/manifests/workers.csv +203 -203
- package/.aios-core/package.json +102 -102
- package/.aios-core/product/templates/activation-instructions-template.md +7 -7
- package/.aios-core/product/templates/adr.hbs +125 -125
- package/.aios-core/product/templates/component-react-tmpl.tsx +98 -98
- package/.aios-core/product/templates/dbdr.hbs +241 -241
- package/.aios-core/product/templates/engine/schemas/adr.schema.json +102 -102
- package/.aios-core/product/templates/engine/schemas/dbdr.schema.json +205 -205
- package/.aios-core/product/templates/engine/schemas/epic.schema.json +175 -175
- package/.aios-core/product/templates/engine/schemas/pmdr.schema.json +175 -175
- package/.aios-core/product/templates/engine/schemas/prd-v2.schema.json +300 -300
- package/.aios-core/product/templates/engine/schemas/prd.schema.json +152 -152
- package/.aios-core/product/templates/engine/schemas/story.schema.json +222 -222
- package/.aios-core/product/templates/engine/schemas/task.schema.json +154 -154
- package/.aios-core/product/templates/epic.hbs +212 -212
- package/.aios-core/product/templates/eslintrc-security.json +32 -32
- package/.aios-core/product/templates/github-actions-cd.yml +212 -212
- package/.aios-core/product/templates/github-actions-ci.yml +172 -172
- package/.aios-core/product/templates/pmdr.hbs +186 -186
- package/.aios-core/product/templates/prd-v2.0.hbs +216 -216
- package/.aios-core/product/templates/prd.hbs +201 -201
- package/.aios-core/product/templates/shock-report-tmpl.html +502 -502
- package/.aios-core/product/templates/story.hbs +263 -263
- package/.aios-core/product/templates/task.hbs +170 -170
- package/.aios-core/product/templates/tmpl-comment-on-examples.sql +158 -158
- package/.aios-core/product/templates/tmpl-migration-script.sql +91 -91
- package/.aios-core/product/templates/tmpl-rls-granular-policies.sql +104 -104
- package/.aios-core/product/templates/tmpl-rls-kiss-policy.sql +10 -10
- package/.aios-core/product/templates/tmpl-rls-roles.sql +135 -135
- package/.aios-core/product/templates/tmpl-rls-simple.sql +77 -77
- package/.aios-core/product/templates/tmpl-rls-tenant.sql +152 -152
- package/.aios-core/product/templates/tmpl-rollback-script.sql +77 -77
- package/.aios-core/product/templates/tmpl-seed-data.sql +140 -140
- package/.aios-core/product/templates/tmpl-smoke-test.sql +16 -16
- package/.aios-core/product/templates/tmpl-staging-copy-merge.sql +139 -139
- package/.aios-core/product/templates/tmpl-stored-proc.sql +140 -140
- package/.aios-core/product/templates/tmpl-trigger.sql +152 -152
- package/.aios-core/product/templates/tmpl-view-materialized.sql +133 -133
- package/.aios-core/product/templates/tmpl-view.sql +177 -177
- package/.aios-core/product/templates/token-exports-css-tmpl.css +240 -240
- package/.aios-core/quality/schemas/quality-metrics.schema.json +233 -233
- package/.aios-core/schemas/squad-schema.json +185 -0
- package/.aios-core/scripts/README.md +90 -322
- package/.aios-core/scripts/migrate-framework-docs.sh +300 -300
- package/.claude/rules/mcp-usage.md +116 -100
- package/LICENSE +48 -48
- package/README.md +3 -4
- package/bin/aios-init.js +11 -6
- package/bin/aios.js +2 -1
- package/package.json +2 -3
- package/packages/installer/package.json +39 -39
- package/packages/installer/tests/integration/environment-configuration.test.js +2 -2
- package/packages/installer/tests/unit/env-template.test.js +4 -3
- package/templates/squad/LICENSE +21 -21
- package/templates/squad/README.md +37 -37
- package/templates/squad/agents/example-agent.yaml +36 -36
- package/templates/squad/package.json +19 -19
- package/templates/squad/squad.yaml +25 -25
- package/templates/squad/tasks/example-task.yaml +46 -46
- package/templates/squad/templates/example-template.md +24 -24
- package/templates/squad/tests/example-agent.test.js +53 -53
- package/templates/squad/workflows/example-workflow.yaml +54 -54
- package/tools/diagnose-npx-issue.ps1 +96 -96
- package/tools/quick-diagnose.cmd +85 -85
- package/tools/quick-diagnose.ps1 +117 -117
- package/.aios-core/core/data/agent-config-requirements.yaml +0 -368
- package/.aios-core/core/data/aios-kb.md +0 -924
- package/.aios-core/core/data/workflow-patterns.yaml +0 -267
- package/.aios-core/product/templates/1mcp-config.yaml +0 -225
- package/.aios-core/scripts/context-detector.js +0 -226
- package/.aios-core/scripts/elicitation-engine.js +0 -385
- package/.aios-core/scripts/elicitation-session-manager.js +0 -300
- package/.claude/CLAUDE.md +0 -221
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Context Detector - Hybrid Session Type Detection
|
|
3
|
-
*
|
|
4
|
-
* Detects session type using conversation history (preferred)
|
|
5
|
-
* with fallback to file-based session tracking.
|
|
6
|
-
*
|
|
7
|
-
* Session Types:
|
|
8
|
-
* - 'new': Fresh session, no prior context
|
|
9
|
-
* - 'existing': Ongoing session with some history
|
|
10
|
-
* - 'workflow': Active workflow detected (e.g., story development)
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
const fs = require('fs');
|
|
14
|
-
const path = require('path');
|
|
15
|
-
|
|
16
|
-
const SESSION_STATE_PATH = path.join(process.cwd(), '.aios', 'session-state.json');
|
|
17
|
-
const SESSION_TTL = 60 * 60 * 1000; // 1 hour
|
|
18
|
-
|
|
19
|
-
class ContextDetector {
|
|
20
|
-
/**
|
|
21
|
-
* Detect session type using hybrid approach
|
|
22
|
-
* @param {Array} conversationHistory - Recent conversation messages
|
|
23
|
-
* @param {string} sessionFilePath - Optional custom session file path
|
|
24
|
-
* @returns {string} 'new' | 'existing' | 'workflow'
|
|
25
|
-
*/
|
|
26
|
-
detectSessionType(conversationHistory = [], sessionFilePath = SESSION_STATE_PATH) {
|
|
27
|
-
// Hybrid approach: Prefer conversation history
|
|
28
|
-
// FIX: Check for null/undefined explicitly to avoid empty array bypassing file detection
|
|
29
|
-
if (conversationHistory != null && conversationHistory.length > 0) {
|
|
30
|
-
return this._detectFromConversation(conversationHistory);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Fallback to file-based detection
|
|
34
|
-
return this._detectFromFile(sessionFilePath);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Detect session type from conversation history
|
|
39
|
-
* @private
|
|
40
|
-
* @param {Array} conversationHistory - Recent conversation messages
|
|
41
|
-
* @returns {string} 'new' | 'existing' | 'workflow'
|
|
42
|
-
*/
|
|
43
|
-
_detectFromConversation(conversationHistory) {
|
|
44
|
-
if (conversationHistory.length === 0) {
|
|
45
|
-
return 'new';
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Extract last 10 commands from conversation
|
|
49
|
-
const recentCommands = this._extractCommands(conversationHistory);
|
|
50
|
-
|
|
51
|
-
// Check for workflow patterns
|
|
52
|
-
if (this._detectWorkflowPattern(recentCommands)) {
|
|
53
|
-
return 'workflow';
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Has history but no workflow
|
|
57
|
-
return 'existing';
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Detect session type from file
|
|
62
|
-
* @private
|
|
63
|
-
* @param {string} sessionFilePath - Path to session state file
|
|
64
|
-
* @returns {string} 'new' | 'existing' | 'workflow'
|
|
65
|
-
*/
|
|
66
|
-
_detectFromFile(sessionFilePath) {
|
|
67
|
-
try {
|
|
68
|
-
if (!fs.existsSync(sessionFilePath)) {
|
|
69
|
-
return 'new';
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const sessionData = JSON.parse(fs.readFileSync(sessionFilePath, 'utf8'));
|
|
73
|
-
|
|
74
|
-
// Check if session expired (TTL)
|
|
75
|
-
const now = Date.now();
|
|
76
|
-
const lastActivity = sessionData.lastActivity || 0;
|
|
77
|
-
|
|
78
|
-
if (now - lastActivity > SESSION_TTL) {
|
|
79
|
-
return 'new';
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Check for active workflow
|
|
83
|
-
if (sessionData.workflowActive && sessionData.lastCommands && sessionData.lastCommands.length > 0) {
|
|
84
|
-
return 'workflow';
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Valid session with some history
|
|
88
|
-
if (sessionData.lastCommands && sessionData.lastCommands.length > 0) {
|
|
89
|
-
return 'existing';
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return 'new';
|
|
93
|
-
} catch (error) {
|
|
94
|
-
// File read error or invalid JSON - assume new session
|
|
95
|
-
console.warn('[ContextDetector] File read error, defaulting to new session:', error.message);
|
|
96
|
-
return 'new';
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Extract command names from conversation history
|
|
102
|
-
* @private
|
|
103
|
-
* @param {Array} conversationHistory - Recent conversation messages
|
|
104
|
-
* @returns {Array<string>} Command names
|
|
105
|
-
*/
|
|
106
|
-
_extractCommands(conversationHistory) {
|
|
107
|
-
const commands = [];
|
|
108
|
-
const commandPattern = /\*([a-z0-9-]+)/g;
|
|
109
|
-
|
|
110
|
-
// Take last 10 messages for analysis
|
|
111
|
-
const recentMessages = conversationHistory.slice(-10);
|
|
112
|
-
|
|
113
|
-
for (const message of recentMessages) {
|
|
114
|
-
const text = message.content || message.text || '';
|
|
115
|
-
const matches = text.matchAll(commandPattern);
|
|
116
|
-
|
|
117
|
-
for (const match of matches) {
|
|
118
|
-
commands.push(match[1]);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return commands.slice(-10); // Last 10 commands
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Detect if commands match a known workflow pattern
|
|
127
|
-
* @private
|
|
128
|
-
* @param {Array<string>} commands - Recent command history
|
|
129
|
-
* @returns {boolean} True if workflow pattern detected
|
|
130
|
-
*/
|
|
131
|
-
_detectWorkflowPattern(commands) {
|
|
132
|
-
if (commands.length < 2) {
|
|
133
|
-
return false;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Common workflow patterns (simplified detection)
|
|
137
|
-
const workflows = {
|
|
138
|
-
story_development: ['validate-story-draft', 'develop', 'review-qa'],
|
|
139
|
-
epic_creation: ['create-epic', 'create-story', 'validate-story-draft'],
|
|
140
|
-
backlog_management: ['backlog-review', 'backlog-prioritize', 'backlog-schedule'],
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
// Check if recent commands match any workflow sequence
|
|
144
|
-
for (const [workflowName, pattern] of Object.entries(workflows)) {
|
|
145
|
-
if (this._matchesPattern(commands, pattern)) {
|
|
146
|
-
return true;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return false;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Check if commands contain workflow pattern
|
|
155
|
-
* @private
|
|
156
|
-
* @param {Array<string>} commands - Recent command history
|
|
157
|
-
* @param {Array<string>} pattern - Workflow pattern to match
|
|
158
|
-
* @returns {boolean} True if pattern found
|
|
159
|
-
*/
|
|
160
|
-
_matchesPattern(commands, pattern) {
|
|
161
|
-
// Simple containment check - commands contain at least 2 from pattern
|
|
162
|
-
const matchCount = pattern.filter(p => commands.includes(p)).length;
|
|
163
|
-
return matchCount >= 2;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Update session state file
|
|
168
|
-
* @param {Object} state - Session state data
|
|
169
|
-
* @param {string} sessionFilePath - Optional custom session file path
|
|
170
|
-
*/
|
|
171
|
-
updateSessionState(state, sessionFilePath = SESSION_STATE_PATH) {
|
|
172
|
-
try {
|
|
173
|
-
// Ensure directory exists
|
|
174
|
-
const dir = path.dirname(sessionFilePath);
|
|
175
|
-
if (!fs.existsSync(dir)) {
|
|
176
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const sessionData = {
|
|
180
|
-
sessionId: state.sessionId || this._generateSessionId(),
|
|
181
|
-
startTime: state.startTime || Date.now(),
|
|
182
|
-
lastActivity: Date.now(),
|
|
183
|
-
workflowActive: state.workflowActive || null,
|
|
184
|
-
lastCommands: state.lastCommands || [],
|
|
185
|
-
agentSequence: state.agentSequence || [],
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
fs.writeFileSync(sessionFilePath, JSON.stringify(sessionData, null, 2), 'utf8');
|
|
189
|
-
} catch (error) {
|
|
190
|
-
console.warn('[ContextDetector] Failed to update session state:', error.message);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Generate unique session ID
|
|
196
|
-
* @private
|
|
197
|
-
* @returns {string} UUID-like session ID
|
|
198
|
-
*/
|
|
199
|
-
_generateSessionId() {
|
|
200
|
-
return `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Clear expired sessions
|
|
205
|
-
* @param {string} sessionFilePath - Optional custom session file path
|
|
206
|
-
*/
|
|
207
|
-
clearExpiredSession(sessionFilePath = SESSION_STATE_PATH) {
|
|
208
|
-
try {
|
|
209
|
-
if (!fs.existsSync(sessionFilePath)) {
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const sessionData = JSON.parse(fs.readFileSync(sessionFilePath, 'utf8'));
|
|
214
|
-
const now = Date.now();
|
|
215
|
-
const lastActivity = sessionData.lastActivity || 0;
|
|
216
|
-
|
|
217
|
-
if (now - lastActivity > SESSION_TTL) {
|
|
218
|
-
fs.unlinkSync(sessionFilePath);
|
|
219
|
-
}
|
|
220
|
-
} catch (error) {
|
|
221
|
-
console.warn('[ContextDetector] Failed to clear expired session:', error.message);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
module.exports = ContextDetector;
|
|
@@ -1,385 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Interactive Elicitation Engine for Synkra AIOS
|
|
3
|
-
* Handles progressive disclosure and contextual validation for component creation
|
|
4
|
-
* @module elicitation-engine
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const inquirer = require('inquirer');
|
|
8
|
-
const fs = require('fs-extra');
|
|
9
|
-
const path = require('path');
|
|
10
|
-
const SecurityChecker = require('../infrastructure/scripts/security-checker');
|
|
11
|
-
const ElicitationSessionManager = require('./elicitation-session-manager');
|
|
12
|
-
const chalk = require('chalk');
|
|
13
|
-
|
|
14
|
-
class ElicitationEngine {
|
|
15
|
-
constructor() {
|
|
16
|
-
this.securityChecker = new SecurityChecker();
|
|
17
|
-
this.sessionManager = new ElicitationSessionManager();
|
|
18
|
-
this.sessionData = {};
|
|
19
|
-
this.sessionFile = null;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Start a new elicitation session
|
|
24
|
-
* @param {string} componentType - Type of component being created
|
|
25
|
-
* @param {Object} options - Session options
|
|
26
|
-
*/
|
|
27
|
-
async startSession(componentType, options = {}) {
|
|
28
|
-
this.sessionData = {
|
|
29
|
-
componentType,
|
|
30
|
-
startTime: new Date().toISOString(),
|
|
31
|
-
answers: {},
|
|
32
|
-
currentStep: 0,
|
|
33
|
-
options,
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
if (options.saveSession) {
|
|
37
|
-
this.sessionFile = path.join(
|
|
38
|
-
process.cwd(),
|
|
39
|
-
'.aios-sessions',
|
|
40
|
-
`${componentType}-${Date.now()}.json`,
|
|
41
|
-
);
|
|
42
|
-
await fs.ensureDir(path.dirname(this.sessionFile));
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Run progressive elicitation workflow
|
|
48
|
-
* @param {Array} steps - Array of elicitation steps
|
|
49
|
-
* @returns {Promise<Object>} Collected answers
|
|
50
|
-
*/
|
|
51
|
-
async runProgressive(steps) {
|
|
52
|
-
// If mocked, return mocked answers immediately
|
|
53
|
-
if (this.isMocked) {
|
|
54
|
-
this.isMocked = false;
|
|
55
|
-
return this.mockedAnswers;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
console.log(chalk.blue(`\n🚀 Starting ${this.sessionData.componentType} creation wizard...\n`));
|
|
59
|
-
|
|
60
|
-
for (let i = 0; i < steps.length; i++) {
|
|
61
|
-
const step = steps[i];
|
|
62
|
-
this.sessionData.currentStep = i;
|
|
63
|
-
|
|
64
|
-
// Show step header
|
|
65
|
-
console.log(chalk.yellow(`\n📋 Step ${i + 1}/${steps.length}: ${step.title}`));
|
|
66
|
-
if (step.description) {
|
|
67
|
-
console.log(chalk.gray(step.description));
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Check if step should be shown based on previous answers
|
|
71
|
-
if (step.condition && !this.evaluateCondition(step.condition)) {
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Run step questions
|
|
76
|
-
const stepAnswers = await this.runStep(step);
|
|
77
|
-
Object.assign(this.sessionData.answers, stepAnswers);
|
|
78
|
-
|
|
79
|
-
// Save session after each step
|
|
80
|
-
if (this.sessionFile) {
|
|
81
|
-
await this.saveSession();
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Allow early exit if requested
|
|
85
|
-
if (stepAnswers._exit) {
|
|
86
|
-
console.log(chalk.yellow('\n⚠️ Elicitation cancelled by user'));
|
|
87
|
-
return null;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return this.sessionData.answers;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Run a single elicitation step
|
|
96
|
-
* @private
|
|
97
|
-
*/
|
|
98
|
-
async runStep(step) {
|
|
99
|
-
const questions = step.questions.map(q => this.enhanceQuestion(q, step));
|
|
100
|
-
|
|
101
|
-
// Add contextual help if available
|
|
102
|
-
if (step.help) {
|
|
103
|
-
questions.unshift({
|
|
104
|
-
type: 'confirm',
|
|
105
|
-
name: '_showHelp',
|
|
106
|
-
message: 'Would you like to see help for this step?',
|
|
107
|
-
default: false,
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const answers = await inquirer.prompt(questions);
|
|
112
|
-
|
|
113
|
-
// Show help if requested
|
|
114
|
-
if (answers._showHelp && step.help) {
|
|
115
|
-
console.log(chalk.cyan('\n💡 ' + step.help));
|
|
116
|
-
delete answers._showHelp;
|
|
117
|
-
return this.runStep(step); // Re-run the step
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Validate answers
|
|
121
|
-
const validation = await this.validateStepAnswers(answers, step);
|
|
122
|
-
if (!validation.valid) {
|
|
123
|
-
console.log(chalk.red('\n❌ Validation errors:'));
|
|
124
|
-
validation.errors.forEach(err => console.log(chalk.red(` - ${err}`)));
|
|
125
|
-
return this.runStep(step); // Re-run the step
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return answers;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Enhance a question with smart defaults and validation
|
|
133
|
-
* @private
|
|
134
|
-
*/
|
|
135
|
-
enhanceQuestion(question, step) {
|
|
136
|
-
const enhanced = { ...question };
|
|
137
|
-
|
|
138
|
-
// Add smart defaults based on previous answers
|
|
139
|
-
if (question.smartDefault) {
|
|
140
|
-
enhanced.default = this.getSmartDefault(question.smartDefault);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Add validation with security checks
|
|
144
|
-
const originalValidate = enhanced.validate;
|
|
145
|
-
enhanced.validate = async (input) => {
|
|
146
|
-
// Type validation
|
|
147
|
-
if (typeof input !== 'string' && question.type === 'input') {
|
|
148
|
-
return 'Invalid input type';
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Security validation using the refactored SecurityChecker
|
|
152
|
-
// Note: SecurityChecker.checkCode expects string input for validation
|
|
153
|
-
const securityResult = this.securityChecker.checkCode(String(input));
|
|
154
|
-
if (!securityResult.valid) {
|
|
155
|
-
return `Security check failed: ${securityResult.errors[0]?.message || 'Invalid input'}`;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Original validation
|
|
159
|
-
if (originalValidate) {
|
|
160
|
-
const result = await originalValidate(input);
|
|
161
|
-
if (result !== true) return result;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Step-specific validation
|
|
165
|
-
if (step.validation && step.validation[question.name]) {
|
|
166
|
-
const validator = step.validation[question.name];
|
|
167
|
-
const result = await this.runValidator(validator, input);
|
|
168
|
-
if (result !== true) return result;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
return true;
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
// Add examples to message if available
|
|
175
|
-
if (question.examples && question.examples.length > 0) {
|
|
176
|
-
enhanced.message += chalk.gray(` (e.g., ${question.examples.join(', ')})`);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
return enhanced;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Get smart default value based on previous answers
|
|
184
|
-
* @private
|
|
185
|
-
*/
|
|
186
|
-
getSmartDefault(smartDefaultConfig) {
|
|
187
|
-
const { type, source, transform } = smartDefaultConfig;
|
|
188
|
-
|
|
189
|
-
switch (type) {
|
|
190
|
-
case 'fromAnswer':
|
|
191
|
-
const value = this.sessionData.answers[source];
|
|
192
|
-
return transform ? transform(value) : value;
|
|
193
|
-
|
|
194
|
-
case 'generated':
|
|
195
|
-
return this.generateDefault(smartDefaultConfig);
|
|
196
|
-
|
|
197
|
-
case 'conditional':
|
|
198
|
-
const condition = this.evaluateCondition(smartDefaultConfig.condition);
|
|
199
|
-
return condition ? smartDefaultConfig.ifTrue : smartDefaultConfig.ifFalse;
|
|
200
|
-
|
|
201
|
-
default:
|
|
202
|
-
return undefined;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Generate a default value
|
|
208
|
-
* @private
|
|
209
|
-
*/
|
|
210
|
-
generateDefault(config) {
|
|
211
|
-
switch (config.generator) {
|
|
212
|
-
case 'kebabCase':
|
|
213
|
-
const source = this.sessionData.answers[config.source] || '';
|
|
214
|
-
return source.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '');
|
|
215
|
-
|
|
216
|
-
case 'timestamp':
|
|
217
|
-
return new Date().toISOString();
|
|
218
|
-
|
|
219
|
-
case 'version':
|
|
220
|
-
return '1.0.0';
|
|
221
|
-
|
|
222
|
-
default:
|
|
223
|
-
return '';
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Evaluate a condition based on answers
|
|
229
|
-
* @private
|
|
230
|
-
*/
|
|
231
|
-
evaluateCondition(condition) {
|
|
232
|
-
const { field, operator, value } = condition;
|
|
233
|
-
const fieldValue = this.sessionData.answers[field];
|
|
234
|
-
|
|
235
|
-
switch (operator) {
|
|
236
|
-
case 'equals':
|
|
237
|
-
return fieldValue === value;
|
|
238
|
-
case 'notEquals':
|
|
239
|
-
return fieldValue !== value;
|
|
240
|
-
case 'includes':
|
|
241
|
-
return Array.isArray(fieldValue) && fieldValue.includes(value);
|
|
242
|
-
case 'exists':
|
|
243
|
-
return fieldValue !== undefined && fieldValue !== null;
|
|
244
|
-
default:
|
|
245
|
-
return true;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Validate step answers
|
|
251
|
-
* @private
|
|
252
|
-
*/
|
|
253
|
-
async validateStepAnswers(answers, step) {
|
|
254
|
-
const errors = [];
|
|
255
|
-
|
|
256
|
-
// Check required fields
|
|
257
|
-
if (step.required) {
|
|
258
|
-
for (const field of step.required) {
|
|
259
|
-
if (!answers[field]) {
|
|
260
|
-
errors.push(`${field} is required`);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Run custom validators
|
|
266
|
-
if (step.validators) {
|
|
267
|
-
for (const validator of step.validators) {
|
|
268
|
-
const result = await this.runValidator(validator, answers);
|
|
269
|
-
if (result !== true) {
|
|
270
|
-
errors.push(result);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
return {
|
|
276
|
-
valid: errors.length === 0,
|
|
277
|
-
errors,
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Run a validator function
|
|
283
|
-
* @private
|
|
284
|
-
*/
|
|
285
|
-
async runValidator(validator, value) {
|
|
286
|
-
if (typeof validator === 'function') {
|
|
287
|
-
return validator(value);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
if (typeof validator === 'object') {
|
|
291
|
-
switch (validator.type) {
|
|
292
|
-
case 'regex':
|
|
293
|
-
const regex = new RegExp(validator.pattern);
|
|
294
|
-
return regex.test(value) || validator.message;
|
|
295
|
-
|
|
296
|
-
case 'length':
|
|
297
|
-
if (validator.min && value.length < validator.min) {
|
|
298
|
-
return `Must be at least ${validator.min} characters`;
|
|
299
|
-
}
|
|
300
|
-
if (validator.max && value.length > validator.max) {
|
|
301
|
-
return `Must be at most ${validator.max} characters`;
|
|
302
|
-
}
|
|
303
|
-
return true;
|
|
304
|
-
|
|
305
|
-
case 'unique':
|
|
306
|
-
const exists = await this.checkExists(validator.path, value);
|
|
307
|
-
return !exists || `${value} already exists`;
|
|
308
|
-
|
|
309
|
-
default:
|
|
310
|
-
return true;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
return true;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* Check if a component already exists
|
|
319
|
-
* @private
|
|
320
|
-
*/
|
|
321
|
-
async checkExists(pathTemplate, name) {
|
|
322
|
-
const filePath = pathTemplate.replace('{name}', name);
|
|
323
|
-
return fs.pathExists(filePath);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Save current session to file
|
|
328
|
-
* @private
|
|
329
|
-
*/
|
|
330
|
-
async saveSession() {
|
|
331
|
-
if (this.sessionFile) {
|
|
332
|
-
await fs.writeJson(this.sessionFile, this.sessionData, { spaces: 2 });
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/**
|
|
337
|
-
* Load a saved session
|
|
338
|
-
* @param {string} sessionPath - Path to session file
|
|
339
|
-
*/
|
|
340
|
-
async loadSession(sessionPath) {
|
|
341
|
-
this.sessionData = await fs.readJson(sessionPath);
|
|
342
|
-
this.sessionFile = sessionPath;
|
|
343
|
-
return this.sessionData;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
* Get session summary
|
|
348
|
-
* @returns {Object} Summary of current session
|
|
349
|
-
*/
|
|
350
|
-
getSessionSummary() {
|
|
351
|
-
return {
|
|
352
|
-
componentType: this.sessionData.componentType,
|
|
353
|
-
completedSteps: this.sessionData.currentStep + 1,
|
|
354
|
-
answers: Object.keys(this.sessionData.answers).length,
|
|
355
|
-
duration: this.sessionData.startTime ?
|
|
356
|
-
Date.now() - new Date(this.sessionData.startTime).getTime() : 0,
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
/**
|
|
361
|
-
* Mock a session with predefined answers for batch creation
|
|
362
|
-
* @param {Object} answers - Predefined answers
|
|
363
|
-
*/
|
|
364
|
-
async mockSession(answers) {
|
|
365
|
-
this.mockedAnswers = answers;
|
|
366
|
-
this.isMocked = true;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
/**
|
|
370
|
-
* Complete elicitation session
|
|
371
|
-
* @param {string} status - Completion status
|
|
372
|
-
*/
|
|
373
|
-
async completeSession(status) {
|
|
374
|
-
if (this.currentSession) {
|
|
375
|
-
this.currentSession.status = status;
|
|
376
|
-
this.currentSession.completedAt = new Date().toISOString();
|
|
377
|
-
|
|
378
|
-
if (this.currentSession.saveSession) {
|
|
379
|
-
await this.sessionManager.saveSession(this.currentSession);
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
module.exports = ElicitationEngine;
|