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,320 @@
|
|
|
1
|
+
const fs = require('fs').promises;
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ContractManager handles interface contract definition and verification
|
|
6
|
+
*/
|
|
7
|
+
class ContractManager {
|
|
8
|
+
constructor(workspaceRoot, metadataManager) {
|
|
9
|
+
this.workspaceRoot = workspaceRoot;
|
|
10
|
+
this.metadataManager = metadataManager;
|
|
11
|
+
this.specsDir = path.join(workspaceRoot, '.kiro', 'specs');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Define an interface contract for a spec
|
|
16
|
+
* @param {string} specName - Name of the spec
|
|
17
|
+
* @param {Object} contract - Contract definition
|
|
18
|
+
* @returns {Promise<void>}
|
|
19
|
+
*/
|
|
20
|
+
async defineContract(specName, contract) {
|
|
21
|
+
// Validate contract structure
|
|
22
|
+
this._validateContract(contract);
|
|
23
|
+
|
|
24
|
+
const contractPath = this._getContractPath(specName, contract.interfaces[0].name);
|
|
25
|
+
const contractDir = path.dirname(contractPath);
|
|
26
|
+
|
|
27
|
+
// Ensure interfaces directory exists
|
|
28
|
+
await fs.mkdir(contractDir, { recursive: true });
|
|
29
|
+
|
|
30
|
+
// Write contract
|
|
31
|
+
const content = JSON.stringify(contract, null, 2);
|
|
32
|
+
await fs.writeFile(contractPath, content, 'utf8');
|
|
33
|
+
|
|
34
|
+
// Update metadata to include this interface
|
|
35
|
+
await this.metadataManager.atomicUpdate(specName, (metadata) => {
|
|
36
|
+
if (!metadata.interfaces) {
|
|
37
|
+
metadata.interfaces = { provides: [], consumes: [] };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const interfaceName = `${contract.interfaces[0].name}.json`;
|
|
41
|
+
if (!metadata.interfaces.provides.includes(interfaceName)) {
|
|
42
|
+
metadata.interfaces.provides.push(interfaceName);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return metadata;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Read an interface contract
|
|
51
|
+
* @param {string} specName - Name of the spec
|
|
52
|
+
* @param {string} interfaceName - Name of the interface
|
|
53
|
+
* @returns {Promise<Object|null>} Contract or null if not found
|
|
54
|
+
*/
|
|
55
|
+
async readContract(specName, interfaceName) {
|
|
56
|
+
const contractPath = this._getContractPath(specName, interfaceName);
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const content = await fs.readFile(contractPath, 'utf8');
|
|
60
|
+
return JSON.parse(content);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
if (error.code === 'ENOENT') {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
throw new Error(`Failed to read contract ${interfaceName} for ${specName}: ${error.message}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Verify that a spec's implementation matches its contracts
|
|
71
|
+
* @param {string} specName - Name of the spec
|
|
72
|
+
* @returns {Promise<Object>} Verification result
|
|
73
|
+
*/
|
|
74
|
+
async verifyImplementation(specName) {
|
|
75
|
+
const metadata = await this.metadataManager.readMetadata(specName);
|
|
76
|
+
|
|
77
|
+
if (!metadata || !metadata.interfaces || !metadata.interfaces.provides) {
|
|
78
|
+
return {
|
|
79
|
+
valid: true,
|
|
80
|
+
message: 'No interfaces to verify'
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const results = [];
|
|
85
|
+
|
|
86
|
+
for (const interfaceFile of metadata.interfaces.provides) {
|
|
87
|
+
const interfaceName = path.basename(interfaceFile, '.json');
|
|
88
|
+
const contract = await this.readContract(specName, interfaceName);
|
|
89
|
+
|
|
90
|
+
if (!contract) {
|
|
91
|
+
results.push({
|
|
92
|
+
interface: interfaceName,
|
|
93
|
+
valid: false,
|
|
94
|
+
error: 'Contract file not found'
|
|
95
|
+
});
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Try to verify implementation
|
|
100
|
+
const verification = await this._verifyJSImplementation(specName, contract);
|
|
101
|
+
results.push({
|
|
102
|
+
interface: interfaceName,
|
|
103
|
+
...verification
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const allValid = results.every(r => r.valid);
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
valid: allValid,
|
|
111
|
+
results,
|
|
112
|
+
message: allValid ? 'All interfaces verified' : 'Some interfaces have mismatches'
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Verify JavaScript/TypeScript implementation against contract
|
|
118
|
+
* @param {string} specName - Name of the spec
|
|
119
|
+
* @param {Object} contract - Contract definition
|
|
120
|
+
* @returns {Promise<Object>} Verification result
|
|
121
|
+
*/
|
|
122
|
+
async _verifyJSImplementation(specName, contract) {
|
|
123
|
+
const libPath = path.join(this.specsDir, specName, 'lib');
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
// Check if lib directory exists
|
|
127
|
+
await fs.access(libPath);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
return {
|
|
130
|
+
valid: false,
|
|
131
|
+
error: 'Implementation directory not found (lib/)',
|
|
132
|
+
mismatches: []
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const mismatches = [];
|
|
137
|
+
|
|
138
|
+
for (const interfaceDef of contract.interfaces) {
|
|
139
|
+
// Try to find the implementation file
|
|
140
|
+
const possibleFiles = [
|
|
141
|
+
path.join(libPath, `${interfaceDef.name.toLowerCase()}.js`),
|
|
142
|
+
path.join(libPath, `${this._kebabCase(interfaceDef.name)}.js`),
|
|
143
|
+
path.join(libPath, 'index.js')
|
|
144
|
+
];
|
|
145
|
+
|
|
146
|
+
let implementationFound = false;
|
|
147
|
+
let implementationContent = '';
|
|
148
|
+
|
|
149
|
+
for (const filePath of possibleFiles) {
|
|
150
|
+
try {
|
|
151
|
+
implementationContent = await fs.readFile(filePath, 'utf8');
|
|
152
|
+
implementationFound = true;
|
|
153
|
+
break;
|
|
154
|
+
} catch (error) {
|
|
155
|
+
// Try next file
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (!implementationFound) {
|
|
160
|
+
mismatches.push({
|
|
161
|
+
type: 'missing-implementation',
|
|
162
|
+
interface: interfaceDef.name,
|
|
163
|
+
message: `Implementation file not found`
|
|
164
|
+
});
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Check for exported members
|
|
169
|
+
for (const exportDef of interfaceDef.exports) {
|
|
170
|
+
if (exportDef.required) {
|
|
171
|
+
// Simple check: look for the export name in the file
|
|
172
|
+
const exportPattern = new RegExp(`(module\\.exports\\s*=|exports\\.${exportDef.name}|export\\s+(function|class|const)\\s+${exportDef.name})`, 'i');
|
|
173
|
+
|
|
174
|
+
if (!exportPattern.test(implementationContent)) {
|
|
175
|
+
mismatches.push({
|
|
176
|
+
type: 'missing-export',
|
|
177
|
+
interface: interfaceDef.name,
|
|
178
|
+
export: exportDef.name,
|
|
179
|
+
message: `Required export '${exportDef.name}' not found`
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
valid: mismatches.length === 0,
|
|
188
|
+
mismatches
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Detect breaking changes between two contract versions
|
|
194
|
+
* @param {Object} oldContract - Old contract version
|
|
195
|
+
* @param {Object} newContract - New contract version
|
|
196
|
+
* @returns {Array<Object>} List of breaking changes
|
|
197
|
+
*/
|
|
198
|
+
detectBreakingChanges(oldContract, newContract) {
|
|
199
|
+
const breakingChanges = [];
|
|
200
|
+
|
|
201
|
+
// Check for removed interfaces
|
|
202
|
+
for (const oldInterface of oldContract.interfaces) {
|
|
203
|
+
const newInterface = newContract.interfaces.find(i => i.name === oldInterface.name);
|
|
204
|
+
|
|
205
|
+
if (!newInterface) {
|
|
206
|
+
breakingChanges.push({
|
|
207
|
+
type: 'interface-removed',
|
|
208
|
+
interface: oldInterface.name,
|
|
209
|
+
message: `Interface '${oldInterface.name}' was removed`
|
|
210
|
+
});
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Check for removed exports
|
|
215
|
+
for (const oldExport of oldInterface.exports) {
|
|
216
|
+
if (oldExport.required) {
|
|
217
|
+
const newExport = newInterface.exports.find(e => e.name === oldExport.name);
|
|
218
|
+
|
|
219
|
+
if (!newExport) {
|
|
220
|
+
breakingChanges.push({
|
|
221
|
+
type: 'export-removed',
|
|
222
|
+
interface: oldInterface.name,
|
|
223
|
+
export: oldExport.name,
|
|
224
|
+
message: `Required export '${oldExport.name}' was removed`
|
|
225
|
+
});
|
|
226
|
+
} else if (oldExport.signature && newExport.signature && oldExport.signature !== newExport.signature) {
|
|
227
|
+
breakingChanges.push({
|
|
228
|
+
type: 'signature-changed',
|
|
229
|
+
interface: oldInterface.name,
|
|
230
|
+
export: oldExport.name,
|
|
231
|
+
oldSignature: oldExport.signature,
|
|
232
|
+
newSignature: newExport.signature,
|
|
233
|
+
message: `Signature changed for '${oldExport.name}'`
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return breakingChanges;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Get all specs that consume a specific interface
|
|
245
|
+
* @param {string} providerSpec - Spec that provides the interface
|
|
246
|
+
* @param {string} interfaceName - Name of the interface
|
|
247
|
+
* @returns {Promise<Array<string>>} List of consumer spec names
|
|
248
|
+
*/
|
|
249
|
+
async getConsumers(providerSpec, interfaceName) {
|
|
250
|
+
const allSpecs = await this.metadataManager.listAllMetadata();
|
|
251
|
+
const consumers = [];
|
|
252
|
+
|
|
253
|
+
const interfaceRef = `${providerSpec}/interfaces/${interfaceName}.json`;
|
|
254
|
+
|
|
255
|
+
for (const { name, metadata } of allSpecs) {
|
|
256
|
+
if (metadata.interfaces && metadata.interfaces.consumes) {
|
|
257
|
+
if (metadata.interfaces.consumes.includes(interfaceRef)) {
|
|
258
|
+
consumers.push(name);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return consumers;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Validate contract structure
|
|
268
|
+
* @param {Object} contract - Contract to validate
|
|
269
|
+
* @throws {Error} If contract is invalid
|
|
270
|
+
*/
|
|
271
|
+
_validateContract(contract) {
|
|
272
|
+
if (!contract.version) {
|
|
273
|
+
throw new Error('Contract must have a version');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (!contract.spec) {
|
|
277
|
+
throw new Error('Contract must specify the spec name');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (!contract.interfaces || !Array.isArray(contract.interfaces)) {
|
|
281
|
+
throw new Error('Contract must have an interfaces array');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
for (const interfaceDef of contract.interfaces) {
|
|
285
|
+
if (!interfaceDef.name) {
|
|
286
|
+
throw new Error('Each interface must have a name');
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (!interfaceDef.type || !['class', 'function', 'module', 'object'].includes(interfaceDef.type)) {
|
|
290
|
+
throw new Error(`Invalid interface type: ${interfaceDef.type}`);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (!interfaceDef.exports || !Array.isArray(interfaceDef.exports)) {
|
|
294
|
+
throw new Error(`Interface ${interfaceDef.name} must have an exports array`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Get contract file path
|
|
301
|
+
* @param {string} specName - Name of the spec
|
|
302
|
+
* @param {string} interfaceName - Name of the interface
|
|
303
|
+
* @returns {string}
|
|
304
|
+
*/
|
|
305
|
+
_getContractPath(specName, interfaceName) {
|
|
306
|
+
const fileName = interfaceName.endsWith('.json') ? interfaceName : `${interfaceName}.json`;
|
|
307
|
+
return path.join(this.specsDir, specName, 'interfaces', fileName);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Convert string to kebab-case
|
|
312
|
+
* @param {string} str
|
|
313
|
+
* @returns {string}
|
|
314
|
+
*/
|
|
315
|
+
_kebabCase(str) {
|
|
316
|
+
return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
module.exports = ContractManager;
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coordinator - Optional central task assignment and progress tracking
|
|
3
|
+
*
|
|
4
|
+
* When coordinator mode is enabled (via MultiAgentConfig), provides:
|
|
5
|
+
* - Ready task computation based on task dependencies within a Spec
|
|
6
|
+
* - Task assignment to requesting Agents (unlocked + ready tasks)
|
|
7
|
+
* - Progress summary across all Specs and Agents
|
|
8
|
+
* - Coordination log persistence to coordination-log.json
|
|
9
|
+
*
|
|
10
|
+
* When coordinator mode is NOT enabled, all methods are no-ops (zero overhead).
|
|
11
|
+
*
|
|
12
|
+
* Task dependency model (within a single Spec's tasks.md):
|
|
13
|
+
* - A parent task (e.g. "4") depends on all its sub-tasks ("4.1", "4.2", …)
|
|
14
|
+
* - A task with no sub-tasks and no incomplete parent dependency is ready
|
|
15
|
+
* if its status is "not-started"
|
|
16
|
+
*
|
|
17
|
+
* Requirements: 6.1, 6.2, 6.3, 6.4, 6.5, 6.6
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const fs = require('fs').promises;
|
|
21
|
+
const path = require('path');
|
|
22
|
+
const fsUtils = require('../utils/fs-utils');
|
|
23
|
+
const { MultiAgentConfig } = require('./multi-agent-config');
|
|
24
|
+
|
|
25
|
+
const TaskClaimer = require('../task/task-claimer');
|
|
26
|
+
|
|
27
|
+
const CONFIG_DIR = '.kiro/config';
|
|
28
|
+
const SPECS_DIR = '.kiro/specs';
|
|
29
|
+
const LOG_FILENAME = 'coordination-log.json';
|
|
30
|
+
|
|
31
|
+
class Coordinator {
|
|
32
|
+
/**
|
|
33
|
+
* @param {string} workspaceRoot
|
|
34
|
+
* @param {import('./dependency-manager')} dependencyManager
|
|
35
|
+
* @param {import('./agent-registry').AgentRegistry} agentRegistry
|
|
36
|
+
* @param {import('../lock/task-lock-manager').TaskLockManager} taskLockManager
|
|
37
|
+
* @param {import('./spec-lifecycle-manager').SpecLifecycleManager} [specLifecycleManager]
|
|
38
|
+
* @param {import('./sync-barrier').SyncBarrier} [syncBarrier]
|
|
39
|
+
*/
|
|
40
|
+
constructor(workspaceRoot, dependencyManager, agentRegistry, taskLockManager, specLifecycleManager, syncBarrier) {
|
|
41
|
+
this._workspaceRoot = workspaceRoot;
|
|
42
|
+
this._dependencyManager = dependencyManager;
|
|
43
|
+
this._agentRegistry = agentRegistry;
|
|
44
|
+
this._taskLockManager = taskLockManager;
|
|
45
|
+
this._specLifecycleManager = specLifecycleManager || null;
|
|
46
|
+
this._syncBarrier = syncBarrier || null;
|
|
47
|
+
this._multiAgentConfig = new MultiAgentConfig(workspaceRoot);
|
|
48
|
+
this._taskClaimer = new TaskClaimer();
|
|
49
|
+
this._specsDir = path.join(workspaceRoot, SPECS_DIR);
|
|
50
|
+
this._logPath = path.join(workspaceRoot, CONFIG_DIR, LOG_FILENAME);
|
|
51
|
+
this._configDir = path.join(workspaceRoot, CONFIG_DIR);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
// ── Public API ─────────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get tasks that are ready to execute within a Spec.
|
|
59
|
+
*
|
|
60
|
+
* A task is "ready" when:
|
|
61
|
+
* 1. Its status is "not-started"
|
|
62
|
+
* 2. All of its sub-tasks (if any) are completed (parent depends on children)
|
|
63
|
+
* 3. It is not currently locked by any Agent
|
|
64
|
+
*
|
|
65
|
+
* Returns [] when coordinator is not enabled (Req 6.5).
|
|
66
|
+
*
|
|
67
|
+
* @param {string} specName
|
|
68
|
+
* @returns {Promise<Array<object>>} Ready tasks
|
|
69
|
+
*/
|
|
70
|
+
async getReadyTasks(specName) {
|
|
71
|
+
const enabled = await this._multiAgentConfig.isCoordinatorEnabled();
|
|
72
|
+
if (!enabled) return [];
|
|
73
|
+
|
|
74
|
+
const tasks = await this._parseSpecTasks(specName);
|
|
75
|
+
if (!tasks || tasks.length === 0) return [];
|
|
76
|
+
|
|
77
|
+
const lockedTasks = await this._taskLockManager.listLockedTasks(specName);
|
|
78
|
+
const lockedIds = new Set(lockedTasks.map((t) => t.taskId));
|
|
79
|
+
|
|
80
|
+
const readyTasks = [];
|
|
81
|
+
|
|
82
|
+
for (const task of tasks) {
|
|
83
|
+
// Only consider not-started tasks
|
|
84
|
+
if (task.status !== 'not-started') continue;
|
|
85
|
+
|
|
86
|
+
// Skip locked tasks
|
|
87
|
+
if (lockedIds.has(task.taskId)) continue;
|
|
88
|
+
|
|
89
|
+
// Check if this is a parent task with sub-tasks
|
|
90
|
+
const subTasks = this._getSubTasks(tasks, task.taskId);
|
|
91
|
+
if (subTasks.length > 0) {
|
|
92
|
+
// Parent task: ready only when ALL sub-tasks are completed
|
|
93
|
+
const allSubsCompleted = subTasks.every((st) => st.status === 'completed');
|
|
94
|
+
if (!allSubsCompleted) continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Check if this task's parent (if any) has other incomplete prerequisites
|
|
98
|
+
// A sub-task (e.g. "4.1") is ready if it has no further sub-tasks of its own
|
|
99
|
+
// and its own status is not-started (parent completion is handled above)
|
|
100
|
+
|
|
101
|
+
readyTasks.push(task);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return readyTasks;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Assign a ready, unlocked task to the requesting Agent.
|
|
109
|
+
*
|
|
110
|
+
* Picks the first available ready task across all Specs, locks it,
|
|
111
|
+
* and returns the assignment. Returns null if no tasks are available
|
|
112
|
+
* or coordinator is not enabled (Req 6.5).
|
|
113
|
+
*
|
|
114
|
+
* @param {string} agentId
|
|
115
|
+
* @returns {Promise<{specName: string, taskId: string, task: object}|null>}
|
|
116
|
+
*/
|
|
117
|
+
async assignTask(agentId) {
|
|
118
|
+
const enabled = await this._multiAgentConfig.isCoordinatorEnabled();
|
|
119
|
+
if (!enabled) return null;
|
|
120
|
+
|
|
121
|
+
const specNames = await this._listSpecNames();
|
|
122
|
+
|
|
123
|
+
for (const specName of specNames) {
|
|
124
|
+
// SyncBarrier: check before accessing tasks for this Spec
|
|
125
|
+
if (this._syncBarrier) {
|
|
126
|
+
const switchResult = await this._syncBarrier.prepareSwitch(specName);
|
|
127
|
+
if (!switchResult.ready) {
|
|
128
|
+
console.warn(`[Coordinator] SyncBarrier: ${switchResult.error}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const readyTasks = await this.getReadyTasks(specName);
|
|
133
|
+
if (readyTasks.length === 0) continue;
|
|
134
|
+
|
|
135
|
+
// Pick the first ready task
|
|
136
|
+
const task = readyTasks[0];
|
|
137
|
+
|
|
138
|
+
// Attempt to lock it
|
|
139
|
+
const lockResult = await this._taskLockManager.acquireTaskLock(
|
|
140
|
+
specName,
|
|
141
|
+
task.taskId,
|
|
142
|
+
agentId,
|
|
143
|
+
{ reason: 'coordinator-assignment' }
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
if (lockResult.success) {
|
|
147
|
+
await this.logAssignment(agentId, specName, task.taskId, 'assign');
|
|
148
|
+
return { specName, taskId: task.taskId, task };
|
|
149
|
+
}
|
|
150
|
+
// Lock failed (race condition) – try next task
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Mark a task as completed and compute newly ready tasks.
|
|
158
|
+
*
|
|
159
|
+
* Returns { newReadyTasks: [] } when coordinator is not enabled (Req 6.5).
|
|
160
|
+
*
|
|
161
|
+
* @param {string} specName
|
|
162
|
+
* @param {string} taskId
|
|
163
|
+
* @param {string} agentId
|
|
164
|
+
* @returns {Promise<{newReadyTasks: Array<object>}>}
|
|
165
|
+
*/
|
|
166
|
+
async completeTask(specName, taskId, agentId) {
|
|
167
|
+
const enabled = await this._multiAgentConfig.isCoordinatorEnabled();
|
|
168
|
+
if (!enabled) return { newReadyTasks: [] };
|
|
169
|
+
|
|
170
|
+
// Release the task lock
|
|
171
|
+
await this._taskLockManager.releaseTaskLock(specName, taskId, agentId);
|
|
172
|
+
|
|
173
|
+
// Log completion
|
|
174
|
+
await this.logAssignment(agentId, specName, taskId, 'complete');
|
|
175
|
+
|
|
176
|
+
// Compute newly ready tasks (tasks that became ready after this completion)
|
|
177
|
+
const newReadyTasks = await this.getReadyTasks(specName);
|
|
178
|
+
|
|
179
|
+
// Check if Spec is now fully completed
|
|
180
|
+
if (this._specLifecycleManager) {
|
|
181
|
+
await this._specLifecycleManager.checkCompletion(specName);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return { newReadyTasks };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Get progress summary across all Specs and Agents.
|
|
189
|
+
*
|
|
190
|
+
* Returns empty summary when coordinator is not enabled (Req 6.5).
|
|
191
|
+
*
|
|
192
|
+
* @returns {Promise<{specs: object, agents: object}>}
|
|
193
|
+
*/
|
|
194
|
+
async getProgress() {
|
|
195
|
+
const enabled = await this._multiAgentConfig.isCoordinatorEnabled();
|
|
196
|
+
if (!enabled) return { specs: {}, agents: {} };
|
|
197
|
+
|
|
198
|
+
const specNames = await this._listSpecNames();
|
|
199
|
+
const specsProgress = {};
|
|
200
|
+
|
|
201
|
+
for (const specName of specNames) {
|
|
202
|
+
const tasks = await this._parseSpecTasks(specName);
|
|
203
|
+
if (!tasks || tasks.length === 0) continue;
|
|
204
|
+
|
|
205
|
+
let totalTasks = 0;
|
|
206
|
+
let completedTasks = 0;
|
|
207
|
+
let inProgressTasks = 0;
|
|
208
|
+
|
|
209
|
+
for (const task of tasks) {
|
|
210
|
+
totalTasks++;
|
|
211
|
+
if (task.status === 'completed') completedTasks++;
|
|
212
|
+
else if (task.status === 'in-progress') inProgressTasks++;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
specsProgress[specName] = {
|
|
216
|
+
totalTasks,
|
|
217
|
+
completedTasks,
|
|
218
|
+
inProgressTasks,
|
|
219
|
+
percentage: totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Agent progress
|
|
224
|
+
const agentsProgress = {};
|
|
225
|
+
const activeAgents = await this._agentRegistry.getActiveAgents();
|
|
226
|
+
|
|
227
|
+
for (const agent of activeAgents) {
|
|
228
|
+
// Count completed tasks from the coordination log
|
|
229
|
+
const completedCount = await this._countAgentCompletions(agent.agentId);
|
|
230
|
+
|
|
231
|
+
agentsProgress[agent.agentId] = {
|
|
232
|
+
status: agent.status,
|
|
233
|
+
currentTask: agent.currentTask || null,
|
|
234
|
+
completedCount,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return { specs: specsProgress, agents: agentsProgress };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Persist a coordination log entry to coordination-log.json.
|
|
243
|
+
*
|
|
244
|
+
* No-op when coordinator is not enabled (Req 6.5).
|
|
245
|
+
*
|
|
246
|
+
* @param {string} agentId
|
|
247
|
+
* @param {string} specName
|
|
248
|
+
* @param {string} taskId
|
|
249
|
+
* @param {'assign'|'complete'|'release'|'timeout'} action
|
|
250
|
+
* @returns {Promise<void>}
|
|
251
|
+
*/
|
|
252
|
+
async logAssignment(agentId, specName, taskId, action) {
|
|
253
|
+
const enabled = await this._multiAgentConfig.isCoordinatorEnabled();
|
|
254
|
+
if (!enabled) return;
|
|
255
|
+
|
|
256
|
+
const entry = {
|
|
257
|
+
timestamp: new Date().toISOString(),
|
|
258
|
+
agentId,
|
|
259
|
+
action,
|
|
260
|
+
specName,
|
|
261
|
+
taskId,
|
|
262
|
+
details: {},
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
await fsUtils.ensureDirectory(this._configDir);
|
|
266
|
+
|
|
267
|
+
let log = [];
|
|
268
|
+
try {
|
|
269
|
+
const exists = await fsUtils.pathExists(this._logPath);
|
|
270
|
+
if (exists) {
|
|
271
|
+
log = await fsUtils.readJSON(this._logPath);
|
|
272
|
+
if (!Array.isArray(log)) log = [];
|
|
273
|
+
}
|
|
274
|
+
} catch (_err) {
|
|
275
|
+
// Corrupted log – start fresh
|
|
276
|
+
log = [];
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
log.push(entry);
|
|
280
|
+
await fsUtils.writeJSON(this._logPath, log);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ── Private helpers ────────────────────────────────────────────────
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Parse tasks.md for a given Spec using TaskClaimer.
|
|
287
|
+
* @param {string} specName
|
|
288
|
+
* @returns {Promise<Array<object>|null>}
|
|
289
|
+
* @private
|
|
290
|
+
*/
|
|
291
|
+
async _parseSpecTasks(specName) {
|
|
292
|
+
const tasksPath = path.join(this._specsDir, specName, 'tasks.md');
|
|
293
|
+
const exists = await fsUtils.pathExists(tasksPath);
|
|
294
|
+
if (!exists) return null;
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
return await this._taskClaimer.parseTasks(tasksPath);
|
|
298
|
+
} catch (_err) {
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Get direct sub-tasks of a parent task.
|
|
305
|
+
* E.g. for parent "4", returns tasks "4.1", "4.2", etc.
|
|
306
|
+
* Does NOT return deeper descendants (e.g. "4.1.1").
|
|
307
|
+
*
|
|
308
|
+
* @param {Array<object>} tasks - All parsed tasks
|
|
309
|
+
* @param {string} parentId - Parent task ID (e.g. "4")
|
|
310
|
+
* @returns {Array<object>}
|
|
311
|
+
* @private
|
|
312
|
+
*/
|
|
313
|
+
_getSubTasks(tasks, parentId) {
|
|
314
|
+
const prefix = parentId + '.';
|
|
315
|
+
return tasks.filter((t) => {
|
|
316
|
+
if (!t.taskId.startsWith(prefix)) return false;
|
|
317
|
+
// Only direct children: "4.1" is a child of "4", but "4.1.1" is not
|
|
318
|
+
const remainder = t.taskId.slice(prefix.length);
|
|
319
|
+
return /^\d+$/.test(remainder);
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* List all Spec directory names that contain a tasks.md file.
|
|
325
|
+
* @returns {Promise<string[]>}
|
|
326
|
+
* @private
|
|
327
|
+
*/
|
|
328
|
+
async _listSpecNames() {
|
|
329
|
+
let entries;
|
|
330
|
+
try {
|
|
331
|
+
entries = await fs.readdir(this._specsDir, { withFileTypes: true });
|
|
332
|
+
} catch (err) {
|
|
333
|
+
if (err.code === 'ENOENT') return [];
|
|
334
|
+
throw err;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const specNames = [];
|
|
338
|
+
for (const entry of entries) {
|
|
339
|
+
if (!entry.isDirectory()) continue;
|
|
340
|
+
const tasksPath = path.join(this._specsDir, entry.name, 'tasks.md');
|
|
341
|
+
const exists = await fsUtils.pathExists(tasksPath);
|
|
342
|
+
if (exists) {
|
|
343
|
+
specNames.push(entry.name);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return specNames;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Count the number of 'complete' actions for an agent in the coordination log.
|
|
351
|
+
* @param {string} agentId
|
|
352
|
+
* @returns {Promise<number>}
|
|
353
|
+
* @private
|
|
354
|
+
*/
|
|
355
|
+
async _countAgentCompletions(agentId) {
|
|
356
|
+
try {
|
|
357
|
+
const exists = await fsUtils.pathExists(this._logPath);
|
|
358
|
+
if (!exists) return 0;
|
|
359
|
+
|
|
360
|
+
const log = await fsUtils.readJSON(this._logPath);
|
|
361
|
+
if (!Array.isArray(log)) return 0;
|
|
362
|
+
|
|
363
|
+
return log.filter((e) => e.agentId === agentId && e.action === 'complete').length;
|
|
364
|
+
} catch (_err) {
|
|
365
|
+
return 0;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
module.exports = { Coordinator };
|