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,418 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task Status Store
|
|
3
|
+
*
|
|
4
|
+
* Provides concurrency-safe reads and writes to tasks.md.
|
|
5
|
+
* In multi-Agent mode, uses file-level locks + exponential backoff retry
|
|
6
|
+
* to prevent concurrent write conflicts.
|
|
7
|
+
* In single-Agent mode, delegates directly to the existing TaskClaimer
|
|
8
|
+
* (no locks, no retries) for full backward compatibility.
|
|
9
|
+
*
|
|
10
|
+
* Requirements: 3.1-3.6
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs-extra');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const { MultiAgentConfig } = require('../collab/multi-agent-config');
|
|
16
|
+
const TaskClaimer = require('./task-claimer');
|
|
17
|
+
const fsUtils = require('../utils/fs-utils');
|
|
18
|
+
|
|
19
|
+
const MAX_RETRIES = 5;
|
|
20
|
+
const BASE_DELAY_MS = 100;
|
|
21
|
+
const STALE_LOCK_MS = 30000; // 30 seconds
|
|
22
|
+
|
|
23
|
+
class TaskStatusStore {
|
|
24
|
+
/**
|
|
25
|
+
* @param {string} workspaceRoot - Absolute path to the project root
|
|
26
|
+
*/
|
|
27
|
+
constructor(workspaceRoot) {
|
|
28
|
+
this._workspaceRoot = workspaceRoot;
|
|
29
|
+
this._config = new MultiAgentConfig(workspaceRoot);
|
|
30
|
+
this._claimer = new TaskClaimer();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Whether multi-Agent mode is currently enabled.
|
|
35
|
+
* @returns {Promise<boolean>}
|
|
36
|
+
*/
|
|
37
|
+
async isMultiAgentMode() {
|
|
38
|
+
return this._config.isEnabled();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Public API
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Safely update a task's status in tasks.md.
|
|
47
|
+
*
|
|
48
|
+
* Multi-Agent mode: acquire file lock → verify line → write → release lock.
|
|
49
|
+
* Single-Agent mode: delegate to TaskClaimer.updateTaskStatus().
|
|
50
|
+
*
|
|
51
|
+
* @param {string} specName
|
|
52
|
+
* @param {string} taskId
|
|
53
|
+
* @param {string} status
|
|
54
|
+
* @param {object} [options]
|
|
55
|
+
* @returns {Promise<object>} UpdateResult
|
|
56
|
+
*/
|
|
57
|
+
async updateStatus(specName, taskId, status, _options = {}) {
|
|
58
|
+
const multiAgent = await this.isMultiAgentMode();
|
|
59
|
+
if (!multiAgent) {
|
|
60
|
+
return this._claimer.updateTaskStatus(this._workspaceRoot, specName, taskId, status);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return this._withLockAndRetry(specName, async () => {
|
|
64
|
+
const tasksPath = this._tasksPath(specName);
|
|
65
|
+
const content = await fs.readFile(tasksPath, 'utf8');
|
|
66
|
+
const lines = content.split(/\r?\n/);
|
|
67
|
+
const tasks = await this._claimer.parseTasks(tasksPath);
|
|
68
|
+
const task = tasks.find(t => t.taskId === taskId);
|
|
69
|
+
|
|
70
|
+
if (!task) {
|
|
71
|
+
return { success: false, error: `Task not found: ${taskId}` };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Verify target line hasn't been modified (Requirement 3.4)
|
|
75
|
+
if (lines[task.lineNumber] !== task.originalLine) {
|
|
76
|
+
throw new ConflictError(`Target line modified by another agent`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Build new line
|
|
80
|
+
const statusChar = this._claimer.statusToChar(status);
|
|
81
|
+
const optionalMarker = task.isOptional ? '*' : '';
|
|
82
|
+
const linePrefix = task.linePrefix || '- ';
|
|
83
|
+
let newLine = `${linePrefix}[${statusChar}]${optionalMarker} ${taskId} ${task.title}`;
|
|
84
|
+
if (task.claimedBy) {
|
|
85
|
+
newLine += ` [@${task.claimedBy}, claimed: ${task.claimedAt}]`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
lines[task.lineNumber] = newLine;
|
|
89
|
+
await fsUtils.atomicWrite(tasksPath, lines.join('\n'));
|
|
90
|
+
|
|
91
|
+
return { success: true, taskId, oldStatus: task.status, newStatus: status };
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Safely claim a task in tasks.md.
|
|
97
|
+
*
|
|
98
|
+
* Multi-Agent mode: acquire file lock → verify line → write → release lock.
|
|
99
|
+
* Single-Agent mode: delegate to TaskClaimer.claimTask().
|
|
100
|
+
*
|
|
101
|
+
* @param {string} specName
|
|
102
|
+
* @param {string} taskId
|
|
103
|
+
* @param {string} agentId
|
|
104
|
+
* @param {string} username
|
|
105
|
+
* @returns {Promise<object>} ClaimResult
|
|
106
|
+
*/
|
|
107
|
+
async claimTask(specName, taskId, agentId, username) {
|
|
108
|
+
const multiAgent = await this.isMultiAgentMode();
|
|
109
|
+
if (!multiAgent) {
|
|
110
|
+
return this._claimer.claimTask(this._workspaceRoot, specName, taskId, username);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return this._withLockAndRetry(specName, async () => {
|
|
114
|
+
const tasksPath = this._tasksPath(specName);
|
|
115
|
+
const content = await fs.readFile(tasksPath, 'utf8');
|
|
116
|
+
const lines = content.split(/\r?\n/);
|
|
117
|
+
const tasks = await this._claimer.parseTasks(tasksPath);
|
|
118
|
+
const task = tasks.find(t => t.taskId === taskId);
|
|
119
|
+
|
|
120
|
+
if (!task) {
|
|
121
|
+
return { success: false, error: `Task not found: ${taskId}` };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Verify target line hasn't been modified (Requirement 3.4)
|
|
125
|
+
if (lines[task.lineNumber] !== task.originalLine) {
|
|
126
|
+
throw new ConflictError(`Target line modified by another agent`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Check if already claimed by another user
|
|
130
|
+
if (task.claimedBy && task.claimedBy !== username) {
|
|
131
|
+
return {
|
|
132
|
+
success: false,
|
|
133
|
+
error: `Task already claimed by ${task.claimedBy}`,
|
|
134
|
+
currentClaim: {
|
|
135
|
+
username: task.claimedBy,
|
|
136
|
+
claimedAt: task.claimedAt,
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Build new line with claim info
|
|
142
|
+
const claimTimestamp = new Date().toISOString();
|
|
143
|
+
const statusChar = this._claimer.statusToChar('in-progress');
|
|
144
|
+
const optionalMarker = task.isOptional ? '*' : '';
|
|
145
|
+
const linePrefix = task.linePrefix || '- ';
|
|
146
|
+
const newLine = `${linePrefix}[${statusChar}]${optionalMarker} ${taskId} ${task.title} [@${username}, claimed: ${claimTimestamp}]`;
|
|
147
|
+
|
|
148
|
+
lines[task.lineNumber] = newLine;
|
|
149
|
+
await fsUtils.atomicWrite(tasksPath, lines.join('\n'));
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
success: true,
|
|
153
|
+
taskId,
|
|
154
|
+
agentId,
|
|
155
|
+
username,
|
|
156
|
+
claimedAt: claimTimestamp,
|
|
157
|
+
previousClaim: task.claimedBy
|
|
158
|
+
? { username: task.claimedBy, claimedAt: task.claimedAt }
|
|
159
|
+
: null,
|
|
160
|
+
};
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Safely unclaim a task in tasks.md.
|
|
166
|
+
*
|
|
167
|
+
* Multi-Agent mode: acquire file lock → verify line → write → release lock.
|
|
168
|
+
* Single-Agent mode: delegate to TaskClaimer.unclaimTask().
|
|
169
|
+
*
|
|
170
|
+
* @param {string} specName
|
|
171
|
+
* @param {string} taskId
|
|
172
|
+
* @param {string} agentId
|
|
173
|
+
* @param {string} username
|
|
174
|
+
* @returns {Promise<object>} UnclaimResult
|
|
175
|
+
*/
|
|
176
|
+
async unclaimTask(specName, taskId, agentId, username) {
|
|
177
|
+
const multiAgent = await this.isMultiAgentMode();
|
|
178
|
+
if (!multiAgent) {
|
|
179
|
+
return this._claimer.unclaimTask(this._workspaceRoot, specName, taskId, username);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return this._withLockAndRetry(specName, async () => {
|
|
183
|
+
const tasksPath = this._tasksPath(specName);
|
|
184
|
+
const content = await fs.readFile(tasksPath, 'utf8');
|
|
185
|
+
const lines = content.split(/\r?\n/);
|
|
186
|
+
const tasks = await this._claimer.parseTasks(tasksPath);
|
|
187
|
+
const task = tasks.find(t => t.taskId === taskId);
|
|
188
|
+
|
|
189
|
+
if (!task) {
|
|
190
|
+
return { success: false, error: `Task not found: ${taskId}` };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Verify target line hasn't been modified (Requirement 3.4)
|
|
194
|
+
if (lines[task.lineNumber] !== task.originalLine) {
|
|
195
|
+
throw new ConflictError(`Target line modified by another agent`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (!task.claimedBy) {
|
|
199
|
+
return { success: false, error: 'Task is not claimed' };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (task.claimedBy !== username) {
|
|
203
|
+
return { success: false, error: `Task is claimed by ${task.claimedBy}, not ${username}` };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Build new line without claim info, reset status
|
|
207
|
+
const statusChar = this._claimer.statusToChar('not-started');
|
|
208
|
+
const optionalMarker = task.isOptional ? '*' : '';
|
|
209
|
+
const linePrefix = task.linePrefix || '- ';
|
|
210
|
+
const newLine = `${linePrefix}[${statusChar}]${optionalMarker} ${taskId} ${task.title}`;
|
|
211
|
+
|
|
212
|
+
lines[task.lineNumber] = newLine;
|
|
213
|
+
await fsUtils.atomicWrite(tasksPath, lines.join('\n'));
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
success: true,
|
|
217
|
+
taskId,
|
|
218
|
+
agentId,
|
|
219
|
+
username,
|
|
220
|
+
unclaimedAt: new Date().toISOString(),
|
|
221
|
+
};
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
// ---------------------------------------------------------------------------
|
|
227
|
+
// Private helpers
|
|
228
|
+
// ---------------------------------------------------------------------------
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Absolute path to tasks.md for a given spec.
|
|
232
|
+
* @param {string} specName
|
|
233
|
+
* @returns {string}
|
|
234
|
+
*/
|
|
235
|
+
_tasksPath(specName) {
|
|
236
|
+
return path.join(this._workspaceRoot, '.kiro/specs', specName, 'tasks.md');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Absolute path to the file-level lock for tasks.md.
|
|
241
|
+
* @param {string} specName
|
|
242
|
+
* @returns {string}
|
|
243
|
+
*/
|
|
244
|
+
_lockPath(specName) {
|
|
245
|
+
return path.join(this._workspaceRoot, '.kiro/specs', specName, 'tasks.md.lock');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// ---------------------------------------------------------------------------
|
|
249
|
+
// File lock primitives
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Try to acquire the file-level lock.
|
|
254
|
+
* Uses `wx` (exclusive create) flag so that only one writer can succeed.
|
|
255
|
+
*
|
|
256
|
+
* @param {string} specName
|
|
257
|
+
* @returns {Promise<boolean>} true if lock acquired
|
|
258
|
+
*/
|
|
259
|
+
async _acquireLock(specName) {
|
|
260
|
+
const lockFile = this._lockPath(specName);
|
|
261
|
+
const lockData = JSON.stringify({
|
|
262
|
+
acquiredAt: new Date().toISOString(),
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
try {
|
|
266
|
+
await fs.writeFile(lockFile, lockData, { flag: 'wx' });
|
|
267
|
+
return true;
|
|
268
|
+
} catch (err) {
|
|
269
|
+
if (err.code === 'EEXIST') {
|
|
270
|
+
// Lock held – check for staleness
|
|
271
|
+
return this._tryClaimStaleLock(lockFile, lockData);
|
|
272
|
+
}
|
|
273
|
+
throw err;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Release the file-level lock by deleting the lock file.
|
|
279
|
+
* @param {string} specName
|
|
280
|
+
*/
|
|
281
|
+
async _releaseLock(specName) {
|
|
282
|
+
const lockFile = this._lockPath(specName);
|
|
283
|
+
try {
|
|
284
|
+
await fs.unlink(lockFile);
|
|
285
|
+
} catch (err) {
|
|
286
|
+
// Ignore ENOENT – lock may have been cleaned up already
|
|
287
|
+
if (err.code !== 'ENOENT') {
|
|
288
|
+
throw err;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* If the existing lock file is older than STALE_LOCK_MS, delete it and
|
|
295
|
+
* re-attempt acquisition.
|
|
296
|
+
*
|
|
297
|
+
* @param {string} lockFile
|
|
298
|
+
* @param {string} lockData
|
|
299
|
+
* @returns {Promise<boolean>}
|
|
300
|
+
*/
|
|
301
|
+
async _tryClaimStaleLock(lockFile, lockData) {
|
|
302
|
+
try {
|
|
303
|
+
const stat = await fs.stat(lockFile);
|
|
304
|
+
const ageMs = Date.now() - stat.mtimeMs;
|
|
305
|
+
if (ageMs > STALE_LOCK_MS) {
|
|
306
|
+
// Stale lock – remove and retry
|
|
307
|
+
await fs.unlink(lockFile);
|
|
308
|
+
try {
|
|
309
|
+
await fs.writeFile(lockFile, lockData, { flag: 'wx' });
|
|
310
|
+
return true;
|
|
311
|
+
} catch (retryErr) {
|
|
312
|
+
// Another agent beat us to it
|
|
313
|
+
if (retryErr.code === 'EEXIST') return false;
|
|
314
|
+
throw retryErr;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
} catch (statErr) {
|
|
318
|
+
// Lock file disappeared between our check and stat – try again
|
|
319
|
+
if (statErr.code === 'ENOENT') {
|
|
320
|
+
try {
|
|
321
|
+
await fs.writeFile(lockFile, lockData, { flag: 'wx' });
|
|
322
|
+
return true;
|
|
323
|
+
} catch (retryErr) {
|
|
324
|
+
if (retryErr.code === 'EEXIST') return false;
|
|
325
|
+
throw retryErr;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// ---------------------------------------------------------------------------
|
|
333
|
+
// Retry orchestration
|
|
334
|
+
// ---------------------------------------------------------------------------
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Execute `fn` while holding the tasks.md file lock.
|
|
338
|
+
* On failure (lock contention or ConflictError), retries with exponential
|
|
339
|
+
* backoff up to MAX_RETRIES times.
|
|
340
|
+
*
|
|
341
|
+
* If `fn` returns a result with `success: false` that is NOT a conflict
|
|
342
|
+
* (e.g. "Task not found"), the result is returned immediately without retry.
|
|
343
|
+
*
|
|
344
|
+
* @param {string} specName
|
|
345
|
+
* @param {Function} fn - async () => result
|
|
346
|
+
* @returns {Promise<object>}
|
|
347
|
+
*/
|
|
348
|
+
async _withLockAndRetry(specName, fn) {
|
|
349
|
+
let lastError = null;
|
|
350
|
+
|
|
351
|
+
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
352
|
+
// Back off before retrying (skip delay on first attempt)
|
|
353
|
+
if (attempt > 0) {
|
|
354
|
+
const delayMs = BASE_DELAY_MS * Math.pow(2, attempt - 1);
|
|
355
|
+
await this._sleep(delayMs);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
let lockAcquired = false;
|
|
359
|
+
try {
|
|
360
|
+
lockAcquired = await this._acquireLock(specName);
|
|
361
|
+
if (!lockAcquired) {
|
|
362
|
+
lastError = new Error('Failed to acquire file lock');
|
|
363
|
+
continue; // retry
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const result = await fn();
|
|
367
|
+
|
|
368
|
+
// Non-conflict failures (e.g. task not found) – return immediately
|
|
369
|
+
if (result && result.success === false) {
|
|
370
|
+
return result;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return result;
|
|
374
|
+
} catch (err) {
|
|
375
|
+
if (err instanceof ConflictError) {
|
|
376
|
+
lastError = err;
|
|
377
|
+
continue; // retry
|
|
378
|
+
}
|
|
379
|
+
// Unexpected error – propagate
|
|
380
|
+
throw err;
|
|
381
|
+
} finally {
|
|
382
|
+
if (lockAcquired) {
|
|
383
|
+
await this._releaseLock(specName);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Retries exhausted – return conflict error (Requirement 3.5)
|
|
389
|
+
return {
|
|
390
|
+
success: false,
|
|
391
|
+
error: 'Conflict: retries exhausted, original file preserved',
|
|
392
|
+
conflict: true,
|
|
393
|
+
lastError: lastError ? lastError.message : 'unknown',
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Promise-based sleep helper.
|
|
399
|
+
* @param {number} ms
|
|
400
|
+
* @returns {Promise<void>}
|
|
401
|
+
*/
|
|
402
|
+
_sleep(ms) {
|
|
403
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// ---------------------------------------------------------------------------
|
|
408
|
+
// Internal error type for conflict detection
|
|
409
|
+
// ---------------------------------------------------------------------------
|
|
410
|
+
|
|
411
|
+
class ConflictError extends Error {
|
|
412
|
+
constructor(message) {
|
|
413
|
+
super(message);
|
|
414
|
+
this.name = 'ConflictError';
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
module.exports = { TaskStatusStore, ConflictError };
|