@hegemonart/get-design-done 1.27.0 → 1.27.5
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +69 -0
- package/SKILL.md +1 -0
- package/agents/design-reflector.md +52 -0
- package/hooks/budget-enforcer.ts +249 -5
- package/package.json +2 -2
- package/reference/bandit-integration.md +163 -0
- package/reference/peer-protocols.md +1 -1
- package/reference/registry.json +7 -0
- package/scripts/install.cjs +100 -1
- package/scripts/lib/bandit-arbitrage.cjs +423 -0
- package/scripts/lib/bandit-router/integration.cjs +309 -0
- package/scripts/lib/peer-cli/spawn-cmd.cjs +2 -2
- package/scripts/lib/session-runner/index.ts +381 -28
- package/skills/bandit-status/SKILL.md +129 -0
- package/skills/peers/SKILL.md +27 -8
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scripts/lib/bandit-router/integration.cjs — Plan 27.5-01
|
|
3
|
+
*
|
|
4
|
+
* Production-integration shim for the Phase 23.5 bandit posterior +
|
|
5
|
+
* Phase 27-07 delegate dimension. Hides the `pull` vs `pullWithDelegate`
|
|
6
|
+
* + `update` vs `updateWithDelegate` choice from callers.
|
|
7
|
+
*
|
|
8
|
+
* Two functions:
|
|
9
|
+
* consultBandit({agent, bin, delegate, agentFrontmatter, adaptiveMode, baseDir?, posteriorPath?})
|
|
10
|
+
* → {tier, decision_log}
|
|
11
|
+
* recordOutcome({agent, bin, delegate, tier, status, costUsd, adaptiveMode, baseDir?, posteriorPath?})
|
|
12
|
+
* → void (best-effort write per D-04)
|
|
13
|
+
*
|
|
14
|
+
* Routing rules (D-05 + D-07):
|
|
15
|
+
* 1. agentFrontmatter.tier_override is set → bypass bandit, return tier_override
|
|
16
|
+
* 2. adaptiveMode !== 'full' → bandit silent, return frontmatter.default_tier
|
|
17
|
+
* (covers 'static' and 'hedge' per D-07)
|
|
18
|
+
* 3. adaptiveMode === 'full' && delegate is 'none' / undefined
|
|
19
|
+
* → call pull()
|
|
20
|
+
* 4. adaptiveMode === 'full' && delegate is a peer name
|
|
21
|
+
* → call pullWithDelegate({delegates:[delegate]})
|
|
22
|
+
*
|
|
23
|
+
* recordOutcome is symmetric on the adaptive_mode gate:
|
|
24
|
+
* - non-'full' → no-op
|
|
25
|
+
* - 'full' && delegate 'none'/undefined → update()
|
|
26
|
+
* - 'full' && delegate is a peer → updateWithDelegate()
|
|
27
|
+
*
|
|
28
|
+
* Reward function = Phase 23.5's computeReward unchanged (D-08).
|
|
29
|
+
*
|
|
30
|
+
* Posterior writes are best-effort — all throws are swallowed. The
|
|
31
|
+
* shim's job is to plumb the call; telemetry resilience is downstream.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
'use strict';
|
|
35
|
+
|
|
36
|
+
const banditRouter = require('../bandit-router.cjs');
|
|
37
|
+
const adaptiveModeLib = require('../adaptive-mode.cjs');
|
|
38
|
+
|
|
39
|
+
const DELEGATE_NONE = banditRouter.DELEGATE_NONE; // 'none'
|
|
40
|
+
const VALID_DELEGATES = banditRouter.DEFAULT_DELEGATES; // ['none','gemini','codex','cursor','copilot','qwen']
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Validate that `delegate` is either undefined, DELEGATE_NONE, or a
|
|
44
|
+
* member of VALID_DELEGATES. Returns the canonical delegate string
|
|
45
|
+
* (undefined → 'none').
|
|
46
|
+
*
|
|
47
|
+
* @param {string|undefined} delegate
|
|
48
|
+
* @param {string} fnName — for error message context
|
|
49
|
+
* @returns {string}
|
|
50
|
+
*/
|
|
51
|
+
function resolveDelegate(delegate, fnName) {
|
|
52
|
+
if (delegate === undefined || delegate === null) return DELEGATE_NONE;
|
|
53
|
+
if (typeof delegate !== 'string') {
|
|
54
|
+
throw new TypeError(
|
|
55
|
+
`integration.${fnName}: delegate must be a string when provided, got ${typeof delegate}`,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
if (!VALID_DELEGATES.includes(delegate)) {
|
|
59
|
+
throw new RangeError(
|
|
60
|
+
`integration.${fnName}: unknown delegate '${delegate}'; expected one of ${VALID_DELEGATES.join(',')}`,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
return delegate;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Resolve the adaptive_mode for a call. If the caller passed it
|
|
68
|
+
* explicitly we use that; otherwise we read it from disk via
|
|
69
|
+
* adaptive-mode.getMode (D-07: single gating surface).
|
|
70
|
+
*
|
|
71
|
+
* @param {string|undefined} adaptiveMode
|
|
72
|
+
* @param {{baseDir?: string}} opts
|
|
73
|
+
* @returns {'static'|'hedge'|'full'}
|
|
74
|
+
*/
|
|
75
|
+
function resolveAdaptiveMode(adaptiveMode, opts) {
|
|
76
|
+
if (typeof adaptiveMode === 'string' && adaptiveMode.length > 0) {
|
|
77
|
+
return /** @type {'static'|'hedge'|'full'} */ (adaptiveMode);
|
|
78
|
+
}
|
|
79
|
+
return adaptiveModeLib.getMode({ baseDir: opts && opts.baseDir, quiet: true });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* consultBandit — single canonical lookup that returns a tier + a
|
|
84
|
+
* decision_log explaining how the tier was chosen. Five paths:
|
|
85
|
+
*
|
|
86
|
+
* Path 1 — static mode → frontmatter.default_tier (or 'sonnet' fallback)
|
|
87
|
+
* Path 2 — tier_override set on frontmatter → bypass bandit
|
|
88
|
+
* Path 3 — full mode + delegate='none' (or undefined) → pull()
|
|
89
|
+
* Path 4 — full mode + delegate=<peer> → pullWithDelegate()
|
|
90
|
+
* Path 5 — hedge mode → frontmatter.default_tier (bandit silent)
|
|
91
|
+
*
|
|
92
|
+
* Path 2 takes precedence over Path 1 / 3 / 4 / 5 (tier_override is the
|
|
93
|
+
* explicit operator override per D-05).
|
|
94
|
+
*
|
|
95
|
+
* @param {{
|
|
96
|
+
* agent: string,
|
|
97
|
+
* bin: string,
|
|
98
|
+
* delegate?: string,
|
|
99
|
+
* agentFrontmatter?: {tier_override?: string, default_tier?: string},
|
|
100
|
+
* adaptiveMode?: 'static'|'hedge'|'full',
|
|
101
|
+
* baseDir?: string,
|
|
102
|
+
* posteriorPath?: string,
|
|
103
|
+
* }} input
|
|
104
|
+
* @returns {{
|
|
105
|
+
* tier: string,
|
|
106
|
+
* decision_log: {
|
|
107
|
+
* source: 'frontmatter'|'tier_override_bypass'|'bandit_pull'|'bandit_pull_with_delegate',
|
|
108
|
+
* samples?: object,
|
|
109
|
+
* delegate?: string,
|
|
110
|
+
* adaptive_mode: 'static'|'hedge'|'full',
|
|
111
|
+
* reason?: string,
|
|
112
|
+
* }
|
|
113
|
+
* }}
|
|
114
|
+
*/
|
|
115
|
+
function consultBandit(input) {
|
|
116
|
+
if (!input || typeof input !== 'object') {
|
|
117
|
+
throw new TypeError('integration.consultBandit: input object required');
|
|
118
|
+
}
|
|
119
|
+
if (typeof input.agent !== 'string' || input.agent.length === 0) {
|
|
120
|
+
throw new TypeError('integration.consultBandit: agent (string) required');
|
|
121
|
+
}
|
|
122
|
+
if (typeof input.bin !== 'string' || input.bin.length === 0) {
|
|
123
|
+
throw new TypeError('integration.consultBandit: bin (string) required');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const agentFrontmatter = input.agentFrontmatter && typeof input.agentFrontmatter === 'object'
|
|
127
|
+
? input.agentFrontmatter
|
|
128
|
+
: {};
|
|
129
|
+
const adaptiveMode = resolveAdaptiveMode(input.adaptiveMode, input);
|
|
130
|
+
const delegate = resolveDelegate(input.delegate, 'consultBandit');
|
|
131
|
+
|
|
132
|
+
// Step 1 — tier_override bypass (D-05). Highest priority; beats both
|
|
133
|
+
// bandit consultation and static/hedge frontmatter.default_tier.
|
|
134
|
+
if (typeof agentFrontmatter.tier_override === 'string' && agentFrontmatter.tier_override.length > 0) {
|
|
135
|
+
return {
|
|
136
|
+
tier: agentFrontmatter.tier_override,
|
|
137
|
+
decision_log: {
|
|
138
|
+
source: 'tier_override_bypass',
|
|
139
|
+
adaptive_mode: adaptiveMode,
|
|
140
|
+
reason: 'frontmatter_tier_override_set',
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Step 2 — non-full short-circuit (D-07). Static and hedge are both
|
|
146
|
+
// "bandit silent"; frontmatter.default_tier (or 'sonnet' fallback)
|
|
147
|
+
// is authoritative. No posterior read or write.
|
|
148
|
+
if (adaptiveMode !== 'full') {
|
|
149
|
+
const fallbackTier = (typeof agentFrontmatter.default_tier === 'string' && agentFrontmatter.default_tier.length > 0)
|
|
150
|
+
? agentFrontmatter.default_tier
|
|
151
|
+
: 'sonnet';
|
|
152
|
+
return {
|
|
153
|
+
tier: fallbackTier,
|
|
154
|
+
decision_log: {
|
|
155
|
+
source: 'frontmatter',
|
|
156
|
+
adaptive_mode: adaptiveMode,
|
|
157
|
+
reason: adaptiveMode === 'hedge' ? 'hedge_mode_skips_bandit' : 'static_mode_authoritative',
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Step 3/4 — full mode → consult the bandit. Choice of pull vs
|
|
163
|
+
// pullWithDelegate is driven by `delegate`:
|
|
164
|
+
// delegate === 'none' (or undefined → 'none') → pull()
|
|
165
|
+
// delegate ∈ {gemini,codex,cursor,copilot,qwen} → pullWithDelegate
|
|
166
|
+
if (delegate === DELEGATE_NONE) {
|
|
167
|
+
const result = banditRouter.pull({
|
|
168
|
+
agent: input.agent,
|
|
169
|
+
bin: input.bin,
|
|
170
|
+
baseDir: input.baseDir,
|
|
171
|
+
posteriorPath: input.posteriorPath,
|
|
172
|
+
});
|
|
173
|
+
return {
|
|
174
|
+
tier: result.tier,
|
|
175
|
+
decision_log: {
|
|
176
|
+
source: 'bandit_pull',
|
|
177
|
+
samples: result.samples,
|
|
178
|
+
delegate: DELEGATE_NONE,
|
|
179
|
+
adaptive_mode: 'full',
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Path 4 — peer delegate. Constrain the delegate axis to the single
|
|
185
|
+
// requested peer so the bandit samples the (tier × delegate) joint
|
|
186
|
+
// restricted to {delegate}. Same posterior file, same arm shape; the
|
|
187
|
+
// arm's `delegate` field is set so the slice is distinct from local.
|
|
188
|
+
const result = banditRouter.pullWithDelegate({
|
|
189
|
+
agent: input.agent,
|
|
190
|
+
bin: input.bin,
|
|
191
|
+
delegates: [delegate],
|
|
192
|
+
baseDir: input.baseDir,
|
|
193
|
+
posteriorPath: input.posteriorPath,
|
|
194
|
+
});
|
|
195
|
+
return {
|
|
196
|
+
tier: result.tier,
|
|
197
|
+
decision_log: {
|
|
198
|
+
source: 'bandit_pull_with_delegate',
|
|
199
|
+
samples: result.samples,
|
|
200
|
+
delegate: result.delegate,
|
|
201
|
+
adaptive_mode: 'full',
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* recordOutcome — post-spawn telemetry update. Computes a reward via
|
|
208
|
+
* computeReward (Phase 23.5 D-08, unchanged) and writes the posterior
|
|
209
|
+
* arm. Best-effort: all errors swallowed so telemetry can never break
|
|
210
|
+
* a session (D-04).
|
|
211
|
+
*
|
|
212
|
+
* No-op when adaptive_mode is not 'full' (D-07).
|
|
213
|
+
*
|
|
214
|
+
* @param {{
|
|
215
|
+
* agent: string,
|
|
216
|
+
* bin: string,
|
|
217
|
+
* delegate?: string,
|
|
218
|
+
* tier: string,
|
|
219
|
+
* status: string,
|
|
220
|
+
* costUsd?: number,
|
|
221
|
+
* adaptiveMode?: 'static'|'hedge'|'full',
|
|
222
|
+
* baseDir?: string,
|
|
223
|
+
* posteriorPath?: string,
|
|
224
|
+
* }} input
|
|
225
|
+
* @returns {void}
|
|
226
|
+
*/
|
|
227
|
+
function recordOutcome(input) {
|
|
228
|
+
if (!input || typeof input !== 'object') {
|
|
229
|
+
throw new TypeError('integration.recordOutcome: input object required');
|
|
230
|
+
}
|
|
231
|
+
if (typeof input.agent !== 'string' || input.agent.length === 0) {
|
|
232
|
+
throw new TypeError('integration.recordOutcome: agent (string) required');
|
|
233
|
+
}
|
|
234
|
+
if (typeof input.bin !== 'string' || input.bin.length === 0) {
|
|
235
|
+
throw new TypeError('integration.recordOutcome: bin (string) required');
|
|
236
|
+
}
|
|
237
|
+
if (typeof input.tier !== 'string' || input.tier.length === 0) {
|
|
238
|
+
throw new TypeError('integration.recordOutcome: tier (string) required');
|
|
239
|
+
}
|
|
240
|
+
if (typeof input.status !== 'string') {
|
|
241
|
+
throw new TypeError('integration.recordOutcome: status (string) required');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const adaptiveMode = resolveAdaptiveMode(input.adaptiveMode, input);
|
|
245
|
+
|
|
246
|
+
// D-07 + D-04: posterior is silent in static/hedge. No-op early.
|
|
247
|
+
if (adaptiveMode !== 'full') {
|
|
248
|
+
return undefined;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const delegate = resolveDelegate(input.delegate, 'recordOutcome');
|
|
252
|
+
|
|
253
|
+
// D-08: reward function unchanged. wall_time_ms always 0 per
|
|
254
|
+
// Phase 23.5 / 27.5 — the wall-time tiebreaker is not used at the
|
|
255
|
+
// recordOutcome boundary; correctness + cost are the only signals.
|
|
256
|
+
const reward = banditRouter.computeReward({
|
|
257
|
+
solidify_pass: input.status === 'completed',
|
|
258
|
+
cost_usd: typeof input.costUsd === 'number' ? input.costUsd : 0,
|
|
259
|
+
wall_time_ms: 0,
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// D-04: best-effort write. Swallow ALL exceptions so a broken
|
|
263
|
+
// posterior file never breaks a session.
|
|
264
|
+
try {
|
|
265
|
+
if (delegate === DELEGATE_NONE) {
|
|
266
|
+
banditRouter.update({
|
|
267
|
+
agent: input.agent,
|
|
268
|
+
bin: input.bin,
|
|
269
|
+
tier: input.tier,
|
|
270
|
+
reward,
|
|
271
|
+
baseDir: input.baseDir,
|
|
272
|
+
posteriorPath: input.posteriorPath,
|
|
273
|
+
});
|
|
274
|
+
} else {
|
|
275
|
+
banditRouter.updateWithDelegate({
|
|
276
|
+
agent: input.agent,
|
|
277
|
+
bin: input.bin,
|
|
278
|
+
tier: input.tier,
|
|
279
|
+
delegate,
|
|
280
|
+
reward,
|
|
281
|
+
baseDir: input.baseDir,
|
|
282
|
+
posteriorPath: input.posteriorPath,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
} catch (err) {
|
|
286
|
+
// Live-tail breadcrumb opt-in via env var. Inner try/catch around
|
|
287
|
+
// the stderr write itself keeps the swallow guarantee even when
|
|
288
|
+
// stderr is closed/unavailable.
|
|
289
|
+
if (process.env.GDD_BANDIT_DEBUG === '1') {
|
|
290
|
+
try {
|
|
291
|
+
process.stderr.write(
|
|
292
|
+
'[bandit-integration] recordOutcome swallowed: ' +
|
|
293
|
+
(err && err.message ? err.message : String(err)) +
|
|
294
|
+
'\n',
|
|
295
|
+
);
|
|
296
|
+
} catch {
|
|
297
|
+
/* swallow */
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return undefined;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
module.exports = {
|
|
306
|
+
consultBandit,
|
|
307
|
+
recordOutcome,
|
|
308
|
+
DELEGATE_NONE,
|
|
309
|
+
};
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
// string with `shell: true`. We forward-slash the path so Windows shell
|
|
22
22
|
// resolves it correctly even when the path contains backslashes:
|
|
23
23
|
//
|
|
24
|
-
// // BROKEN on Windows for .cmd shims:
|
|
25
|
-
// spawn(
|
|
24
|
+
// // BROKEN on Windows for .cmd shims (absPath ends in .cmd):
|
|
25
|
+
// spawn(absPath, ['app-server'])
|
|
26
26
|
//
|
|
27
27
|
// // WORKS everywhere (.cmd via cmd.exe; non-.cmd via direct exec):
|
|
28
28
|
// const fwd = absPath.replace(/\\/g, '/');
|