scene-capability-engine 3.0.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.
- package/CHANGELOG.md +2513 -0
- package/LICENSE +21 -0
- package/README.md +765 -0
- package/README.zh.md +630 -0
- package/bin/kiro-spec-engine.js +796 -0
- package/bin/kse.js +3 -0
- package/bin/sce.js +3 -0
- package/bin/sco.js +3 -0
- package/docs/331-poc-adaptation-roadmap.md +156 -0
- package/docs/331-poc-dual-track-integration-guide.md +120 -0
- package/docs/331-poc-weekly-delivery-checklist.md +52 -0
- package/docs/OFFLINE_INSTALL.md +96 -0
- package/docs/README.md +279 -0
- package/docs/adopt-migration-guide.md +599 -0
- package/docs/adoption-guide.md +616 -0
- package/docs/agent-hooks-analysis.md +815 -0
- package/docs/architecture.md +733 -0
- package/docs/articles/ai-driven-development-philosophy-and-practice-review.md +208 -0
- package/docs/articles/ai-driven-development-philosophy-and-practice.en.md +459 -0
- package/docs/articles/ai-driven-development-philosophy-and-practice.md +492 -0
- package/docs/autonomous-control-guide.md +851 -0
- package/docs/command-reference.md +1368 -0
- package/docs/community.md +115 -0
- package/docs/cross-tool-guide.md +555 -0
- package/docs/developer-guide.md +619 -0
- package/docs/document-governance.md +865 -0
- package/docs/environment-management-guide.md +526 -0
- package/docs/examples/add-export-command/design.md +194 -0
- package/docs/examples/add-export-command/requirements.md +110 -0
- package/docs/examples/add-export-command/tasks.md +88 -0
- package/docs/examples/add-rest-api/design.md +855 -0
- package/docs/examples/add-rest-api/requirements.md +323 -0
- package/docs/examples/add-rest-api/tasks.md +355 -0
- package/docs/examples/add-user-dashboard/design.md +192 -0
- package/docs/examples/add-user-dashboard/requirements.md +143 -0
- package/docs/examples/add-user-dashboard/tasks.md +91 -0
- package/docs/faq.md +697 -0
- package/docs/handoffs/evidence/ontology/moqui-template-baseline-2026-02-17-232922.json +156 -0
- package/docs/handoffs/evidence/ontology/moqui-template-baseline-2026-02-17-232922.md +24 -0
- package/docs/images/wechat-qr.png +0 -0
- package/docs/integration-modes.md +529 -0
- package/docs/integration-philosophy.md +313 -0
- package/docs/knowledge-management-guide.md +263 -0
- package/docs/manual-workflows-guide.md +418 -0
- package/docs/moqui-capability-matrix.md +73 -0
- package/docs/moqui-template-core-library-playbook.md +109 -0
- package/docs/multi-agent-coordination-guide.md +553 -0
- package/docs/multi-repo-management-guide.md +1344 -0
- package/docs/quick-start-with-ai-tools.md +375 -0
- package/docs/quick-start.md +146 -0
- package/docs/release-checklist.md +121 -0
- package/docs/releases/README.md +13 -0
- package/docs/releases/v1.46.2-validation.md +45 -0
- package/docs/releases/v1.46.2.md +50 -0
- package/docs/scene-runtime-guide.md +347 -0
- package/docs/spec-collaboration-guide.md +369 -0
- package/docs/spec-locking-guide.md +225 -0
- package/docs/spec-numbering-guide.md +348 -0
- package/docs/spec-workflow.md +519 -0
- package/docs/steering-strategy-guide.md +196 -0
- package/docs/team-collaboration-guide.md +465 -0
- package/docs/testing-strategy.md +272 -0
- package/docs/tools/claude-guide.md +654 -0
- package/docs/tools/cursor-guide.md +706 -0
- package/docs/tools/generic-guide.md +446 -0
- package/docs/tools/kiro-guide.md +308 -0
- package/docs/tools/vscode-guide.md +445 -0
- package/docs/tools/windsurf-guide.md +391 -0
- package/docs/troubleshooting.md +1135 -0
- package/docs/upgrade-guide.md +639 -0
- package/docs/value-observability-guide.md +127 -0
- package/docs/zh/README.md +341 -0
- package/docs/zh/quick-start.md +764 -0
- package/docs/zh/release-checklist.md +121 -0
- package/docs/zh/releases/README.md +13 -0
- package/docs/zh/releases/v1.46.2-validation.md +45 -0
- package/docs/zh/releases/v1.46.2.md +50 -0
- package/docs/zh/spec-numbering-guide.md +348 -0
- package/docs/zh/tools/claude-guide.md +349 -0
- package/docs/zh/tools/cursor-guide.md +281 -0
- package/docs/zh/tools/generic-guide.md +499 -0
- package/docs/zh/tools/kiro-guide.md +342 -0
- package/docs/zh/tools/vscode-guide.md +449 -0
- package/docs/zh/tools/windsurf-guide.md +378 -0
- package/docs/zh/value-observability-guide.md +127 -0
- package/docs//344/272/244/344/273/230/346/270/205/345/215/225.md +75 -0
- package/lib/adoption/adoption-logger.js +487 -0
- package/lib/adoption/adoption-strategy.js +538 -0
- package/lib/adoption/backup-manager.js +420 -0
- package/lib/adoption/conflict-resolver.js +410 -0
- package/lib/adoption/detection-engine.js +275 -0
- package/lib/adoption/diff-viewer.js +226 -0
- package/lib/adoption/error-formatter.js +509 -0
- package/lib/adoption/file-classifier.js +385 -0
- package/lib/adoption/progress-reporter.js +534 -0
- package/lib/adoption/smart-orchestrator.js +470 -0
- package/lib/adoption/strategy-selector.js +218 -0
- package/lib/adoption/summary-generator.js +493 -0
- package/lib/adoption/template-sync.js +605 -0
- package/lib/auto/autonomous-engine.js +485 -0
- package/lib/auto/checkpoint-manager.js +300 -0
- package/lib/auto/close-loop-runner.js +2476 -0
- package/lib/auto/config-schema.js +176 -0
- package/lib/auto/decision-engine.js +344 -0
- package/lib/auto/error-recovery-manager.js +580 -0
- package/lib/auto/goal-decomposer.js +278 -0
- package/lib/auto/progress-tracker.js +502 -0
- package/lib/auto/safety-manager.js +186 -0
- package/lib/auto/semantic-decomposer.js +137 -0
- package/lib/auto/state-manager.js +126 -0
- package/lib/auto/task-queue-manager.js +340 -0
- package/lib/backup/backup-system.js +372 -0
- package/lib/backup/selective-backup.js +207 -0
- package/lib/collab/agent-registry.js +240 -0
- package/lib/collab/collab-manager.js +285 -0
- package/lib/collab/contract-manager.js +320 -0
- package/lib/collab/coordinator.js +370 -0
- package/lib/collab/dependency-manager.js +280 -0
- package/lib/collab/index.js +20 -0
- package/lib/collab/integration-manager.js +202 -0
- package/lib/collab/merge-coordinator.js +252 -0
- package/lib/collab/metadata-manager.js +233 -0
- package/lib/collab/multi-agent-config.js +120 -0
- package/lib/collab/spec-lifecycle-manager.js +304 -0
- package/lib/collab/sync-barrier.js +88 -0
- package/lib/collab/visualizer.js +208 -0
- package/lib/commands/adopt.js +749 -0
- package/lib/commands/auto.js +19559 -0
- package/lib/commands/collab.js +275 -0
- package/lib/commands/context.js +99 -0
- package/lib/commands/docs.js +808 -0
- package/lib/commands/doctor.js +273 -0
- package/lib/commands/env.js +420 -0
- package/lib/commands/knowledge.js +309 -0
- package/lib/commands/lock.js +235 -0
- package/lib/commands/ops.js +409 -0
- package/lib/commands/orchestrate.js +446 -0
- package/lib/commands/prompt.js +105 -0
- package/lib/commands/repo.js +118 -0
- package/lib/commands/rollback.js +219 -0
- package/lib/commands/scene.js +15549 -0
- package/lib/commands/spec-bootstrap.js +147 -0
- package/lib/commands/spec-gate.js +157 -0
- package/lib/commands/spec-pipeline.js +205 -0
- package/lib/commands/status.js +321 -0
- package/lib/commands/task.js +199 -0
- package/lib/commands/templates.js +654 -0
- package/lib/commands/upgrade.js +231 -0
- package/lib/commands/value.js +569 -0
- package/lib/commands/watch.js +684 -0
- package/lib/commands/workflows.js +240 -0
- package/lib/commands/workspace-multi.js +325 -0
- package/lib/commands/workspace.js +189 -0
- package/lib/context/context-exporter.js +378 -0
- package/lib/context/prompt-generator.js +482 -0
- package/lib/data/moqui-capability-lexicon.json +45 -0
- package/lib/environment/backup-system.js +189 -0
- package/lib/environment/environment-manager.js +379 -0
- package/lib/environment/environment-registry.js +168 -0
- package/lib/gitignore/gitignore-backup.js +229 -0
- package/lib/gitignore/gitignore-detector.js +239 -0
- package/lib/gitignore/gitignore-integration.js +267 -0
- package/lib/gitignore/gitignore-transformer.js +193 -0
- package/lib/gitignore/layered-rules-template.js +42 -0
- package/lib/governance/archive-tool.js +284 -0
- package/lib/governance/cleanup-tool.js +237 -0
- package/lib/governance/config-manager.js +186 -0
- package/lib/governance/diagnostic-engine.js +271 -0
- package/lib/governance/doc-reference-checker.js +200 -0
- package/lib/governance/execution-logger.js +243 -0
- package/lib/governance/file-scanner.js +285 -0
- package/lib/governance/hooks-manager.js +333 -0
- package/lib/governance/reporter.js +337 -0
- package/lib/governance/validation-engine.js +181 -0
- package/lib/i18n.js +79 -0
- package/lib/knowledge/entry-manager.js +208 -0
- package/lib/knowledge/index-manager.js +261 -0
- package/lib/knowledge/knowledge-manager.js +273 -0
- package/lib/knowledge/template-manager.js +191 -0
- package/lib/lock/index.js +21 -0
- package/lib/lock/lock-file.js +192 -0
- package/lib/lock/lock-manager.js +321 -0
- package/lib/lock/machine-identifier.js +135 -0
- package/lib/lock/steering-file-lock.js +207 -0
- package/lib/lock/task-lock-manager.js +345 -0
- package/lib/operations/audit-logger.js +293 -0
- package/lib/operations/feedback-manager.js +1147 -0
- package/lib/operations/index.js +23 -0
- package/lib/operations/models/index.js +170 -0
- package/lib/operations/operations-manager.js +151 -0
- package/lib/operations/operations-validator.js +280 -0
- package/lib/operations/permission-manager.js +354 -0
- package/lib/operations/template-loader.js +143 -0
- package/lib/orchestrator/agent-spawner.js +629 -0
- package/lib/orchestrator/bootstrap-prompt-builder.js +236 -0
- package/lib/orchestrator/index.js +19 -0
- package/lib/orchestrator/orchestration-engine.js +1270 -0
- package/lib/orchestrator/orchestrator-config.js +173 -0
- package/lib/orchestrator/status-monitor.js +591 -0
- package/lib/python-checker.js +209 -0
- package/lib/repo/config-manager.js +580 -0
- package/lib/repo/errors/config-error.js +13 -0
- package/lib/repo/errors/git-error.js +15 -0
- package/lib/repo/errors/repo-error.js +14 -0
- package/lib/repo/git-operations.js +181 -0
- package/lib/repo/handlers/.gitkeep +1 -0
- package/lib/repo/handlers/exec-handler.js +155 -0
- package/lib/repo/handlers/health-handler.js +169 -0
- package/lib/repo/handlers/init-handler.js +197 -0
- package/lib/repo/handlers/status-handler.js +176 -0
- package/lib/repo/output-formatter.js +184 -0
- package/lib/repo/path-resolver.js +178 -0
- package/lib/repo/repo-manager.js +514 -0
- package/lib/scene-runtime/audit-emitter.js +59 -0
- package/lib/scene-runtime/binding-plugin-loader.js +351 -0
- package/lib/scene-runtime/binding-registry.js +349 -0
- package/lib/scene-runtime/eval-bridge.js +44 -0
- package/lib/scene-runtime/index.js +19 -0
- package/lib/scene-runtime/moqui-adapter.js +620 -0
- package/lib/scene-runtime/moqui-client.js +606 -0
- package/lib/scene-runtime/moqui-extractor.js +2029 -0
- package/lib/scene-runtime/plan-compiler.js +208 -0
- package/lib/scene-runtime/policy-gate.js +58 -0
- package/lib/scene-runtime/runtime-executor.js +358 -0
- package/lib/scene-runtime/scene-loader.js +96 -0
- package/lib/scene-runtime/scene-ontology.js +959 -0
- package/lib/scene-runtime/scene-template-linter.js +852 -0
- package/lib/scene-runtime/templates/scene-template-erp-query-v0.1.yaml +28 -0
- package/lib/scene-runtime/templates/scene-template-hybrid-shadow-v0.1.yaml +34 -0
- package/lib/spec/bootstrap/context-collector.js +48 -0
- package/lib/spec/bootstrap/draft-generator.js +158 -0
- package/lib/spec/bootstrap/questionnaire-engine.js +70 -0
- package/lib/spec/bootstrap/trace-emitter.js +59 -0
- package/lib/spec/multi-spec-orchestrate.js +93 -0
- package/lib/spec/pipeline/constants.js +6 -0
- package/lib/spec/pipeline/stage-adapters.js +118 -0
- package/lib/spec/pipeline/stage-runner.js +146 -0
- package/lib/spec/pipeline/state-store.js +119 -0
- package/lib/spec-gate/engine/gate-engine.js +165 -0
- package/lib/spec-gate/policy/default-policy.js +22 -0
- package/lib/spec-gate/policy/policy-loader.js +103 -0
- package/lib/spec-gate/result-emitter.js +81 -0
- package/lib/spec-gate/rules/default-rules.js +156 -0
- package/lib/spec-gate/rules/rule-registry.js +51 -0
- package/lib/steering/adoption-config.js +164 -0
- package/lib/steering/compliance-auto-fixer.js +204 -0
- package/lib/steering/compliance-cache.js +99 -0
- package/lib/steering/compliance-error-reporter.js +70 -0
- package/lib/steering/context-sync-manager.js +273 -0
- package/lib/steering/index.js +92 -0
- package/lib/steering/spec-steering.js +230 -0
- package/lib/steering/steering-compliance-checker.js +73 -0
- package/lib/steering/steering-loader.js +144 -0
- package/lib/steering/steering-manager.js +289 -0
- package/lib/task/index.js +12 -0
- package/lib/task/task-claimer.js +489 -0
- package/lib/task/task-status-store.js +418 -0
- package/lib/templates/cache-manager.js +440 -0
- package/lib/templates/content-generalizer.js +247 -0
- package/lib/templates/frontmatter-generator.js +128 -0
- package/lib/templates/git-handler.js +471 -0
- package/lib/templates/metadata-collector.js +328 -0
- package/lib/templates/path-utils.js +144 -0
- package/lib/templates/registry-parser.js +505 -0
- package/lib/templates/spec-reader.js +216 -0
- package/lib/templates/template-applicator.js +249 -0
- package/lib/templates/template-creator.js +256 -0
- package/lib/templates/template-error.js +143 -0
- package/lib/templates/template-exporter.js +502 -0
- package/lib/templates/template-manager.js +782 -0
- package/lib/templates/template-validator.js +361 -0
- package/lib/upgrade/migration-engine.js +382 -0
- package/lib/upgrade/migrations/.gitkeep +52 -0
- package/lib/upgrade/migrations/1.0.0-to-1.1.0.js +78 -0
- package/lib/utils/file-diff.js +177 -0
- package/lib/utils/fs-utils.js +274 -0
- package/lib/utils/tool-detector.js +383 -0
- package/lib/utils/validation.js +324 -0
- package/lib/value/gate-summary-emitter.js +99 -0
- package/lib/value/metric-contract-loader.js +210 -0
- package/lib/value/risk-evaluator.js +117 -0
- package/lib/value/weekly-snapshot-builder.js +61 -0
- package/lib/version/version-checker.js +156 -0
- package/lib/version/version-manager.js +327 -0
- package/lib/watch/action-executor.js +458 -0
- package/lib/watch/event-debouncer.js +323 -0
- package/lib/watch/execution-logger.js +550 -0
- package/lib/watch/file-watcher.js +499 -0
- package/lib/watch/presets.js +266 -0
- package/lib/watch/watch-manager.js +533 -0
- package/lib/workspace/multi/global-config.js +150 -0
- package/lib/workspace/multi/index.js +22 -0
- package/lib/workspace/multi/path-utils.js +173 -0
- package/lib/workspace/multi/workspace-context-resolver.js +244 -0
- package/lib/workspace/multi/workspace-registry.js +196 -0
- package/lib/workspace/multi/workspace-state-manager.js +537 -0
- package/lib/workspace/multi/workspace.js +90 -0
- package/lib/workspace/workspace-manager.js +370 -0
- package/lib/workspace/workspace-sync.js +356 -0
- package/locales/en.json +114 -0
- package/locales/zh.json +114 -0
- package/package.json +102 -0
- package/template/.kiro/README.md +247 -0
- package/template/.kiro/hooks/check-spec-on-create.kiro.hook +17 -0
- package/template/.kiro/hooks/run-tests-on-save.kiro.hook +13 -0
- package/template/.kiro/hooks/sync-tasks-on-edit.kiro.hook +16 -0
- package/template/.kiro/specs/SPEC_WORKFLOW_GUIDE.md +134 -0
- package/template/.kiro/steering/CORE_PRINCIPLES.md +133 -0
- package/template/.kiro/steering/CURRENT_CONTEXT.md +30 -0
- package/template/.kiro/steering/ENVIRONMENT.md +35 -0
- package/template/.kiro/steering/RULES_GUIDE.md +46 -0
- package/template/.kiro/templates/operations/default/change-impact.md +112 -0
- package/template/.kiro/templates/operations/default/deployment.md +91 -0
- package/template/.kiro/templates/operations/default/feedback-response.md +269 -0
- package/template/.kiro/templates/operations/default/migration-plan.md +172 -0
- package/template/.kiro/templates/operations/default/monitoring.md +135 -0
- package/template/.kiro/templates/operations/default/operations.md +135 -0
- package/template/.kiro/templates/operations/default/rollback.md +143 -0
- package/template/.kiro/templates/operations/default/tools.yaml +364 -0
- package/template/.kiro/templates/operations/default/troubleshooting.md +123 -0
- package/template/.kiro/tools/backup_manager.py +295 -0
- package/template/.kiro/tools/configuration_manager.py +218 -0
- package/template/.kiro/tools/document_evaluator.py +550 -0
- package/template/.kiro/tools/enhancement_logger.py +168 -0
- package/template/.kiro/tools/error_handler.py +335 -0
- package/template/.kiro/tools/improvement_identifier.py +444 -0
- package/template/.kiro/tools/modification_applicator.py +737 -0
- package/template/.kiro/tools/quality_gate_enforcer.py +207 -0
- package/template/.kiro/tools/quality_scorer.py +305 -0
- package/template/.kiro/tools/report_generator.py +154 -0
- package/template/.kiro/tools/ultrawork_enhancer.py +676 -0
- package/template/.kiro/tools/ultrawork_enhancer_refactored.py +0 -0
- package/template/.kiro/tools/ultrawork_enhancer_v2.py +463 -0
- package/template/.kiro/tools/ultrawork_enhancer_v3.py +606 -0
- package/template/.kiro/tools/workflow_quality_gate.py +100 -0
- package/template/README.md +111 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SpecLifecycleManager - Manages Spec lifecycle state machine
|
|
3
|
+
*
|
|
4
|
+
* Tracks Spec status through: planned → assigned → in-progress → completed → released.
|
|
5
|
+
* Persists state to `.kiro/specs/{specName}/lifecycle.json`.
|
|
6
|
+
* On completion, triggers ContextSyncManager update and AgentRegistry notification.
|
|
7
|
+
* In single-Agent mode, all operations are no-ops.
|
|
8
|
+
*
|
|
9
|
+
* Requirements: 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 6.3
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const fs = require('fs-extra');
|
|
14
|
+
const fsUtils = require('../utils/fs-utils');
|
|
15
|
+
const { MultiAgentConfig } = require('./multi-agent-config');
|
|
16
|
+
|
|
17
|
+
const SPECS_DIR = '.kiro/specs';
|
|
18
|
+
|
|
19
|
+
const VALID_TRANSITIONS = {
|
|
20
|
+
planned: ['assigned'],
|
|
21
|
+
assigned: ['in-progress', 'planned'],
|
|
22
|
+
'in-progress': ['completed', 'assigned'],
|
|
23
|
+
completed: ['released'],
|
|
24
|
+
released: [],
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const DEFAULT_STATUS = 'planned';
|
|
28
|
+
|
|
29
|
+
class SpecLifecycleManager {
|
|
30
|
+
/**
|
|
31
|
+
* @param {string} workspaceRoot - Absolute path to the project root
|
|
32
|
+
* @param {import('../steering/context-sync-manager').ContextSyncManager} contextSyncManager
|
|
33
|
+
* @param {import('./agent-registry').AgentRegistry} agentRegistry
|
|
34
|
+
*/
|
|
35
|
+
constructor(workspaceRoot, contextSyncManager, agentRegistry) {
|
|
36
|
+
this._workspaceRoot = workspaceRoot;
|
|
37
|
+
this._contextSyncManager = contextSyncManager;
|
|
38
|
+
this._agentRegistry = agentRegistry;
|
|
39
|
+
this._multiAgentConfig = new MultiAgentConfig(workspaceRoot);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get the lifecycle.json path for a Spec.
|
|
44
|
+
* @param {string} specName
|
|
45
|
+
* @returns {string}
|
|
46
|
+
* @private
|
|
47
|
+
*/
|
|
48
|
+
_lifecyclePath(specName) {
|
|
49
|
+
return path.join(this._workspaceRoot, SPECS_DIR, specName, 'lifecycle.json');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get the current lifecycle status of a Spec.
|
|
54
|
+
* Single-Agent mode: returns 'planned' (default).
|
|
55
|
+
*
|
|
56
|
+
* @param {string} specName
|
|
57
|
+
* @returns {Promise<string>}
|
|
58
|
+
*/
|
|
59
|
+
async getStatus(specName) {
|
|
60
|
+
const enabled = await this._multiAgentConfig.isEnabled();
|
|
61
|
+
if (!enabled) {
|
|
62
|
+
return DEFAULT_STATUS;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const lifecycle = await this.readLifecycle(specName);
|
|
66
|
+
return lifecycle.status;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Transition a Spec to a new status.
|
|
71
|
+
* Validates the transition against VALID_TRANSITIONS.
|
|
72
|
+
* Single-Agent mode: returns success without persisting.
|
|
73
|
+
*
|
|
74
|
+
* @param {string} specName
|
|
75
|
+
* @param {string} newStatus
|
|
76
|
+
* @returns {Promise<{success: boolean, oldStatus: string, newStatus: string, error?: string}>}
|
|
77
|
+
*/
|
|
78
|
+
async transition(specName, newStatus) {
|
|
79
|
+
const enabled = await this._multiAgentConfig.isEnabled();
|
|
80
|
+
if (!enabled) {
|
|
81
|
+
return { success: true, oldStatus: DEFAULT_STATUS, newStatus };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const lifecycle = await this.readLifecycle(specName);
|
|
85
|
+
const oldStatus = lifecycle.status;
|
|
86
|
+
|
|
87
|
+
// Validate transition
|
|
88
|
+
const allowed = VALID_TRANSITIONS[oldStatus];
|
|
89
|
+
if (!allowed || !allowed.includes(newStatus)) {
|
|
90
|
+
return {
|
|
91
|
+
success: false,
|
|
92
|
+
oldStatus,
|
|
93
|
+
newStatus,
|
|
94
|
+
error: `Invalid transition: ${oldStatus} → ${newStatus}. Allowed: [${(allowed || []).join(', ')}]`,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Record transition
|
|
99
|
+
lifecycle.status = newStatus;
|
|
100
|
+
lifecycle.transitions.push({
|
|
101
|
+
from: oldStatus,
|
|
102
|
+
to: newStatus,
|
|
103
|
+
timestamp: new Date().toISOString(),
|
|
104
|
+
agentId: null, // Will be set by caller if needed
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
await this.writeLifecycle(specName, lifecycle);
|
|
108
|
+
|
|
109
|
+
// On completion: trigger ContextSyncManager update and AgentRegistry notification
|
|
110
|
+
if (newStatus === 'completed') {
|
|
111
|
+
await this._onCompleted(specName);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return { success: true, oldStatus, newStatus };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Check if all tasks in a Spec are completed and auto-transition to 'completed'.
|
|
119
|
+
* Only transitions if current status is 'in-progress'.
|
|
120
|
+
* Single-Agent mode: returns {completed: false, transitioned: false}.
|
|
121
|
+
*
|
|
122
|
+
* @param {string} specName
|
|
123
|
+
* @returns {Promise<{completed: boolean, transitioned: boolean}>}
|
|
124
|
+
*/
|
|
125
|
+
async checkCompletion(specName) {
|
|
126
|
+
const enabled = await this._multiAgentConfig.isEnabled();
|
|
127
|
+
if (!enabled) {
|
|
128
|
+
return { completed: false, transitioned: false };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const tasksPath = path.join(this._workspaceRoot, SPECS_DIR, specName, 'tasks.md');
|
|
132
|
+
const exists = await fs.pathExists(tasksPath);
|
|
133
|
+
if (!exists) {
|
|
134
|
+
return { completed: false, transitioned: false };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let content;
|
|
138
|
+
try {
|
|
139
|
+
content = await fs.readFile(tasksPath, 'utf8');
|
|
140
|
+
} catch (err) {
|
|
141
|
+
console.warn(`[SpecLifecycleManager] Failed to read ${tasksPath}: ${err.message}`);
|
|
142
|
+
return { completed: false, transitioned: false };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const allCompleted = this._areAllLeafTasksCompleted(content);
|
|
146
|
+
if (!allCompleted) {
|
|
147
|
+
return { completed: false, transitioned: false };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Only transition if currently in-progress
|
|
151
|
+
const lifecycle = await this.readLifecycle(specName);
|
|
152
|
+
if (lifecycle.status !== 'in-progress') {
|
|
153
|
+
return { completed: true, transitioned: false };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const result = await this.transition(specName, 'completed');
|
|
157
|
+
return { completed: true, transitioned: result.success };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Read lifecycle.json for a Spec.
|
|
162
|
+
* Returns default lifecycle if file doesn't exist or is corrupted.
|
|
163
|
+
*
|
|
164
|
+
* @param {string} specName
|
|
165
|
+
* @returns {Promise<{specName: string, status: string, transitions: Array}>}
|
|
166
|
+
*/
|
|
167
|
+
async readLifecycle(specName) {
|
|
168
|
+
const filePath = this._lifecyclePath(specName);
|
|
169
|
+
const exists = await fs.pathExists(filePath);
|
|
170
|
+
|
|
171
|
+
if (!exists) {
|
|
172
|
+
return { specName, status: DEFAULT_STATUS, transitions: [] };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
const data = await fsUtils.readJSON(filePath);
|
|
177
|
+
if (!data || typeof data.status !== 'string' || !Array.isArray(data.transitions)) {
|
|
178
|
+
console.warn(`[SpecLifecycleManager] Corrupted lifecycle.json for ${specName}, rebuilding`);
|
|
179
|
+
return { specName, status: DEFAULT_STATUS, transitions: [] };
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
specName: data.specName || specName,
|
|
183
|
+
status: data.status,
|
|
184
|
+
transitions: data.transitions,
|
|
185
|
+
};
|
|
186
|
+
} catch (err) {
|
|
187
|
+
console.warn(`[SpecLifecycleManager] Failed to read lifecycle.json for ${specName}: ${err.message}`);
|
|
188
|
+
return { specName, status: DEFAULT_STATUS, transitions: [] };
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Write lifecycle.json for a Spec (atomic write).
|
|
194
|
+
*
|
|
195
|
+
* @param {string} specName
|
|
196
|
+
* @param {{specName?: string, status: string, transitions: Array}} lifecycle
|
|
197
|
+
* @returns {Promise<void>}
|
|
198
|
+
*/
|
|
199
|
+
async writeLifecycle(specName, lifecycle) {
|
|
200
|
+
const filePath = this._lifecyclePath(specName);
|
|
201
|
+
const dir = path.dirname(filePath);
|
|
202
|
+
await fsUtils.ensureDirectory(dir);
|
|
203
|
+
|
|
204
|
+
const data = {
|
|
205
|
+
specName: lifecycle.specName || specName,
|
|
206
|
+
status: lifecycle.status,
|
|
207
|
+
transitions: lifecycle.transitions || [],
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
await fsUtils.writeJSON(filePath, data);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ── Private helpers ──────────────────────────────────────────────
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Handle completion side-effects: update ContextSyncManager and notify via AgentRegistry.
|
|
217
|
+
*
|
|
218
|
+
* @param {string} specName
|
|
219
|
+
* @returns {Promise<void>}
|
|
220
|
+
* @private
|
|
221
|
+
*/
|
|
222
|
+
async _onCompleted(specName) {
|
|
223
|
+
// Update ContextSyncManager with completed status
|
|
224
|
+
if (this._contextSyncManager) {
|
|
225
|
+
try {
|
|
226
|
+
await this._contextSyncManager.updateSpecProgress(specName, {
|
|
227
|
+
status: 'completed',
|
|
228
|
+
progress: 100,
|
|
229
|
+
summary: `Spec ${specName} completed`,
|
|
230
|
+
});
|
|
231
|
+
} catch (err) {
|
|
232
|
+
console.warn(`[SpecLifecycleManager] Failed to update context for ${specName}: ${err.message}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Notify active agents via AgentRegistry (log for now)
|
|
237
|
+
if (this._agentRegistry) {
|
|
238
|
+
try {
|
|
239
|
+
const activeAgents = await this._agentRegistry.getActiveAgents();
|
|
240
|
+
if (activeAgents.length > 0) {
|
|
241
|
+
console.log(
|
|
242
|
+
`[SpecLifecycleManager] Spec ${specName} completed. Notifying ${activeAgents.length} active agent(s): ${activeAgents.map(a => a.agentId).join(', ')}`
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
} catch (err) {
|
|
246
|
+
console.warn(`[SpecLifecycleManager] Failed to notify agents for ${specName}: ${err.message}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Check if all leaf tasks in tasks.md content are completed.
|
|
253
|
+
* Leaf tasks are checkbox lines with no deeper-indented sub-tasks following them.
|
|
254
|
+
* A task is completed when its checkbox is [x].
|
|
255
|
+
*
|
|
256
|
+
* @param {string} content
|
|
257
|
+
* @returns {boolean}
|
|
258
|
+
* @private
|
|
259
|
+
*/
|
|
260
|
+
_areAllLeafTasksCompleted(content) {
|
|
261
|
+
const lines = content.split('\n');
|
|
262
|
+
const taskPattern = /^(\s*)- \[([ x\-~])\]\*?\s/;
|
|
263
|
+
|
|
264
|
+
// First pass: identify all task lines with their indentation levels
|
|
265
|
+
const tasks = [];
|
|
266
|
+
for (let i = 0; i < lines.length; i++) {
|
|
267
|
+
const match = lines[i].match(taskPattern);
|
|
268
|
+
if (match) {
|
|
269
|
+
tasks.push({
|
|
270
|
+
index: i,
|
|
271
|
+
indent: match[1].length,
|
|
272
|
+
status: match[2],
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (tasks.length === 0) {
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Second pass: identify leaf tasks and check completion
|
|
282
|
+
let total = 0;
|
|
283
|
+
let completed = 0;
|
|
284
|
+
|
|
285
|
+
for (let i = 0; i < tasks.length; i++) {
|
|
286
|
+
const current = tasks[i];
|
|
287
|
+
const next = tasks[i + 1];
|
|
288
|
+
|
|
289
|
+
// A task is a leaf if the next task is not more indented
|
|
290
|
+
const isLeaf = !next || next.indent <= current.indent;
|
|
291
|
+
|
|
292
|
+
if (isLeaf) {
|
|
293
|
+
total++;
|
|
294
|
+
if (current.status === 'x') {
|
|
295
|
+
completed++;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return total > 0 && completed === total;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
module.exports = { SpecLifecycleManager, VALID_TRANSITIONS, DEFAULT_STATUS };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SyncBarrier - Agent 切换 Spec 时的同步屏障
|
|
3
|
+
*
|
|
4
|
+
* 确保 Agent 切换 Spec 时基于一致的代码库和 Steering 工作。
|
|
5
|
+
* - 检查工作区是否有未提交的更改(git status --porcelain)
|
|
6
|
+
* - 重新加载所有层级的 Steering 约束
|
|
7
|
+
* - 单 Agent 模式下为 no-op(返回 {ready: true})
|
|
8
|
+
*
|
|
9
|
+
* Requirements: 5.1, 5.2, 5.3, 5.4
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { execSync } = require('child_process');
|
|
13
|
+
const { MultiAgentConfig } = require('./multi-agent-config');
|
|
14
|
+
|
|
15
|
+
class SyncBarrier {
|
|
16
|
+
/**
|
|
17
|
+
* @param {string} workspaceRoot - Absolute path to the project root
|
|
18
|
+
* @param {import('../steering/steering-loader').SteeringLoader} steeringLoader
|
|
19
|
+
*/
|
|
20
|
+
constructor(workspaceRoot, steeringLoader) {
|
|
21
|
+
this._workspaceRoot = workspaceRoot;
|
|
22
|
+
this._steeringLoader = steeringLoader;
|
|
23
|
+
this._multiAgentConfig = new MultiAgentConfig(workspaceRoot);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 执行 Spec 切换前的同步检查。
|
|
28
|
+
* 单 Agent 模式下为 no-op(返回 {ready: true})。
|
|
29
|
+
*
|
|
30
|
+
* @param {string} specName - 目标 Spec
|
|
31
|
+
* @returns {Promise<{ready: boolean, error?: string, steering?: object}>}
|
|
32
|
+
*/
|
|
33
|
+
async prepareSwitch(specName) {
|
|
34
|
+
const enabled = await this._multiAgentConfig.isEnabled();
|
|
35
|
+
if (!enabled) {
|
|
36
|
+
return { ready: true };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Check for uncommitted changes
|
|
40
|
+
try {
|
|
41
|
+
const hasChanges = await this.hasUncommittedChanges();
|
|
42
|
+
if (hasChanges) {
|
|
43
|
+
return {
|
|
44
|
+
ready: false,
|
|
45
|
+
error: 'Uncommitted changes detected. Please commit or stash your changes before switching Spec.',
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
} catch (err) {
|
|
49
|
+
return {
|
|
50
|
+
ready: false,
|
|
51
|
+
error: `Failed to check git status: ${err.message}`,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Reload steering for the target Spec
|
|
56
|
+
try {
|
|
57
|
+
const steering = await this._steeringLoader.loadMerged(specName);
|
|
58
|
+
return { ready: true, steering };
|
|
59
|
+
} catch (err) {
|
|
60
|
+
return {
|
|
61
|
+
ready: false,
|
|
62
|
+
error: `Failed to reload steering for ${specName}: ${err.message}`,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 检查工作区是否有未提交的更改。
|
|
69
|
+
* Git 命令失败时返回 false(优雅降级)。
|
|
70
|
+
*
|
|
71
|
+
* @returns {Promise<boolean>}
|
|
72
|
+
*/
|
|
73
|
+
async hasUncommittedChanges() {
|
|
74
|
+
try {
|
|
75
|
+
const output = execSync('git status --porcelain', {
|
|
76
|
+
encoding: 'utf8',
|
|
77
|
+
cwd: this._workspaceRoot,
|
|
78
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
79
|
+
});
|
|
80
|
+
return output.trim().length > 0;
|
|
81
|
+
} catch (_err) {
|
|
82
|
+
// Git command failed — graceful degradation
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = { SyncBarrier };
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Visualizer generates dependency graph visualizations
|
|
3
|
+
*/
|
|
4
|
+
class Visualizer {
|
|
5
|
+
/**
|
|
6
|
+
* Generate text-based dependency graph
|
|
7
|
+
* @param {Object} graph - Graph with nodes and edges
|
|
8
|
+
* @param {Object} options - Visualization options
|
|
9
|
+
* @returns {string} Text-based graph
|
|
10
|
+
*/
|
|
11
|
+
generateTextGraph(graph, options = {}) {
|
|
12
|
+
const lines = [];
|
|
13
|
+
const { showCriticalPath = false } = options;
|
|
14
|
+
|
|
15
|
+
// Get critical path if requested
|
|
16
|
+
let criticalPath = [];
|
|
17
|
+
if (showCriticalPath) {
|
|
18
|
+
criticalPath = this._getCriticalPath(graph);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Group by master specs
|
|
22
|
+
const masterSpecs = graph.nodes.filter(n => n.type === 'master');
|
|
23
|
+
const subSpecs = graph.nodes.filter(n => n.type === 'sub');
|
|
24
|
+
|
|
25
|
+
if (masterSpecs.length > 0) {
|
|
26
|
+
for (const master of masterSpecs) {
|
|
27
|
+
lines.push(this._formatNode(master, criticalPath));
|
|
28
|
+
|
|
29
|
+
// Find sub-specs of this master
|
|
30
|
+
const children = this._getChildren(master.id, graph);
|
|
31
|
+
for (const child of children) {
|
|
32
|
+
lines.push(` ${this._formatNode(child, criticalPath)}`);
|
|
33
|
+
|
|
34
|
+
// Show dependencies
|
|
35
|
+
const deps = graph.edges.filter(e => e.from === child.id);
|
|
36
|
+
for (const dep of deps) {
|
|
37
|
+
lines.push(` └─ requires: ${dep.to} (${dep.type})`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
lines.push('');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Show standalone specs
|
|
45
|
+
const standalone = subSpecs.filter(s => {
|
|
46
|
+
const hasParent = graph.edges.some(e => e.to === s.id && graph.nodes.find(n => n.id === e.from && n.type === 'master'));
|
|
47
|
+
return !hasParent;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (standalone.length > 0) {
|
|
51
|
+
lines.push('Standalone Specs:');
|
|
52
|
+
for (const spec of standalone) {
|
|
53
|
+
lines.push(this._formatNode(spec, criticalPath));
|
|
54
|
+
|
|
55
|
+
const deps = graph.edges.filter(e => e.from === spec.id);
|
|
56
|
+
for (const dep of deps) {
|
|
57
|
+
lines.push(` └─ requires: ${dep.to} (${dep.type})`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (showCriticalPath && criticalPath.length > 0) {
|
|
63
|
+
lines.push('');
|
|
64
|
+
lines.push(`Critical Path: ${criticalPath.join(' → ')}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return lines.join('\n');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Generate Mermaid format graph
|
|
72
|
+
* @param {Object} graph - Graph with nodes and edges
|
|
73
|
+
* @returns {string} Mermaid graph definition
|
|
74
|
+
*/
|
|
75
|
+
generateMermaidGraph(graph) {
|
|
76
|
+
const lines = ['graph TD'];
|
|
77
|
+
|
|
78
|
+
// Add nodes
|
|
79
|
+
for (const node of graph.nodes) {
|
|
80
|
+
const symbol = this._getStatusSymbol(node.status);
|
|
81
|
+
const label = `${node.id} ${symbol}`;
|
|
82
|
+
const nodeId = this._sanitizeId(node.id);
|
|
83
|
+
|
|
84
|
+
// Use different shapes for different types
|
|
85
|
+
if (node.type === 'master') {
|
|
86
|
+
lines.push(` ${nodeId}[["${label}"]]`);
|
|
87
|
+
} else {
|
|
88
|
+
lines.push(` ${nodeId}["${label}"]`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Add edges
|
|
93
|
+
for (const edge of edges) {
|
|
94
|
+
const fromId = this._sanitizeId(edge.from);
|
|
95
|
+
const toId = this._sanitizeId(edge.to);
|
|
96
|
+
const label = edge.type === 'requires-completion' ? 'requires' : edge.type;
|
|
97
|
+
lines.push(` ${fromId} -->|${label}| ${toId}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return lines.join('\n');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Highlight critical path in graph
|
|
105
|
+
* @param {Object} graph - Graph with nodes and edges
|
|
106
|
+
* @returns {Array<string>} Critical path node IDs
|
|
107
|
+
*/
|
|
108
|
+
highlightCriticalPath(graph) {
|
|
109
|
+
return this._getCriticalPath(graph);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Format a spec node for display
|
|
114
|
+
* @param {Object} node - Node to format
|
|
115
|
+
* @param {Array<string>} criticalPath - Critical path nodes
|
|
116
|
+
* @returns {string} Formatted node string
|
|
117
|
+
*/
|
|
118
|
+
_formatNode(node, criticalPath = []) {
|
|
119
|
+
const symbol = this._getStatusSymbol(node.status);
|
|
120
|
+
const assignment = node.kiroInstance ? ` (${node.kiroInstance})` : ' (unassigned)';
|
|
121
|
+
const critical = criticalPath.includes(node.id) ? ' [CRITICAL]' : '';
|
|
122
|
+
|
|
123
|
+
return `${symbol} ${node.id}${assignment}${critical}`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Get status symbol for a node
|
|
128
|
+
* @param {string} status - Node status
|
|
129
|
+
* @returns {string} Symbol
|
|
130
|
+
*/
|
|
131
|
+
_getStatusSymbol(status) {
|
|
132
|
+
const symbols = {
|
|
133
|
+
'completed': '✓',
|
|
134
|
+
'in-progress': '⧗',
|
|
135
|
+
'not-started': '○',
|
|
136
|
+
'blocked': '✗'
|
|
137
|
+
};
|
|
138
|
+
return symbols[status] || '?';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Get children of a master spec
|
|
143
|
+
* @param {string} masterId - Master spec ID
|
|
144
|
+
* @param {Object} graph - Graph
|
|
145
|
+
* @returns {Array<Object>} Child nodes
|
|
146
|
+
*/
|
|
147
|
+
_getChildren(masterId, graph) {
|
|
148
|
+
// In our model, children reference parent via metadata
|
|
149
|
+
// For visualization, we can infer from naming or metadata
|
|
150
|
+
// For now, return empty array (would need metadata access)
|
|
151
|
+
return [];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Get critical path (longest dependency chain)
|
|
156
|
+
* @param {Object} graph - Graph
|
|
157
|
+
* @returns {Array<string>} Node IDs in critical path
|
|
158
|
+
*/
|
|
159
|
+
_getCriticalPath(graph) {
|
|
160
|
+
const memo = new Map();
|
|
161
|
+
|
|
162
|
+
const getLongestPath = (nodeId) => {
|
|
163
|
+
if (memo.has(nodeId)) {
|
|
164
|
+
return memo.get(nodeId);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const incoming = graph.edges.filter(e => e.to === nodeId);
|
|
168
|
+
|
|
169
|
+
if (incoming.length === 0) {
|
|
170
|
+
memo.set(nodeId, [nodeId]);
|
|
171
|
+
return [nodeId];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
let longestPath = [];
|
|
175
|
+
for (const edge of incoming) {
|
|
176
|
+
const path = getLongestPath(edge.from);
|
|
177
|
+
if (path.length > longestPath.length) {
|
|
178
|
+
longestPath = path;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const result = [...longestPath, nodeId];
|
|
183
|
+
memo.set(nodeId, result);
|
|
184
|
+
return result;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
let criticalPath = [];
|
|
188
|
+
for (const node of graph.nodes) {
|
|
189
|
+
const path = getLongestPath(node.id);
|
|
190
|
+
if (path.length > criticalPath.length) {
|
|
191
|
+
criticalPath = path;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return criticalPath;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Sanitize ID for Mermaid
|
|
200
|
+
* @param {string} id - Node ID
|
|
201
|
+
* @returns {string} Sanitized ID
|
|
202
|
+
*/
|
|
203
|
+
_sanitizeId(id) {
|
|
204
|
+
return id.replace(/[^a-zA-Z0-9]/g, '_');
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
module.exports = Visualizer;
|