claude-dev-env 7.7.0 → 8.0.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 (109) 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/e-code-review/reference/fix.md +4 -2
  7. package/.agents/skills/e-code-review/reference/loop.md +4 -2
  8. package/.agents/skills/issue-tracker/SKILL.md +1 -1
  9. package/.agents/skills/orchestrator/SKILL.md +4 -5
  10. package/.agents/skills/pull-request/SKILL.md +8 -7
  11. package/.agents/skills/pull-request/reference/publication-tasks.md +1 -1
  12. package/.agents/skills/team-advisor/SKILL.md +2 -3
  13. package/_shared/advisor/advisor-protocol.md +0 -1
  14. package/_shared/advisor/reference/lifecycle.md +0 -1
  15. package/_shared/advisor/reference/warm-up.md +1 -2
  16. package/audit-rubrics/prompts/category-e-dead-code.md +3 -3
  17. package/bin/install-constants.mjs +0 -5
  18. package/bin/install-pstack-plugin.mjs +192 -0
  19. package/bin/install-pstack-plugin.test.mjs +181 -0
  20. package/bin/install.agents-home.test.mjs +0 -4
  21. package/bin/install.codex-rules.test.mjs +6 -0
  22. package/bin/install.cursor-rules.test.mjs +3 -253
  23. package/bin/install.mjs +55 -160
  24. package/bin/install.test.mjs +110 -149
  25. package/bin/prune-retired-pstack-store.mjs +83 -0
  26. package/bin/prune-retired-pstack-store.test.mjs +105 -0
  27. package/bin/prune-session-continuity.mjs +57 -0
  28. package/bin/prune-session-continuity.test.mjs +70 -0
  29. package/bin/resolve-install-root.mjs +2 -0
  30. package/bin/resolve-install-root.test.mjs +69 -0
  31. package/bin/subagent-model-policy.test.mjs +56 -19
  32. package/docs/CODE_RULES.md +1 -1
  33. package/docs/agent-spawn-protocol.md +1 -1
  34. package/docs/codex-compatibility.md +4 -16
  35. package/docs/nas-ssh-invocation.md +1 -1
  36. package/docs/references/prose-style-enforcement.md +2 -2
  37. package/docs/subagent-model-routing.md +21 -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/routing/subagent_model_routing.test.mjs +65 -28
  51. package/hooks/session/test_untracked_repo_detector.py +1 -1
  52. package/hooks/validators/code_quality_checks.py +15 -2
  53. package/hooks/validators/test_code_quality_checks.py +22 -0
  54. package/package.json +1 -2
  55. package/rules/ci-owns-the-gate.md +58 -0
  56. package/rules/code-standards.md +1 -2
  57. package/rules/docstring-prose-matches-implementation.md +2 -2
  58. package/rules/failure-blast-radius.md +1 -1
  59. package/rules/git-workflow.md +23 -11
  60. package/rules/orphan-css-class.md +1 -1
  61. package/rules/paired-test-coverage.md +1 -1
  62. package/rules/retired-hook-prose.md +1 -1
  63. package/rules/subagent-model-policy.json +1 -1
  64. package/rules/verify-before-asking.md +1 -1
  65. package/rules/windows-filesystem-safe.md +1 -1
  66. package/rules/workflow-substitution-slots.md +1 -1
  67. package/scripts/Sync-RepoMain.ps1 +71 -6
  68. package/scripts/policy_lint/adapter_detectors.py +116 -10
  69. package/scripts/policy_lint/config/approved_test_pairs.py +12 -0
  70. package/scripts/policy_lint/config/constants.py +3 -0
  71. package/scripts/tests/Sync-RepoMain.Tests.ps1 +108 -0
  72. package/scripts/tests/test_policy_lint_rules_validator_baseline.py +126 -0
  73. package/.agents/skills/session-continuity/AGENTS.md +0 -1
  74. package/.agents/skills/session-continuity/CLAUDE.md +0 -1
  75. package/.agents/skills/session-continuity/README.md +0 -223
  76. package/.agents/skills/session-continuity/SKILL.md +0 -125
  77. package/.agents/skills/session-continuity/continuity.mjs +0 -445
  78. package/.agents/skills/session-continuity/continuity.test.mjs +0 -381
  79. package/bin/install-session-continuity.mjs +0 -131
  80. package/bin/install-session-continuity.test.mjs +0 -106
  81. package/bin/install.pstack-plugin.test.mjs +0 -115
  82. package/bin/pstack-codex-discovery.test.mjs +0 -79
  83. package/bin/pstack-model-preferences.claude.json +0 -23
  84. package/bin/pstack-model-preferences.codex.json +0 -24
  85. package/bin/pstack.mjs +0 -392
  86. package/bin/pstack.test.mjs +0 -347
  87. package/bin/refresh-pstack-plugin-skills.test.mjs +0 -211
  88. package/bin/select-pstack-models.test.mjs +0 -727
  89. package/hooks/blocking/test_code_rules_enforcer_file_global_constants.py +0 -231
  90. package/rules/agent-spawn-protocol.md +0 -9
  91. package/rules/ask-user-question-required.md +0 -31
  92. package/rules/claims-as-quotes.md +0 -55
  93. package/rules/confirm-implementation-forks.md +0 -11
  94. package/rules/file-global-constants.md +0 -81
  95. package/rules/hedging-claims.md +0 -7
  96. package/rules/measurement-denominators.md +0 -39
  97. package/rules/prompt-workflow-context-controls.md +0 -19
  98. package/rules/pstack-host-mapping.md +0 -48
  99. package/rules/pstack-models.md +0 -142
  100. package/rules/vault-context.md +0 -5
  101. package/scripts/pstack-adapters/common.md +0 -17
  102. package/scripts/pstack-adapters/create-skill.md +0 -14
  103. package/scripts/pstack-adapters/host-claude.md +0 -7
  104. package/scripts/pstack-adapters/host-codex.md +0 -11
  105. package/scripts/pstack-adapters/host-cursor.md +0 -7
  106. package/scripts/pstack.lock.json +0 -11
  107. package/scripts/refresh_pstack_plugin_skills.mjs +0 -200
  108. package/scripts/select_pstack_models.mjs +0 -469
  109. /package/.agents/{skills → skills-archived}/source-command-logifix/SKILL.md +0 -0
@@ -1,727 +0,0 @@
1
- import { test } from 'node:test';
2
- import { strict as assert } from 'node:assert';
3
- import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
4
- import { tmpdir } from 'node:os';
5
- import { join } from 'node:path';
6
- import { defaultSubagentModelPolicyPath } from '../scripts/subagent_model_policy.mjs';
7
- import {
8
- allPstackRoleRequirements,
9
- selectPstackDelegation,
10
- } from '../scripts/select_pstack_models.mjs';
11
-
12
- const ALL_PSTACK_ROLES = [
13
- 'feature, refactoring',
14
- 'bug-fix',
15
- 'perf-issue',
16
- 'hillclimb',
17
- 'judgment and prose',
18
- 'hardest tasks',
19
- 'how explorer',
20
- 'how explainer',
21
- 'how critics',
22
- 'why investigators',
23
- 'why synthesizer',
24
- 'reflect tooling',
25
- 'reflect judgment, divergent, synthesizer',
26
- 'arena runners',
27
- 'arena cross-judge pool',
28
- 'swarm workers',
29
- 'architect runners',
30
- 'interrogate reviewers',
31
- ];
32
-
33
- function writeHostPreferences(preferencesDirectory, host, modelsByRole, defaultEffort) {
34
- mkdirSync(preferencesDirectory, { recursive: true });
35
- const preferences = { host, modelsByRole };
36
- if (defaultEffort !== undefined) preferences.defaultEffort = defaultEffort;
37
- writeFileSync(
38
- join(preferencesDirectory, 'pstack-model-preferences.' + host + '.json'),
39
- JSON.stringify(preferences),
40
- );
41
- }
42
-
43
- function writePolicyWithModels(preferencesDirectory, modelEntries) {
44
- const policy = JSON.parse(readFileSync(defaultSubagentModelPolicyPath(), 'utf8'));
45
- for (const [name, id] of Object.entries(modelEntries)) {
46
- policy.models[name] = { id, aliases: [] };
47
- policy.approvedPairs.push({ model: name, effort: 'medium' });
48
- }
49
- const policyPath = join(preferencesDirectory, 'subagent-model-policy.json');
50
- writeFileSync(policyPath, JSON.stringify(policy));
51
- return policyPath;
52
- }
53
-
54
- function selectWithPreferences(preferencesDirectory, overrides) {
55
- return selectPstackDelegation({
56
- host: 'codex',
57
- inventoryHost: 'codex',
58
- role: 'feature, refactoring',
59
- delegationIndex: 0,
60
- availableModelIds: ['gpt-5.6-sol'],
61
- confirmedSuitableModelIds: [],
62
- parentFallback: {
63
- isAllowed: false,
64
- hasMaterialCapabilityLoss: false,
65
- },
66
- requestedEffort: 'medium',
67
- preferencesDirectory,
68
- ...overrides,
69
- });
70
- }
71
-
72
- test('portable policy represents every pstack role without model ids', () => {
73
- assert.deepEqual(Object.keys(allPstackRoleRequirements), ALL_PSTACK_ROLES);
74
- const serializedRequirements = JSON.stringify(allPstackRoleRequirements);
75
- assert.doesNotMatch(serializedRequirements, /gpt-|claude-|grok-/i);
76
- });
77
-
78
- test('Codex preferences skip an excluded pair in a panel', () => {
79
- const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-codex-'));
80
- try {
81
- writeHostPreferences(preferencesDirectory, 'codex', {
82
- 'how critics': ['gpt-5.6-terra', 'gpt-5.6-sol', 'gpt-6-astra'],
83
- });
84
- const selection = selectWithPreferences(preferencesDirectory, {
85
- role: 'how critics',
86
- delegationIndex: 1,
87
- availableModelIds: [
88
- 'gpt-5.6-sol',
89
- 'gpt-6-astra',
90
- 'gpt-5.6-terra',
91
- 'gpt-5.6-luna',
92
- ],
93
- });
94
- assert.equal(selection.canDelegate, true);
95
- assert.equal(selection.selectedHost, 'codex');
96
- assert.equal(selection.selectionSource, 'host-preference');
97
- assert.deepEqual(selection.panel.selectedModelIds, [
98
- 'gpt-5.6-luna',
99
- 'gpt-6-astra',
100
- 'gpt-5.6-luna',
101
- ]);
102
- assert.equal(selection.panel.isCrossModelDiverse, false);
103
- assert.deepEqual(selection.nativeSpawnArguments, {
104
- model: 'gpt-6-astra',
105
- reasoning_effort: 'medium',
106
- });
107
- assert.equal(selection.omitNativeModelArgument, false);
108
- assert.equal(selection.availabilityValidated, true);
109
- } finally {
110
- rmSync(preferencesDirectory, { recursive: true, force: true });
111
- }
112
- });
113
-
114
- test('a foreign host reads only its own saved preferences', () => {
115
- const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-host-'));
116
- try {
117
- writeHostPreferences(preferencesDirectory, 'codex', {
118
- 'feature, refactoring': ['gpt-5.6-sol'],
119
- });
120
- writeHostPreferences(preferencesDirectory, 'claude', {
121
- 'feature, refactoring': ['confirmed-claude-model'],
122
- });
123
- const selection = selectWithPreferences(preferencesDirectory, {
124
- host: 'claude',
125
- inventoryHost: 'claude',
126
- availableModelIds: ['confirmed-claude-model', 'gpt-5.6-sol'],
127
- });
128
- assert.equal(selection.selectedHost, 'claude');
129
- assert.deepEqual(selection.nativeSpawnArguments, {
130
- model: 'confirmed-claude-model',
131
- });
132
- } finally {
133
- rmSync(preferencesDirectory, { recursive: true, force: true });
134
- }
135
- });
136
-
137
- test('an unavailable preferred model stops before a confirmed host alternative', () => {
138
- const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-alternative-'));
139
- try {
140
- writeHostPreferences(preferencesDirectory, 'codex', {
141
- 'feature, refactoring': ['gpt-5.6-terra'],
142
- });
143
- const policyPath = writePolicyWithModels(preferencesDirectory, {
144
- current: 'confirmed-current-model',
145
- });
146
- const selection = selectWithPreferences(preferencesDirectory, {
147
- availableModelIds: ['confirmed-current-model'],
148
- confirmedSuitableModelIds: ['confirmed-current-model'],
149
- policyPath,
150
- });
151
- assert.equal(selection.canDelegate, false);
152
- assert.match(selection.failure, /model-routing-failed: replacement model is unavailable/);
153
- assert.deepEqual(selection.nativeSpawnArguments, {});
154
- } finally {
155
- rmSync(preferencesDirectory, { recursive: true, force: true });
156
- }
157
- });
158
-
159
- test('the arena cross-judge pool selects one model other than the parent', () => {
160
- const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-judge-'));
161
- try {
162
- writeHostPreferences(preferencesDirectory, 'codex', {
163
- 'arena cross-judge pool': ['gpt-5.6-sol', 'gpt-6-astra'],
164
- });
165
- const selection = selectWithPreferences(preferencesDirectory, {
166
- role: 'arena cross-judge pool',
167
- parentModelId: 'gpt-5.6-sol',
168
- availableModelIds: ['gpt-5.6-sol', 'gpt-6-astra'],
169
- });
170
- assert.equal(selection.panel.agentCount, 1);
171
- assert.deepEqual(selection.nativeSpawnArguments, {
172
- model: 'gpt-6-astra',
173
- reasoning_effort: 'medium',
174
- });
175
- } finally {
176
- rmSync(preferencesDirectory, { recursive: true, force: true });
177
- }
178
- });
179
- test('the arena cross-judge prefers a real alternative over parent inheritance', () => {
180
- const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-judge-parent-'));
181
- try {
182
- writeHostPreferences(preferencesDirectory, 'codex', {
183
- 'arena cross-judge pool': ['auto', 'confirmed-other-model'],
184
- });
185
- const policyPath = writePolicyWithModels(preferencesDirectory, {
186
- other: 'confirmed-other-model',
187
- });
188
- const selection = selectWithPreferences(preferencesDirectory, {
189
- role: 'arena cross-judge pool',
190
- parentModelId: 'current-parent-model',
191
- availableModelIds: ['confirmed-other-model'],
192
- parentFallback: {
193
- isAllowed: true,
194
- hasMaterialCapabilityLoss: false,
195
- },
196
- policyPath,
197
- });
198
- assert.deepEqual(selection.nativeSpawnArguments, {
199
- model: 'confirmed-other-model',
200
- reasoning_effort: 'medium',
201
- });
202
- assert.equal(selection.omitNativeModelArgument, false);
203
- } finally {
204
- rmSync(preferencesDirectory, { recursive: true, force: true });
205
- }
206
- });
207
-
208
- test('a same-model swarm reports its actual panel composition', () => {
209
- const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-swarm-'));
210
- try {
211
- writeHostPreferences(preferencesDirectory, 'codex', {
212
- 'swarm workers': ['gpt-5.6-luna'],
213
- });
214
- const selection = selectWithPreferences(preferencesDirectory, {
215
- role: 'swarm workers',
216
- delegationIndex: 2,
217
- panel: {
218
- agentCount: 4,
219
- requiresDistinctModels: false,
220
- },
221
- availableModelIds: ['gpt-5.6-luna'],
222
- });
223
- assert.equal(selection.canDelegate, true);
224
- assert.equal(selection.panel.agentCount, 4);
225
- assert.deepEqual(selection.panel.selectedModelIds, [
226
- 'gpt-5.6-luna',
227
- 'gpt-5.6-luna',
228
- 'gpt-5.6-luna',
229
- 'gpt-5.6-luna',
230
- ]);
231
- assert.equal(selection.panel.isCrossModelDiverse, false);
232
- assert.deepEqual(selection.nativeSpawnArguments, {
233
- model: 'gpt-5.6-luna',
234
- reasoning_effort: 'high',
235
- });
236
- } finally {
237
- rmSync(preferencesDirectory, { recursive: true, force: true });
238
- }
239
- });
240
-
241
- test('a diversity-required panel fails without enough distinct models', () => {
242
- const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-diversity-'));
243
- try {
244
- writeHostPreferences(preferencesDirectory, 'codex', {
245
- 'interrogate reviewers': ['gpt-5.6-terra', 'auto', 'gpt-5.6-sol'],
246
- });
247
- const selection = selectWithPreferences(preferencesDirectory, {
248
- role: 'interrogate reviewers',
249
- availableModelIds: ['gpt-5.6-terra', 'gpt-5.6-sol'],
250
- });
251
- assert.equal(selection.canDelegate, false);
252
- assert.equal(selection.requiresUserChoice, true);
253
- assert.match(selection.failure, /model-routing-failed:/);
254
- assert.equal(selection.panel.isCrossModelDiverse, false);
255
- assert.deepEqual(selection.nativeSpawnArguments, {});
256
- } finally {
257
- rmSync(preferencesDirectory, { recursive: true, force: true });
258
- }
259
- });
260
-
261
- test('parent inheritance omits the native model argument', () => {
262
- const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-parent-'));
263
- try {
264
- writeHostPreferences(preferencesDirectory, 'codex', {
265
- 'feature, refactoring': ['inherit-parent'],
266
- });
267
- const selection = selectWithPreferences(preferencesDirectory, {
268
- availableModelIds: [],
269
- parentFallback: {
270
- isAllowed: true,
271
- hasMaterialCapabilityLoss: false,
272
- },
273
- });
274
- assert.equal(selection.canDelegate, true);
275
- assert.equal(selection.selectionSource, 'parent-inheritance');
276
- assert.deepEqual(selection.nativeSpawnArguments, {});
277
- assert.equal(selection.omitNativeModelArgument, true);
278
- } finally {
279
- rmSync(preferencesDirectory, { recursive: true, force: true });
280
- }
281
- });
282
-
283
- test('parent inheritance rejects an explicit preference effort', () => {
284
- const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-parent-effort-'));
285
- try {
286
- writeHostPreferences(preferencesDirectory, 'codex', {
287
- 'feature, refactoring': [{ model: 'auto', effort: 'medium' }],
288
- });
289
- const selection = selectWithPreferences(preferencesDirectory, {
290
- availableModelIds: [],
291
- parentFallback: {
292
- isAllowed: true,
293
- hasMaterialCapabilityLoss: false,
294
- },
295
- });
296
- assert.equal(selection.canDelegate, false);
297
- assert.equal(selection.failure, 'model-routing-failed: parent inheritance cannot set an effort');
298
- assert.deepEqual(selection.panel.selectedModelIds, []);
299
- assert.deepEqual(selection.nativeSpawnArguments, {});
300
- } finally {
301
- rmSync(preferencesDirectory, { recursive: true, force: true });
302
- }
303
- });
304
-
305
- test('selector rejects policy-defined inheritance aliases in model inventories', () => {
306
- const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-inventory-alias-'));
307
- try {
308
- assert.throws(
309
- () => selectWithPreferences(preferencesDirectory, {
310
- availableModelIds: ['auto'],
311
- confirmedSuitableModelIds: ['auto'],
312
- parentFallback: {
313
- isAllowed: true,
314
- hasMaterialCapabilityLoss: false,
315
- },
316
- }),
317
- /confirmed suitable model ids must contain native model ids/,
318
- );
319
- const policy = JSON.parse(readFileSync(defaultSubagentModelPolicyPath(), 'utf8'));
320
- policy.inheritanceAliases = ['custom-parent'];
321
- const policyPath = join(preferencesDirectory, 'subagent-model-policy.json');
322
- writeFileSync(policyPath, JSON.stringify(policy));
323
- assert.throws(
324
- () => selectWithPreferences(preferencesDirectory, {
325
- availableModelIds: ['custom-parent'],
326
- confirmedSuitableModelIds: [],
327
- parentFallback: {
328
- isAllowed: true,
329
- hasMaterialCapabilityLoss: false,
330
- },
331
- policyPath,
332
- }),
333
- /available model ids must contain native model ids/,
334
- );
335
- } finally {
336
- rmSync(preferencesDirectory, { recursive: true, force: true });
337
- }
338
- });
339
-
340
- test('material capability loss requires a user choice before inheritance', () => {
341
- const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-parent-loss-'));
342
- try {
343
- writeHostPreferences(preferencesDirectory, 'codex', {
344
- 'hardest tasks': ['inherit-parent'],
345
- });
346
- const selection = selectWithPreferences(preferencesDirectory, {
347
- role: 'hardest tasks',
348
- availableModelIds: [],
349
- parentFallback: {
350
- isAllowed: true,
351
- hasMaterialCapabilityLoss: true,
352
- },
353
- });
354
- assert.equal(selection.canDelegate, false);
355
- assert.equal(selection.requiresUserChoice, true);
356
- assert.equal(selection.failure, 'parent-capability-loss');
357
- assert.deepEqual(selection.nativeSpawnArguments, {});
358
- } finally {
359
- rmSync(preferencesDirectory, { recursive: true, force: true });
360
- }
361
- });
362
-
363
- test('inventory and confirmed alternatives must belong to the selected host', () => {
364
- const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-inventory-'));
365
- try {
366
- assert.throws(
367
- () => selectWithPreferences(preferencesDirectory, {
368
- host: 'codex',
369
- inventoryHost: 'claude',
370
- }),
371
- /inventory host must match selected host/,
372
- );
373
- assert.throws(
374
- () => selectWithPreferences(preferencesDirectory, {
375
- availableModelIds: ['confirmed-current-model'],
376
- confirmedSuitableModelIds: ['missing-model'],
377
- }),
378
- /confirmed suitable models must be available/,
379
- );
380
- } finally {
381
- rmSync(preferencesDirectory, { recursive: true, force: true });
382
- }
383
- });
384
-
385
- test('an empty model set materializes allowed parent inheritance', () => {
386
- const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-empty-parent-'));
387
- try {
388
- const selection = selectWithPreferences(preferencesDirectory, {
389
- availableModelIds: [],
390
- parentFallback: {
391
- isAllowed: true,
392
- hasMaterialCapabilityLoss: false,
393
- },
394
- });
395
- assert.equal(selection.canDelegate, true);
396
- assert.equal(selection.selectionSource, 'parent-inheritance');
397
- assert.deepEqual(selection.panel.selectedModelIds, [null]);
398
- assert.equal(selection.omitNativeModelArgument, true);
399
- assert.deepEqual(selection.nativeSpawnArguments, {});
400
- } finally {
401
- rmSync(preferencesDirectory, { recursive: true, force: true });
402
- }
403
- });
404
-
405
- test('delegation index and fallback flags require exact boundary types', () => {
406
- const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-boundary-'));
407
- try {
408
- for (const invalidIndex of [undefined, Number.NaN, 0.5, -1]) {
409
- assert.throws(
410
- () => selectWithPreferences(preferencesDirectory, {
411
- delegationIndex: invalidIndex,
412
- }),
413
- /delegation index must be a non-negative integer/,
414
- );
415
- }
416
- for (const invalidRole of ['toString', ['feature, refactoring']]) {
417
- assert.throws(
418
- () => selectWithPreferences(preferencesDirectory, {
419
- role: invalidRole,
420
- }),
421
- /role must name a portable pstack role/,
422
- );
423
- }
424
- assert.throws(
425
- () => selectWithPreferences(preferencesDirectory, {
426
- parentFallback: {
427
- isAllowed: 'yes',
428
- hasMaterialCapabilityLoss: false,
429
- },
430
- }),
431
- /parent fallback flags must be booleans/,
432
- );
433
- assert.throws(
434
- () => selectWithPreferences(preferencesDirectory, {
435
- parentFallback: {
436
- isAllowed: true,
437
- },
438
- }),
439
- /parent fallback flags must be booleans/,
440
- );
441
- } finally {
442
- rmSync(preferencesDirectory, { recursive: true, force: true });
443
- }
444
- });
445
-
446
- test('an optional-diversity review panel reports repeated models', () => {
447
- const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-review-panel-'));
448
- try {
449
- writeHostPreferences(preferencesDirectory, 'codex', {
450
- 'how critics': ['gpt-5.6-luna'],
451
- });
452
- const selection = selectWithPreferences(preferencesDirectory, {
453
- role: 'how critics',
454
- delegationIndex: 2,
455
- panel: {
456
- agentCount: 3,
457
- requiresDistinctModels: false,
458
- },
459
- availableModelIds: ['gpt-5.6-luna'],
460
- });
461
- assert.equal(selection.canDelegate, true);
462
- assert.deepEqual(selection.panel.selectedModelIds, [
463
- 'gpt-5.6-luna',
464
- 'gpt-5.6-luna',
465
- 'gpt-5.6-luna',
466
- ]);
467
- assert.equal(selection.panel.isCrossModelDiverse, false);
468
- } finally {
469
- rmSync(preferencesDirectory, { recursive: true, force: true });
470
- }
471
- });
472
-
473
- test('a model-only preference still goes through policy validation', () => {
474
- const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-routing-model-only-'));
475
- try {
476
- writeHostPreferences(preferencesDirectory, 'codex', {
477
- 'feature, refactoring': ['unknown-model'],
478
- });
479
- const selection = selectWithPreferences(preferencesDirectory, {
480
- availableModelIds: ['unknown-model'],
481
- });
482
- assert.equal(selection.canDelegate, false);
483
- assert.match(selection.failure, /model-routing-failed: model is unknown/);
484
- assert.deepEqual(selection.nativeSpawnArguments, {});
485
- } finally {
486
- rmSync(preferencesDirectory, { recursive: true, force: true });
487
- }
488
- });
489
-
490
- test('a model-only Terra preference uses the host effort before routing', () => {
491
- const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-routing-terra-default-'));
492
- try {
493
- writeHostPreferences(preferencesDirectory, 'codex', {
494
- 'feature, refactoring': ['Terra'],
495
- });
496
- const selection = selectWithPreferences(preferencesDirectory, {
497
- requestedEffort: 'medium',
498
- availableModelIds: ['gpt-5.6-terra', 'gpt-5.6-luna'],
499
- });
500
- assert.equal(selection.canDelegate, true);
501
- assert.deepEqual(selection.nativeSpawnArguments, {
502
- model: 'gpt-5.6-luna',
503
- reasoning_effort: 'xhigh',
504
- });
505
- } finally {
506
- rmSync(preferencesDirectory, { recursive: true, force: true });
507
- }
508
- });
509
-
510
- test('a profile default effort supplies a missing model-only effort', () => {
511
- const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-routing-profile-default-'));
512
- try {
513
- writeHostPreferences(preferencesDirectory, 'codex', {
514
- 'feature, refactoring': ['Terra'],
515
- }, 'medium');
516
- const selection = selectWithPreferences(preferencesDirectory, {
517
- requestedEffort: undefined,
518
- availableModelIds: ['gpt-5.6-terra', 'gpt-5.6-luna'],
519
- });
520
- assert.deepEqual(selection.nativeSpawnArguments, {
521
- model: 'gpt-5.6-luna',
522
- reasoning_effort: 'xhigh',
523
- });
524
- } finally {
525
- rmSync(preferencesDirectory, { recursive: true, force: true });
526
- }
527
- });
528
-
529
- test('a model-only worker Astra preference uses the host effort before routing', () => {
530
- const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-routing-astra-default-'));
531
- try {
532
- writeHostPreferences(preferencesDirectory, 'codex', {
533
- 'feature, refactoring': ['Astra'],
534
- });
535
- const selection = selectWithPreferences(preferencesDirectory, {
536
- requestedEffort: 'high',
537
- availableModelIds: ['gpt-6-astra'],
538
- });
539
- assert.equal(selection.canDelegate, true);
540
- assert.deepEqual(selection.nativeSpawnArguments, {
541
- model: 'gpt-6-astra',
542
- reasoning_effort: 'low',
543
- });
544
- } finally {
545
- rmSync(preferencesDirectory, { recursive: true, force: true });
546
- }
547
- });
548
-
549
- test('a model-only preference holds when the host supplies no usable effort', () => {
550
- const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-routing-missing-effort-'));
551
- try {
552
- writeHostPreferences(preferencesDirectory, 'codex', {
553
- 'feature, refactoring': ['Terra'],
554
- });
555
- const selection = selectWithPreferences(preferencesDirectory, {
556
- requestedEffort: undefined,
557
- availableModelIds: ['gpt-5.6-terra'],
558
- });
559
- assert.equal(selection.canDelegate, false);
560
- assert.match(selection.failure, /effort is required for this model/);
561
- } finally {
562
- rmSync(preferencesDirectory, { recursive: true, force: true });
563
- }
564
- });
565
-
566
- test('non-Codex inheritance keeps its ordered parent fallback', () => {
567
- const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-routing-parent-'));
568
- try {
569
- writeHostPreferences(preferencesDirectory, 'claude', {
570
- 'feature, refactoring': ['auto', 'opus'],
571
- });
572
- const selection = selectPstackDelegation({
573
- host: 'claude',
574
- inventoryHost: 'claude',
575
- role: 'feature, refactoring',
576
- delegationIndex: 0,
577
- availableModelIds: ['opus'],
578
- confirmedSuitableModelIds: [],
579
- parentFallback: {
580
- isAllowed: true,
581
- hasMaterialCapabilityLoss: false,
582
- },
583
- preferencesDirectory,
584
- });
585
- assert.equal(selection.canDelegate, true);
586
- assert.equal(selection.omitNativeModelArgument, true);
587
- assert.equal(selection.selectionSource, 'parent-inheritance');
588
- assert.deepEqual(selection.panel.selectedModelIds, [null]);
589
- } finally {
590
- rmSync(preferencesDirectory, { recursive: true, force: true });
591
- }
592
- });
593
-
594
- test('selector routes preference pairs before panel dedupe', () => {
595
- const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-routing-'));
596
- try {
597
- writeHostPreferences(preferencesDirectory, 'codex', {
598
- 'feature, refactoring': [
599
- { model: 'gpt-5.6-terra', effort: 'medium' },
600
- { model: 'gpt-5.6-luna', effort: 'medium' },
601
- ],
602
- });
603
- const selection = selectWithPreferences(preferencesDirectory, {
604
- panel: { agentCount: 2, requiresDistinctModels: false },
605
- availableModelIds: ['gpt-5.6-terra', 'gpt-5.6-luna'],
606
- });
607
- assert.equal(selection.canDelegate, true);
608
- assert.deepEqual(selection.panel.selectedModelIds, [
609
- 'gpt-5.6-luna',
610
- 'gpt-5.6-luna',
611
- ]);
612
- assert.deepEqual(selection.panel.selectedModelPairs, [
613
- { model: 'gpt-5.6-luna', effort: 'xhigh' },
614
- { model: 'gpt-5.6-luna', effort: 'xhigh' },
615
- ]);
616
- assert.equal(selection.panel.isCrossModelDiverse, false);
617
- assert.deepEqual(selection.nativeSpawnArguments, {
618
- model: 'gpt-5.6-luna',
619
- reasoning_effort: 'xhigh',
620
- });
621
- } finally {
622
- rmSync(preferencesDirectory, { recursive: true, force: true });
623
- }
624
- });
625
-
626
- test('panel diversity uses selected models after routing', () => {
627
- const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-routing-panel-'));
628
- try {
629
- writeHostPreferences(preferencesDirectory, 'codex', {
630
- 'arena runners': [
631
- { model: 'gpt-5.6-terra', effort: 'medium' },
632
- { model: 'gpt-5.6-sol', effort: 'medium' },
633
- { model: 'gpt-6-astra', effort: 'medium' },
634
- ],
635
- });
636
- const selection = selectWithPreferences(preferencesDirectory, {
637
- role: 'arena runners',
638
- availableModelIds: [
639
- 'gpt-5.6-terra',
640
- 'gpt-5.6-sol',
641
- 'gpt-6-astra',
642
- 'gpt-5.6-luna',
643
- ],
644
- });
645
- assert.equal(selection.canDelegate, false);
646
- assert.equal(selection.failure, 'distinct-models-unavailable');
647
- assert.deepEqual(selection.panel.selectedModelIds, [
648
- 'gpt-5.6-luna',
649
- 'gpt-6-astra',
650
- ]);
651
- assert.equal(selection.panel.isCrossModelDiverse, false);
652
- assert.deepEqual(selection.routingDiagnostics, []);
653
- } finally {
654
- rmSync(preferencesDirectory, { recursive: true, force: true });
655
- }
656
- });
657
-
658
- test('selector keeps other Sol remaps as contenders', () => {
659
- const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-sol-remap-'));
660
- try {
661
- writeHostPreferences(preferencesDirectory, 'codex', {
662
- 'feature, refactoring': [{ model: 'gpt-5.6-sol', effort: 'high' }],
663
- });
664
- const selection = selectWithPreferences(preferencesDirectory, {
665
- availableModelIds: ['gpt-5.6-sol', 'gpt-6-astra'],
666
- });
667
- assert.equal(selection.canDelegate, true);
668
- assert.deepEqual(selection.panel.selectedModelIds, ['gpt-6-astra']);
669
- assert.deepEqual(selection.panel.selectedModelPairs, [
670
- { model: 'gpt-6-astra', effort: 'low' },
671
- ]);
672
- assert.deepEqual(selection.routingDiagnostics, []);
673
- } finally {
674
- rmSync(preferencesDirectory, { recursive: true, force: true });
675
- }
676
- });
677
-
678
- test('unavailable routed models hold selection', () => {
679
- const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-routing-unavailable-'));
680
- try {
681
- writeHostPreferences(preferencesDirectory, 'codex', {
682
- 'feature, refactoring': [{ model: 'gpt-5.6-terra', effort: 'medium' }],
683
- });
684
- const selection = selectWithPreferences(preferencesDirectory, {
685
- availableModelIds: ['gpt-5.6-terra'],
686
- });
687
- assert.equal(selection.canDelegate, false);
688
- assert.equal(selection.requiresUserChoice, true);
689
- assert.match(selection.failure, /model-routing-failed: replacement model is unavailable/);
690
- assert.deepEqual(selection.nativeSpawnArguments, {});
691
- } finally {
692
- rmSync(preferencesDirectory, { recursive: true, force: true });
693
- }
694
- });
695
-
696
- test('malformed routing policy holds selection with a diagnostic', () => {
697
- const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-routing-policy-'));
698
- try {
699
- writeHostPreferences(preferencesDirectory, 'codex', {
700
- 'feature, refactoring': [{ model: 'gpt-5.6-terra', effort: 'medium' }],
701
- });
702
- const policyPath = join(preferencesDirectory, 'subagent-model-policy.json');
703
- writeFileSync(policyPath, '{"schemaVersion":1}\n');
704
- const selection = selectWithPreferences(preferencesDirectory, { policyPath });
705
- assert.equal(selection.canDelegate, false);
706
- assert.match(selection.failure, /model-routing-failed: models must be an object/);
707
- assert.deepEqual(selection.nativeSpawnArguments, {});
708
- } finally {
709
- rmSync(preferencesDirectory, { recursive: true, force: true });
710
- }
711
- });
712
-
713
- test('an explicit missing policy path holds selection', () => {
714
- const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-missing-policy-'));
715
- try {
716
- writeHostPreferences(preferencesDirectory, 'codex', {
717
- 'feature, refactoring': [{ model: 'Terra', effort: 'medium' }],
718
- });
719
- const selection = selectWithPreferences(preferencesDirectory, {
720
- policyPath: join(preferencesDirectory, 'missing-policy.json'),
721
- });
722
- assert.equal(selection.canDelegate, false);
723
- assert.match(selection.failure, /policy file is missing/);
724
- } finally {
725
- rmSync(preferencesDirectory, { recursive: true, force: true });
726
- }
727
- });