sinapse-ai 1.6.1 → 1.8.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.
Files changed (131) hide show
  1. package/.claude/CLAUDE.md +5 -11
  2. package/.claude/hooks/README.md +14 -1
  3. package/.claude/hooks/code-intel-pretool.cjs +115 -0
  4. package/.claude/hooks/enforce-delegation.cjs +31 -3
  5. package/.claude/hooks/enforce-framework-boundary.cjs +324 -0
  6. package/.claude/hooks/enforce-permission-mode.cjs +249 -0
  7. package/.claude/hooks/secret-scanning.cjs +34 -43
  8. package/.claude/hooks/synapse-engine.cjs +23 -23
  9. package/.claude/hooks/telemetry-post-tool.cjs +128 -0
  10. package/.claude/hooks/telemetry-stop.cjs +132 -0
  11. package/.claude/hooks/verify-packages.cjs +9 -2
  12. package/.claude/rules/documentation-first.md +1 -1
  13. package/.claude/rules/hook-governance.md +2 -0
  14. package/.sinapse-ai/cli/commands/health/index.js +24 -0
  15. package/.sinapse-ai/core/README.md +11 -0
  16. package/.sinapse-ai/core/config/config-loader.js +19 -0
  17. package/.sinapse-ai/core/config/merge-utils.js +8 -0
  18. package/.sinapse-ai/core/errors/constants.js +147 -0
  19. package/.sinapse-ai/core/errors/error-registry.js +176 -0
  20. package/.sinapse-ai/core/errors/index.js +50 -0
  21. package/.sinapse-ai/core/errors/serializer.js +147 -0
  22. package/.sinapse-ai/core/errors/sinapse-error.js +144 -0
  23. package/.sinapse-ai/core/errors/utils.js +187 -0
  24. package/.sinapse-ai/core/execution/build-orchestrator.js +47 -49
  25. package/.sinapse-ai/core/execution/build-state-manager.js +183 -31
  26. package/.sinapse-ai/core/execution/parallel-executor.js +7 -1
  27. package/.sinapse-ai/core/execution/semantic-merge-engine.js +26 -14
  28. package/.sinapse-ai/core/execution/subagent-dispatcher.js +201 -60
  29. package/.sinapse-ai/core/execution/wave-executor.js +4 -1
  30. package/.sinapse-ai/core/grounding/README.md +71 -11
  31. package/.sinapse-ai/core/health-check/checks/project/framework-config.js +38 -2
  32. package/.sinapse-ai/core/health-check/checks/project/package-json.js +47 -3
  33. package/.sinapse-ai/core/health-check/checks/services/gemini-cli.js +117 -0
  34. package/.sinapse-ai/core/health-check/checks/services/index.js +2 -0
  35. package/.sinapse-ai/core/health-check/healers/index.js +40 -3
  36. package/.sinapse-ai/core/ideation/ideation-engine.js +212 -107
  37. package/.sinapse-ai/core/ids/gate-evaluator.js +318 -0
  38. package/.sinapse-ai/core/ids/gates/g5-semantic-handshake.js +190 -0
  39. package/.sinapse-ai/core/ids/gates/g6-ci-integrity.js +162 -0
  40. package/.sinapse-ai/core/ids/index.js +30 -0
  41. package/.sinapse-ai/core/memory/__tests__/active-modules.verify.js +11 -0
  42. package/.sinapse-ai/core/memory/gotchas-memory.js +37 -2
  43. package/.sinapse-ai/core/orchestration/agent-invoker.js +29 -6
  44. package/.sinapse-ai/core/orchestration/brownfield-handler.js +36 -3
  45. package/.sinapse-ai/core/orchestration/condition-evaluator.js +57 -0
  46. package/.sinapse-ai/core/orchestration/executors/epic-3-executor.js +76 -5
  47. package/.sinapse-ai/core/orchestration/executors/epic-4-executor.js +63 -17
  48. package/.sinapse-ai/core/orchestration/executors/epic-6-executor.js +153 -41
  49. package/.sinapse-ai/core/orchestration/executors/epic-executor.js +40 -0
  50. package/.sinapse-ai/core/orchestration/greenfield-handler.js +87 -3
  51. package/.sinapse-ai/core/orchestration/master-orchestrator.js +150 -10
  52. package/.sinapse-ai/core/orchestration/parallel-executor.js +6 -1
  53. package/.sinapse-ai/core/orchestration/recovery-handler.js +81 -8
  54. package/.sinapse-ai/core/orchestration/workflow-executor.js +41 -0
  55. package/.sinapse-ai/core/registry/registry-loader.js +71 -5
  56. package/.sinapse-ai/core/registry/squad-agent-resolver.js +253 -0
  57. package/.sinapse-ai/core/synapse/context/context-tracker.js +104 -9
  58. package/.sinapse-ai/core/synapse/context/index.js +19 -0
  59. package/.sinapse-ai/core/synapse/context/semantic-handshake-engine.js +555 -0
  60. package/.sinapse-ai/core/synapse/diagnostics/collectors/pipeline-collector.js +4 -2
  61. package/.sinapse-ai/core/synapse/engine.js +43 -3
  62. package/.sinapse-ai/core/telemetry/ids-sink.js +188 -0
  63. package/.sinapse-ai/core/utils/output-formatter.js +8 -290
  64. package/.sinapse-ai/core/utils/spawn-safe.js +186 -0
  65. package/.sinapse-ai/core-config.yaml +68 -1
  66. package/.sinapse-ai/data/entity-registry.yaml +15082 -13618
  67. package/.sinapse-ai/data/registry-update-log.jsonl +143 -0
  68. package/.sinapse-ai/development/agents/developer.md +2 -0
  69. package/.sinapse-ai/development/agents/devops.md +9 -0
  70. package/.sinapse-ai/development/external-executors/README.md +18 -0
  71. package/.sinapse-ai/development/external-executors/codex.md +56 -0
  72. package/.sinapse-ai/development/scripts/populate-entity-registry.js +65 -9
  73. package/.sinapse-ai/development/scripts/squad/squad-downloader.js +169 -14
  74. package/.sinapse-ai/development/tasks/delegate-to-external-executor.md +152 -0
  75. package/.sinapse-ai/development/tasks/github-devops-pre-push-quality-gate.md +46 -29
  76. package/.sinapse-ai/development/tasks/update-sinapse.md +3 -3
  77. package/.sinapse-ai/hooks/sinapse-brand-grounding.cjs +4 -7
  78. package/.sinapse-ai/hooks/sinapse-ds-grounding.cjs +5 -8
  79. package/.sinapse-ai/hooks/sinapse-vault-grounding.cjs +6 -9
  80. package/.sinapse-ai/infrastructure/integrations/ai-providers/ai-provider-factory.js +4 -1
  81. package/.sinapse-ai/infrastructure/integrations/ai-providers/claude-provider.js +57 -55
  82. package/.sinapse-ai/infrastructure/integrations/pm-adapters/github-adapter.js +9 -7
  83. package/.sinapse-ai/infrastructure/scripts/ide-sync/gemini-commands.js +298 -0
  84. package/.sinapse-ai/infrastructure/scripts/ide-sync/index.js +127 -6
  85. package/.sinapse-ai/infrastructure/scripts/ide-sync/persona-renderer.js +97 -0
  86. package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/antigravity.js +121 -0
  87. package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/cursor.js +119 -0
  88. package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/github-copilot.js +191 -0
  89. package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/kimi.js +448 -0
  90. package/.sinapse-ai/install-manifest.yaml +218 -114
  91. package/.sinapse-ai/product/templates/engine/renderer.js +20 -1
  92. package/.sinapse-ai/scripts/pm.sh +18 -6
  93. package/bin/cli.js +17 -0
  94. package/bin/commands/agents.js +96 -0
  95. package/bin/commands/doctor.js +15 -0
  96. package/bin/commands/ideate.js +129 -0
  97. package/bin/commands/uninstall.js +40 -0
  98. package/bin/postinstall.js +50 -4
  99. package/bin/sinapse.js +146 -2
  100. package/bin/utils/secret-scanner-core.js +253 -0
  101. package/bin/utils/staged-secret-scan.js +106 -40
  102. package/docs/framework/collaboration-autonomy-plan.md +18 -18
  103. package/docs/guides/parallel-workflow.md +6 -6
  104. package/package.json +22 -5
  105. package/packages/installer/src/installer/git-hooks-installer.js +384 -0
  106. package/packages/installer/src/installer/sinapse-ai-installer.js +16 -0
  107. package/packages/installer/src/wizard/ide-config-generator.js +23 -0
  108. package/packages/installer/src/wizard/validators.js +38 -1
  109. package/packages/installer/tests/unit/artifact-copy-pipeline/artifact-copy-pipeline.test.js +5 -1
  110. package/packages/installer/tests/unit/doctor/doctor-checks.test.js +44 -22
  111. package/packages/installer/tests/unit/git-hooks-installer.test.js +262 -0
  112. package/scripts/eval-runner.js +422 -0
  113. package/scripts/generate-install-manifest.js +13 -9
  114. package/scripts/generate-synapse-runtime.js +51 -0
  115. package/scripts/regenerate-orqx-stubs.ps1 +6 -5
  116. package/scripts/validate-all.js +1 -0
  117. package/scripts/validate-evals.js +466 -0
  118. package/scripts/validate-schemas.js +539 -0
  119. package/scripts/validate-squad-orqx.js +9 -2
  120. package/squads/claude-code-mastery/knowledge-base/memory-systems-reference.md +1 -1
  121. package/squads/squad-brand/templates/client-delivery-template.md +1 -1
  122. package/squads/squad-content/knowledge-base/social-compression-framework.md +1 -1
  123. package/squads/squad-council/knowledge-base/brand-strategy-models.md +1 -1
  124. package/.sinapse-ai/development/scripts/elicitation-engine.js +0 -385
  125. package/.sinapse-ai/development/scripts/elicitation-session-manager.js +0 -300
  126. package/.sinapse-ai/development/tasks/test-validation-task.md +0 -172
  127. package/docs/chrome-brain-upgrade-plan.md +0 -624
  128. package/docs/constitution-compliance.md +0 -87
  129. package/docs/mega-upgrade-orchestration-plan.md +0 -71
  130. package/docs/research-synthesis-for-upgrade.md +0 -511
  131. package/docs/security-audit-report.md +0 -306
@@ -1,385 +0,0 @@
1
- /**
2
- * Interactive Elicitation Engine for SINAPSE-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
- '.sinapse-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;
@@ -1,300 +0,0 @@
1
- /**
2
- * Elicitation Session Manager
3
- * Handles saving and loading elicitation sessions
4
- * @module elicitation-session-manager
5
- */
6
-
7
- const fs = require('fs-extra');
8
- const path = require('path');
9
- const crypto = require('crypto');
10
-
11
- class ElicitationSessionManager {
12
- constructor(sessionDir = '.sinapse-sessions') {
13
- this.sessionDir = path.resolve(process.cwd(), sessionDir);
14
- this.activeSession = null;
15
- }
16
-
17
- /**
18
- * Initialize session storage
19
- */
20
- async init() {
21
- await fs.ensureDir(this.sessionDir);
22
- }
23
-
24
- /**
25
- * Create a new session
26
- * @param {string} type - Component type (agent, task, workflow)
27
- * @param {Object} metadata - Additional session metadata
28
- * @returns {Promise<string>} Session ID
29
- */
30
- async createSession(type, metadata = {}) {
31
- const sessionId = this.generateSessionId();
32
- const session = {
33
- id: sessionId,
34
- type,
35
- version: '1.0',
36
- created: new Date().toISOString(),
37
- updated: new Date().toISOString(),
38
- status: 'active',
39
- currentStep: 0,
40
- totalSteps: 0,
41
- answers: {},
42
- metadata: {
43
- ...metadata,
44
- user: process.env.USER || 'unknown',
45
- hostname: require('os').hostname()
46
- }
47
- };
48
-
49
- this.activeSession = session;
50
- await this.saveSession(session);
51
-
52
- return sessionId;
53
- }
54
-
55
- /**
56
- * Save current session state
57
- * @param {Object} session - Session data to save
58
- */
59
- async saveSession(session = null) {
60
- const sessionToSave = session || this.activeSession;
61
- if (!sessionToSave) {
62
- throw new Error('No active session to save');
63
- }
64
-
65
- sessionToSave.updated = new Date().toISOString();
66
-
67
- const sessionPath = this.getSessionPath(sessionToSave.id);
68
- await fs.writeJson(sessionPath, sessionToSave, { spaces: 2 });
69
- }
70
-
71
- /**
72
- * Load an existing session
73
- * @param {string} sessionId - Session ID to load
74
- * @returns {Promise<Object>} Session data
75
- */
76
- async loadSession(sessionId) {
77
- const sessionPath = this.getSessionPath(sessionId);
78
-
79
- if (!await fs.pathExists(sessionPath)) {
80
- throw new Error(`Session ${sessionId} not found`);
81
- }
82
-
83
- const session = await fs.readJson(sessionPath);
84
- this.activeSession = session;
85
-
86
- return session;
87
- }
88
-
89
- /**
90
- * Update session answers
91
- * @param {Object} answers - New answers to merge
92
- * @param {number} stepIndex - Current step index
93
- */
94
- async updateAnswers(answers, stepIndex = null) {
95
- if (!this.activeSession) {
96
- throw new Error('No active session');
97
- }
98
-
99
- // Merge answers
100
- Object.assign(this.activeSession.answers, answers);
101
-
102
- // Update step index if provided
103
- if (stepIndex !== null) {
104
- this.activeSession.currentStep = stepIndex;
105
- }
106
-
107
- await this.saveSession();
108
- }
109
-
110
- /**
111
- * List all sessions
112
- * @param {Object} filters - Filter options
113
- * @returns {Promise<Array>} List of sessions
114
- */
115
- async listSessions(filters = {}) {
116
- const files = await fs.readdir(this.sessionDir);
117
- const sessions = [];
118
-
119
- for (const file of files) {
120
- if (file.endsWith('.json')) {
121
- try {
122
- const sessionPath = path.join(this.sessionDir, file);
123
- const session = await fs.readJson(sessionPath);
124
-
125
- // Apply filters
126
- if (filters.type && session.type !== filters.type) continue;
127
- if (filters.status && session.status !== filters.status) continue;
128
- if (filters.after && new Date(session.created) < new Date(filters.after)) continue;
129
-
130
- sessions.push({
131
- id: session.id,
132
- type: session.type,
133
- created: session.created,
134
- updated: session.updated,
135
- status: session.status,
136
- progress: session.totalSteps > 0 ?
137
- Math.round((session.currentStep / session.totalSteps) * 100) : 0
138
- });
139
- } catch (_error) {
140
- // Skip invalid session files
141
- console.warn(`Invalid session file: ${file}`);
142
- }
143
- }
144
- }
145
-
146
- // Sort by updated date (newest first)
147
- sessions.sort((a, b) => new Date(b.updated) - new Date(a.updated));
148
-
149
- return sessions;
150
- }
151
-
152
- /**
153
- * Resume a session
154
- * @param {string} sessionId - Session ID to resume
155
- * @returns {Promise<Object>} Session data with resume info
156
- */
157
- async resumeSession(sessionId) {
158
- const session = await this.loadSession(sessionId);
159
-
160
- // Calculate resume information
161
- const resumeInfo = {
162
- ...session,
163
- resumeFrom: session.currentStep,
164
- completedSteps: Object.keys(session.answers).length,
165
- remainingSteps: session.totalSteps - session.currentStep,
166
- percentComplete: session.totalSteps > 0 ?
167
- Math.round((session.currentStep / session.totalSteps) * 100) : 0
168
- };
169
-
170
- return resumeInfo;
171
- }
172
-
173
- /**
174
- * Complete a session
175
- * @param {string} result - Completion result (success, cancelled, error)
176
- */
177
- async completeSession(result = 'success') {
178
- if (!this.activeSession) {
179
- throw new Error('No active session');
180
- }
181
-
182
- this.activeSession.status = 'completed';
183
- this.activeSession.completedAt = new Date().toISOString();
184
- this.activeSession.result = result;
185
-
186
- await this.saveSession();
187
-
188
- // Move to completed directory if success
189
- if (result === 'success') {
190
- const completedDir = path.join(this.sessionDir, 'completed');
191
- await fs.ensureDir(completedDir);
192
-
193
- const oldPath = this.getSessionPath(this.activeSession.id);
194
- const newPath = path.join(completedDir, path.basename(oldPath));
195
-
196
- await fs.move(oldPath, newPath, { overwrite: true });
197
- }
198
-
199
- this.activeSession = null;
200
- }
201
-
202
- /**
203
- * Delete a session
204
- * @param {string} sessionId - Session ID to delete
205
- */
206
- async deleteSession(sessionId) {
207
- const sessionPath = this.getSessionPath(sessionId);
208
- const completedPath = path.join(this.sessionDir, 'completed', `${sessionId}.json`);
209
-
210
- // Check both active and completed directories
211
- if (await fs.pathExists(sessionPath)) {
212
- await fs.remove(sessionPath);
213
- } else if (await fs.pathExists(completedPath)) {
214
- await fs.remove(completedPath);
215
- } else {
216
- throw new Error(`Session ${sessionId} not found`);
217
- }
218
-
219
- // Clear active session if it matches
220
- if (this.activeSession && this.activeSession.id === sessionId) {
221
- this.activeSession = null;
222
- }
223
- }
224
-
225
- /**
226
- * Export session data
227
- * @param {string} sessionId - Session ID to export
228
- * @param {string} format - Export format (json, yaml)
229
- * @returns {Promise<string>} Exported data
230
- */
231
- async exportSession(sessionId, format = 'json') {
232
- const session = await this.loadSession(sessionId);
233
-
234
- switch (format) {
235
- case 'json':
236
- return JSON.stringify(session, null, 2);
237
-
238
- case 'yaml':
239
- const yaml = require('js-yaml');
240
- return yaml.dump(session);
241
-
242
- default:
243
- throw new Error(`Unsupported export format: ${format}`);
244
- }
245
- }
246
-
247
- /**
248
- * Clean up old sessions
249
- * @param {number} daysOld - Delete sessions older than this many days
250
- */
251
- async cleanupOldSessions(daysOld = 30) {
252
- const sessions = await this.listSessions();
253
- const cutoffDate = new Date();
254
- cutoffDate.setDate(cutoffDate.getDate() - daysOld);
255
-
256
- let deletedCount = 0;
257
-
258
- for (const session of sessions) {
259
- if (new Date(session.updated) < cutoffDate && session.status !== 'active') {
260
- await this.deleteSession(session.id);
261
- deletedCount++;
262
- }
263
- }
264
-
265
- return deletedCount;
266
- }
267
-
268
- /**
269
- * Generate a unique session ID
270
- * @private
271
- */
272
- generateSessionId() {
273
- return crypto.randomBytes(8).toString('hex');
274
- }
275
-
276
- /**
277
- * Get session file path
278
- * @private
279
- */
280
- getSessionPath(sessionId) {
281
- return path.join(this.sessionDir, `${sessionId}.json`);
282
- }
283
-
284
- /**
285
- * Get active session
286
- * @returns {Object|null} Active session or null
287
- */
288
- getActiveSession() {
289
- return this.activeSession;
290
- }
291
-
292
- /**
293
- * Clear active session
294
- */
295
- clearActiveSession() {
296
- this.activeSession = null;
297
- }
298
- }
299
-
300
- module.exports = ElicitationSessionManager;