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.
Files changed (336) hide show
  1. package/CHANGELOG.md +2513 -0
  2. package/LICENSE +21 -0
  3. package/README.md +765 -0
  4. package/README.zh.md +630 -0
  5. package/bin/kiro-spec-engine.js +796 -0
  6. package/bin/kse.js +3 -0
  7. package/bin/sce.js +3 -0
  8. package/bin/sco.js +3 -0
  9. package/docs/331-poc-adaptation-roadmap.md +156 -0
  10. package/docs/331-poc-dual-track-integration-guide.md +120 -0
  11. package/docs/331-poc-weekly-delivery-checklist.md +52 -0
  12. package/docs/OFFLINE_INSTALL.md +96 -0
  13. package/docs/README.md +279 -0
  14. package/docs/adopt-migration-guide.md +599 -0
  15. package/docs/adoption-guide.md +616 -0
  16. package/docs/agent-hooks-analysis.md +815 -0
  17. package/docs/architecture.md +733 -0
  18. package/docs/articles/ai-driven-development-philosophy-and-practice-review.md +208 -0
  19. package/docs/articles/ai-driven-development-philosophy-and-practice.en.md +459 -0
  20. package/docs/articles/ai-driven-development-philosophy-and-practice.md +492 -0
  21. package/docs/autonomous-control-guide.md +851 -0
  22. package/docs/command-reference.md +1368 -0
  23. package/docs/community.md +115 -0
  24. package/docs/cross-tool-guide.md +555 -0
  25. package/docs/developer-guide.md +619 -0
  26. package/docs/document-governance.md +865 -0
  27. package/docs/environment-management-guide.md +526 -0
  28. package/docs/examples/add-export-command/design.md +194 -0
  29. package/docs/examples/add-export-command/requirements.md +110 -0
  30. package/docs/examples/add-export-command/tasks.md +88 -0
  31. package/docs/examples/add-rest-api/design.md +855 -0
  32. package/docs/examples/add-rest-api/requirements.md +323 -0
  33. package/docs/examples/add-rest-api/tasks.md +355 -0
  34. package/docs/examples/add-user-dashboard/design.md +192 -0
  35. package/docs/examples/add-user-dashboard/requirements.md +143 -0
  36. package/docs/examples/add-user-dashboard/tasks.md +91 -0
  37. package/docs/faq.md +697 -0
  38. package/docs/handoffs/evidence/ontology/moqui-template-baseline-2026-02-17-232922.json +156 -0
  39. package/docs/handoffs/evidence/ontology/moqui-template-baseline-2026-02-17-232922.md +24 -0
  40. package/docs/images/wechat-qr.png +0 -0
  41. package/docs/integration-modes.md +529 -0
  42. package/docs/integration-philosophy.md +313 -0
  43. package/docs/knowledge-management-guide.md +263 -0
  44. package/docs/manual-workflows-guide.md +418 -0
  45. package/docs/moqui-capability-matrix.md +73 -0
  46. package/docs/moqui-template-core-library-playbook.md +109 -0
  47. package/docs/multi-agent-coordination-guide.md +553 -0
  48. package/docs/multi-repo-management-guide.md +1344 -0
  49. package/docs/quick-start-with-ai-tools.md +375 -0
  50. package/docs/quick-start.md +146 -0
  51. package/docs/release-checklist.md +121 -0
  52. package/docs/releases/README.md +13 -0
  53. package/docs/releases/v1.46.2-validation.md +45 -0
  54. package/docs/releases/v1.46.2.md +50 -0
  55. package/docs/scene-runtime-guide.md +347 -0
  56. package/docs/spec-collaboration-guide.md +369 -0
  57. package/docs/spec-locking-guide.md +225 -0
  58. package/docs/spec-numbering-guide.md +348 -0
  59. package/docs/spec-workflow.md +519 -0
  60. package/docs/steering-strategy-guide.md +196 -0
  61. package/docs/team-collaboration-guide.md +465 -0
  62. package/docs/testing-strategy.md +272 -0
  63. package/docs/tools/claude-guide.md +654 -0
  64. package/docs/tools/cursor-guide.md +706 -0
  65. package/docs/tools/generic-guide.md +446 -0
  66. package/docs/tools/kiro-guide.md +308 -0
  67. package/docs/tools/vscode-guide.md +445 -0
  68. package/docs/tools/windsurf-guide.md +391 -0
  69. package/docs/troubleshooting.md +1135 -0
  70. package/docs/upgrade-guide.md +639 -0
  71. package/docs/value-observability-guide.md +127 -0
  72. package/docs/zh/README.md +341 -0
  73. package/docs/zh/quick-start.md +764 -0
  74. package/docs/zh/release-checklist.md +121 -0
  75. package/docs/zh/releases/README.md +13 -0
  76. package/docs/zh/releases/v1.46.2-validation.md +45 -0
  77. package/docs/zh/releases/v1.46.2.md +50 -0
  78. package/docs/zh/spec-numbering-guide.md +348 -0
  79. package/docs/zh/tools/claude-guide.md +349 -0
  80. package/docs/zh/tools/cursor-guide.md +281 -0
  81. package/docs/zh/tools/generic-guide.md +499 -0
  82. package/docs/zh/tools/kiro-guide.md +342 -0
  83. package/docs/zh/tools/vscode-guide.md +449 -0
  84. package/docs/zh/tools/windsurf-guide.md +378 -0
  85. package/docs/zh/value-observability-guide.md +127 -0
  86. package/docs//344/272/244/344/273/230/346/270/205/345/215/225.md +75 -0
  87. package/lib/adoption/adoption-logger.js +487 -0
  88. package/lib/adoption/adoption-strategy.js +538 -0
  89. package/lib/adoption/backup-manager.js +420 -0
  90. package/lib/adoption/conflict-resolver.js +410 -0
  91. package/lib/adoption/detection-engine.js +275 -0
  92. package/lib/adoption/diff-viewer.js +226 -0
  93. package/lib/adoption/error-formatter.js +509 -0
  94. package/lib/adoption/file-classifier.js +385 -0
  95. package/lib/adoption/progress-reporter.js +534 -0
  96. package/lib/adoption/smart-orchestrator.js +470 -0
  97. package/lib/adoption/strategy-selector.js +218 -0
  98. package/lib/adoption/summary-generator.js +493 -0
  99. package/lib/adoption/template-sync.js +605 -0
  100. package/lib/auto/autonomous-engine.js +485 -0
  101. package/lib/auto/checkpoint-manager.js +300 -0
  102. package/lib/auto/close-loop-runner.js +2476 -0
  103. package/lib/auto/config-schema.js +176 -0
  104. package/lib/auto/decision-engine.js +344 -0
  105. package/lib/auto/error-recovery-manager.js +580 -0
  106. package/lib/auto/goal-decomposer.js +278 -0
  107. package/lib/auto/progress-tracker.js +502 -0
  108. package/lib/auto/safety-manager.js +186 -0
  109. package/lib/auto/semantic-decomposer.js +137 -0
  110. package/lib/auto/state-manager.js +126 -0
  111. package/lib/auto/task-queue-manager.js +340 -0
  112. package/lib/backup/backup-system.js +372 -0
  113. package/lib/backup/selective-backup.js +207 -0
  114. package/lib/collab/agent-registry.js +240 -0
  115. package/lib/collab/collab-manager.js +285 -0
  116. package/lib/collab/contract-manager.js +320 -0
  117. package/lib/collab/coordinator.js +370 -0
  118. package/lib/collab/dependency-manager.js +280 -0
  119. package/lib/collab/index.js +20 -0
  120. package/lib/collab/integration-manager.js +202 -0
  121. package/lib/collab/merge-coordinator.js +252 -0
  122. package/lib/collab/metadata-manager.js +233 -0
  123. package/lib/collab/multi-agent-config.js +120 -0
  124. package/lib/collab/spec-lifecycle-manager.js +304 -0
  125. package/lib/collab/sync-barrier.js +88 -0
  126. package/lib/collab/visualizer.js +208 -0
  127. package/lib/commands/adopt.js +749 -0
  128. package/lib/commands/auto.js +19559 -0
  129. package/lib/commands/collab.js +275 -0
  130. package/lib/commands/context.js +99 -0
  131. package/lib/commands/docs.js +808 -0
  132. package/lib/commands/doctor.js +273 -0
  133. package/lib/commands/env.js +420 -0
  134. package/lib/commands/knowledge.js +309 -0
  135. package/lib/commands/lock.js +235 -0
  136. package/lib/commands/ops.js +409 -0
  137. package/lib/commands/orchestrate.js +446 -0
  138. package/lib/commands/prompt.js +105 -0
  139. package/lib/commands/repo.js +118 -0
  140. package/lib/commands/rollback.js +219 -0
  141. package/lib/commands/scene.js +15549 -0
  142. package/lib/commands/spec-bootstrap.js +147 -0
  143. package/lib/commands/spec-gate.js +157 -0
  144. package/lib/commands/spec-pipeline.js +205 -0
  145. package/lib/commands/status.js +321 -0
  146. package/lib/commands/task.js +199 -0
  147. package/lib/commands/templates.js +654 -0
  148. package/lib/commands/upgrade.js +231 -0
  149. package/lib/commands/value.js +569 -0
  150. package/lib/commands/watch.js +684 -0
  151. package/lib/commands/workflows.js +240 -0
  152. package/lib/commands/workspace-multi.js +325 -0
  153. package/lib/commands/workspace.js +189 -0
  154. package/lib/context/context-exporter.js +378 -0
  155. package/lib/context/prompt-generator.js +482 -0
  156. package/lib/data/moqui-capability-lexicon.json +45 -0
  157. package/lib/environment/backup-system.js +189 -0
  158. package/lib/environment/environment-manager.js +379 -0
  159. package/lib/environment/environment-registry.js +168 -0
  160. package/lib/gitignore/gitignore-backup.js +229 -0
  161. package/lib/gitignore/gitignore-detector.js +239 -0
  162. package/lib/gitignore/gitignore-integration.js +267 -0
  163. package/lib/gitignore/gitignore-transformer.js +193 -0
  164. package/lib/gitignore/layered-rules-template.js +42 -0
  165. package/lib/governance/archive-tool.js +284 -0
  166. package/lib/governance/cleanup-tool.js +237 -0
  167. package/lib/governance/config-manager.js +186 -0
  168. package/lib/governance/diagnostic-engine.js +271 -0
  169. package/lib/governance/doc-reference-checker.js +200 -0
  170. package/lib/governance/execution-logger.js +243 -0
  171. package/lib/governance/file-scanner.js +285 -0
  172. package/lib/governance/hooks-manager.js +333 -0
  173. package/lib/governance/reporter.js +337 -0
  174. package/lib/governance/validation-engine.js +181 -0
  175. package/lib/i18n.js +79 -0
  176. package/lib/knowledge/entry-manager.js +208 -0
  177. package/lib/knowledge/index-manager.js +261 -0
  178. package/lib/knowledge/knowledge-manager.js +273 -0
  179. package/lib/knowledge/template-manager.js +191 -0
  180. package/lib/lock/index.js +21 -0
  181. package/lib/lock/lock-file.js +192 -0
  182. package/lib/lock/lock-manager.js +321 -0
  183. package/lib/lock/machine-identifier.js +135 -0
  184. package/lib/lock/steering-file-lock.js +207 -0
  185. package/lib/lock/task-lock-manager.js +345 -0
  186. package/lib/operations/audit-logger.js +293 -0
  187. package/lib/operations/feedback-manager.js +1147 -0
  188. package/lib/operations/index.js +23 -0
  189. package/lib/operations/models/index.js +170 -0
  190. package/lib/operations/operations-manager.js +151 -0
  191. package/lib/operations/operations-validator.js +280 -0
  192. package/lib/operations/permission-manager.js +354 -0
  193. package/lib/operations/template-loader.js +143 -0
  194. package/lib/orchestrator/agent-spawner.js +629 -0
  195. package/lib/orchestrator/bootstrap-prompt-builder.js +236 -0
  196. package/lib/orchestrator/index.js +19 -0
  197. package/lib/orchestrator/orchestration-engine.js +1270 -0
  198. package/lib/orchestrator/orchestrator-config.js +173 -0
  199. package/lib/orchestrator/status-monitor.js +591 -0
  200. package/lib/python-checker.js +209 -0
  201. package/lib/repo/config-manager.js +580 -0
  202. package/lib/repo/errors/config-error.js +13 -0
  203. package/lib/repo/errors/git-error.js +15 -0
  204. package/lib/repo/errors/repo-error.js +14 -0
  205. package/lib/repo/git-operations.js +181 -0
  206. package/lib/repo/handlers/.gitkeep +1 -0
  207. package/lib/repo/handlers/exec-handler.js +155 -0
  208. package/lib/repo/handlers/health-handler.js +169 -0
  209. package/lib/repo/handlers/init-handler.js +197 -0
  210. package/lib/repo/handlers/status-handler.js +176 -0
  211. package/lib/repo/output-formatter.js +184 -0
  212. package/lib/repo/path-resolver.js +178 -0
  213. package/lib/repo/repo-manager.js +514 -0
  214. package/lib/scene-runtime/audit-emitter.js +59 -0
  215. package/lib/scene-runtime/binding-plugin-loader.js +351 -0
  216. package/lib/scene-runtime/binding-registry.js +349 -0
  217. package/lib/scene-runtime/eval-bridge.js +44 -0
  218. package/lib/scene-runtime/index.js +19 -0
  219. package/lib/scene-runtime/moqui-adapter.js +620 -0
  220. package/lib/scene-runtime/moqui-client.js +606 -0
  221. package/lib/scene-runtime/moqui-extractor.js +2029 -0
  222. package/lib/scene-runtime/plan-compiler.js +208 -0
  223. package/lib/scene-runtime/policy-gate.js +58 -0
  224. package/lib/scene-runtime/runtime-executor.js +358 -0
  225. package/lib/scene-runtime/scene-loader.js +96 -0
  226. package/lib/scene-runtime/scene-ontology.js +959 -0
  227. package/lib/scene-runtime/scene-template-linter.js +852 -0
  228. package/lib/scene-runtime/templates/scene-template-erp-query-v0.1.yaml +28 -0
  229. package/lib/scene-runtime/templates/scene-template-hybrid-shadow-v0.1.yaml +34 -0
  230. package/lib/spec/bootstrap/context-collector.js +48 -0
  231. package/lib/spec/bootstrap/draft-generator.js +158 -0
  232. package/lib/spec/bootstrap/questionnaire-engine.js +70 -0
  233. package/lib/spec/bootstrap/trace-emitter.js +59 -0
  234. package/lib/spec/multi-spec-orchestrate.js +93 -0
  235. package/lib/spec/pipeline/constants.js +6 -0
  236. package/lib/spec/pipeline/stage-adapters.js +118 -0
  237. package/lib/spec/pipeline/stage-runner.js +146 -0
  238. package/lib/spec/pipeline/state-store.js +119 -0
  239. package/lib/spec-gate/engine/gate-engine.js +165 -0
  240. package/lib/spec-gate/policy/default-policy.js +22 -0
  241. package/lib/spec-gate/policy/policy-loader.js +103 -0
  242. package/lib/spec-gate/result-emitter.js +81 -0
  243. package/lib/spec-gate/rules/default-rules.js +156 -0
  244. package/lib/spec-gate/rules/rule-registry.js +51 -0
  245. package/lib/steering/adoption-config.js +164 -0
  246. package/lib/steering/compliance-auto-fixer.js +204 -0
  247. package/lib/steering/compliance-cache.js +99 -0
  248. package/lib/steering/compliance-error-reporter.js +70 -0
  249. package/lib/steering/context-sync-manager.js +273 -0
  250. package/lib/steering/index.js +92 -0
  251. package/lib/steering/spec-steering.js +230 -0
  252. package/lib/steering/steering-compliance-checker.js +73 -0
  253. package/lib/steering/steering-loader.js +144 -0
  254. package/lib/steering/steering-manager.js +289 -0
  255. package/lib/task/index.js +12 -0
  256. package/lib/task/task-claimer.js +489 -0
  257. package/lib/task/task-status-store.js +418 -0
  258. package/lib/templates/cache-manager.js +440 -0
  259. package/lib/templates/content-generalizer.js +247 -0
  260. package/lib/templates/frontmatter-generator.js +128 -0
  261. package/lib/templates/git-handler.js +471 -0
  262. package/lib/templates/metadata-collector.js +328 -0
  263. package/lib/templates/path-utils.js +144 -0
  264. package/lib/templates/registry-parser.js +505 -0
  265. package/lib/templates/spec-reader.js +216 -0
  266. package/lib/templates/template-applicator.js +249 -0
  267. package/lib/templates/template-creator.js +256 -0
  268. package/lib/templates/template-error.js +143 -0
  269. package/lib/templates/template-exporter.js +502 -0
  270. package/lib/templates/template-manager.js +782 -0
  271. package/lib/templates/template-validator.js +361 -0
  272. package/lib/upgrade/migration-engine.js +382 -0
  273. package/lib/upgrade/migrations/.gitkeep +52 -0
  274. package/lib/upgrade/migrations/1.0.0-to-1.1.0.js +78 -0
  275. package/lib/utils/file-diff.js +177 -0
  276. package/lib/utils/fs-utils.js +274 -0
  277. package/lib/utils/tool-detector.js +383 -0
  278. package/lib/utils/validation.js +324 -0
  279. package/lib/value/gate-summary-emitter.js +99 -0
  280. package/lib/value/metric-contract-loader.js +210 -0
  281. package/lib/value/risk-evaluator.js +117 -0
  282. package/lib/value/weekly-snapshot-builder.js +61 -0
  283. package/lib/version/version-checker.js +156 -0
  284. package/lib/version/version-manager.js +327 -0
  285. package/lib/watch/action-executor.js +458 -0
  286. package/lib/watch/event-debouncer.js +323 -0
  287. package/lib/watch/execution-logger.js +550 -0
  288. package/lib/watch/file-watcher.js +499 -0
  289. package/lib/watch/presets.js +266 -0
  290. package/lib/watch/watch-manager.js +533 -0
  291. package/lib/workspace/multi/global-config.js +150 -0
  292. package/lib/workspace/multi/index.js +22 -0
  293. package/lib/workspace/multi/path-utils.js +173 -0
  294. package/lib/workspace/multi/workspace-context-resolver.js +244 -0
  295. package/lib/workspace/multi/workspace-registry.js +196 -0
  296. package/lib/workspace/multi/workspace-state-manager.js +537 -0
  297. package/lib/workspace/multi/workspace.js +90 -0
  298. package/lib/workspace/workspace-manager.js +370 -0
  299. package/lib/workspace/workspace-sync.js +356 -0
  300. package/locales/en.json +114 -0
  301. package/locales/zh.json +114 -0
  302. package/package.json +102 -0
  303. package/template/.kiro/README.md +247 -0
  304. package/template/.kiro/hooks/check-spec-on-create.kiro.hook +17 -0
  305. package/template/.kiro/hooks/run-tests-on-save.kiro.hook +13 -0
  306. package/template/.kiro/hooks/sync-tasks-on-edit.kiro.hook +16 -0
  307. package/template/.kiro/specs/SPEC_WORKFLOW_GUIDE.md +134 -0
  308. package/template/.kiro/steering/CORE_PRINCIPLES.md +133 -0
  309. package/template/.kiro/steering/CURRENT_CONTEXT.md +30 -0
  310. package/template/.kiro/steering/ENVIRONMENT.md +35 -0
  311. package/template/.kiro/steering/RULES_GUIDE.md +46 -0
  312. package/template/.kiro/templates/operations/default/change-impact.md +112 -0
  313. package/template/.kiro/templates/operations/default/deployment.md +91 -0
  314. package/template/.kiro/templates/operations/default/feedback-response.md +269 -0
  315. package/template/.kiro/templates/operations/default/migration-plan.md +172 -0
  316. package/template/.kiro/templates/operations/default/monitoring.md +135 -0
  317. package/template/.kiro/templates/operations/default/operations.md +135 -0
  318. package/template/.kiro/templates/operations/default/rollback.md +143 -0
  319. package/template/.kiro/templates/operations/default/tools.yaml +364 -0
  320. package/template/.kiro/templates/operations/default/troubleshooting.md +123 -0
  321. package/template/.kiro/tools/backup_manager.py +295 -0
  322. package/template/.kiro/tools/configuration_manager.py +218 -0
  323. package/template/.kiro/tools/document_evaluator.py +550 -0
  324. package/template/.kiro/tools/enhancement_logger.py +168 -0
  325. package/template/.kiro/tools/error_handler.py +335 -0
  326. package/template/.kiro/tools/improvement_identifier.py +444 -0
  327. package/template/.kiro/tools/modification_applicator.py +737 -0
  328. package/template/.kiro/tools/quality_gate_enforcer.py +207 -0
  329. package/template/.kiro/tools/quality_scorer.py +305 -0
  330. package/template/.kiro/tools/report_generator.py +154 -0
  331. package/template/.kiro/tools/ultrawork_enhancer.py +676 -0
  332. package/template/.kiro/tools/ultrawork_enhancer_refactored.py +0 -0
  333. package/template/.kiro/tools/ultrawork_enhancer_v2.py +463 -0
  334. package/template/.kiro/tools/ultrawork_enhancer_v3.py +606 -0
  335. package/template/.kiro/tools/workflow_quality_gate.py +100 -0
  336. package/template/README.md +111 -0
@@ -0,0 +1,629 @@
1
+ /**
2
+ * Agent Spawner — Process Manager
3
+ *
4
+ * Manages Codex CLI sub-processes via Node.js child_process.spawn.
5
+ * Each spawned agent executes a single Spec in full-auto mode and
6
+ * streams JSON Lines events back to the orchestrator.
7
+ *
8
+ * Requirements: 1.1 (spawn via child_process), 1.2 (CODEX_API_KEY env),
9
+ * 1.3 (--json flag), 1.4 (exit 0 → completed),
10
+ * 1.5 (exit non-0 → failed), 1.6 (timeout → terminate),
11
+ * 1.7 (register in AgentRegistry)
12
+ */
13
+
14
+ const { EventEmitter } = require('events');
15
+ const { spawn, spawnSync } = require('child_process');
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+ const os = require('os');
19
+
20
+ class AgentSpawner extends EventEmitter {
21
+ /**
22
+ * @param {string} workspaceRoot - Absolute path to the project root
23
+ * @param {import('./orchestrator-config').OrchestratorConfig} orchestratorConfig
24
+ * @param {import('../collab/agent-registry').AgentRegistry} agentRegistry
25
+ * @param {import('./bootstrap-prompt-builder').BootstrapPromptBuilder} bootstrapPromptBuilder
26
+ */
27
+ constructor(workspaceRoot, orchestratorConfig, agentRegistry, bootstrapPromptBuilder) {
28
+ super();
29
+ this._workspaceRoot = workspaceRoot;
30
+ this._orchestratorConfig = orchestratorConfig;
31
+ this._agentRegistry = agentRegistry;
32
+ this._bootstrapPromptBuilder = bootstrapPromptBuilder;
33
+ this._commandAvailabilityCache = new Map();
34
+ /** @type {Map<string, import('./agent-spawner').SpawnedAgent>} */
35
+ this._agents = new Map();
36
+ }
37
+
38
+ /**
39
+ * Spawn a Codex CLI sub-process to execute the given Spec.
40
+ *
41
+ * 1. Builds the bootstrap prompt via BootstrapPromptBuilder
42
+ * 2. Registers the agent in AgentRegistry
43
+ * 3. Spawns `codex exec --full-auto --json --sandbox danger-full-access "<prompt>"`
44
+ * 4. Sets up stdout/stderr/close handlers and timeout timer
45
+ *
46
+ * @param {string} specName - Spec to execute (e.g. "96-00-agent-orchestrator")
47
+ * @returns {Promise<object>} The SpawnedAgent record
48
+ */
49
+ async spawn(specName) {
50
+ const config = await this._orchestratorConfig.getConfig();
51
+
52
+ // Resolve API key: env var → ~/.codex/auth.json fallback
53
+ const apiKeyEnvVar = config.apiKeyEnvVar || 'CODEX_API_KEY';
54
+ let apiKey = process.env[apiKeyEnvVar];
55
+ if (!apiKey) {
56
+ apiKey = this._readCodexAuthFile();
57
+ }
58
+ if (!apiKey) {
59
+ throw new Error(
60
+ `Cannot find API key. Set environment variable ${apiKeyEnvVar}, ` +
61
+ 'or configure Codex CLI auth via `codex auth` (~/.codex/auth.json).'
62
+ );
63
+ }
64
+
65
+ // Build the bootstrap prompt (Req 2.1-2.3)
66
+ const prompt = await this._bootstrapPromptBuilder.buildPrompt(specName);
67
+ this._assertValidBootstrapPrompt(prompt, specName, 'BootstrapPromptBuilder.buildPrompt()');
68
+
69
+ // Register in AgentRegistry (Req 1.7)
70
+ const { agentId } = await this._agentRegistry.register({
71
+ currentTask: { specName },
72
+ });
73
+
74
+ // Assemble command arguments (Req 1.1, 1.3)
75
+ const args = [
76
+ 'exec',
77
+ '--full-auto',
78
+ '--json',
79
+ '--sandbox', 'danger-full-access',
80
+ ...(config.codexArgs || []),
81
+ prompt,
82
+ ];
83
+
84
+ // Resolve codex command: config → auto-detect
85
+ const { command, prependArgs } = this._resolveCodexCommand(config);
86
+
87
+ // Spawn the child process (Req 1.1, 1.2)
88
+ //
89
+ // On Windows we must use a shell to execute .cmd/.ps1 wrappers, but
90
+ // cmd.exe has an 8191-character command-line limit which the bootstrap
91
+ // prompt easily exceeds. To avoid this we write the prompt to a temp
92
+ // file and pass the file path to codex via a shell read expression.
93
+ //
94
+ // Strategy per platform:
95
+ // Windows → write prompt to temp file, spawn via cmd.exe with
96
+ // `type <file>` piped through a FOR /F or via PowerShell.
97
+ // Simplest: use stdin pipe (stdio[0] = 'pipe') so the
98
+ // prompt never appears on the command line at all.
99
+ // Others → pass prompt directly as argument (no length issue).
100
+ const isWindows = process.platform === 'win32';
101
+ const needsShell = isWindows || command === 'npx';
102
+
103
+ // On Windows, remove the prompt from args and pipe it via stdin instead,
104
+ // completely bypassing the cmd.exe 8191-char command-line limit.
105
+ let useStdinPrompt = false;
106
+ let stdinPrompt = null;
107
+ const finalArgs = [...prependArgs, ...args];
108
+
109
+ // When shell: true, Node.js concatenates args into a single string without
110
+ // escaping. Arguments containing spaces must be quoted so the shell does
111
+ // not split them into separate tokens.
112
+ if (needsShell) {
113
+ for (let i = 0; i < finalArgs.length; i++) {
114
+ if (/\s/.test(finalArgs[i])) {
115
+ finalArgs[i] = `"${finalArgs[i].replace(/"/g, '\\"')}"`;
116
+ }
117
+ }
118
+ }
119
+
120
+ if (isWindows) {
121
+ // Remove the prompt (last element of args portion) from command line
122
+ // and deliver it via stdin to avoid cmd.exe length limit.
123
+ stdinPrompt = finalArgs.pop(); // remove prompt
124
+ this._assertValidBootstrapPrompt(stdinPrompt, specName, 'Windows prompt extraction');
125
+
126
+ // If the prompt was quoted by the escaping above, unwrap it
127
+ if (stdinPrompt.startsWith('"') && stdinPrompt.endsWith('"')) {
128
+ stdinPrompt = stdinPrompt.slice(1, -1).replace(/\\"/g, '"');
129
+ }
130
+ useStdinPrompt = true;
131
+ }
132
+
133
+ const env = { ...process.env, [apiKeyEnvVar]: apiKey };
134
+
135
+ // When using stdin for the prompt, write it to a temp file and pass
136
+ // the file path as the last argument using a short placeholder.
137
+ // Codex exec reads the prompt from argv, so we use a temp-file approach:
138
+ // write prompt → pass file path via shell read.
139
+ let promptTmpFile = null;
140
+ if (useStdinPrompt) {
141
+ // Sanitize agentId for use in filename to avoid Windows invalid path characters.
142
+ const safeAgentId = this._sanitizeWindowsFilenamePart(agentId);
143
+ promptTmpFile = path.join(os.tmpdir(), `kse-prompt-${safeAgentId}-${Date.now()}.txt`);
144
+ fs.writeFileSync(promptTmpFile, stdinPrompt, 'utf-8');
145
+ }
146
+
147
+ // Build the final spawn arguments
148
+ let spawnCommand = command;
149
+ let spawnArgs = finalArgs;
150
+ let spawnShell = needsShell;
151
+
152
+ if (promptTmpFile) {
153
+ // On Windows, use PowerShell to read the temp file as the last argument.
154
+ // PowerShell does not have the 8191-char limit of cmd.exe.
155
+ // We construct a PowerShell script that:
156
+ // 1. Reads the prompt file into a variable (with UTF-8 encoding)
157
+ // 2. Passes the variable as a single argument to codex
158
+ const cmdParts = [command, ...finalArgs].map(a => {
159
+ if (a.startsWith('"') && a.endsWith('"')) return a;
160
+ return /\s/.test(a) ? `"${a}"` : a;
161
+ });
162
+
163
+ // Use -Encoding UTF8 to correctly read UTF-8 files with non-ASCII characters (e.g., Chinese steering files).
164
+ // Pipe prompt via stdin (`-` prompt argument) to avoid Windows native argument splitting for long/multi-line prompts.
165
+ const psScript = `$prompt = Get-Content -Raw -Encoding UTF8 '${promptTmpFile.replace(/'/g, "''")}'; $prompt | & ${cmdParts.join(' ')} -`;
166
+
167
+ spawnCommand = 'powershell.exe';
168
+ spawnArgs = ['-NoProfile', '-Command', psScript];
169
+ spawnShell = false; // spawning powershell.exe directly
170
+ }
171
+
172
+ const child = spawn(spawnCommand, spawnArgs, {
173
+ cwd: this._workspaceRoot,
174
+ env,
175
+ stdio: ['ignore', 'pipe', 'pipe'],
176
+ windowsHide: true,
177
+ shell: spawnShell,
178
+ });
179
+
180
+ const now = new Date().toISOString();
181
+
182
+ /** @type {object} */
183
+ const agent = {
184
+ agentId,
185
+ specName,
186
+ process: child,
187
+ status: 'running',
188
+ startedAt: now,
189
+ completedAt: null,
190
+ exitCode: null,
191
+ retryCount: 0,
192
+ stderr: '',
193
+ events: [],
194
+ _promptTmpFile: promptTmpFile,
195
+ };
196
+
197
+ this._agents.set(agentId, agent);
198
+
199
+ // --- stdout: parse JSON Lines events ---
200
+ this._setupStdoutHandler(agent);
201
+
202
+ // --- stderr: buffer for error reporting ---
203
+ this._setupStderrHandler(agent);
204
+
205
+ // --- process close: determine final status ---
206
+ this._setupCloseHandler(agent);
207
+
208
+ // --- timeout detection (Req 1.6) ---
209
+ this._setupTimeout(agent, config.timeoutSeconds);
210
+
211
+ return agent;
212
+ }
213
+
214
+ /**
215
+ * Terminate a specific sub-process.
216
+ * Sends SIGTERM first, then SIGKILL after a 5-second grace period.
217
+ *
218
+ * @param {string} agentId
219
+ * @returns {Promise<void>}
220
+ */
221
+ async kill(agentId) {
222
+ const agent = this._agents.get(agentId);
223
+ if (!agent || agent.status !== 'running') {
224
+ return;
225
+ }
226
+ await this._terminateProcess(agent);
227
+ }
228
+
229
+ /**
230
+ * Terminate all running sub-processes.
231
+ * @returns {Promise<void>}
232
+ */
233
+ async killAll() {
234
+ const killPromises = [];
235
+ for (const agent of this._agents.values()) {
236
+ if (agent.status === 'running') {
237
+ killPromises.push(this._terminateProcess(agent));
238
+ }
239
+ }
240
+ await Promise.all(killPromises);
241
+ }
242
+
243
+ /**
244
+ * Get all agents (active and completed).
245
+ * @returns {Map<string, object>}
246
+ */
247
+ getActiveAgents() {
248
+ return new Map(this._agents);
249
+ }
250
+
251
+ // ---------------------------------------------------------------------------
252
+ // Private helpers
253
+ // ---------------------------------------------------------------------------
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
+
288
+ /**
289
+ * Parse stdout line-by-line for JSON Lines events.
290
+ * @param {object} agent
291
+ * @private
292
+ */
293
+ _setupStdoutHandler(agent) {
294
+ let buffer = '';
295
+
296
+ agent.process.stdout.on('data', (chunk) => {
297
+ buffer += chunk.toString();
298
+ const lines = buffer.split('\n');
299
+ // Keep the last (possibly incomplete) line in the buffer
300
+ buffer = lines.pop() || '';
301
+
302
+ for (const line of lines) {
303
+ const trimmed = line.trim();
304
+ if (!trimmed) continue;
305
+
306
+ try {
307
+ const event = JSON.parse(trimmed);
308
+ agent.events.push(event);
309
+ this.emit('agent:output', {
310
+ agentId: agent.agentId,
311
+ specName: agent.specName,
312
+ event,
313
+ });
314
+ } catch (_err) {
315
+ // Non-JSON line — ignore silently
316
+ }
317
+ }
318
+ });
319
+ }
320
+
321
+ /**
322
+ * Buffer stderr output for error reporting.
323
+ * @param {object} agent
324
+ * @private
325
+ */
326
+ _setupStderrHandler(agent) {
327
+ agent.process.stderr.on('data', (chunk) => {
328
+ agent.stderr += chunk.toString();
329
+ });
330
+ }
331
+
332
+ /**
333
+ * Handle process close event to determine final status.
334
+ * @param {object} agent
335
+ * @private
336
+ */
337
+ _setupCloseHandler(agent) {
338
+ agent.process.on('close', async (code) => {
339
+ // Clean up prompt temp file if used
340
+ this._cleanupPromptTmpFile(agent);
341
+
342
+ // Clear timeout timer if still pending
343
+ if (agent._timeoutTimer) {
344
+ clearTimeout(agent._timeoutTimer);
345
+ agent._timeoutTimer = null;
346
+ }
347
+ if (agent._killTimer) {
348
+ clearTimeout(agent._killTimer);
349
+ agent._killTimer = null;
350
+ }
351
+
352
+ // Already finalized (e.g. by timeout handler) — skip
353
+ if (agent.status !== 'running') {
354
+ return;
355
+ }
356
+
357
+ agent.exitCode = code;
358
+ agent.completedAt = new Date().toISOString();
359
+
360
+ if (code === 0) {
361
+ // Req 1.4: exit 0 → completed
362
+ agent.status = 'completed';
363
+ this.emit('agent:completed', {
364
+ agentId: agent.agentId,
365
+ specName: agent.specName,
366
+ exitCode: code,
367
+ });
368
+ } else {
369
+ // Req 1.5: exit non-0 → failed
370
+ agent.status = 'failed';
371
+ this.emit('agent:failed', {
372
+ agentId: agent.agentId,
373
+ specName: agent.specName,
374
+ exitCode: code,
375
+ stderr: agent.stderr,
376
+ });
377
+ }
378
+
379
+ // Deregister from AgentRegistry
380
+ await this._deregisterAgent(agent.agentId);
381
+ });
382
+
383
+ // Handle spawn errors (e.g. command not found)
384
+ agent.process.on('error', async (err) => {
385
+ // Clean up prompt temp file if used
386
+ this._cleanupPromptTmpFile(agent);
387
+
388
+ if (agent._timeoutTimer) {
389
+ clearTimeout(agent._timeoutTimer);
390
+ agent._timeoutTimer = null;
391
+ }
392
+
393
+ if (agent.status !== 'running') return;
394
+
395
+ agent.status = 'failed';
396
+ agent.completedAt = new Date().toISOString();
397
+ agent.stderr += `\nSpawn error: ${err.message}`;
398
+
399
+ this.emit('agent:failed', {
400
+ agentId: agent.agentId,
401
+ specName: agent.specName,
402
+ exitCode: null,
403
+ stderr: agent.stderr,
404
+ error: err.message,
405
+ });
406
+
407
+ await this._deregisterAgent(agent.agentId);
408
+ });
409
+ }
410
+
411
+ /**
412
+ * Set up a timeout timer that terminates the process if it runs too long.
413
+ * Sends SIGTERM first, then SIGKILL after 5 seconds (Req 1.6).
414
+ *
415
+ * @param {object} agent
416
+ * @param {number} timeoutSeconds
417
+ * @private
418
+ */
419
+ _setupTimeout(agent, timeoutSeconds) {
420
+ if (!timeoutSeconds || timeoutSeconds <= 0) return;
421
+
422
+ agent._timeoutTimer = setTimeout(async () => {
423
+ if (agent.status !== 'running') return;
424
+
425
+ // Clean up prompt temp file if used
426
+ this._cleanupPromptTmpFile(agent);
427
+
428
+ agent.status = 'timeout';
429
+ agent.completedAt = new Date().toISOString();
430
+
431
+ this.emit('agent:timeout', {
432
+ agentId: agent.agentId,
433
+ specName: agent.specName,
434
+ timeoutSeconds,
435
+ });
436
+
437
+ // SIGTERM → 5s grace → SIGKILL
438
+ try {
439
+ agent.process.kill('SIGTERM');
440
+ } catch (_err) {
441
+ // Process may have already exited
442
+ }
443
+
444
+ agent._killTimer = setTimeout(() => {
445
+ try {
446
+ agent.process.kill('SIGKILL');
447
+ } catch (_err) {
448
+ // Process may have already exited
449
+ }
450
+ }, 5000);
451
+
452
+ await this._deregisterAgent(agent.agentId);
453
+ }, timeoutSeconds * 1000);
454
+ }
455
+
456
+ /**
457
+ * Terminate a process: SIGTERM first, SIGKILL after 5s grace period.
458
+ * @param {object} agent
459
+ * @returns {Promise<void>}
460
+ * @private
461
+ */
462
+ _terminateProcess(agent) {
463
+ return new Promise((resolve) => {
464
+ let settled = false;
465
+ let killTimer = null;
466
+ let safetyTimer = null;
467
+
468
+ const settle = () => {
469
+ if (settled) return;
470
+ settled = true;
471
+ if (killTimer) clearTimeout(killTimer);
472
+ if (safetyTimer) clearTimeout(safetyTimer);
473
+ resolve();
474
+ };
475
+
476
+ // Clear any existing timeout timer
477
+ if (agent._timeoutTimer) {
478
+ clearTimeout(agent._timeoutTimer);
479
+ agent._timeoutTimer = null;
480
+ }
481
+
482
+ try {
483
+ agent.process.kill('SIGTERM');
484
+ } catch (_err) {
485
+ settle();
486
+ return;
487
+ }
488
+
489
+ killTimer = setTimeout(() => {
490
+ try {
491
+ agent.process.kill('SIGKILL');
492
+ } catch (_err) {
493
+ // Already exited
494
+ }
495
+ }, 5000);
496
+
497
+ agent.process.once('close', () => {
498
+ settle();
499
+ });
500
+
501
+ // Safety net: resolve after 10s regardless
502
+ safetyTimer = setTimeout(() => {
503
+ settle();
504
+ }, 10000);
505
+ });
506
+ }
507
+
508
+ /**
509
+ * Deregister an agent from the AgentRegistry.
510
+ * Failures are logged but do not propagate (non-fatal).
511
+ *
512
+ * @param {string} agentId
513
+ * @returns {Promise<void>}
514
+ * @private
515
+ */
516
+ async _deregisterAgent(agentId) {
517
+ try {
518
+ await this._agentRegistry.deregister(agentId);
519
+ } catch (err) {
520
+ console.warn(
521
+ `[AgentSpawner] Failed to deregister agent ${agentId}: ${err.message}`
522
+ );
523
+ }
524
+ }
525
+
526
+ /**
527
+ * Remove the temporary prompt file created for Windows spawns.
528
+ * Silently ignores errors (file may already be gone).
529
+ * @param {object} agent
530
+ * @private
531
+ */
532
+ _cleanupPromptTmpFile(agent) {
533
+ if (agent._promptTmpFile) {
534
+ try {
535
+ fs.unlinkSync(agent._promptTmpFile);
536
+ } catch (_err) {
537
+ // Ignore — file may already be deleted
538
+ }
539
+ agent._promptTmpFile = null;
540
+ }
541
+ }
542
+
543
+ /**
544
+ * Read API key from Codex CLI's native auth file (~/.codex/auth.json).
545
+ * Returns the key string or null if not found.
546
+ *
547
+ * @returns {string|null}
548
+ * @private
549
+ */
550
+ _readCodexAuthFile() {
551
+ try {
552
+ const authPath = path.join(os.homedir(), '.codex', 'auth.json');
553
+ if (!fs.existsSync(authPath)) {
554
+ return null;
555
+ }
556
+ const auth = JSON.parse(fs.readFileSync(authPath, 'utf-8'));
557
+ return auth.OPENAI_API_KEY || auth.CODEX_API_KEY || null;
558
+ } catch (_err) {
559
+ return null;
560
+ }
561
+ }
562
+
563
+ /**
564
+ * Resolve the codex command and any prepended arguments.
565
+ *
566
+ * Priority:
567
+ * 1. config.codexCommand (user-specified, e.g. "npx @openai/codex" or "codex")
568
+ * 2. "codex" (default — assumes global install)
569
+ *
570
+ * When codexCommand contains spaces (e.g. "npx @openai/codex"),
571
+ * the first token becomes the command and the rest become prependArgs.
572
+ *
573
+ * @param {object} config
574
+ * @returns {{ command: string, prependArgs: string[] }}
575
+ * @private
576
+ */
577
+ _resolveCodexCommand(config) {
578
+ let raw = config.codexCommand;
579
+ if (!raw) {
580
+ if (this._isCommandAvailable('codex')) {
581
+ raw = 'codex';
582
+ } else if (this._isCommandAvailable('npx')) {
583
+ raw = 'npx @openai/codex';
584
+ } else {
585
+ // Keep historical default to preserve error semantics on misconfigured hosts.
586
+ raw = 'codex';
587
+ }
588
+ }
589
+
590
+ const parts = raw.trim().split(/\s+/);
591
+ return {
592
+ command: parts[0],
593
+ prependArgs: parts.slice(1),
594
+ };
595
+ }
596
+
597
+ /**
598
+ * Best-effort command availability probe.
599
+ * Uses `where` on Windows and `which` on POSIX systems.
600
+ *
601
+ * @param {string} command
602
+ * @returns {boolean}
603
+ * @private
604
+ */
605
+ _isCommandAvailable(command) {
606
+ if (!command || typeof command !== 'string') {
607
+ return false;
608
+ }
609
+ if (this._commandAvailabilityCache.has(command)) {
610
+ return this._commandAvailabilityCache.get(command);
611
+ }
612
+
613
+ try {
614
+ const lookupCmd = process.platform === 'win32' ? 'where' : 'which';
615
+ const result = spawnSync(lookupCmd, [command], {
616
+ windowsHide: true,
617
+ stdio: 'ignore',
618
+ });
619
+ const available = result.status === 0;
620
+ this._commandAvailabilityCache.set(command, available);
621
+ return available;
622
+ } catch (_err) {
623
+ this._commandAvailabilityCache.set(command, false);
624
+ return false;
625
+ }
626
+ }
627
+ }
628
+
629
+ module.exports = { AgentSpawner };