kiro-spec-engine 1.47.12 → 1.47.14

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/README.md CHANGED
@@ -413,6 +413,12 @@ Tip: `kse spec bootstrap|pipeline run|gate run --specs ...` now defaults to this
413
413
  "maxParallel": 3,
414
414
  "timeoutSeconds": 900,
415
415
  "maxRetries": 2,
416
+ "rateLimitMaxRetries": 6,
417
+ "rateLimitBackoffBaseMs": 1000,
418
+ "rateLimitBackoffMaxMs": 30000,
419
+ "rateLimitAdaptiveParallel": true,
420
+ "rateLimitParallelFloor": 1,
421
+ "rateLimitCooldownMs": 30000,
416
422
  "apiKeyEnvVar": "CODEX_API_KEY",
417
423
  "codexArgs": ["--skip-git-repo-check"],
418
424
  "codexCommand": "npx @openai/codex"
@@ -420,6 +426,7 @@ Tip: `kse spec bootstrap|pipeline run|gate run --specs ...` now defaults to this
420
426
  ```
421
427
 
422
428
  If Codex CLI is globally installed, you can set `"codexCommand": "codex"`.
429
+ Use the `rateLimit*` settings to absorb transient 429/too-many-requests failures without stalling orchestration.
423
430
 
424
431
  ### Spec-Level Steering & Context Sync 🚀 NEW in v1.44.0
425
432
  - **Spec Steering (L4)**: Independent `steering.md` per Spec with constraints, notes, and decisions — zero cross-agent conflict
package/README.zh.md CHANGED
@@ -367,6 +367,12 @@ kse orchestrate stop
367
367
  "maxParallel": 3,
368
368
  "timeoutSeconds": 900,
369
369
  "maxRetries": 2,
370
+ "rateLimitMaxRetries": 6,
371
+ "rateLimitBackoffBaseMs": 1000,
372
+ "rateLimitBackoffMaxMs": 30000,
373
+ "rateLimitAdaptiveParallel": true,
374
+ "rateLimitParallelFloor": 1,
375
+ "rateLimitCooldownMs": 30000,
370
376
  "apiKeyEnvVar": "CODEX_API_KEY",
371
377
  "codexArgs": ["--skip-git-repo-check"],
372
378
  "codexCommand": "npx @openai/codex"
@@ -374,6 +380,7 @@ kse orchestrate stop
374
380
  ```
375
381
 
376
382
  如果你已全局安装 Codex CLI,可将 `"codexCommand"` 改为 `"codex"`。
383
+ 可通过 `rateLimit*` 配置吸收 429/too-many-requests 等限流抖动,避免编排流程卡死。
377
384
 
378
385
  ### Spec 级 Steering 与上下文同步 🚀 v1.44.0 新增
379
386
  - **Spec Steering (L4)**: 每个 Spec 独立的 `steering.md`,包含约束、注意事项、决策记录 — 跨 Agent 零冲突
@@ -787,12 +787,20 @@ Recommended `.kiro/config/orchestrator.json`:
787
787
  "maxParallel": 3,
788
788
  "timeoutSeconds": 900,
789
789
  "maxRetries": 2,
790
+ "rateLimitMaxRetries": 6,
791
+ "rateLimitBackoffBaseMs": 1000,
792
+ "rateLimitBackoffMaxMs": 30000,
793
+ "rateLimitAdaptiveParallel": true,
794
+ "rateLimitParallelFloor": 1,
795
+ "rateLimitCooldownMs": 30000,
790
796
  "apiKeyEnvVar": "CODEX_API_KEY",
791
797
  "codexArgs": ["--skip-git-repo-check"],
792
798
  "codexCommand": "npx @openai/codex"
793
799
  }
794
800
  ```
795
801
 
802
+ `rateLimit*` settings provide dedicated retry/backoff and adaptive parallel throttling when providers return 429 / too-many-requests errors.
803
+
796
804
  ### Scene Template Engine
797
805
 
798
806
  ```bash
@@ -124,6 +124,9 @@ async function runOrchestration(options = {}, dependencies = {}) {
124
124
  'spec:start',
125
125
  'spec:complete',
126
126
  'spec:failed',
127
+ 'spec:rate-limited',
128
+ 'parallel:throttled',
129
+ 'parallel:recovered',
127
130
  'orchestration:complete'
128
131
  ];
129
132
 
@@ -322,6 +325,17 @@ function _printStatus(status) {
322
325
  if (status.currentBatch !== undefined && status.totalBatches !== undefined) {
323
326
  console.log(`Batch: ${status.currentBatch} / ${status.totalBatches}`);
324
327
  }
328
+ if (status.parallel) {
329
+ const effective = status.parallel.effectiveMaxParallel ?? '-';
330
+ const max = status.parallel.maxParallel ?? '-';
331
+ const adaptive = status.parallel.adaptive === false ? 'off' : 'on';
332
+ console.log(`Parallel: ${effective} / ${max} (adaptive: ${adaptive})`);
333
+ }
334
+ if (status.rateLimit) {
335
+ const signals = status.rateLimit.signalCount || 0;
336
+ const backoff = status.rateLimit.totalBackoffMs || 0;
337
+ console.log(`Rate-limit: ${signals} signal(s), total backoff ${backoff}ms`);
338
+ }
325
339
  if (status.specs) {
326
340
  console.log('');
327
341
  for (const [name, info] of Object.entries(status.specs)) {
@@ -463,6 +463,12 @@ class OrchestrationEngine extends EventEmitter {
463
463
  : 0;
464
464
  if (retryDelayMs > 0) {
465
465
  this._onRateLimitSignal();
466
+ this._updateStatusMonitorRateLimit({
467
+ specName,
468
+ retryCount,
469
+ retryDelayMs,
470
+ error: resolvedError,
471
+ });
466
472
  this.emit('spec:rate-limited', {
467
473
  specName,
468
474
  retryCount,
@@ -650,6 +656,15 @@ class OrchestrationEngine extends EventEmitter {
650
656
  this._baseMaxParallel = boundedMax;
651
657
  this._effectiveMaxParallel = boundedMax;
652
658
  this._rateLimitCooldownUntil = 0;
659
+ this._updateStatusMonitorParallelTelemetry({
660
+ adaptive: this._isAdaptiveParallelEnabled(),
661
+ maxParallel: boundedMax,
662
+ effectiveMaxParallel: boundedMax,
663
+ floor: Math.min(
664
+ boundedMax,
665
+ this._toPositiveInteger(this._rateLimitParallelFloor, DEFAULT_RATE_LIMIT_PARALLEL_FLOOR)
666
+ ),
667
+ });
653
668
  }
654
669
 
655
670
  /**
@@ -659,20 +674,35 @@ class OrchestrationEngine extends EventEmitter {
659
674
  */
660
675
  _getEffectiveMaxParallel(maxParallel) {
661
676
  const boundedMax = this._toPositiveInteger(maxParallel, 1);
677
+ const floor = Math.min(
678
+ boundedMax,
679
+ this._toPositiveInteger(this._rateLimitParallelFloor, DEFAULT_RATE_LIMIT_PARALLEL_FLOOR)
680
+ );
681
+
662
682
  if (!this._isAdaptiveParallelEnabled()) {
663
683
  this._baseMaxParallel = boundedMax;
684
+ this._effectiveMaxParallel = boundedMax;
685
+ this._updateStatusMonitorParallelTelemetry({
686
+ adaptive: false,
687
+ maxParallel: boundedMax,
688
+ effectiveMaxParallel: boundedMax,
689
+ floor,
690
+ });
664
691
  return boundedMax;
665
692
  }
666
693
 
667
694
  this._baseMaxParallel = boundedMax;
668
695
  this._maybeRecoverParallelLimit(boundedMax);
669
696
 
670
- const floor = Math.min(
671
- boundedMax,
672
- this._toPositiveInteger(this._rateLimitParallelFloor, DEFAULT_RATE_LIMIT_PARALLEL_FLOOR)
673
- );
674
697
  const effective = this._toPositiveInteger(this._effectiveMaxParallel, boundedMax);
675
- return Math.max(floor, Math.min(boundedMax, effective));
698
+ const resolved = Math.max(floor, Math.min(boundedMax, effective));
699
+ this._updateStatusMonitorParallelTelemetry({
700
+ adaptive: true,
701
+ maxParallel: boundedMax,
702
+ effectiveMaxParallel: resolved,
703
+ floor,
704
+ });
705
+ return resolved;
676
706
  }
677
707
 
678
708
  /**
@@ -693,6 +723,14 @@ class OrchestrationEngine extends EventEmitter {
693
723
 
694
724
  if (next < current) {
695
725
  this._effectiveMaxParallel = next;
726
+ this._updateStatusMonitorParallelTelemetry({
727
+ event: 'throttled',
728
+ reason: 'rate-limit',
729
+ adaptive: true,
730
+ maxParallel: base,
731
+ effectiveMaxParallel: next,
732
+ floor,
733
+ });
696
734
  this.emit('parallel:throttled', {
697
735
  reason: 'rate-limit',
698
736
  previousMaxParallel: current,
@@ -730,6 +768,12 @@ class OrchestrationEngine extends EventEmitter {
730
768
  if (next > current) {
731
769
  this._effectiveMaxParallel = next;
732
770
  this._rateLimitCooldownUntil = this._getNow() + this._rateLimitCooldownMs;
771
+ this._updateStatusMonitorParallelTelemetry({
772
+ event: 'recovered',
773
+ adaptive: true,
774
+ maxParallel: boundedMax,
775
+ effectiveMaxParallel: next,
776
+ });
733
777
  this.emit('parallel:recovered', {
734
778
  previousMaxParallel: current,
735
779
  effectiveMaxParallel: next,
@@ -841,6 +885,40 @@ class OrchestrationEngine extends EventEmitter {
841
885
  return new Promise((resolve) => setTimeout(resolve, ms));
842
886
  }
843
887
 
888
+ /**
889
+ * Safely update StatusMonitor rate-limit telemetry.
890
+ *
891
+ * @param {object} payload
892
+ * @private
893
+ */
894
+ _updateStatusMonitorRateLimit(payload) {
895
+ const handler = this._statusMonitor && this._statusMonitor.recordRateLimitEvent;
896
+ if (typeof handler === 'function') {
897
+ try {
898
+ handler.call(this._statusMonitor, payload);
899
+ } catch (_err) {
900
+ // Non-fatal status telemetry update.
901
+ }
902
+ }
903
+ }
904
+
905
+ /**
906
+ * Safely update StatusMonitor adaptive parallel telemetry.
907
+ *
908
+ * @param {object} payload
909
+ * @private
910
+ */
911
+ _updateStatusMonitorParallelTelemetry(payload) {
912
+ const handler = this._statusMonitor && this._statusMonitor.updateParallelTelemetry;
913
+ if (typeof handler === 'function') {
914
+ try {
915
+ handler.call(this._statusMonitor, payload);
916
+ } catch (_err) {
917
+ // Non-fatal status telemetry update.
918
+ }
919
+ }
920
+ }
921
+
844
922
  /**
845
923
  * Validate that all spec directories exist (Req 6.4).
846
924
  *
@@ -56,6 +56,37 @@ class StatusMonitor {
56
56
  * @type {Map<string, {status: string, batch: number, agentId: string|null, retryCount: number, error: string|null, turnCount: number}>}
57
57
  */
58
58
  this._specs = new Map();
59
+
60
+ /**
61
+ * Rate-limit telemetry summary.
62
+ * @type {{signalCount: number, retryCount: number, totalBackoffMs: number, lastSignalAt: string|null, lastSpecName: string|null, lastRetryCount: number, lastDelayMs: number, lastError: string|null}}
63
+ */
64
+ this._rateLimit = {
65
+ signalCount: 0,
66
+ retryCount: 0,
67
+ totalBackoffMs: 0,
68
+ lastSignalAt: null,
69
+ lastSpecName: null,
70
+ lastRetryCount: 0,
71
+ lastDelayMs: 0,
72
+ lastError: null,
73
+ };
74
+
75
+ /**
76
+ * Parallelism telemetry summary.
77
+ * @type {{adaptive: boolean|null, maxParallel: number|null, effectiveMaxParallel: number|null, floor: number|null, throttledCount: number, recoveredCount: number, lastReason: string|null, lastThrottledAt: string|null, lastRecoveredAt: string|null}}
78
+ */
79
+ this._parallel = {
80
+ adaptive: null,
81
+ maxParallel: null,
82
+ effectiveMaxParallel: null,
83
+ floor: null,
84
+ throttledCount: 0,
85
+ recoveredCount: 0,
86
+ lastReason: null,
87
+ lastThrottledAt: null,
88
+ lastRecoveredAt: null,
89
+ };
59
90
  }
60
91
 
61
92
  // ---------------------------------------------------------------------------
@@ -125,6 +156,80 @@ class StatusMonitor {
125
156
  }
126
157
  }
127
158
 
159
+ /**
160
+ * Record a rate-limit retry/backoff signal.
161
+ *
162
+ * @param {object} data
163
+ * @param {string} [data.specName]
164
+ * @param {number} [data.retryCount]
165
+ * @param {number} [data.retryDelayMs]
166
+ * @param {string} [data.error]
167
+ */
168
+ recordRateLimitEvent(data = {}) {
169
+ const delay = Number(data.retryDelayMs);
170
+ const retryCount = Number(data.retryCount);
171
+
172
+ this._rateLimit.signalCount++;
173
+ this._rateLimit.retryCount++;
174
+ this._rateLimit.totalBackoffMs += Number.isFinite(delay) && delay > 0 ? Math.round(delay) : 0;
175
+ this._rateLimit.lastSignalAt = new Date().toISOString();
176
+
177
+ if (typeof data.specName === 'string' && data.specName.trim()) {
178
+ this._rateLimit.lastSpecName = data.specName.trim();
179
+ }
180
+ if (Number.isFinite(retryCount) && retryCount >= 0) {
181
+ this._rateLimit.lastRetryCount = Math.floor(retryCount);
182
+ }
183
+ this._rateLimit.lastDelayMs = Number.isFinite(delay) && delay > 0 ? Math.round(delay) : 0;
184
+ if (data.error !== undefined && data.error !== null) {
185
+ this._rateLimit.lastError = `${data.error}`;
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Update adaptive parallel telemetry.
191
+ *
192
+ * @param {object} data
193
+ * @param {'throttled'|'recovered'} [data.event]
194
+ * @param {string} [data.reason]
195
+ * @param {number} [data.maxParallel]
196
+ * @param {number} [data.effectiveMaxParallel]
197
+ * @param {number} [data.floor]
198
+ * @param {boolean} [data.adaptive]
199
+ */
200
+ updateParallelTelemetry(data = {}) {
201
+ if (typeof data.adaptive === 'boolean') {
202
+ this._parallel.adaptive = data.adaptive;
203
+ }
204
+
205
+ const maxParallel = Number(data.maxParallel);
206
+ if (Number.isFinite(maxParallel) && maxParallel > 0) {
207
+ this._parallel.maxParallel = Math.floor(maxParallel);
208
+ }
209
+
210
+ const effective = Number(data.effectiveMaxParallel);
211
+ if (Number.isFinite(effective) && effective > 0) {
212
+ this._parallel.effectiveMaxParallel = Math.floor(effective);
213
+ }
214
+
215
+ const floor = Number(data.floor);
216
+ if (Number.isFinite(floor) && floor > 0) {
217
+ this._parallel.floor = Math.floor(floor);
218
+ }
219
+
220
+ if (typeof data.reason === 'string' && data.reason.trim()) {
221
+ this._parallel.lastReason = data.reason.trim();
222
+ }
223
+
224
+ if (data.event === 'throttled') {
225
+ this._parallel.throttledCount++;
226
+ this._parallel.lastThrottledAt = new Date().toISOString();
227
+ } else if (data.event === 'recovered') {
228
+ this._parallel.recoveredCount++;
229
+ this._parallel.lastRecoveredAt = new Date().toISOString();
230
+ }
231
+ }
232
+
128
233
  /**
129
234
  * Set the overall orchestration state.
130
235
  *
@@ -219,6 +324,8 @@ class StatusMonitor {
219
324
  currentBatch: this._currentBatch,
220
325
  totalBatches: this._totalBatches,
221
326
  specs,
327
+ rateLimit: { ...this._rateLimit },
328
+ parallel: { ...this._parallel },
222
329
  };
223
330
  }
224
331
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kiro-spec-engine",
3
- "version": "1.47.12",
3
+ "version": "1.47.14",
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": {