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 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**: Agent backend, parallelism, timeout, retries via `.kiro/config/orchestrator.json`
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.0
654
- **Last Updated**: 2026-02-12
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` 配置 Agent 后端、并行度、超时、重试次数
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.0
517
- **最后更新**:2026-02-12
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.42.0
6
- **Last Updated**: 2026-02-11
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 - remove Windows reserved characters: < > : " | ? *
148
- const safeAgentId = agentId.replace(/[:<>"|?*]/g, '-');
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
- const raw = config.codexCommand || 'codex';
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.12",
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": {