@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.
@@ -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('C:\\Users\\me\\AppData\\Local\\codex.cmd', ['app-server'])
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, '/');