claude-dev-env 7.7.1 → 8.1.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 (118) hide show
  1. package/.agents/agents/clean-coder.md +5 -6
  2. package/.agents/agents/issue-tracker.md +1 -1
  3. package/.agents/agents/pr-description-writer.md +1 -1
  4. package/.agents/agents/skill-writer-agent.md +1 -12
  5. package/.agents/agents/test_agent_frontmatter.py +2 -19
  6. package/.agents/skills/issue-tracker/SKILL.md +1 -1
  7. package/.agents/skills/orchestrator/SKILL.md +4 -5
  8. package/.agents/skills/pull-request/SKILL.md +8 -7
  9. package/.agents/skills/pull-request/reference/publication-tasks.md +1 -1
  10. package/.agents/skills/team-advisor/SKILL.md +2 -3
  11. package/_shared/advisor/advisor-protocol.md +0 -1
  12. package/_shared/advisor/reference/lifecycle.md +0 -1
  13. package/_shared/advisor/reference/warm-up.md +1 -2
  14. package/audit-rubrics/audit-categories.json +0 -4
  15. package/audit-rubrics/category_rubrics/category-e-dead-code.md +1 -1
  16. package/audit-rubrics/category_rubrics/category-j-code-rules-compliance.md +1 -2
  17. package/audit-rubrics/prompts/category-e-dead-code.md +5 -5
  18. package/audit-rubrics/prompts/category-j-code-rules-compliance.md +1 -24
  19. package/bin/install-constants.mjs +0 -5
  20. package/bin/install-pstack-plugin.mjs +192 -0
  21. package/bin/install-pstack-plugin.test.mjs +181 -0
  22. package/bin/install.agents-home.test.mjs +0 -4
  23. package/bin/install.codex-rules.test.mjs +6 -0
  24. package/bin/install.cursor-rules.test.mjs +3 -253
  25. package/bin/install.mjs +55 -160
  26. package/bin/install.test.mjs +110 -149
  27. package/bin/prune-retired-pstack-store.mjs +83 -0
  28. package/bin/prune-retired-pstack-store.test.mjs +105 -0
  29. package/bin/prune-session-continuity.mjs +57 -0
  30. package/bin/prune-session-continuity.test.mjs +70 -0
  31. package/bin/resolve-install-root.mjs +2 -0
  32. package/bin/resolve-install-root.test.mjs +69 -0
  33. package/docs/CODE_RULES.md +1 -1
  34. package/docs/agent-spawn-protocol.md +1 -1
  35. package/docs/codex-compatibility.md +4 -16
  36. package/docs/nas-ssh-invocation.md +1 -1
  37. package/docs/references/prose-style-enforcement.md +2 -2
  38. package/hooks/HOOK_SPECS_PROMPT_WORKFLOW.md +1 -3
  39. package/hooks/blocking/code_rules_constants_config.py +1 -108
  40. package/hooks/blocking/code_rules_enforcer.py +9 -5
  41. package/hooks/blocking/code_rules_test_layout.py +6 -7
  42. package/hooks/blocking/test_code_rules_constants_config.py +13 -0
  43. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +0 -1
  44. package/hooks/blocking/test_code_rules_enforcer_dispatch_wiring.py +15 -0
  45. package/hooks/blocking/test_code_rules_enforcer_split_constants_config.py +0 -60
  46. package/hooks/blocking/test_context_control_policy_files.py +0 -11
  47. package/hooks/hooks_constants/blocking_check_limits.py +1 -1
  48. package/hooks/hooks_constants/code_rules_enforcer_constants.py +0 -1
  49. package/hooks/hooks_constants/setup_project_paths_constants.py +1 -2
  50. package/hooks/session/test_untracked_repo_detector.py +1 -1
  51. package/hooks/validators/code_quality_checks.py +15 -2
  52. package/hooks/validators/test_code_quality_checks.py +22 -0
  53. package/package.json +1 -2
  54. package/rules/anti-corollary-tests.md +3 -1
  55. package/rules/archiving-agent-config.md +70 -0
  56. package/rules/asd-ste100-language.md +1 -1
  57. package/rules/ci-owns-the-gate.md +73 -0
  58. package/rules/cleanup-temp-files.md +1 -1
  59. package/rules/code-standards.md +3 -4
  60. package/rules/docstring-prose-matches-implementation.md +3 -3
  61. package/rules/explore-thoroughly.md +2 -2
  62. package/rules/failure-blast-radius.md +10 -9
  63. package/rules/git-workflow.md +27 -12
  64. package/rules/long-horizon-autonomy.md +3 -1
  65. package/rules/nas-ssh-invocation.md +1 -1
  66. package/rules/orphan-css-class.md +1 -1
  67. package/rules/paired-test-coverage.md +1 -1
  68. package/rules/research-mode.md +3 -1
  69. package/rules/retired-hook-prose.md +10 -2
  70. package/rules/testing.md +3 -3
  71. package/rules/verify-before-asking.md +4 -1
  72. package/rules/verify-runtime-state.md +2 -2
  73. package/rules/windows-filesystem-safe.md +1 -1
  74. package/rules/workflow-substitution-slots.md +1 -1
  75. package/scripts/Sync-RepoMain.ps1 +71 -6
  76. package/scripts/policy_lint/adapter_detectors.py +116 -10
  77. package/scripts/policy_lint/config/approved_test_pairs.py +12 -0
  78. package/scripts/policy_lint/config/constants.py +3 -0
  79. package/scripts/tests/Sync-RepoMain.Tests.ps1 +108 -0
  80. package/scripts/tests/test_codex_compat_materializer.py +3 -3
  81. package/scripts/tests/test_policy_lint_rules_validator_baseline.py +126 -0
  82. package/.agents/skills/session-continuity/AGENTS.md +0 -1
  83. package/.agents/skills/session-continuity/CLAUDE.md +0 -1
  84. package/.agents/skills/session-continuity/README.md +0 -223
  85. package/.agents/skills/session-continuity/SKILL.md +0 -125
  86. package/.agents/skills/session-continuity/continuity.mjs +0 -445
  87. package/.agents/skills/session-continuity/continuity.test.mjs +0 -381
  88. package/bin/install-session-continuity.mjs +0 -131
  89. package/bin/install-session-continuity.test.mjs +0 -106
  90. package/bin/install.pstack-plugin.test.mjs +0 -115
  91. package/bin/pstack-codex-discovery.test.mjs +0 -79
  92. package/bin/pstack-model-preferences.claude.json +0 -23
  93. package/bin/pstack-model-preferences.codex.json +0 -24
  94. package/bin/pstack.mjs +0 -392
  95. package/bin/pstack.test.mjs +0 -347
  96. package/bin/refresh-pstack-plugin-skills.test.mjs +0 -211
  97. package/bin/select-pstack-models.test.mjs +0 -776
  98. package/hooks/blocking/test_code_rules_enforcer_file_global_constants.py +0 -231
  99. package/rules/agent-spawn-protocol.md +0 -9
  100. package/rules/ask-user-question-required.md +0 -31
  101. package/rules/claims-as-quotes.md +0 -55
  102. package/rules/confirm-implementation-forks.md +0 -11
  103. package/rules/file-global-constants.md +0 -81
  104. package/rules/hedging-claims.md +0 -7
  105. package/rules/measurement-denominators.md +0 -39
  106. package/rules/prompt-workflow-context-controls.md +0 -19
  107. package/rules/pstack-host-mapping.md +0 -48
  108. package/rules/pstack-models.md +0 -142
  109. package/rules/vault-context.md +0 -5
  110. package/scripts/pstack-adapters/common.md +0 -17
  111. package/scripts/pstack-adapters/create-skill.md +0 -14
  112. package/scripts/pstack-adapters/host-claude.md +0 -7
  113. package/scripts/pstack-adapters/host-codex.md +0 -11
  114. package/scripts/pstack-adapters/host-cursor.md +0 -7
  115. package/scripts/pstack.lock.json +0 -11
  116. package/scripts/refresh_pstack_plugin_skills.mjs +0 -200
  117. package/scripts/select_pstack_models.mjs +0 -469
  118. /package/.agents/{skills → skills-archived}/source-command-logifix/SKILL.md +0 -0
@@ -1,469 +0,0 @@
1
- import { existsSync, readFileSync } from 'node:fs';
2
- import { dirname, join, resolve } from 'node:path';
3
- import { fileURLToPath } from 'node:url';
4
- import {
5
- defaultSubagentModelPolicyPath,
6
- isSelectorPairExcluded,
7
- loadSubagentModelPolicy,
8
- resolveSubagentModelRoute,
9
- } from './subagent_model_policy.mjs';
10
-
11
- const hostNamePattern = /^[a-z0-9][a-z0-9_-]*$/;
12
- const defaultSingleDelegationPanel = Object.freeze({
13
- agentCount: 1,
14
- requiresDistinctModels: false,
15
- });
16
-
17
- export const allPstackRoleRequirements = Object.freeze({
18
- 'feature, refactoring': 'reliable code execution',
19
- 'bug-fix': 'careful diagnosis and code execution',
20
- 'perf-issue': 'systems reasoning and performance analysis',
21
- 'hillclimb': 'strong iterative judgment',
22
- 'judgment and prose': 'strong judgment and clear prose',
23
- 'hardest tasks': 'highest available reasoning capability',
24
- 'how explorer': 'fast codebase exploration',
25
- 'how explainer': 'clear technical synthesis',
26
- 'how critics': 'independent technical criticism',
27
- 'why investigators': 'fast evidence gathering',
28
- 'why synthesizer': 'strong evidence synthesis',
29
- 'reflect tooling': 'reliable tool and workflow analysis',
30
- 'reflect judgment, divergent, synthesizer': 'strong judgment and divergent analysis',
31
- 'arena runners': 'independent solution development',
32
- 'arena cross-judge pool': 'independent comparative judgment',
33
- 'swarm workers': 'fast bounded task execution',
34
- 'architect runners': 'independent architecture design',
35
- 'interrogate reviewers': 'independent adversarial review',
36
- });
37
-
38
- export const defaultPanelByPstackRole = Object.freeze({
39
- 'how critics': Object.freeze({ agentCount: 3, requiresDistinctModels: false }),
40
- 'arena runners': Object.freeze({ agentCount: 3, requiresDistinctModels: true }),
41
- 'swarm workers': defaultSingleDelegationPanel,
42
- 'architect runners': Object.freeze({ agentCount: 2, requiresDistinctModels: true }),
43
- 'interrogate reviewers': Object.freeze({ agentCount: 3, requiresDistinctModels: true }),
44
- });
45
-
46
- export function selectPstackDelegation(input) {
47
- validateSelectionInput(input);
48
- const panel = resolvePanel(input);
49
- validateDelegationIndex(input.delegationIndex, panel.agentCount);
50
- const hostPreferences = readHostPreferences(input);
51
- const routingInput = (input.requestedEffort === undefined || input.requestedEffort === null)
52
- && hostPreferences.defaultEffort !== undefined
53
- ? { ...input, requestedEffort: hostPreferences.defaultEffort }
54
- : input;
55
- const selectionPolicy = loadSelectionPolicy(input);
56
- if (selectionPolicy.policy) validateModelIdInventories(input, selectionPolicy.policy);
57
- const { allCandidates, routingFailures } = collectCandidates(
58
- routingInput,
59
- hostPreferences.modelsByRole[routingInput.role] ?? [],
60
- selectionPolicy,
61
- );
62
- const allSelectedCandidates = selectPanelCandidates(allCandidates, panel);
63
- return buildSelection(routingInput, panel, allSelectedCandidates, routingFailures);
64
- }
65
-
66
- function validateSelectionInput(input) {
67
- if (!input || typeof input !== 'object' || Array.isArray(input)) {
68
- throw new Error('selection input must be an object');
69
- }
70
- validateHost(input.host, input.inventoryHost);
71
- validateDelegationIndex(input.delegationIndex);
72
- requireStringArray(input.availableModelIds, 'available model ids');
73
- requireStringArray(input.confirmedSuitableModelIds, 'confirmed suitable model ids');
74
- if (input.requestedEffort !== undefined && input.requestedEffort !== null
75
- && (typeof input.requestedEffort !== 'string' || input.requestedEffort.trim() === '')) {
76
- throw new Error('requested effort must be a non-empty string');
77
- }
78
- validateParentFallback(input.parentFallback);
79
- validateConfirmedModels(input);
80
- if (typeof input.role !== 'string' || !Object.hasOwn(allPstackRoleRequirements, input.role)) {
81
- throw new Error('role must name a portable pstack role');
82
- }
83
- }
84
-
85
- function validateHost(host, inventoryHost) {
86
- if (typeof host !== 'string' || !hostNamePattern.test(host)) {
87
- throw new Error('host must be a lowercase host identifier');
88
- }
89
- if (inventoryHost !== host) {
90
- throw new Error('inventory host must match selected host');
91
- }
92
- }
93
-
94
- function validateDelegationIndex(delegationIndex, agentCount) {
95
- if (!Number.isInteger(delegationIndex) || delegationIndex < 0) {
96
- throw new Error('delegation index must be a non-negative integer');
97
- }
98
- if (agentCount !== undefined && delegationIndex >= agentCount) {
99
- throw new Error('delegation index must identify an agent in the panel');
100
- }
101
- }
102
-
103
- function requireStringArray(allEntries, label) {
104
- if (!Array.isArray(allEntries) || allEntries.some(
105
- eachEntry => typeof eachEntry !== 'string' || eachEntry === '',
106
- )) {
107
- throw new Error(label + ' must be an array of non-empty strings');
108
- }
109
- }
110
-
111
- function validateParentFallback(parentFallback) {
112
- const hasBooleanFlags = parentFallback
113
- && typeof parentFallback === 'object'
114
- && typeof parentFallback.isAllowed === 'boolean'
115
- && typeof parentFallback.hasMaterialCapabilityLoss === 'boolean';
116
- if (!hasBooleanFlags) {
117
- throw new Error('parent fallback flags must be booleans');
118
- }
119
- }
120
-
121
- function validateConfirmedModels(input) {
122
- const allAvailableModelIds = new Set(input.availableModelIds);
123
- if (input.confirmedSuitableModelIds.some(
124
- eachModelId => !allAvailableModelIds.has(eachModelId),
125
- )) {
126
- throw new Error('confirmed suitable models must be available');
127
- }
128
- }
129
-
130
- function validateModelIdInventories(input, policy) {
131
- const inventories = [
132
- ['confirmed suitable model ids', input.confirmedSuitableModelIds],
133
- ['available model ids', input.availableModelIds],
134
- ];
135
- for (const [label, modelIds] of inventories) {
136
- if (modelIds.some(eachModelId => policy.inheritanceAliases.has(eachModelId.trim().toLowerCase()))) {
137
- throw new Error(label + ' must contain native model ids');
138
- }
139
- }
140
- }
141
-
142
- function resolvePanel(input) {
143
- const panel = input.panel
144
- ?? defaultPanelByPstackRole[input.role]
145
- ?? defaultSingleDelegationPanel;
146
- const hasValidShape = panel
147
- && typeof panel === 'object'
148
- && Number.isInteger(panel.agentCount)
149
- && panel.agentCount > 0
150
- && typeof panel.requiresDistinctModels === 'boolean';
151
- if (!hasValidShape) {
152
- throw new Error('panel requires a positive agent count and a diversity boolean');
153
- }
154
- return {
155
- agentCount: panel.agentCount,
156
- requiresDistinctModels: panel.requiresDistinctModels,
157
- };
158
- }
159
-
160
- function readHostPreferences(input) {
161
- const preferencesDirectory = input.preferencesDirectory
162
- ?? resolve(dirname(fileURLToPath(import.meta.url)), '..', 'rules');
163
- const preferencesPath = join(
164
- preferencesDirectory,
165
- 'pstack-model-preferences.' + input.host + '.json',
166
- );
167
- if (!existsSync(preferencesPath)) {
168
- return { modelsByRole: {}, defaultEffort: undefined };
169
- }
170
- const preferences = JSON.parse(readFileSync(preferencesPath, 'utf8'));
171
- if (preferences.host !== input.host) {
172
- throw new Error('preference host must match selected host');
173
- }
174
- return {
175
- modelsByRole: preferences.modelsByRole ?? {},
176
- defaultEffort: preferences.defaultEffort,
177
- };
178
- }
179
-
180
- function loadSelectionPolicy(input) {
181
- try {
182
- return { policy: loadSubagentModelPolicy(resolvePolicyPath(input)), error: null };
183
- } catch (error) {
184
- return {
185
- policy: null,
186
- error: error instanceof Error ? error.message : String(error),
187
- };
188
- }
189
- }
190
-
191
- function collectCandidates(input, allPreferredModelEntries, selectionPolicy) {
192
- validatePreferenceEntries(allPreferredModelEntries, 'role preferences');
193
- if (!selectionPolicy.policy) {
194
- return { allCandidates: [], routingFailures: [selectionPolicy.error] };
195
- }
196
- const routingFailures = [];
197
- const allPreferredCandidates = allPreferredModelEntries
198
- .map(eachEntry => preferenceCandidate(
199
- eachEntry,
200
- input,
201
- 'host-preference',
202
- routingFailures,
203
- selectionPolicy.policy,
204
- ))
205
- .filter(Boolean);
206
- const allAlternativeCandidates = input.confirmedSuitableModelIds
207
- .map(eachModelId => preferenceCandidate(
208
- eachModelId,
209
- input,
210
- 'confirmed-host-alternative',
211
- routingFailures,
212
- selectionPolicy.policy,
213
- ))
214
- .filter(Boolean);
215
- const allCandidates = deduplicateCandidates([
216
- ...allPreferredCandidates,
217
- ...allAlternativeCandidates,
218
- ]);
219
- const allCandidatesWithFallback = allCandidates.length === 0 && routingFailures.length === 0
220
- && input.parentFallback.isAllowed
221
- ? [parentCandidate()]
222
- : allCandidates;
223
- return {
224
- allCandidates: orderCrossJudgeCandidates(input, allCandidatesWithFallback),
225
- routingFailures,
226
- };
227
- }
228
-
229
- function preferenceCandidate(
230
- preferenceEntry,
231
- input,
232
- source,
233
- routingFailures,
234
- policy,
235
- ) {
236
- const resolvedEntry = resolvePreferenceEntry(preferenceEntry, input, policy);
237
- if (resolvedEntry.error) {
238
- routingFailures.push(resolvedEntry.error);
239
- return null;
240
- }
241
- const { modelId, effort } = resolvedEntry;
242
- if (isSelectorPairExcluded(modelId, effort, policy)) return null;
243
- if (policy.inheritanceAliases.has(modelId.trim().toLowerCase())) {
244
- if (effort !== null) {
245
- routingFailures.push('parent inheritance cannot set an effort');
246
- return null;
247
- }
248
- return input.parentFallback.isAllowed ? parentCandidate() : null;
249
- }
250
- if (input.host !== 'codex') {
251
- return input.availableModelIds.includes(modelId)
252
- ? modelCandidate(modelId, source)
253
- : null;
254
- }
255
- const routeRequest = { model: modelId, agent_type: input.routingRole ?? 'worker' };
256
- if (effort !== null) routeRequest.reasoning_effort = effort;
257
- const route = resolveSubagentModelRoute(
258
- routeRequest,
259
- {
260
- policyPath: resolvePolicyPath(input),
261
- policy,
262
- availableModelIds: input.availableModelIds,
263
- allowMissingEffort: effort === null,
264
- trustedSessionMetadata: input.trustedSessionMetadata,
265
- },
266
- );
267
- if (route.status === 'blocked') {
268
- routingFailures.push(route.diagnostic);
269
- return null;
270
- }
271
- if (isSelectorPairExcluded(route.selected.model, route.selected.effort, policy)) return null;
272
- if (route.status === 'inherited') return input.parentFallback.isAllowed ? parentCandidate() : null;
273
- return modelCandidate(route.selected.model, source, route);
274
- }
275
-
276
- function modelCandidate(modelId, source, route = null) {
277
- return {
278
- kind: 'model',
279
- modelId,
280
- source,
281
- requestedPair: route?.requested ?? null,
282
- selectedPair: route?.selected ?? null,
283
- effort: route?.selected?.effort ?? null,
284
- };
285
- }
286
-
287
- function parentCandidate() {
288
- return {
289
- kind: 'parent',
290
- modelId: null,
291
- source: 'parent-inheritance',
292
- requestedPair: null,
293
- selectedPair: null,
294
- effort: null,
295
- };
296
- }
297
-
298
- function resolvePreferenceEntry(preferenceEntry, input, policy = null) {
299
- const preferenceModel = typeof preferenceEntry === 'string'
300
- ? preferenceEntry
301
- : preferenceEntry?.modelId ?? preferenceEntry?.model;
302
- const isInheritanceAlias = typeof preferenceModel === 'string'
303
- && policy?.inheritanceAliases.has(preferenceModel.trim().toLowerCase());
304
- const fallbackEffort = isInheritanceAlias ? null : input.requestedEffort ?? null;
305
- if (typeof preferenceEntry === 'string' && preferenceEntry !== '') {
306
- return { modelId: preferenceEntry, effort: fallbackEffort, error: null };
307
- }
308
- if (!preferenceEntry || typeof preferenceEntry !== 'object' || Array.isArray(preferenceEntry)) {
309
- return { modelId: null, effort: null, error: 'role preferences must contain model ids or pair objects' };
310
- }
311
- const modelId = preferenceEntry.modelId ?? preferenceEntry.model;
312
- const effortFields = ['reasoning_effort', 'effort'].filter(
313
- field => Object.hasOwn(preferenceEntry, field),
314
- );
315
- if (effortFields.length > 1) {
316
- return { modelId: null, effort: null, error: 'role preference effort fields are ambiguous' };
317
- }
318
- const effort = preferenceEntry[effortFields[0]] ?? fallbackEffort;
319
- if (typeof modelId !== 'string' || modelId === '') {
320
- return { modelId: null, effort: null, error: 'role preference model must be a non-empty string' };
321
- }
322
- return { modelId, effort, error: null };
323
- }
324
-
325
- function validatePreferenceEntries(allEntries, label) {
326
- if (!Array.isArray(allEntries)) throw new Error(label + ' must be an array');
327
- allEntries.forEach(eachEntry => {
328
- const resolvedEntry = resolvePreferenceEntry(eachEntry, {});
329
- if (resolvedEntry.error) throw new Error(resolvedEntry.error);
330
- });
331
- }
332
-
333
- function resolvePolicyPath(input) {
334
- if (typeof input.policyPath === 'string' && input.policyPath.trim() !== '') {
335
- return input.policyPath;
336
- }
337
- const allPolicyPaths = [
338
- input.preferencesDirectory
339
- ? join(input.preferencesDirectory, 'subagent-model-policy.json')
340
- : null,
341
- defaultSubagentModelPolicyPath(),
342
- ].filter(Boolean);
343
- return allPolicyPaths.find(eachPath => existsSync(eachPath)) ?? allPolicyPaths[0];
344
- }
345
-
346
- function deduplicateCandidates(allCandidates) {
347
- const seenCandidateKeys = new Set();
348
- return allCandidates.filter(eachCandidate => {
349
- const candidateKey = eachCandidate.kind + ':' + (eachCandidate.modelId ?? '');
350
- if (seenCandidateKeys.has(candidateKey)) {
351
- return false;
352
- }
353
- seenCandidateKeys.add(candidateKey);
354
- return true;
355
- });
356
- }
357
-
358
- function orderCrossJudgeCandidates(input, allCandidates) {
359
- if (input.role !== 'arena cross-judge pool' || !input.parentModelId) {
360
- return allCandidates;
361
- }
362
- const allOtherCandidates = allCandidates.filter(
363
- eachCandidate => eachCandidate.kind === 'model'
364
- && eachCandidate.modelId !== input.parentModelId,
365
- );
366
- const allParentCandidates = allCandidates.filter(
367
- eachCandidate => eachCandidate.kind === 'parent'
368
- || eachCandidate.modelId === input.parentModelId,
369
- );
370
- return [...allOtherCandidates, ...allParentCandidates];
371
- }
372
-
373
- function selectPanelCandidates(allCandidates, panel) {
374
- if (panel.requiresDistinctModels) {
375
- return allCandidates
376
- .filter(eachCandidate => eachCandidate.kind === 'model')
377
- .slice(0, panel.agentCount);
378
- }
379
- if (allCandidates.length === 0) {
380
- return [];
381
- }
382
- return Array.from(
383
- { length: panel.agentCount },
384
- (_, index) => allCandidates[index % allCandidates.length],
385
- );
386
- }
387
-
388
- function buildSelection(input, requestedPanel, allSelectedCandidates, routingFailures) {
389
- const panel = reportPanel(requestedPanel, allSelectedCandidates);
390
- const failure = selectionFailure(input, panel, allSelectedCandidates, routingFailures);
391
- const selectedCandidate = allSelectedCandidates[input.delegationIndex];
392
- return {
393
- selectedHost: input.host,
394
- role: input.role,
395
- requiredCapability: allPstackRoleRequirements[input.role],
396
- availabilityValidated: true,
397
- canDelegate: failure === null,
398
- requiresUserChoice: failure !== null,
399
- failure,
400
- selectionSource: selectedCandidate?.source ?? null,
401
- panel,
402
- routingDiagnostics: routingFailures,
403
- nativeSpawnArguments: nativeSpawnArguments(selectedCandidate, failure),
404
- omitNativeModelArgument: selectedCandidate?.kind === 'parent',
405
- };
406
- }
407
-
408
- function reportPanel(requestedPanel, allSelectedCandidates) {
409
- const allSelectedModelIds = allSelectedCandidates.map(
410
- eachCandidate => eachCandidate.modelId,
411
- );
412
- const distinctModelCount = new Set(allSelectedModelIds.filter(Boolean)).size;
413
- return {
414
- agentCount: requestedPanel.agentCount,
415
- requiresDistinctModels: requestedPanel.requiresDistinctModels,
416
- selectedModelIds: allSelectedModelIds,
417
- requestedModelPairs: allSelectedCandidates.map(eachCandidate => eachCandidate.requestedPair),
418
- selectedModelPairs: allSelectedCandidates.map(eachCandidate => eachCandidate.selectedPair),
419
- selectionSources: allSelectedCandidates.map(eachCandidate => eachCandidate.source),
420
- isCrossModelDiverse: requestedPanel.agentCount > 1
421
- && distinctModelCount === requestedPanel.agentCount,
422
- };
423
- }
424
-
425
- function selectionFailure(input, panel, allSelectedCandidates, routingFailures) {
426
- if (routingFailures.length > 0) {
427
- return 'model-routing-failed: ' + routingFailures[0];
428
- }
429
- if (panel.requiresDistinctModels && !panel.isCrossModelDiverse) {
430
- return 'distinct-models-unavailable';
431
- }
432
- if (allSelectedCandidates.length < panel.agentCount) {
433
- return 'no-supported-model';
434
- }
435
- if (allSelectedCandidates.some(eachCandidate => eachCandidate.kind === 'parent')
436
- && input.parentFallback.hasMaterialCapabilityLoss) {
437
- return 'parent-capability-loss';
438
- }
439
- return null;
440
- }
441
-
442
- function nativeSpawnArguments(selectedCandidate, failure) {
443
- if (failure || !selectedCandidate || selectedCandidate.kind === 'parent') {
444
- return {};
445
- }
446
- const spawnArguments = { model: selectedCandidate.modelId };
447
- if (selectedCandidate.effort !== null) {
448
- spawnArguments.reasoning_effort = selectedCandidate.effort;
449
- }
450
- return spawnArguments;
451
- }
452
-
453
- async function readSelectionInput() {
454
- let inputText = '';
455
- for await (const eachChunk of process.stdin) {
456
- inputText += eachChunk;
457
- }
458
- return JSON.parse(inputText);
459
- }
460
-
461
- function isEntryPoint() {
462
- return process.argv[1]
463
- && resolve(process.argv[1]) === fileURLToPath(import.meta.url);
464
- }
465
-
466
- if (isEntryPoint()) {
467
- const selectionInput = await readSelectionInput();
468
- console.log(JSON.stringify(selectPstackDelegation(selectionInput)));
469
- }