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
|
-
|
|
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.
|
|
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": {
|