aios-core 4.2.13 → 4.2.15
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/core/code-intel/helpers/dev-helper.js +206 -0
- package/.aios-core/core/registry/registry-schema.json +166 -166
- package/.aios-core/core/synapse/diagnostics/collectors/hook-collector.js +3 -3
- package/.aios-core/data/entity-registry.yaml +27 -0
- package/.aios-core/development/scripts/approval-workflow.js +642 -642
- package/.aios-core/development/scripts/backup-manager.js +606 -606
- package/.aios-core/development/scripts/branch-manager.js +389 -389
- package/.aios-core/development/scripts/code-quality-improver.js +1311 -1311
- package/.aios-core/development/scripts/commit-message-generator.js +849 -849
- package/.aios-core/development/scripts/conflict-resolver.js +674 -674
- package/.aios-core/development/scripts/dependency-analyzer.js +637 -637
- package/.aios-core/development/scripts/diff-generator.js +351 -351
- package/.aios-core/development/scripts/elicitation-engine.js +384 -384
- package/.aios-core/development/scripts/elicitation-session-manager.js +299 -299
- package/.aios-core/development/scripts/git-wrapper.js +461 -461
- package/.aios-core/development/scripts/manifest-preview.js +244 -244
- package/.aios-core/development/scripts/metrics-tracker.js +775 -775
- package/.aios-core/development/scripts/modification-validator.js +554 -554
- package/.aios-core/development/scripts/pattern-learner.js +1224 -1224
- package/.aios-core/development/scripts/performance-analyzer.js +757 -757
- package/.aios-core/development/scripts/refactoring-suggester.js +1138 -1138
- package/.aios-core/development/scripts/rollback-handler.js +530 -530
- package/.aios-core/development/scripts/security-checker.js +358 -358
- package/.aios-core/development/scripts/template-engine.js +239 -239
- package/.aios-core/development/scripts/template-validator.js +278 -278
- package/.aios-core/development/scripts/test-generator.js +843 -843
- package/.aios-core/development/scripts/transaction-manager.js +589 -589
- package/.aios-core/development/scripts/usage-tracker.js +673 -673
- package/.aios-core/development/scripts/validate-filenames.js +226 -226
- package/.aios-core/development/scripts/version-tracker.js +526 -526
- package/.aios-core/development/scripts/yaml-validator.js +396 -396
- package/.aios-core/development/tasks/build-autonomous.md +10 -4
- package/.aios-core/development/tasks/create-service.md +23 -0
- package/.aios-core/development/tasks/dev-develop-story.md +12 -6
- package/.aios-core/development/tasks/dev-suggest-refactoring.md +7 -1
- package/.aios-core/development/tasks/publish-npm.md +3 -3
- package/.aios-core/hooks/unified/README.md +1 -1
- package/.aios-core/install-manifest.yaml +65 -61
- package/.aios-core/manifests/schema/manifest-schema.json +190 -190
- package/.aios-core/product/templates/component-react-tmpl.tsx +98 -98
- 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/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/shock-report-tmpl.html +502 -502
- 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/scripts/migrate-framework-docs.sh +300 -300
- package/README.en.md +747 -0
- package/README.md +4 -2
- package/bin/aios.js +7 -4
- package/package.json +1 -1
- package/packages/aios-pro-cli/src/recover.js +1 -1
- package/packages/installer/src/wizard/ide-config-generator.js +6 -6
- package/packages/installer/src/wizard/pro-setup.js +3 -3
- package/pro/license/degradation.js +220 -220
- package/pro/license/errors.js +450 -450
- package/pro/license/feature-gate.js +354 -354
- package/pro/license/index.js +181 -181
- package/pro/license/license-cache.js +523 -523
- package/pro/license/license-crypto.js +303 -303
- package/scripts/package-synapse.js +5 -5
- package/scripts/validate-package-completeness.js +3 -3
- package/.aios-core/.session/current-session.json +0 -14
- package/.aios-core/data/registry-update-log.jsonl +0 -191
- package/.aios-core/docs/SHARD-TRANSLATION-GUIDE.md +0 -335
- package/.aios-core/docs/component-creation-guide.md +0 -458
- package/.aios-core/docs/session-update-pattern.md +0 -307
- package/.aios-core/docs/standards/AIOS-FRAMEWORK-MASTER.md +0 -1963
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1-SUMMARY.md +0 -1190
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1.md +0 -439
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO.md +0 -5398
- package/.aios-core/docs/standards/V3-ARCHITECTURAL-DECISIONS.md +0 -523
- package/.aios-core/docs/template-syntax.md +0 -267
- package/.aios-core/docs/troubleshooting-guide.md +0 -625
- package/.aios-core/infrastructure/tests/utilities-audit-results.json +0 -501
- package/.aios-core/manifests/agents.csv +0 -29
- package/.aios-core/manifests/tasks.csv +0 -198
- package/.aios-core/manifests/workers.csv +0 -204
- package/.claude/rules/agent-authority.md +0 -105
- package/.claude/rules/coderabbit-integration.md +0 -93
- package/.claude/rules/ids-principles.md +0 -112
- package/.claude/rules/story-lifecycle.md +0 -139
- package/.claude/rules/workflow-execution.md +0 -150
- package/scripts/glue/README.md +0 -355
- package/scripts/glue/compose-agent-prompt.cjs +0 -362
- /package/.claude/hooks/{precompact-session-digest.js → precompact-session-digest.cjs} +0 -0
- /package/.claude/hooks/{synapse-engine.js → synapse-engine.cjs} +0 -0
|
@@ -1,385 +1,385 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Interactive Elicitation Engine for AIOS-FULLSTACK
|
|
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('./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
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Interactive Elicitation Engine for AIOS-FULLSTACK
|
|
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('./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
385
|
module.exports = ElicitationEngine;
|