kiro-spec-engine 1.45.12 → 1.45.13
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 +11 -0
- package/README.md +18 -3
- package/README.zh.md +18 -3
- package/docs/command-reference.md +29 -2
- package/lib/orchestrator/agent-spawner.js +82 -13
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.45.13] - 2026-02-13
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **Windows prompt guard hardening**: `AgentSpawner.spawn()` now validates bootstrap prompt both right after prompt build and after Windows prompt extraction, failing fast before any temp file write when prompt is missing/empty.
|
|
14
|
+
- **Windows temp filename safety**: prompt temp filename generation now sanitizes full Windows-invalid character set (including `/`, `\`, control chars, and trailing dot/space cases) to prevent invalid path/stream edge cases.
|
|
15
|
+
- **Codex command fallback**: when `codexCommand` is not configured, spawner now auto-detects `codex` and falls back to `npx @openai/codex` when global `codex` is unavailable.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- **Regression tests**: added unit tests for undefined/empty prompt guardrails, Windows agentId filename sanitization, and codex→npx command fallback path.
|
|
19
|
+
- **Codex orchestration docs**: added recommended Codex-only orchestrator configuration examples in README, README.zh, `.kiro/README.md`, and command reference.
|
|
20
|
+
|
|
10
21
|
## [1.45.12] - 2026-02-13
|
|
11
22
|
|
|
12
23
|
### Fixed
|
package/README.md
CHANGED
|
@@ -340,7 +340,7 @@ Structure your work with Requirements → Design → Tasks workflow
|
|
|
340
340
|
- **Retry Mechanism**: Configurable automatic retry for failed Specs
|
|
341
341
|
- **Real-Time Monitoring**: Track per-Spec status and overall orchestration progress
|
|
342
342
|
- **Graceful Termination**: Stop all sub-agents cleanly (SIGTERM → SIGKILL)
|
|
343
|
-
- **Configurable**:
|
|
343
|
+
- **Configurable**: Codex command, args, parallelism, timeout, retries via `.kiro/config/orchestrator.json`
|
|
344
344
|
|
|
345
345
|
**Quick Start**:
|
|
346
346
|
```bash
|
|
@@ -354,6 +354,21 @@ kse orchestrate status
|
|
|
354
354
|
kse orchestrate stop
|
|
355
355
|
```
|
|
356
356
|
|
|
357
|
+
**Recommended Codex-Orchestrator config (`.kiro/config/orchestrator.json`)**:
|
|
358
|
+
```json
|
|
359
|
+
{
|
|
360
|
+
"agentBackend": "codex",
|
|
361
|
+
"maxParallel": 3,
|
|
362
|
+
"timeoutSeconds": 900,
|
|
363
|
+
"maxRetries": 2,
|
|
364
|
+
"apiKeyEnvVar": "CODEX_API_KEY",
|
|
365
|
+
"codexArgs": ["--skip-git-repo-check"],
|
|
366
|
+
"codexCommand": "npx @openai/codex"
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
If Codex CLI is globally installed, you can set `"codexCommand": "codex"`.
|
|
371
|
+
|
|
357
372
|
### Spec-Level Steering & Context Sync 🚀 NEW in v1.44.0
|
|
358
373
|
- **Spec Steering (L4)**: Independent `steering.md` per Spec with constraints, notes, and decisions — zero cross-agent conflict
|
|
359
374
|
- **Steering Loader**: Unified L1-L4 four-layer steering loader with priority-based merging
|
|
@@ -650,5 +665,5 @@ A deep conversation about AI development trends, Neo-Confucian philosophy, and s
|
|
|
650
665
|
|
|
651
666
|
---
|
|
652
667
|
|
|
653
|
-
**Version**: 1.45.
|
|
654
|
-
**Last Updated**: 2026-02-
|
|
668
|
+
**Version**: 1.45.13
|
|
669
|
+
**Last Updated**: 2026-02-13
|
package/README.zh.md
CHANGED
|
@@ -296,7 +296,7 @@ sequenceDiagram
|
|
|
296
296
|
- **重试机制**: 可配置的失败自动重试
|
|
297
297
|
- **实时监控**: 跟踪每个 Spec 状态和整体编排进度
|
|
298
298
|
- **优雅终止**: 干净停止所有子 Agent(SIGTERM → SIGKILL)
|
|
299
|
-
- **可配置**: 通过 `.kiro/config/orchestrator.json` 配置
|
|
299
|
+
- **可配置**: 通过 `.kiro/config/orchestrator.json` 配置 Codex 命令、参数、并行度、超时、重试次数
|
|
300
300
|
|
|
301
301
|
**快速开始**:
|
|
302
302
|
```bash
|
|
@@ -310,6 +310,21 @@ kse orchestrate status
|
|
|
310
310
|
kse orchestrate stop
|
|
311
311
|
```
|
|
312
312
|
|
|
313
|
+
**推荐 Codex 编排配置(`.kiro/config/orchestrator.json`)**:
|
|
314
|
+
```json
|
|
315
|
+
{
|
|
316
|
+
"agentBackend": "codex",
|
|
317
|
+
"maxParallel": 3,
|
|
318
|
+
"timeoutSeconds": 900,
|
|
319
|
+
"maxRetries": 2,
|
|
320
|
+
"apiKeyEnvVar": "CODEX_API_KEY",
|
|
321
|
+
"codexArgs": ["--skip-git-repo-check"],
|
|
322
|
+
"codexCommand": "npx @openai/codex"
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
如果你已全局安装 Codex CLI,可将 `"codexCommand"` 改为 `"codex"`。
|
|
327
|
+
|
|
313
328
|
### Spec 级 Steering 与上下文同步 🚀 v1.44.0 新增
|
|
314
329
|
- **Spec Steering (L4)**: 每个 Spec 独立的 `steering.md`,包含约束、注意事项、决策记录 — 跨 Agent 零冲突
|
|
315
330
|
- **Steering 加载器**: 统一 L1-L4 四层 Steering 加载,优先级合并
|
|
@@ -513,5 +528,5 @@ kse create-spec 01-00-my-first-feature
|
|
|
513
528
|
|
|
514
529
|
---
|
|
515
530
|
|
|
516
|
-
**版本**:1.45.
|
|
517
|
-
**最后更新**:2026-02-
|
|
531
|
+
**版本**:1.45.13
|
|
532
|
+
**最后更新**:2026-02-13
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
> Quick reference for all kse commands
|
|
4
4
|
|
|
5
|
-
**Version**: 1.
|
|
6
|
-
**Last Updated**: 2026-02-
|
|
5
|
+
**Version**: 1.45.13
|
|
6
|
+
**Last Updated**: 2026-02-13
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
@@ -198,6 +198,33 @@ kse repo exec "<command>" [--dry-run]
|
|
|
198
198
|
kse repo health [--json]
|
|
199
199
|
```
|
|
200
200
|
|
|
201
|
+
### Agent Orchestration (Codex)
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
# Start orchestration for multiple specs
|
|
205
|
+
kse orchestrate run --specs "spec-a,spec-b,spec-c" --max-parallel 3
|
|
206
|
+
|
|
207
|
+
# Show orchestration status
|
|
208
|
+
kse orchestrate status [--json]
|
|
209
|
+
|
|
210
|
+
# Stop all running sub-agents
|
|
211
|
+
kse orchestrate stop
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Recommended `.kiro/config/orchestrator.json`:
|
|
215
|
+
|
|
216
|
+
```json
|
|
217
|
+
{
|
|
218
|
+
"agentBackend": "codex",
|
|
219
|
+
"maxParallel": 3,
|
|
220
|
+
"timeoutSeconds": 900,
|
|
221
|
+
"maxRetries": 2,
|
|
222
|
+
"apiKeyEnvVar": "CODEX_API_KEY",
|
|
223
|
+
"codexArgs": ["--skip-git-repo-check"],
|
|
224
|
+
"codexCommand": "npx @openai/codex"
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
201
228
|
### Scene Template Engine
|
|
202
229
|
|
|
203
230
|
```bash
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
const { EventEmitter } = require('events');
|
|
15
|
-
const { spawn } = require('child_process');
|
|
15
|
+
const { spawn, spawnSync } = require('child_process');
|
|
16
16
|
const fs = require('fs');
|
|
17
17
|
const path = require('path');
|
|
18
18
|
const os = require('os');
|
|
@@ -30,6 +30,7 @@ class AgentSpawner extends EventEmitter {
|
|
|
30
30
|
this._orchestratorConfig = orchestratorConfig;
|
|
31
31
|
this._agentRegistry = agentRegistry;
|
|
32
32
|
this._bootstrapPromptBuilder = bootstrapPromptBuilder;
|
|
33
|
+
this._commandAvailabilityCache = new Map();
|
|
33
34
|
/** @type {Map<string, import('./agent-spawner').SpawnedAgent>} */
|
|
34
35
|
this._agents = new Map();
|
|
35
36
|
}
|
|
@@ -63,6 +64,7 @@ class AgentSpawner extends EventEmitter {
|
|
|
63
64
|
|
|
64
65
|
// Build the bootstrap prompt (Req 2.1-2.3)
|
|
65
66
|
const prompt = await this._bootstrapPromptBuilder.buildPrompt(specName);
|
|
67
|
+
this._assertValidBootstrapPrompt(prompt, specName, 'BootstrapPromptBuilder.buildPrompt()');
|
|
66
68
|
|
|
67
69
|
// Register in AgentRegistry (Req 1.7)
|
|
68
70
|
const { agentId } = await this._agentRegistry.register({
|
|
@@ -119,15 +121,7 @@ class AgentSpawner extends EventEmitter {
|
|
|
119
121
|
// Remove the prompt (last element of args portion) from command line
|
|
120
122
|
// and deliver it via stdin to avoid cmd.exe length limit.
|
|
121
123
|
stdinPrompt = finalArgs.pop(); // remove prompt
|
|
122
|
-
|
|
123
|
-
// Validate stdinPrompt before proceeding
|
|
124
|
-
if (!stdinPrompt || typeof stdinPrompt !== 'string' || stdinPrompt.length === 0) {
|
|
125
|
-
throw new Error(
|
|
126
|
-
`Invalid bootstrap prompt: expected non-empty string, got ${typeof stdinPrompt} ` +
|
|
127
|
-
`with length ${stdinPrompt ? stdinPrompt.length : 0}. ` +
|
|
128
|
-
`This may indicate BootstrapPromptBuilder.buildPrompt() failed or args array is malformed.`
|
|
129
|
-
);
|
|
130
|
-
}
|
|
124
|
+
this._assertValidBootstrapPrompt(stdinPrompt, specName, 'Windows prompt extraction');
|
|
131
125
|
|
|
132
126
|
// If the prompt was quoted by the escaping above, unwrap it
|
|
133
127
|
if (stdinPrompt.startsWith('"') && stdinPrompt.endsWith('"')) {
|
|
@@ -144,8 +138,8 @@ class AgentSpawner extends EventEmitter {
|
|
|
144
138
|
// write prompt → pass file path via shell read.
|
|
145
139
|
let promptTmpFile = null;
|
|
146
140
|
if (useStdinPrompt) {
|
|
147
|
-
// Sanitize agentId for use in filename
|
|
148
|
-
const safeAgentId =
|
|
141
|
+
// Sanitize agentId for use in filename to avoid Windows invalid path characters.
|
|
142
|
+
const safeAgentId = this._sanitizeWindowsFilenamePart(agentId);
|
|
149
143
|
promptTmpFile = path.join(os.tmpdir(), `kse-prompt-${safeAgentId}-${Date.now()}.txt`);
|
|
150
144
|
fs.writeFileSync(promptTmpFile, stdinPrompt, 'utf-8');
|
|
151
145
|
}
|
|
@@ -258,6 +252,39 @@ class AgentSpawner extends EventEmitter {
|
|
|
258
252
|
// Private helpers
|
|
259
253
|
// ---------------------------------------------------------------------------
|
|
260
254
|
|
|
255
|
+
/**
|
|
256
|
+
* Ensure bootstrap prompt is a non-empty string before using it in spawn args.
|
|
257
|
+
* @param {unknown} prompt
|
|
258
|
+
* @param {string} specName
|
|
259
|
+
* @param {string} source
|
|
260
|
+
* @private
|
|
261
|
+
*/
|
|
262
|
+
_assertValidBootstrapPrompt(prompt, specName, source) {
|
|
263
|
+
const isString = typeof prompt === 'string';
|
|
264
|
+
const length = isString ? prompt.length : 0;
|
|
265
|
+
const hasContent = isString && prompt.trim().length > 0;
|
|
266
|
+
if (!hasContent) {
|
|
267
|
+
throw new Error(
|
|
268
|
+
`Invalid bootstrap prompt for spec "${specName}" from ${source}: ` +
|
|
269
|
+
`expected non-empty string, got ${typeof prompt} with length ${length}.`
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Convert an arbitrary identifier into a Windows-safe filename segment.
|
|
276
|
+
* @param {string} value
|
|
277
|
+
* @returns {string}
|
|
278
|
+
* @private
|
|
279
|
+
*/
|
|
280
|
+
_sanitizeWindowsFilenamePart(value) {
|
|
281
|
+
const sanitized = String(value)
|
|
282
|
+
.replace(/[<>:"/\\|?*\x00-\x1F]/g, '-')
|
|
283
|
+
.replace(/[. ]+$/g, '')
|
|
284
|
+
.slice(0, 120);
|
|
285
|
+
return sanitized || 'agent';
|
|
286
|
+
}
|
|
287
|
+
|
|
261
288
|
/**
|
|
262
289
|
* Parse stdout line-by-line for JSON Lines events.
|
|
263
290
|
* @param {object} agent
|
|
@@ -538,13 +565,55 @@ class AgentSpawner extends EventEmitter {
|
|
|
538
565
|
* @private
|
|
539
566
|
*/
|
|
540
567
|
_resolveCodexCommand(config) {
|
|
541
|
-
|
|
568
|
+
let raw = config.codexCommand;
|
|
569
|
+
if (!raw) {
|
|
570
|
+
if (this._isCommandAvailable('codex')) {
|
|
571
|
+
raw = 'codex';
|
|
572
|
+
} else if (this._isCommandAvailable('npx')) {
|
|
573
|
+
raw = 'npx @openai/codex';
|
|
574
|
+
} else {
|
|
575
|
+
// Keep historical default to preserve error semantics on misconfigured hosts.
|
|
576
|
+
raw = 'codex';
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
542
580
|
const parts = raw.trim().split(/\s+/);
|
|
543
581
|
return {
|
|
544
582
|
command: parts[0],
|
|
545
583
|
prependArgs: parts.slice(1),
|
|
546
584
|
};
|
|
547
585
|
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Best-effort command availability probe.
|
|
589
|
+
* Uses `where` on Windows and `which` on POSIX systems.
|
|
590
|
+
*
|
|
591
|
+
* @param {string} command
|
|
592
|
+
* @returns {boolean}
|
|
593
|
+
* @private
|
|
594
|
+
*/
|
|
595
|
+
_isCommandAvailable(command) {
|
|
596
|
+
if (!command || typeof command !== 'string') {
|
|
597
|
+
return false;
|
|
598
|
+
}
|
|
599
|
+
if (this._commandAvailabilityCache.has(command)) {
|
|
600
|
+
return this._commandAvailabilityCache.get(command);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
try {
|
|
604
|
+
const lookupCmd = process.platform === 'win32' ? 'where' : 'which';
|
|
605
|
+
const result = spawnSync(lookupCmd, [command], {
|
|
606
|
+
windowsHide: true,
|
|
607
|
+
stdio: 'ignore',
|
|
608
|
+
});
|
|
609
|
+
const available = result.status === 0;
|
|
610
|
+
this._commandAvailabilityCache.set(command, available);
|
|
611
|
+
return available;
|
|
612
|
+
} catch (_err) {
|
|
613
|
+
this._commandAvailabilityCache.set(command, false);
|
|
614
|
+
return false;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
548
617
|
}
|
|
549
618
|
|
|
550
619
|
module.exports = { AgentSpawner };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kiro-spec-engine",
|
|
3
|
-
"version": "1.45.
|
|
3
|
+
"version": "1.45.13",
|
|
4
4
|
"description": "kiro-spec-engine (kse) - A CLI tool and npm package for spec-driven development with AI coding assistants. NOT the Kiro IDE desktop application.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|