aios-core 2.2.2 → 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.
Files changed (105) hide show
  1. package/.aios-core/.session/current-session.json +14 -14
  2. package/.aios-core/cli/commands/migrate/validate.js +1 -1
  3. package/.aios-core/core/docs/session-update-pattern.md +17 -10
  4. package/.aios-core/core/elicitation/elicitation-engine.js +11 -6
  5. package/.aios-core/core/elicitation/session-manager.js +2 -1
  6. package/.aios-core/core/registry/registry-schema.json +166 -166
  7. package/.aios-core/core/registry/service-registry.json +6585 -6585
  8. package/.aios-core/core-config.yaml +12 -1
  9. package/.aios-core/data/agent-config-requirements.yaml +5 -5
  10. package/.aios-core/development/agents/devops.md +12 -0
  11. package/.aios-core/development/scripts/squad/README.md +112 -0
  12. package/.aios-core/development/scripts/squad/index.js +41 -0
  13. package/.aios-core/development/scripts/squad/squad-loader.js +359 -0
  14. package/.aios-core/development/scripts/squad/squad-validator.js +685 -0
  15. package/.aios-core/development/tasks/add-mcp.md +11 -5
  16. package/.aios-core/development/tasks/search-mcp.md +309 -0
  17. package/.aios-core/development/tasks/setup-mcp-docker.md +11 -8
  18. package/.aios-core/development/tasks/squad-creator-validate.md +151 -0
  19. package/.aios-core/docs/standards/AGENT-PERSONALIZATION-STANDARD-V1.md +3 -3
  20. package/.aios-core/index.d.ts +7 -7
  21. package/.aios-core/index.js +1 -1
  22. package/.aios-core/infrastructure/scripts/batch-creator.js +1 -1
  23. package/.aios-core/infrastructure/scripts/component-generator.js +1 -1
  24. package/.aios-core/infrastructure/templates/coderabbit.yaml.template +279 -279
  25. package/.aios-core/infrastructure/templates/github-workflows/ci.yml.template +169 -169
  26. package/.aios-core/infrastructure/templates/github-workflows/pr-automation.yml.template +330 -330
  27. package/.aios-core/infrastructure/templates/github-workflows/release.yml.template +196 -196
  28. package/.aios-core/infrastructure/templates/gitignore/gitignore-aios-base.tmpl +63 -63
  29. package/.aios-core/infrastructure/templates/gitignore/gitignore-brownfield-merge.tmpl +18 -18
  30. package/.aios-core/infrastructure/templates/gitignore/gitignore-node.tmpl +85 -85
  31. package/.aios-core/infrastructure/templates/gitignore/gitignore-python.tmpl +145 -145
  32. package/.aios-core/infrastructure/tests/utilities-audit-results.json +500 -500
  33. package/.aios-core/infrastructure/tools/README.md +1 -1
  34. package/.aios-core/install-manifest.yaml +4 -1
  35. package/.aios-core/manifests/schema/manifest-schema.json +190 -190
  36. package/.aios-core/manifests/workers.csv +203 -203
  37. package/.aios-core/package.json +102 -102
  38. package/.aios-core/product/templates/activation-instructions-template.md +7 -7
  39. package/.aios-core/product/templates/adr.hbs +125 -125
  40. package/.aios-core/product/templates/component-react-tmpl.tsx +98 -98
  41. package/.aios-core/product/templates/dbdr.hbs +241 -241
  42. package/.aios-core/product/templates/engine/schemas/adr.schema.json +102 -102
  43. package/.aios-core/product/templates/engine/schemas/dbdr.schema.json +205 -205
  44. package/.aios-core/product/templates/engine/schemas/epic.schema.json +175 -175
  45. package/.aios-core/product/templates/engine/schemas/pmdr.schema.json +175 -175
  46. package/.aios-core/product/templates/engine/schemas/prd-v2.schema.json +300 -300
  47. package/.aios-core/product/templates/engine/schemas/prd.schema.json +152 -152
  48. package/.aios-core/product/templates/engine/schemas/story.schema.json +222 -222
  49. package/.aios-core/product/templates/engine/schemas/task.schema.json +154 -154
  50. package/.aios-core/product/templates/epic.hbs +212 -212
  51. package/.aios-core/product/templates/eslintrc-security.json +32 -32
  52. package/.aios-core/product/templates/github-actions-cd.yml +212 -212
  53. package/.aios-core/product/templates/github-actions-ci.yml +172 -172
  54. package/.aios-core/product/templates/pmdr.hbs +186 -186
  55. package/.aios-core/product/templates/prd-v2.0.hbs +216 -216
  56. package/.aios-core/product/templates/prd.hbs +201 -201
  57. package/.aios-core/product/templates/shock-report-tmpl.html +502 -502
  58. package/.aios-core/product/templates/story.hbs +263 -263
  59. package/.aios-core/product/templates/task.hbs +170 -170
  60. package/.aios-core/product/templates/tmpl-comment-on-examples.sql +158 -158
  61. package/.aios-core/product/templates/tmpl-migration-script.sql +91 -91
  62. package/.aios-core/product/templates/tmpl-rls-granular-policies.sql +104 -104
  63. package/.aios-core/product/templates/tmpl-rls-kiss-policy.sql +10 -10
  64. package/.aios-core/product/templates/tmpl-rls-roles.sql +135 -135
  65. package/.aios-core/product/templates/tmpl-rls-simple.sql +77 -77
  66. package/.aios-core/product/templates/tmpl-rls-tenant.sql +152 -152
  67. package/.aios-core/product/templates/tmpl-rollback-script.sql +77 -77
  68. package/.aios-core/product/templates/tmpl-seed-data.sql +140 -140
  69. package/.aios-core/product/templates/tmpl-smoke-test.sql +16 -16
  70. package/.aios-core/product/templates/tmpl-staging-copy-merge.sql +139 -139
  71. package/.aios-core/product/templates/tmpl-stored-proc.sql +140 -140
  72. package/.aios-core/product/templates/tmpl-trigger.sql +152 -152
  73. package/.aios-core/product/templates/tmpl-view-materialized.sql +133 -133
  74. package/.aios-core/product/templates/tmpl-view.sql +177 -177
  75. package/.aios-core/product/templates/token-exports-css-tmpl.css +240 -240
  76. package/.aios-core/quality/schemas/quality-metrics.schema.json +233 -233
  77. package/.aios-core/schemas/squad-schema.json +185 -0
  78. package/.aios-core/scripts/README.md +90 -322
  79. package/.aios-core/scripts/migrate-framework-docs.sh +300 -300
  80. package/.claude/rules/mcp-usage.md +116 -100
  81. package/LICENSE +48 -48
  82. package/README.md +3 -4
  83. package/bin/aios.js +2 -1
  84. package/package.json +1 -3
  85. package/packages/installer/package.json +39 -39
  86. package/templates/squad/LICENSE +21 -21
  87. package/templates/squad/README.md +37 -37
  88. package/templates/squad/agents/example-agent.yaml +36 -36
  89. package/templates/squad/package.json +19 -19
  90. package/templates/squad/squad.yaml +25 -25
  91. package/templates/squad/tasks/example-task.yaml +46 -46
  92. package/templates/squad/templates/example-template.md +24 -24
  93. package/templates/squad/tests/example-agent.test.js +53 -53
  94. package/templates/squad/workflows/example-workflow.yaml +54 -54
  95. package/tools/diagnose-npx-issue.ps1 +96 -96
  96. package/tools/quick-diagnose.cmd +85 -85
  97. package/tools/quick-diagnose.ps1 +117 -117
  98. package/.aios-core/core/data/agent-config-requirements.yaml +0 -368
  99. package/.aios-core/core/data/aios-kb.md +0 -924
  100. package/.aios-core/core/data/workflow-patterns.yaml +0 -267
  101. package/.aios-core/product/templates/1mcp-config.yaml +0 -225
  102. package/.aios-core/scripts/context-detector.js +0 -226
  103. package/.aios-core/scripts/elicitation-engine.js +0 -385
  104. package/.aios-core/scripts/elicitation-session-manager.js +0 -300
  105. 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;