aios-core 4.2.6 → 4.2.7

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 (26) hide show
  1. package/.aios-core/core/orchestration/context-manager.js +333 -5
  2. package/.aios-core/core/orchestration/dashboard-integration.js +17 -1
  3. package/.aios-core/core/orchestration/execution-profile-resolver.js +107 -0
  4. package/.aios-core/core/orchestration/index.js +3 -0
  5. package/.aios-core/core/orchestration/skill-dispatcher.js +2 -0
  6. package/.aios-core/core/orchestration/subagent-prompt-builder.js +2 -0
  7. package/.aios-core/core/orchestration/workflow-orchestrator.js +113 -5
  8. package/.aios-core/data/entity-registry.yaml +1114 -1336
  9. package/.aios-core/development/agents/ux-design-expert.md +1 -1
  10. package/.aios-core/development/checklists/brownfield-compatibility-checklist.md +114 -0
  11. package/.aios-core/development/scripts/workflow-state-manager.js +128 -1
  12. package/.aios-core/development/tasks/next.md +36 -5
  13. package/.aios-core/hooks/ids-post-commit.js +29 -1
  14. package/.aios-core/hooks/ids-pre-push.js +29 -1
  15. package/.aios-core/infrastructure/contracts/compatibility/aios-4.0.4.yaml +44 -0
  16. package/.aios-core/infrastructure/scripts/validate-parity.js +238 -2
  17. package/.aios-core/install-manifest.yaml +43 -27
  18. package/.aios-core/product/templates/brownfield-risk-report-tmpl.yaml +277 -0
  19. package/.aios-core/workflow-intelligence/engine/suggestion-engine.js +114 -5
  20. package/LICENSE +13 -1
  21. package/README.md +39 -9
  22. package/package.json +8 -6
  23. package/packages/installer/src/wizard/ide-config-generator.js +0 -117
  24. package/packages/installer/src/wizard/index.js +2 -118
  25. package/packages/installer/src/wizard/pro-setup.js +50 -5
  26. package/scripts/semantic-lint.js +190 -0
@@ -32,6 +32,8 @@ class ContextManager {
32
32
  // State file path
33
33
  this.stateDir = path.join(projectRoot, '.aios', 'workflow-state');
34
34
  this.statePath = path.join(this.stateDir, `${workflowId}.json`);
35
+ this.handoffDir = path.join(this.stateDir, 'handoffs');
36
+ this.confidenceDir = path.join(this.stateDir, 'confidence');
35
37
 
36
38
  // In-memory cache
37
39
  this._stateCache = null;
@@ -43,6 +45,8 @@ class ContextManager {
43
45
  */
44
46
  async ensureStateDir() {
45
47
  await fs.ensureDir(this.stateDir);
48
+ await fs.ensureDir(this.handoffDir);
49
+ await fs.ensureDir(this.confidenceDir);
46
50
  }
47
51
 
48
52
  /**
@@ -76,6 +80,7 @@ class ContextManager {
76
80
  phases: {},
77
81
  metadata: {
78
82
  projectRoot: this.projectRoot,
83
+ delivery_confidence: null,
79
84
  },
80
85
  };
81
86
  }
@@ -113,19 +118,25 @@ class ContextManager {
113
118
  * @param {number} phaseNum - Phase number
114
119
  * @param {Object} output - Phase output data
115
120
  */
116
- async savePhaseOutput(phaseNum, output) {
121
+ async savePhaseOutput(phaseNum, output, options = {}) {
117
122
  const state = await this.loadState();
123
+ const completedAt = new Date().toISOString();
124
+ state.currentPhase = phaseNum;
125
+ state.status = 'in_progress';
126
+ const handoff = this._buildHandoffPackage(phaseNum, output, state, options, completedAt);
118
127
 
119
128
  state.phases[phaseNum] = {
120
129
  ...output,
121
- completedAt: new Date().toISOString(),
130
+ completedAt,
131
+ handoff,
122
132
  };
123
-
124
- state.currentPhase = phaseNum;
125
- state.status = 'in_progress';
133
+ state.metadata = state.metadata || {};
134
+ state.metadata.delivery_confidence = this._calculateDeliveryConfidence(state);
126
135
 
127
136
  this._stateCache = state;
128
137
  await this._saveState();
138
+ await this._saveHandoffFile(handoff);
139
+ await this._saveConfidenceFile(state.metadata.delivery_confidence);
129
140
  }
130
141
 
131
142
  /**
@@ -145,10 +156,18 @@ class ContextManager {
145
156
  }
146
157
  }
147
158
 
159
+ const previousHandoffs = {};
160
+ for (const [phaseId, phaseData] of Object.entries(previousPhases)) {
161
+ if (phaseData && phaseData.handoff) {
162
+ previousHandoffs[phaseId] = phaseData.handoff;
163
+ }
164
+ }
165
+
148
166
  return {
149
167
  workflowId: this.workflowId,
150
168
  currentPhase: phaseNum,
151
169
  previousPhases,
170
+ previousHandoffs,
152
171
  metadata: state.metadata,
153
172
  };
154
173
  }
@@ -247,9 +266,318 @@ class ContextManager {
247
266
  currentPhase: state.currentPhase,
248
267
  completedPhases: phases,
249
268
  totalPhases: phases.length,
269
+ deliveryConfidence: state.metadata?.delivery_confidence || null,
250
270
  };
251
271
  }
252
272
 
273
+ /**
274
+ * Get latest delivery confidence score metadata.
275
+ * @returns {Object|null} Confidence payload
276
+ */
277
+ getDeliveryConfidence() {
278
+ return this._stateCache?.metadata?.delivery_confidence || null;
279
+ }
280
+
281
+ /**
282
+ * Build standardized handoff package for inter-agent transfer.
283
+ * @private
284
+ */
285
+ _buildHandoffPackage(phaseNum, output, state, options, completedAt) {
286
+ const handoffTarget = options.handoffTarget || {};
287
+ const decision = this._extractDecisionLog(output);
288
+ const evidence = this._extractEvidenceLinks(output);
289
+ const risks = this._extractOpenRisks(output);
290
+
291
+ return {
292
+ version: '1.0.0',
293
+ workflow_id: this.workflowId,
294
+ generated_at: completedAt,
295
+ from: {
296
+ phase: phaseNum,
297
+ agent: output.agent || null,
298
+ action: output.action || null,
299
+ task: output.task || null,
300
+ },
301
+ to: {
302
+ phase: handoffTarget.phase || null,
303
+ agent: handoffTarget.agent || null,
304
+ },
305
+ context_snapshot: {
306
+ workflow_status: state.status,
307
+ current_phase: state.currentPhase,
308
+ metadata: state.metadata || {},
309
+ },
310
+ decision_log: decision,
311
+ evidence_links: evidence,
312
+ open_risks: risks,
313
+ };
314
+ }
315
+
316
+ /**
317
+ * Persist handoff package as a dedicated artifact.
318
+ * @private
319
+ */
320
+ async _saveHandoffFile(handoff) {
321
+ const phase = handoff?.from?.phase || 'unknown';
322
+ const filePath = path.join(this.handoffDir, `${this.workflowId}-phase-${phase}.handoff.json`);
323
+ await fs.ensureDir(this.handoffDir);
324
+ await fs.writeJson(filePath, handoff, { spaces: 2 });
325
+ }
326
+
327
+ /**
328
+ * Persist confidence score as a dedicated artifact.
329
+ * @private
330
+ */
331
+ async _saveConfidenceFile(confidence) {
332
+ if (!confidence) return;
333
+ const filePath = path.join(this.confidenceDir, `${this.workflowId}.delivery-confidence.json`);
334
+ await fs.ensureDir(this.confidenceDir);
335
+ await fs.writeJson(filePath, confidence, { spaces: 2 });
336
+ }
337
+
338
+ /**
339
+ * @private
340
+ */
341
+ _extractDecisionLog(output = {}) {
342
+ const result = output.result || {};
343
+ const entries = Array.isArray(result.decisions)
344
+ ? result.decisions
345
+ : Array.isArray(result.decision_log)
346
+ ? result.decision_log
347
+ : [];
348
+ const sourcePaths = [];
349
+
350
+ if (result.decisionLogPath) sourcePaths.push(result.decisionLogPath);
351
+ if (result.decision_log_path) sourcePaths.push(result.decision_log_path);
352
+
353
+ return {
354
+ entries,
355
+ source_paths: sourcePaths,
356
+ count: entries.length,
357
+ };
358
+ }
359
+
360
+ /**
361
+ * @private
362
+ */
363
+ _extractEvidenceLinks(output = {}) {
364
+ const evidence = [];
365
+ const result = output.result || {};
366
+ const validation = output.validation || {};
367
+
368
+ if (Array.isArray(result.evidence_links)) {
369
+ evidence.push(...result.evidence_links);
370
+ }
371
+
372
+ if (Array.isArray(validation.checks)) {
373
+ for (const check of validation.checks) {
374
+ if (check.path) evidence.push(check.path);
375
+ if (check.checklist) evidence.push(check.checklist);
376
+ }
377
+ }
378
+
379
+ return [...new Set(evidence)];
380
+ }
381
+
382
+ /**
383
+ * @private
384
+ */
385
+ _extractOpenRisks(output = {}) {
386
+ const result = output.result || {};
387
+ const risks = [];
388
+
389
+ if (Array.isArray(result.open_risks)) {
390
+ risks.push(...result.open_risks);
391
+ }
392
+ if (Array.isArray(result.risks)) {
393
+ risks.push(...result.risks);
394
+ }
395
+ if (Array.isArray(result.risk_register)) {
396
+ risks.push(...result.risk_register);
397
+ }
398
+
399
+ return risks;
400
+ }
401
+
402
+ /**
403
+ * Compute delivery confidence score from workflow state.
404
+ * @private
405
+ */
406
+ _calculateDeliveryConfidence(state) {
407
+ const phases = Object.values(state.phases || {});
408
+ const weights = {
409
+ test_coverage: 0.25,
410
+ ac_completion: 0.30,
411
+ risk_score_inv: 0.20,
412
+ debt_score_inv: 0.15,
413
+ regression_clear: 0.10,
414
+ };
415
+ const components = {
416
+ test_coverage: this._calculateTestCoverage(phases),
417
+ ac_completion: this._calculateAcCompletion(phases),
418
+ risk_score_inv: this._calculateRiskInverseScore(phases),
419
+ debt_score_inv: this._calculateDebtInverseScore(phases),
420
+ regression_clear: this._calculateRegressionClear(phases),
421
+ };
422
+
423
+ const scoreBase = Object.keys(weights).reduce(
424
+ (acc, key) => acc + (components[key] || 0) * weights[key],
425
+ 0,
426
+ );
427
+ const score = Number((scoreBase * 100).toFixed(2));
428
+ const threshold = this._resolveConfidenceThreshold();
429
+
430
+ return {
431
+ version: '1.0.0',
432
+ calculated_at: new Date().toISOString(),
433
+ score,
434
+ threshold,
435
+ gate_passed: score >= threshold,
436
+ formula: {
437
+ expression:
438
+ 'confidence = (test_coverage*0.25 + ac_completion*0.30 + risk_score_inv*0.20 + debt_score_inv*0.15 + regression_clear*0.10) * 100',
439
+ weights,
440
+ },
441
+ components,
442
+ phase_count: phases.length,
443
+ };
444
+ }
445
+
446
+ /**
447
+ * @private
448
+ */
449
+ _resolveConfidenceThreshold() {
450
+ const raw = process.env.AIOS_DELIVERY_CONFIDENCE_THRESHOLD;
451
+ const parsed = Number(raw);
452
+ return Number.isFinite(parsed) ? parsed : 70;
453
+ }
454
+
455
+ /**
456
+ * @private
457
+ */
458
+ _calculateTestCoverage(phases) {
459
+ let totalChecks = 0;
460
+ let passedChecks = 0;
461
+
462
+ for (const phase of phases) {
463
+ const checks = phase?.validation?.checks;
464
+ if (!Array.isArray(checks)) continue;
465
+ for (const check of checks) {
466
+ totalChecks += 1;
467
+ if (check?.passed === true) passedChecks += 1;
468
+ }
469
+ }
470
+
471
+ if (totalChecks === 0) {
472
+ return phases.length > 0 ? 1 : 0;
473
+ }
474
+
475
+ return passedChecks / totalChecks;
476
+ }
477
+
478
+ /**
479
+ * @private
480
+ */
481
+ _calculateAcCompletion(phases) {
482
+ let total = 0;
483
+ let done = 0;
484
+ let hasExplicitData = false;
485
+
486
+ for (const phase of phases) {
487
+ const result = phase?.result || {};
488
+ if (Number.isFinite(result.ac_total) && Number.isFinite(result.ac_completed)) {
489
+ hasExplicitData = true;
490
+ total += Math.max(0, result.ac_total);
491
+ done += Math.min(Math.max(0, result.ac_completed), Math.max(0, result.ac_total));
492
+ } else if (Array.isArray(result.acceptance_criteria)) {
493
+ hasExplicitData = true;
494
+ total += result.acceptance_criteria.length;
495
+ done += result.acceptance_criteria.filter((item) => item?.done || item?.status === 'done').length;
496
+ }
497
+ }
498
+
499
+ if (hasExplicitData && total > 0) {
500
+ return done / total;
501
+ }
502
+
503
+ if (phases.length === 0) {
504
+ return 0;
505
+ }
506
+
507
+ const successful = phases.filter((phase) => phase?.result?.status !== 'failed').length;
508
+ return successful / phases.length;
509
+ }
510
+
511
+ /**
512
+ * @private
513
+ */
514
+ _calculateRiskInverseScore(phases) {
515
+ const totalRisks = phases.reduce((sum, phase) => {
516
+ const handoffRisks = Array.isArray(phase?.handoff?.open_risks) ? phase.handoff.open_risks.length : 0;
517
+ const resultRisks = this._extractOpenRisks(phase).length;
518
+ return sum + Math.max(handoffRisks, resultRisks);
519
+ }, 0);
520
+
521
+ return Math.max(0, 1 - totalRisks / 10);
522
+ }
523
+
524
+ /**
525
+ * @private
526
+ */
527
+ _calculateDebtInverseScore(phases) {
528
+ const totalDebt = phases.reduce((sum, phase) => {
529
+ const result = phase?.result || {};
530
+ const explicitCount = Number.isFinite(result.technical_debt_count)
531
+ ? result.technical_debt_count
532
+ : Number.isFinite(result.debt_count)
533
+ ? result.debt_count
534
+ : 0;
535
+ const listCount = [
536
+ result.technical_debt,
537
+ result.debt_items,
538
+ result.todos,
539
+ result.hacks,
540
+ ].reduce((listSum, list) => listSum + (Array.isArray(list) ? list.length : 0), 0);
541
+ return sum + explicitCount + listCount;
542
+ }, 0);
543
+
544
+ return Math.max(0, 1 - totalDebt / 10);
545
+ }
546
+
547
+ /**
548
+ * @private
549
+ */
550
+ _calculateRegressionClear(phases) {
551
+ let totalRegressionChecks = 0;
552
+ let passedRegressionChecks = 0;
553
+
554
+ for (const phase of phases) {
555
+ const checks = phase?.validation?.checks;
556
+ if (!Array.isArray(checks)) continue;
557
+
558
+ for (const check of checks) {
559
+ const type = String(check?.type || '').toLowerCase();
560
+ const pathValue = String(check?.path || '').toLowerCase();
561
+ const checklist = String(check?.checklist || '').toLowerCase();
562
+ const isRegression = type.includes('regression')
563
+ || pathValue.includes('regression')
564
+ || checklist.includes('regression');
565
+
566
+ if (!isRegression) continue;
567
+ totalRegressionChecks += 1;
568
+ if (check?.passed === true) {
569
+ passedRegressionChecks += 1;
570
+ }
571
+ }
572
+ }
573
+
574
+ if (totalRegressionChecks === 0) {
575
+ return this._calculateTestCoverage(phases);
576
+ }
577
+
578
+ return passedRegressionChecks / totalRegressionChecks;
579
+ }
580
+
253
581
  /**
254
582
  * Reset workflow state (for re-execution)
255
583
  * @param {boolean} keepMetadata - Whether to preserve metadata
@@ -252,12 +252,28 @@ class DashboardIntegration extends EventEmitter {
252
252
  await fs.writeJson(this.statusPath, status, { spaces: 2 });
253
253
  this.emit('statusUpdated', status);
254
254
  } catch (error) {
255
- this.emit('error', { type: 'statusUpdate', error });
255
+ this._emitSafeError({ type: 'statusUpdate', error });
256
256
  }
257
257
 
258
258
  return status;
259
259
  }
260
260
 
261
+ /**
262
+ * Emit error event only when listeners are present, otherwise degrade to warning.
263
+ * Avoids unhandled EventEmitter 'error' exceptions in background update flows.
264
+ * @private
265
+ * @param {Object} payload
266
+ */
267
+ _emitSafeError(payload) {
268
+ if (this.listenerCount('error') > 0) {
269
+ this.emit('error', payload);
270
+ return;
271
+ }
272
+
273
+ const message = payload?.error?.message || 'unknown dashboard error';
274
+ console.warn(`[DashboardIntegration] ${payload?.type || 'error'}: ${message}`);
275
+ }
276
+
261
277
  /**
262
278
  * Build status object (AC2)
263
279
  * @returns {Object} Status object
@@ -0,0 +1,107 @@
1
+ 'use strict';
2
+
3
+ const VALID_PROFILES = ['safe', 'balanced', 'aggressive'];
4
+ const VALID_CONTEXTS = ['production', 'migration', 'security-sensitive', 'development'];
5
+
6
+ const PROFILE_POLICIES = {
7
+ safe: {
8
+ require_confirmation: true,
9
+ require_tests_before_handoff: true,
10
+ max_parallel_changes: 1,
11
+ allow_destructive_operations: false,
12
+ allow_autonomous_refactors: false,
13
+ },
14
+ balanced: {
15
+ require_confirmation: 'high-risk-only',
16
+ require_tests_before_handoff: true,
17
+ max_parallel_changes: 3,
18
+ allow_destructive_operations: false,
19
+ allow_autonomous_refactors: true,
20
+ },
21
+ aggressive: {
22
+ require_confirmation: false,
23
+ require_tests_before_handoff: false,
24
+ max_parallel_changes: 8,
25
+ allow_destructive_operations: false,
26
+ allow_autonomous_refactors: true,
27
+ },
28
+ };
29
+
30
+ function normalizeProfile(profile) {
31
+ const value = String(profile || '').trim().toLowerCase();
32
+ return VALID_PROFILES.includes(value) ? value : null;
33
+ }
34
+
35
+ function normalizeContext(context) {
36
+ const value = String(context || '').trim().toLowerCase();
37
+ return VALID_CONTEXTS.includes(value) ? value : 'development';
38
+ }
39
+
40
+ function resolveExecutionProfile(input = {}) {
41
+ const context = normalizeContext(input.context);
42
+ const explicitProfile = normalizeProfile(input.explicitProfile);
43
+ const yolo = Boolean(input.yolo);
44
+ const reasons = [];
45
+
46
+ if (explicitProfile) {
47
+ reasons.push(`explicit profile selected: ${explicitProfile}`);
48
+ return {
49
+ profile: explicitProfile,
50
+ context,
51
+ policy: PROFILE_POLICIES[explicitProfile],
52
+ reasons,
53
+ source: 'explicit',
54
+ };
55
+ }
56
+
57
+ if (context === 'production' || context === 'security-sensitive') {
58
+ reasons.push(`context "${context}" enforces safe profile`);
59
+ return {
60
+ profile: 'safe',
61
+ context,
62
+ policy: PROFILE_POLICIES.safe,
63
+ reasons,
64
+ source: 'context',
65
+ };
66
+ }
67
+
68
+ if (context === 'migration') {
69
+ reasons.push('migration context enforces balanced profile');
70
+ return {
71
+ profile: 'balanced',
72
+ context,
73
+ policy: PROFILE_POLICIES.balanced,
74
+ reasons,
75
+ source: 'context',
76
+ };
77
+ }
78
+
79
+ if (yolo) {
80
+ reasons.push('yolo mode enabled for non-critical context');
81
+ return {
82
+ profile: 'aggressive',
83
+ context,
84
+ policy: PROFILE_POLICIES.aggressive,
85
+ reasons,
86
+ source: 'yolo',
87
+ };
88
+ }
89
+
90
+ reasons.push('default profile for standard development context');
91
+ return {
92
+ profile: 'balanced',
93
+ context,
94
+ policy: PROFILE_POLICIES.balanced,
95
+ reasons,
96
+ source: 'default',
97
+ };
98
+ }
99
+
100
+ module.exports = {
101
+ VALID_PROFILES,
102
+ VALID_CONTEXTS,
103
+ PROFILE_POLICIES,
104
+ normalizeProfile,
105
+ normalizeContext,
106
+ resolveExecutionProfile,
107
+ };
@@ -22,6 +22,7 @@ const ParallelExecutor = require('./parallel-executor');
22
22
  const TechStackDetector = require('./tech-stack-detector');
23
23
  const ConditionEvaluator = require('./condition-evaluator');
24
24
  const SkillDispatcher = require('./skill-dispatcher');
25
+ const executionProfileResolver = require('./execution-profile-resolver');
25
26
 
26
27
  // Epic 0: Master Orchestrator (ADE)
27
28
  const MasterOrchestrator = require('./master-orchestrator');
@@ -159,6 +160,8 @@ module.exports = {
159
160
  TechStackDetector,
160
161
  ConditionEvaluator,
161
162
  SkillDispatcher,
163
+ ExecutionProfileResolver: executionProfileResolver,
164
+ resolveExecutionProfile: executionProfileResolver.resolveExecutionProfile,
162
165
 
163
166
  // Epic 0: Orchestrator constants
164
167
  OrchestratorState: MasterOrchestrator.OrchestratorState,
@@ -170,6 +170,8 @@ class SkillDispatcher {
170
170
  workflowId: context.workflowId,
171
171
  yoloMode: context.yoloMode || false,
172
172
  previousPhases: context.previousPhases || {},
173
+ executionProfile: context.executionProfile || null,
174
+ executionPolicy: context.executionPolicy || null,
173
175
  },
174
176
  };
175
177
  }
@@ -299,7 +299,9 @@ ${agentDef}
299
299
  **Task File:** ${taskFile}
300
300
  **Expected Output:** ${context.creates || 'See task definition'}
301
301
  **Execution Mode:** ${context.yoloMode ? 'YOLO (autonomous)' : 'Interactive'}
302
+ **Execution Profile:** ${context.executionProfile || 'balanced'}
302
303
  **Elicitation Required:** ${context.elicit ? 'Yes' : 'No'}
304
+ **Risk Policy:** ${JSON.stringify(context.executionPolicy || {}, null, 0)}
303
305
 
304
306
  ### Complete Task Definition:
305
307