kiro-spec-engine 1.44.0 → 1.45.2
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 +32 -0
- package/README.md +29 -2
- package/README.zh.md +29 -2
- package/bin/kiro-spec-engine.js +4 -0
- package/lib/commands/orchestrate.js +315 -0
- package/lib/orchestrator/agent-spawner.js +432 -0
- package/lib/orchestrator/bootstrap-prompt-builder.js +236 -0
- package/lib/orchestrator/index.js +19 -0
- package/lib/orchestrator/orchestration-engine.js +631 -0
- package/lib/orchestrator/orchestrator-config.js +157 -0
- package/lib/orchestrator/status-monitor.js +438 -0
- package/package.json +1 -1
- package/template/.kiro/README.md +14 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.45.2] - 2026-02-13
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **AgentSpawner auth fallback**: Added `~/.codex/auth.json` fallback when `CODEX_API_KEY` env var is not set, supporting users who configured auth via `codex auth`
|
|
14
|
+
- **AgentSpawner codex command**: Added `codexCommand` config option (e.g. `"npx @openai/codex"`) for users without global Codex CLI install
|
|
15
|
+
- **OrchestratorConfig**: Added `codexCommand` to known config keys and defaults
|
|
16
|
+
|
|
17
|
+
## [1.45.1] - 2026-02-12
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- **StatusMonitor property test**: Replaced `fc.date()` with `fc.integer`-based timestamp generator to prevent `Invalid Date` during fast-check shrinking
|
|
21
|
+
- **ExecutionLogger rotation test**: Replaced `Array(200000).fill() + JSON.stringify` with string repeat for large file generation, fixing CI timeout (10s → 45ms)
|
|
22
|
+
|
|
23
|
+
## [1.45.0] - 2026-02-12
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
- **Agent Orchestrator**: Multi-agent parallel Spec execution via Codex CLI
|
|
27
|
+
- **OrchestratorConfig** (`lib/orchestrator/orchestrator-config.js`): Configuration management for orchestrator settings (agent backend, parallelism, timeout, retries)
|
|
28
|
+
- **BootstrapPromptBuilder** (`lib/orchestrator/bootstrap-prompt-builder.js`): Builds bootstrap prompts with Spec path, steering context, and execution instructions for sub-agents
|
|
29
|
+
- **AgentSpawner** (`lib/orchestrator/agent-spawner.js`): Process manager for Codex CLI sub-agents with timeout detection, graceful termination (SIGTERM → SIGKILL), and event emission
|
|
30
|
+
- **StatusMonitor** (`lib/orchestrator/status-monitor.js`): Codex JSON Lines event parsing, per-Spec status tracking, orchestration-level status aggregation
|
|
31
|
+
- **OrchestrationEngine** (`lib/orchestrator/orchestration-engine.js`): Core engine with DAG-based dependency analysis, batch scheduling, parallel execution (≤ maxParallel), failure propagation, and retry mechanism
|
|
32
|
+
- **CLI Commands** (`kse orchestrate`):
|
|
33
|
+
- `kse orchestrate run --specs "spec-a,spec-b" --max-parallel 3` — Start multi-agent orchestration
|
|
34
|
+
- `kse orchestrate status` — View orchestration progress
|
|
35
|
+
- `kse orchestrate stop` — Gracefully stop all sub-agents
|
|
36
|
+
- 11 correctness properties verified via property-based testing (fast-check)
|
|
37
|
+
- 236+ new tests across unit, property, and integration test suites
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
- **StatusMonitor property test**: Fixed `fc.date()` generating invalid dates causing `RangeError: Invalid time value` in `toISOString()` — constrained date range to 2000-2100
|
|
41
|
+
|
|
10
42
|
## [1.44.0] - 2026-02-12
|
|
11
43
|
|
|
12
44
|
### Added
|
package/README.md
CHANGED
|
@@ -332,6 +332,28 @@ Structure your work with Requirements → Design → Tasks workflow
|
|
|
332
332
|
|
|
333
333
|
[Learn more about Multi-Agent Coordination →](docs/multi-agent-coordination-guide.md)
|
|
334
334
|
|
|
335
|
+
### Agent Orchestrator 🚀 NEW in v1.45.0
|
|
336
|
+
- **Automated Multi-Agent Spec Execution**: Replace manual multi-terminal workflows with a single command
|
|
337
|
+
- **DAG-Based Scheduling**: Analyze Spec dependencies, compute execution batches via topological sort
|
|
338
|
+
- **Parallel Execution**: Run up to N Specs simultaneously via Codex CLI sub-agents (`--max-parallel`)
|
|
339
|
+
- **Failure Propagation**: Failed Spec's dependents automatically marked as skipped
|
|
340
|
+
- **Retry Mechanism**: Configurable automatic retry for failed Specs
|
|
341
|
+
- **Real-Time Monitoring**: Track per-Spec status and overall orchestration progress
|
|
342
|
+
- **Graceful Termination**: Stop all sub-agents cleanly (SIGTERM → SIGKILL)
|
|
343
|
+
- **Configurable**: Agent backend, parallelism, timeout, retries via `.kiro/config/orchestrator.json`
|
|
344
|
+
|
|
345
|
+
**Quick Start**:
|
|
346
|
+
```bash
|
|
347
|
+
# Run 3 Specs in parallel via Codex CLI
|
|
348
|
+
kse orchestrate run --specs "spec-a,spec-b,spec-c" --max-parallel 3
|
|
349
|
+
|
|
350
|
+
# Check orchestration progress
|
|
351
|
+
kse orchestrate status
|
|
352
|
+
|
|
353
|
+
# Stop all sub-agents
|
|
354
|
+
kse orchestrate stop
|
|
355
|
+
```
|
|
356
|
+
|
|
335
357
|
### Spec-Level Steering & Context Sync 🚀 NEW in v1.44.0
|
|
336
358
|
- **Spec Steering (L4)**: Independent `steering.md` per Spec with constraints, notes, and decisions — zero cross-agent conflict
|
|
337
359
|
- **Steering Loader**: Unified L1-L4 four-layer steering loader with priority-based merging
|
|
@@ -500,6 +522,11 @@ kse scene ontology lineage --ref <ref> # Show data lineage
|
|
|
500
522
|
kse scene ontology agent-info --package <path> # Show agent hints
|
|
501
523
|
kse scene contribute --package <path> # One-stop validate → lint → score → publish
|
|
502
524
|
|
|
525
|
+
# Agent orchestration (NEW in v1.45.0)
|
|
526
|
+
kse orchestrate run --specs "<spec-list>" --max-parallel <N> # Start multi-agent orchestration
|
|
527
|
+
kse orchestrate status # View orchestration progress
|
|
528
|
+
kse orchestrate stop # Stop all sub-agents
|
|
529
|
+
|
|
503
530
|
# DevOps operations
|
|
504
531
|
kse ops init <project-name> # Initialize operations specs
|
|
505
532
|
kse ops validate [<project>] # Validate operations completeness
|
|
@@ -623,5 +650,5 @@ A deep conversation about AI development trends, Neo-Confucian philosophy, and s
|
|
|
623
650
|
|
|
624
651
|
---
|
|
625
652
|
|
|
626
|
-
**Version**: 1.
|
|
627
|
-
**Last Updated**: 2026-02-
|
|
653
|
+
**Version**: 1.45.0
|
|
654
|
+
**Last Updated**: 2026-02-12
|
package/README.zh.md
CHANGED
|
@@ -288,6 +288,28 @@ sequenceDiagram
|
|
|
288
288
|
|
|
289
289
|
[了解更多多 Agent 协调 →](docs/multi-agent-coordination-guide.md)
|
|
290
290
|
|
|
291
|
+
### Agent 编排器 🚀 v1.45.0 新增
|
|
292
|
+
- **自动化多 Agent Spec 执行**: 一条命令替代手工开多个终端分配 Spec 给 Codex Agent
|
|
293
|
+
- **DAG 依赖调度**: 分析 Spec 间依赖关系,拓扑排序计算执行批次
|
|
294
|
+
- **并行执行**: 通过 Codex CLI 子进程同时运行多个 Spec(`--max-parallel` 控制并行度)
|
|
295
|
+
- **失败传播**: 失败 Spec 的下游依赖自动标记为 skipped
|
|
296
|
+
- **重试机制**: 可配置的失败自动重试
|
|
297
|
+
- **实时监控**: 跟踪每个 Spec 状态和整体编排进度
|
|
298
|
+
- **优雅终止**: 干净停止所有子 Agent(SIGTERM → SIGKILL)
|
|
299
|
+
- **可配置**: 通过 `.kiro/config/orchestrator.json` 配置 Agent 后端、并行度、超时、重试次数
|
|
300
|
+
|
|
301
|
+
**快速开始**:
|
|
302
|
+
```bash
|
|
303
|
+
# 并行运行 3 个 Spec
|
|
304
|
+
kse orchestrate run --specs "spec-a,spec-b,spec-c" --max-parallel 3
|
|
305
|
+
|
|
306
|
+
# 查看编排进度
|
|
307
|
+
kse orchestrate status
|
|
308
|
+
|
|
309
|
+
# 停止所有子 Agent
|
|
310
|
+
kse orchestrate stop
|
|
311
|
+
```
|
|
312
|
+
|
|
291
313
|
### Spec 级 Steering 与上下文同步 🚀 v1.44.0 新增
|
|
292
314
|
- **Spec Steering (L4)**: 每个 Spec 独立的 `steering.md`,包含约束、注意事项、决策记录 — 跨 Agent 零冲突
|
|
293
315
|
- **Steering 加载器**: 统一 L1-L4 四层 Steering 加载,优先级合并
|
|
@@ -403,6 +425,11 @@ kse scene ontology actions --ref <ref> # 显示 Action Abstraction
|
|
|
403
425
|
kse scene ontology lineage --ref <ref> # 显示数据血缘
|
|
404
426
|
kse scene ontology agent-info --package <path> # 显示 Agent Hints
|
|
405
427
|
|
|
428
|
+
# Agent 编排 (v1.45.0 新增)
|
|
429
|
+
kse orchestrate run --specs "<spec列表>" --max-parallel <N> # 启动多 Agent 编排
|
|
430
|
+
kse orchestrate status # 查看编排进度
|
|
431
|
+
kse orchestrate stop # 停止所有子 Agent
|
|
432
|
+
|
|
406
433
|
# DevOps 运维
|
|
407
434
|
kse ops init <project-name> # 初始化运维 specs
|
|
408
435
|
kse ops validate [<project>] # 验证运维完整性
|
|
@@ -486,5 +513,5 @@ kse create-spec 01-00-my-first-feature
|
|
|
486
513
|
|
|
487
514
|
---
|
|
488
515
|
|
|
489
|
-
**版本**:1.
|
|
490
|
-
**最后更新**:2026-02-
|
|
516
|
+
**版本**:1.45.0
|
|
517
|
+
**最后更新**:2026-02-12
|
package/bin/kiro-spec-engine.js
CHANGED
|
@@ -571,6 +571,10 @@ registerLockCommands(program);
|
|
|
571
571
|
const { registerKnowledgeCommands } = require('../lib/commands/knowledge');
|
|
572
572
|
registerKnowledgeCommands(program);
|
|
573
573
|
|
|
574
|
+
// Orchestration commands
|
|
575
|
+
const { registerOrchestrateCommands } = require('../lib/commands/orchestrate');
|
|
576
|
+
registerOrchestrateCommands(program);
|
|
577
|
+
|
|
574
578
|
// Template management commands
|
|
575
579
|
const templatesCommand = require('../lib/commands/templates');
|
|
576
580
|
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI commands for Agent Orchestration
|
|
3
|
+
*
|
|
4
|
+
* Provides `kse orchestrate run|status|stop` subcommands for managing
|
|
5
|
+
* parallel Spec execution via Codex CLI sub-agents.
|
|
6
|
+
*
|
|
7
|
+
* Requirements: 6.1 (run), 6.2 (status), 6.3 (stop), 6.4 (spec validation), 6.5 (maxParallel validation)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const chalk = require('chalk');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const fs = require('fs-extra');
|
|
13
|
+
|
|
14
|
+
const SPECS_DIR = '.kiro/specs';
|
|
15
|
+
const STATUS_FILE = '.kiro/config/orchestration-status.json';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Register orchestrate commands on the given Commander program.
|
|
19
|
+
* @param {import('commander').Command} program
|
|
20
|
+
*/
|
|
21
|
+
function registerOrchestrateCommands(program) {
|
|
22
|
+
const orchestrate = program
|
|
23
|
+
.command('orchestrate')
|
|
24
|
+
.description('Manage agent orchestration for parallel Spec execution');
|
|
25
|
+
|
|
26
|
+
// ── kse orchestrate run ──────────────────────────────────────────
|
|
27
|
+
orchestrate
|
|
28
|
+
.command('run')
|
|
29
|
+
.description('Start orchestration for specified Specs')
|
|
30
|
+
.requiredOption('--specs <specs>', 'Comma-separated list of Spec names')
|
|
31
|
+
.option('--max-parallel <n>', 'Maximum parallel agents', parseInt)
|
|
32
|
+
.option('--json', 'Output in JSON format')
|
|
33
|
+
.action(async (options) => {
|
|
34
|
+
try {
|
|
35
|
+
const workspaceRoot = process.cwd();
|
|
36
|
+
const specNames = options.specs
|
|
37
|
+
.split(',')
|
|
38
|
+
.map(s => s.trim())
|
|
39
|
+
.filter(Boolean);
|
|
40
|
+
|
|
41
|
+
if (specNames.length === 0) {
|
|
42
|
+
_errorAndExit('No specs specified', options.json);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Validate maxParallel
|
|
46
|
+
const maxParallel = options.maxParallel;
|
|
47
|
+
if (maxParallel !== undefined && (isNaN(maxParallel) || maxParallel < 1)) {
|
|
48
|
+
_errorAndExit('--max-parallel must be >= 1', options.json);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Validate spec existence
|
|
52
|
+
const missing = await _validateSpecs(workspaceRoot, specNames);
|
|
53
|
+
if (missing.length > 0) {
|
|
54
|
+
_errorAndExit(
|
|
55
|
+
`Specs not found: ${missing.join(', ')}`,
|
|
56
|
+
options.json
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Lazy-require orchestrator modules
|
|
61
|
+
const { OrchestratorConfig } = require('../orchestrator/orchestrator-config');
|
|
62
|
+
const { BootstrapPromptBuilder } = require('../orchestrator/bootstrap-prompt-builder');
|
|
63
|
+
const { AgentSpawner } = require('../orchestrator/agent-spawner');
|
|
64
|
+
const { StatusMonitor } = require('../orchestrator/status-monitor');
|
|
65
|
+
const { OrchestrationEngine } = require('../orchestrator/orchestration-engine');
|
|
66
|
+
|
|
67
|
+
// Lazy-require collab modules
|
|
68
|
+
const DependencyManager = require('../collab/dependency-manager');
|
|
69
|
+
const MetadataManager = require('../collab/metadata-manager');
|
|
70
|
+
const { AgentRegistry } = require('../collab/agent-registry');
|
|
71
|
+
const { SpecLifecycleManager } = require('../collab/spec-lifecycle-manager');
|
|
72
|
+
const { MachineIdentifier } = require('../lock/machine-identifier');
|
|
73
|
+
|
|
74
|
+
// Optional: ContextSyncManager
|
|
75
|
+
let contextSyncManager = null;
|
|
76
|
+
try {
|
|
77
|
+
const { ContextSyncManager } = require('../steering/context-sync-manager');
|
|
78
|
+
contextSyncManager = new ContextSyncManager(workspaceRoot);
|
|
79
|
+
} catch (_err) {
|
|
80
|
+
// Non-fatal — status sync will be skipped
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Wire dependencies
|
|
84
|
+
const orchestratorConfig = new OrchestratorConfig(workspaceRoot);
|
|
85
|
+
const config = await orchestratorConfig.getConfig();
|
|
86
|
+
const effectiveMaxParallel = maxParallel || config.maxParallel;
|
|
87
|
+
|
|
88
|
+
const bootstrapPromptBuilder = new BootstrapPromptBuilder(workspaceRoot, orchestratorConfig);
|
|
89
|
+
const machineIdentifier = new MachineIdentifier(
|
|
90
|
+
path.join(workspaceRoot, '.kiro', 'config')
|
|
91
|
+
);
|
|
92
|
+
const agentRegistry = new AgentRegistry(workspaceRoot, machineIdentifier);
|
|
93
|
+
const agentSpawner = new AgentSpawner(
|
|
94
|
+
workspaceRoot, orchestratorConfig, agentRegistry, bootstrapPromptBuilder
|
|
95
|
+
);
|
|
96
|
+
const metadataManager = new MetadataManager(workspaceRoot);
|
|
97
|
+
const dependencyManager = new DependencyManager(metadataManager);
|
|
98
|
+
const specLifecycleManager = new SpecLifecycleManager(
|
|
99
|
+
workspaceRoot, contextSyncManager, agentRegistry
|
|
100
|
+
);
|
|
101
|
+
const statusMonitor = new StatusMonitor(specLifecycleManager, contextSyncManager);
|
|
102
|
+
|
|
103
|
+
const engine = new OrchestrationEngine(workspaceRoot, {
|
|
104
|
+
agentSpawner,
|
|
105
|
+
dependencyManager,
|
|
106
|
+
specLifecycleManager,
|
|
107
|
+
statusMonitor,
|
|
108
|
+
orchestratorConfig,
|
|
109
|
+
agentRegistry,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
if (!options.json) {
|
|
113
|
+
console.log(
|
|
114
|
+
chalk.blue('🚀'),
|
|
115
|
+
`Starting orchestration for ${specNames.length} spec(s) (max-parallel: ${effectiveMaxParallel})...`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const result = await engine.start(specNames, { maxParallel: effectiveMaxParallel });
|
|
120
|
+
|
|
121
|
+
// Persist status for the `status` subcommand
|
|
122
|
+
await _writeStatus(workspaceRoot, engine.getStatus());
|
|
123
|
+
|
|
124
|
+
if (options.json) {
|
|
125
|
+
console.log(JSON.stringify(result, null, 2));
|
|
126
|
+
} else {
|
|
127
|
+
_printResult(result);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (result.status === 'failed') {
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
} catch (err) {
|
|
134
|
+
_errorAndExit(err.message, options.json);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// ── kse orchestrate status ───────────────────────────────────────
|
|
139
|
+
orchestrate
|
|
140
|
+
.command('status')
|
|
141
|
+
.description('Show current orchestration status')
|
|
142
|
+
.option('--json', 'Output in JSON format')
|
|
143
|
+
.action(async (options) => {
|
|
144
|
+
try {
|
|
145
|
+
const workspaceRoot = process.cwd();
|
|
146
|
+
const status = await _readStatus(workspaceRoot);
|
|
147
|
+
|
|
148
|
+
if (!status) {
|
|
149
|
+
if (options.json) {
|
|
150
|
+
console.log(JSON.stringify({ status: 'idle', message: 'No orchestration data found' }));
|
|
151
|
+
} else {
|
|
152
|
+
console.log(chalk.gray('No orchestration data found. Run `kse orchestrate run` first.'));
|
|
153
|
+
}
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (options.json) {
|
|
158
|
+
console.log(JSON.stringify(status, null, 2));
|
|
159
|
+
} else {
|
|
160
|
+
_printStatus(status);
|
|
161
|
+
}
|
|
162
|
+
} catch (err) {
|
|
163
|
+
_errorAndExit(err.message, options.json);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// ── kse orchestrate stop ─────────────────────────────────────────
|
|
168
|
+
orchestrate
|
|
169
|
+
.command('stop')
|
|
170
|
+
.description('Stop all running agents')
|
|
171
|
+
.action(async () => {
|
|
172
|
+
try {
|
|
173
|
+
const workspaceRoot = process.cwd();
|
|
174
|
+
const status = await _readStatus(workspaceRoot);
|
|
175
|
+
|
|
176
|
+
if (!status || status.status !== 'running') {
|
|
177
|
+
console.log(chalk.gray('No running orchestration to stop.'));
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Write a stop signal into the status file
|
|
182
|
+
status.status = 'stopped';
|
|
183
|
+
status.completedAt = new Date().toISOString();
|
|
184
|
+
await _writeStatus(workspaceRoot, status);
|
|
185
|
+
|
|
186
|
+
console.log(chalk.yellow('⏹'), 'Stop signal sent. Running agents will be terminated.');
|
|
187
|
+
} catch (err) {
|
|
188
|
+
console.error(chalk.red('Error:'), err.message);
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ── Helpers ──────────────────────────────────────────────────────────
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Validate that all spec directories exist.
|
|
198
|
+
* @param {string} workspaceRoot
|
|
199
|
+
* @param {string[]} specNames
|
|
200
|
+
* @returns {Promise<string[]>} Missing spec names
|
|
201
|
+
*/
|
|
202
|
+
async function _validateSpecs(workspaceRoot, specNames) {
|
|
203
|
+
const missing = [];
|
|
204
|
+
for (const name of specNames) {
|
|
205
|
+
const specDir = path.join(workspaceRoot, SPECS_DIR, name);
|
|
206
|
+
const exists = await fs.pathExists(specDir);
|
|
207
|
+
if (!exists) {
|
|
208
|
+
missing.push(name);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return missing;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Read persisted orchestration status.
|
|
216
|
+
* @param {string} workspaceRoot
|
|
217
|
+
* @returns {Promise<object|null>}
|
|
218
|
+
*/
|
|
219
|
+
async function _readStatus(workspaceRoot) {
|
|
220
|
+
const statusPath = path.join(workspaceRoot, STATUS_FILE);
|
|
221
|
+
try {
|
|
222
|
+
return await fs.readJson(statusPath);
|
|
223
|
+
} catch (_err) {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Persist orchestration status to disk.
|
|
230
|
+
* @param {string} workspaceRoot
|
|
231
|
+
* @param {object} status
|
|
232
|
+
*/
|
|
233
|
+
async function _writeStatus(workspaceRoot, status) {
|
|
234
|
+
const statusPath = path.join(workspaceRoot, STATUS_FILE);
|
|
235
|
+
await fs.ensureDir(path.dirname(statusPath));
|
|
236
|
+
await fs.writeJson(statusPath, status, { spaces: 2 });
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Print a human-readable orchestration result.
|
|
241
|
+
* @param {object} result
|
|
242
|
+
*/
|
|
243
|
+
function _printResult(result) {
|
|
244
|
+
const icon = result.status === 'completed' ? chalk.green('✓') : chalk.red('✗');
|
|
245
|
+
console.log(`${icon} Orchestration ${result.status}`);
|
|
246
|
+
if (result.totalSpecs !== undefined) {
|
|
247
|
+
console.log(chalk.gray(` Total: ${result.totalSpecs} Completed: ${result.completedSpecs} Failed: ${result.failedSpecs}`));
|
|
248
|
+
}
|
|
249
|
+
if (result.specs) {
|
|
250
|
+
for (const [name, info] of Object.entries(result.specs)) {
|
|
251
|
+
const sym = _statusSymbol(info.status);
|
|
252
|
+
const extra = info.error ? chalk.gray(` — ${info.error}`) : '';
|
|
253
|
+
console.log(` ${sym} ${name}${extra}`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Print a human-readable orchestration status.
|
|
260
|
+
* @param {object} status
|
|
261
|
+
*/
|
|
262
|
+
function _printStatus(status) {
|
|
263
|
+
console.log(chalk.bold('Orchestration Status'));
|
|
264
|
+
console.log(chalk.gray('===================='));
|
|
265
|
+
console.log(`Status: ${_statusSymbol(status.status)} ${status.status}`);
|
|
266
|
+
if (status.totalSpecs !== undefined) {
|
|
267
|
+
console.log(`Total: ${status.totalSpecs} Completed: ${status.completedSpecs || 0} Failed: ${status.failedSpecs || 0} Running: ${status.runningSpecs || 0}`);
|
|
268
|
+
}
|
|
269
|
+
if (status.currentBatch !== undefined && status.totalBatches !== undefined) {
|
|
270
|
+
console.log(`Batch: ${status.currentBatch} / ${status.totalBatches}`);
|
|
271
|
+
}
|
|
272
|
+
if (status.specs) {
|
|
273
|
+
console.log('');
|
|
274
|
+
for (const [name, info] of Object.entries(status.specs)) {
|
|
275
|
+
const sym = _statusSymbol(info.status);
|
|
276
|
+
const extra = info.error ? chalk.gray(` — ${info.error}`) : '';
|
|
277
|
+
console.log(` ${sym} ${name}${extra}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Map status string to a coloured symbol.
|
|
284
|
+
* @param {string} status
|
|
285
|
+
* @returns {string}
|
|
286
|
+
*/
|
|
287
|
+
function _statusSymbol(status) {
|
|
288
|
+
const map = {
|
|
289
|
+
completed: chalk.green('✓'),
|
|
290
|
+
running: chalk.yellow('⧗'),
|
|
291
|
+
pending: chalk.gray('○'),
|
|
292
|
+
failed: chalk.red('✗'),
|
|
293
|
+
timeout: chalk.red('⏱'),
|
|
294
|
+
skipped: chalk.gray('⊘'),
|
|
295
|
+
idle: chalk.gray('○'),
|
|
296
|
+
stopped: chalk.yellow('⏹'),
|
|
297
|
+
};
|
|
298
|
+
return map[status] || '?';
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Print an error and exit. Respects --json mode.
|
|
303
|
+
* @param {string} message
|
|
304
|
+
* @param {boolean} [json]
|
|
305
|
+
*/
|
|
306
|
+
function _errorAndExit(message, json) {
|
|
307
|
+
if (json) {
|
|
308
|
+
console.log(JSON.stringify({ error: message }));
|
|
309
|
+
} else {
|
|
310
|
+
console.error(chalk.red('Error:'), message);
|
|
311
|
+
}
|
|
312
|
+
process.exit(1);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
module.exports = { registerOrchestrateCommands };
|