claude-dev-env 2.35.0 → 4.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.
- package/.agents/skills/_shared/advisor/scripts/README.md +1 -1
- package/.agents/skills/autoconverge/SKILL.md +19 -15
- package/.agents/skills/autoconverge/reference/gotchas.md +3 -3
- package/.agents/skills/autoconverge/reference/multi-pr.md +6 -2
- package/.agents/skills/autoconverge/workflow/converge.contract.test.mjs +11 -0
- package/.agents/skills/autoconverge/workflow/converge.mjs +26 -33
- package/.agents/skills/autoconverge/workflow/converge.run-input.test.mjs +26 -2
- package/.agents/skills/autoconverge/workflow/converge_multi.mjs +12 -28
- package/.agents/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +16 -0
- package/.agents/skills/team-advisor/SKILL.md +11 -11
- package/_shared/advisor/advisor-protocol.md +51 -51
- package/_shared/advisor/reference/advisor-block.md +9 -9
- package/_shared/advisor/reference/astra-rung.md +39 -0
- package/_shared/advisor/reference/identity.md +4 -4
- package/_shared/advisor/reference/lifecycle.md +6 -6
- package/_shared/advisor/reference/spawn-walk-log.md +9 -9
- package/_shared/advisor/reference/third-party-bind.md +11 -11
- package/_shared/advisor/reference/warm-up.md +13 -13
- package/_shared/advisor/scripts/codex_astra_advisor.py +308 -0
- package/_shared/advisor/scripts/codex_astra_preflight.py +115 -0
- package/_shared/advisor/scripts/codex_astra_reply.py +159 -0
- package/_shared/advisor/scripts/config/advisor_scripts_constants/advisor_route_constants.py +3 -3
- package/_shared/advisor/scripts/config/advisor_scripts_constants/astra_advisor_constants.py +38 -0
- package/_shared/advisor/scripts/model_tier_run_validator.py +17 -18
- package/_shared/advisor/scripts/tests/test_codex_astra_advisor.py +271 -0
- package/_shared/advisor/scripts/tests/test_codex_astra_preflight.py +24 -0
- package/_shared/advisor/scripts/tests/test_codex_astra_reply.py +42 -0
- package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +55 -30
- package/_shared/advisor/scripts/tests/test_tier_model_ids.py +14 -4
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +14 -0
- package/_shared/pr-loop/scripts/terminology_sweep.py +138 -50
- package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +13 -1
- package/bin/cde.mjs +175 -0
- package/bin/cde.test.mjs +222 -0
- package/bin/install-constants.mjs +4 -0
- package/bin/install.cursor-rules.test.mjs +233 -2
- package/bin/install.mjs +49 -3
- package/bin/pstack-model-preferences.codex.json +23 -0
- package/bin/select-pstack-models.test.mjs +378 -0
- package/docs/references/team-advisor-skill.md +3 -3
- package/hooks/hooks_constants/tool_names.py +20 -0
- package/hooks/validators/README.md +35 -1
- package/hooks/validators/fast_save_validators.py +63 -89
- package/hooks/validators/run_all_validators.py +16 -8
- package/hooks/validators/test_fast_save_validators_policy_lint.py +25 -0
- package/hooks/validators/test_run_all_validators_policy_lint.py +52 -0
- package/package.json +3 -2
- package/rules/pstack-models.md +119 -0
- package/scripts/cde_lint.py +258 -0
- package/scripts/check.ps1 +18 -6
- package/scripts/policy_lint/__init__.py +41 -0
- package/scripts/policy_lint/adapter_configuration.py +91 -0
- package/scripts/policy_lint/adapter_detectors.py +368 -0
- package/scripts/policy_lint/adapter_pairing.py +145 -0
- package/scripts/policy_lint/adapter_support.py +97 -0
- package/scripts/policy_lint/adapters.py +376 -0
- package/scripts/policy_lint/config/__init__.py +1 -0
- package/scripts/policy_lint/config/constants.py +77 -0
- package/scripts/policy_lint/engine.py +224 -0
- package/scripts/policy_lint/model.py +388 -0
- package/scripts/policy_lint/registry.py +171 -0
- package/scripts/policy_lint/render.py +91 -0
- package/scripts/policy_lint/selection.py +372 -0
- package/scripts/policy_lint/selection_git.py +147 -0
- package/scripts/repository_checks/__init__.py +1 -0
- package/scripts/repository_checks/claude_md.py +108 -0
- package/scripts/repository_checks/config/__init__.py +1 -0
- package/scripts/repository_checks/config/constants.py +150 -0
- package/scripts/repository_checks/env_var_documentation.py +243 -0
- package/scripts/repository_checks/hook_modules.py +34 -0
- package/scripts/repository_checks/models.py +37 -0
- package/scripts/repository_checks/package_inventory.py +118 -0
- package/scripts/repository_checks/pytest_testpaths.py +84 -0
- package/scripts/repository_checks/reporting.py +39 -0
- package/scripts/repository_checks/runner.py +93 -0
- package/scripts/repository_checks/tracked_secrets.py +147 -0
- package/scripts/repository_checks/tracked_tree.py +28 -0
- package/scripts/repository_policy.py +120 -0
- package/scripts/select_pstack_models.mjs +304 -0
- package/scripts/tests/repository_policy_test_support.py +89 -0
- package/scripts/tests/test_cde_lint.py +398 -0
- package/scripts/tests/test_claude_md.py +68 -0
- package/scripts/tests/test_env_var_documentation.py +133 -0
- package/scripts/tests/test_hook_modules.py +17 -0
- package/scripts/tests/test_models.py +26 -0
- package/scripts/tests/test_package_inventory.py +96 -0
- package/scripts/tests/test_policy_lint_rules_engine.py +381 -0
- package/scripts/tests/test_policy_lint_rules_registry.py +369 -0
- package/scripts/tests/test_policy_lint_selection.py +373 -0
- package/scripts/tests/test_pytest_testpaths.py +66 -0
- package/scripts/tests/test_reporting.py +54 -0
- package/scripts/tests/test_repository_policy.py +71 -0
- package/scripts/tests/test_runner.py +59 -0
- package/scripts/tests/test_tracked_secrets.py +175 -0
- package/scripts/tests/test_tracked_tree.py +28 -0
- package/_shared/advisor/reference/sol-rung.md +0 -39
- package/_shared/advisor/scripts/codex_sol_advisor.py +0 -581
- package/_shared/advisor/scripts/config/advisor_scripts_constants/sol_advisor_constants.py +0 -38
- package/_shared/advisor/scripts/tests/test_codex_sol_advisor.py +0 -740
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
import { test } from 'node:test';
|
|
2
|
+
import { strict as assert } from 'node:assert';
|
|
3
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import {
|
|
7
|
+
allPstackRoleRequirements,
|
|
8
|
+
selectPstackDelegation,
|
|
9
|
+
} from '../scripts/select_pstack_models.mjs';
|
|
10
|
+
|
|
11
|
+
const ALL_PSTACK_ROLES = [
|
|
12
|
+
'feature, refactoring',
|
|
13
|
+
'bug-fix',
|
|
14
|
+
'perf-issue',
|
|
15
|
+
'hillclimb',
|
|
16
|
+
'judgment and prose',
|
|
17
|
+
'hardest tasks',
|
|
18
|
+
'how explorer',
|
|
19
|
+
'how explainer',
|
|
20
|
+
'how critics',
|
|
21
|
+
'why investigators',
|
|
22
|
+
'why synthesizer',
|
|
23
|
+
'reflect tooling',
|
|
24
|
+
'reflect judgment, divergent, synthesizer',
|
|
25
|
+
'arena runners',
|
|
26
|
+
'arena cross-judge pool',
|
|
27
|
+
'swarm workers',
|
|
28
|
+
'architect runners',
|
|
29
|
+
'interrogate reviewers',
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
function writeHostPreferences(preferencesDirectory, host, modelsByRole) {
|
|
33
|
+
mkdirSync(preferencesDirectory, { recursive: true });
|
|
34
|
+
writeFileSync(
|
|
35
|
+
join(preferencesDirectory, 'pstack-model-preferences.' + host + '.json'),
|
|
36
|
+
JSON.stringify({ host, modelsByRole }),
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function selectWithPreferences(preferencesDirectory, overrides) {
|
|
41
|
+
return selectPstackDelegation({
|
|
42
|
+
host: 'codex',
|
|
43
|
+
inventoryHost: 'codex',
|
|
44
|
+
role: 'feature, refactoring',
|
|
45
|
+
delegationIndex: 0,
|
|
46
|
+
availableModelIds: ['gpt-5.6-sol'],
|
|
47
|
+
confirmedSuitableModelIds: [],
|
|
48
|
+
parentFallback: {
|
|
49
|
+
isAllowed: false,
|
|
50
|
+
hasMaterialCapabilityLoss: false,
|
|
51
|
+
},
|
|
52
|
+
preferencesDirectory,
|
|
53
|
+
...overrides,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
test('portable policy represents every pstack role without model ids', () => {
|
|
58
|
+
assert.deepEqual(Object.keys(allPstackRoleRequirements), ALL_PSTACK_ROLES);
|
|
59
|
+
const serializedRequirements = JSON.stringify(allPstackRoleRequirements);
|
|
60
|
+
assert.doesNotMatch(serializedRequirements, /gpt-|claude-|grok-/i);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('Codex preferences select every available model in a diverse panel', () => {
|
|
64
|
+
const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-codex-'));
|
|
65
|
+
try {
|
|
66
|
+
writeHostPreferences(preferencesDirectory, 'codex', {
|
|
67
|
+
'how critics': ['gpt-5.6-terra', 'gpt-5.6-sol', 'gpt-6-astra'],
|
|
68
|
+
});
|
|
69
|
+
const selection = selectWithPreferences(preferencesDirectory, {
|
|
70
|
+
role: 'how critics',
|
|
71
|
+
delegationIndex: 1,
|
|
72
|
+
availableModelIds: ['gpt-5.6-sol', 'gpt-6-astra', 'gpt-5.6-terra'],
|
|
73
|
+
});
|
|
74
|
+
assert.equal(selection.canDelegate, true);
|
|
75
|
+
assert.equal(selection.selectedHost, 'codex');
|
|
76
|
+
assert.equal(selection.selectionSource, 'host-preference');
|
|
77
|
+
assert.deepEqual(selection.panel.selectedModelIds, [
|
|
78
|
+
'gpt-5.6-terra',
|
|
79
|
+
'gpt-5.6-sol',
|
|
80
|
+
'gpt-6-astra',
|
|
81
|
+
]);
|
|
82
|
+
assert.equal(selection.panel.isCrossModelDiverse, true);
|
|
83
|
+
assert.deepEqual(selection.nativeSpawnArguments, { model: 'gpt-5.6-sol' });
|
|
84
|
+
assert.equal(selection.omitNativeModelArgument, false);
|
|
85
|
+
assert.equal(selection.availabilityValidated, true);
|
|
86
|
+
} finally {
|
|
87
|
+
rmSync(preferencesDirectory, { recursive: true, force: true });
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('a foreign host reads only its own saved preferences', () => {
|
|
92
|
+
const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-host-'));
|
|
93
|
+
try {
|
|
94
|
+
writeHostPreferences(preferencesDirectory, 'codex', {
|
|
95
|
+
'feature, refactoring': ['gpt-5.6-sol'],
|
|
96
|
+
});
|
|
97
|
+
writeHostPreferences(preferencesDirectory, 'claude', {
|
|
98
|
+
'feature, refactoring': ['confirmed-claude-model'],
|
|
99
|
+
});
|
|
100
|
+
const selection = selectWithPreferences(preferencesDirectory, {
|
|
101
|
+
host: 'claude',
|
|
102
|
+
inventoryHost: 'claude',
|
|
103
|
+
availableModelIds: ['confirmed-claude-model', 'gpt-5.6-sol'],
|
|
104
|
+
});
|
|
105
|
+
assert.equal(selection.selectedHost, 'claude');
|
|
106
|
+
assert.deepEqual(selection.nativeSpawnArguments, {
|
|
107
|
+
model: 'confirmed-claude-model',
|
|
108
|
+
});
|
|
109
|
+
} finally {
|
|
110
|
+
rmSync(preferencesDirectory, { recursive: true, force: true });
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('an unavailable preferred model uses a confirmed host alternative', () => {
|
|
115
|
+
const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-alternative-'));
|
|
116
|
+
try {
|
|
117
|
+
writeHostPreferences(preferencesDirectory, 'codex', {
|
|
118
|
+
'feature, refactoring': ['gpt-5.6-sol'],
|
|
119
|
+
});
|
|
120
|
+
const selection = selectWithPreferences(preferencesDirectory, {
|
|
121
|
+
availableModelIds: ['confirmed-current-model'],
|
|
122
|
+
confirmedSuitableModelIds: ['confirmed-current-model'],
|
|
123
|
+
});
|
|
124
|
+
assert.equal(selection.canDelegate, true);
|
|
125
|
+
assert.equal(selection.selectionSource, 'confirmed-host-alternative');
|
|
126
|
+
assert.deepEqual(selection.nativeSpawnArguments, {
|
|
127
|
+
model: 'confirmed-current-model',
|
|
128
|
+
});
|
|
129
|
+
} finally {
|
|
130
|
+
rmSync(preferencesDirectory, { recursive: true, force: true });
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('the arena cross-judge pool selects one model other than the parent', () => {
|
|
135
|
+
const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-judge-'));
|
|
136
|
+
try {
|
|
137
|
+
writeHostPreferences(preferencesDirectory, 'codex', {
|
|
138
|
+
'arena cross-judge pool': ['gpt-5.6-sol', 'gpt-6-astra'],
|
|
139
|
+
});
|
|
140
|
+
const selection = selectWithPreferences(preferencesDirectory, {
|
|
141
|
+
role: 'arena cross-judge pool',
|
|
142
|
+
parentModelId: 'gpt-5.6-sol',
|
|
143
|
+
availableModelIds: ['gpt-5.6-sol', 'gpt-6-astra'],
|
|
144
|
+
});
|
|
145
|
+
assert.equal(selection.panel.agentCount, 1);
|
|
146
|
+
assert.deepEqual(selection.nativeSpawnArguments, { model: 'gpt-6-astra' });
|
|
147
|
+
} finally {
|
|
148
|
+
rmSync(preferencesDirectory, { recursive: true, force: true });
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
test('the arena cross-judge prefers a real alternative over parent inheritance', () => {
|
|
152
|
+
const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-judge-parent-'));
|
|
153
|
+
try {
|
|
154
|
+
writeHostPreferences(preferencesDirectory, 'codex', {
|
|
155
|
+
'arena cross-judge pool': ['auto', 'confirmed-other-model'],
|
|
156
|
+
});
|
|
157
|
+
const selection = selectWithPreferences(preferencesDirectory, {
|
|
158
|
+
role: 'arena cross-judge pool',
|
|
159
|
+
parentModelId: 'current-parent-model',
|
|
160
|
+
availableModelIds: ['confirmed-other-model'],
|
|
161
|
+
parentFallback: {
|
|
162
|
+
isAllowed: true,
|
|
163
|
+
hasMaterialCapabilityLoss: false,
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
assert.deepEqual(selection.nativeSpawnArguments, {
|
|
167
|
+
model: 'confirmed-other-model',
|
|
168
|
+
});
|
|
169
|
+
assert.equal(selection.omitNativeModelArgument, false);
|
|
170
|
+
} finally {
|
|
171
|
+
rmSync(preferencesDirectory, { recursive: true, force: true });
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test('a same-model swarm reports its actual panel composition', () => {
|
|
176
|
+
const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-swarm-'));
|
|
177
|
+
try {
|
|
178
|
+
writeHostPreferences(preferencesDirectory, 'codex', {
|
|
179
|
+
'swarm workers': ['gpt-5.6-luna'],
|
|
180
|
+
});
|
|
181
|
+
const selection = selectWithPreferences(preferencesDirectory, {
|
|
182
|
+
role: 'swarm workers',
|
|
183
|
+
delegationIndex: 2,
|
|
184
|
+
panel: {
|
|
185
|
+
agentCount: 4,
|
|
186
|
+
requiresDistinctModels: false,
|
|
187
|
+
},
|
|
188
|
+
availableModelIds: ['gpt-5.6-luna'],
|
|
189
|
+
});
|
|
190
|
+
assert.equal(selection.canDelegate, true);
|
|
191
|
+
assert.equal(selection.panel.agentCount, 4);
|
|
192
|
+
assert.deepEqual(selection.panel.selectedModelIds, [
|
|
193
|
+
'gpt-5.6-luna',
|
|
194
|
+
'gpt-5.6-luna',
|
|
195
|
+
'gpt-5.6-luna',
|
|
196
|
+
'gpt-5.6-luna',
|
|
197
|
+
]);
|
|
198
|
+
assert.equal(selection.panel.isCrossModelDiverse, false);
|
|
199
|
+
assert.deepEqual(selection.nativeSpawnArguments, { model: 'gpt-5.6-luna' });
|
|
200
|
+
} finally {
|
|
201
|
+
rmSync(preferencesDirectory, { recursive: true, force: true });
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test('a diversity-required panel fails without enough distinct real models', () => {
|
|
206
|
+
const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-diversity-'));
|
|
207
|
+
try {
|
|
208
|
+
writeHostPreferences(preferencesDirectory, 'codex', {
|
|
209
|
+
'interrogate reviewers': ['gpt-5.6-terra', 'auto', 'gpt-5.6-sol'],
|
|
210
|
+
});
|
|
211
|
+
const selection = selectWithPreferences(preferencesDirectory, {
|
|
212
|
+
role: 'interrogate reviewers',
|
|
213
|
+
availableModelIds: ['gpt-5.6-terra', 'gpt-5.6-sol'],
|
|
214
|
+
});
|
|
215
|
+
assert.equal(selection.canDelegate, false);
|
|
216
|
+
assert.equal(selection.requiresUserChoice, true);
|
|
217
|
+
assert.equal(selection.failure, 'distinct-models-unavailable');
|
|
218
|
+
assert.equal(selection.panel.isCrossModelDiverse, false);
|
|
219
|
+
assert.deepEqual(selection.nativeSpawnArguments, {});
|
|
220
|
+
} finally {
|
|
221
|
+
rmSync(preferencesDirectory, { recursive: true, force: true });
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test('parent inheritance omits the native model argument', () => {
|
|
226
|
+
const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-parent-'));
|
|
227
|
+
try {
|
|
228
|
+
writeHostPreferences(preferencesDirectory, 'codex', {
|
|
229
|
+
'feature, refactoring': ['inherit-parent'],
|
|
230
|
+
});
|
|
231
|
+
const selection = selectWithPreferences(preferencesDirectory, {
|
|
232
|
+
availableModelIds: [],
|
|
233
|
+
parentFallback: {
|
|
234
|
+
isAllowed: true,
|
|
235
|
+
hasMaterialCapabilityLoss: false,
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
assert.equal(selection.canDelegate, true);
|
|
239
|
+
assert.equal(selection.selectionSource, 'parent-inheritance');
|
|
240
|
+
assert.deepEqual(selection.nativeSpawnArguments, {});
|
|
241
|
+
assert.equal(selection.omitNativeModelArgument, true);
|
|
242
|
+
} finally {
|
|
243
|
+
rmSync(preferencesDirectory, { recursive: true, force: true });
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test('material capability loss requires a user choice before inheritance', () => {
|
|
248
|
+
const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-parent-loss-'));
|
|
249
|
+
try {
|
|
250
|
+
writeHostPreferences(preferencesDirectory, 'codex', {
|
|
251
|
+
'hardest tasks': ['inherit-parent'],
|
|
252
|
+
});
|
|
253
|
+
const selection = selectWithPreferences(preferencesDirectory, {
|
|
254
|
+
role: 'hardest tasks',
|
|
255
|
+
availableModelIds: [],
|
|
256
|
+
parentFallback: {
|
|
257
|
+
isAllowed: true,
|
|
258
|
+
hasMaterialCapabilityLoss: true,
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
assert.equal(selection.canDelegate, false);
|
|
262
|
+
assert.equal(selection.requiresUserChoice, true);
|
|
263
|
+
assert.equal(selection.failure, 'parent-capability-loss');
|
|
264
|
+
assert.deepEqual(selection.nativeSpawnArguments, {});
|
|
265
|
+
} finally {
|
|
266
|
+
rmSync(preferencesDirectory, { recursive: true, force: true });
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
test('inventory and confirmed alternatives must belong to the selected host', () => {
|
|
271
|
+
const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-inventory-'));
|
|
272
|
+
try {
|
|
273
|
+
assert.throws(
|
|
274
|
+
() => selectWithPreferences(preferencesDirectory, {
|
|
275
|
+
host: 'codex',
|
|
276
|
+
inventoryHost: 'claude',
|
|
277
|
+
}),
|
|
278
|
+
/inventory host must match selected host/,
|
|
279
|
+
);
|
|
280
|
+
assert.throws(
|
|
281
|
+
() => selectWithPreferences(preferencesDirectory, {
|
|
282
|
+
availableModelIds: ['confirmed-current-model'],
|
|
283
|
+
confirmedSuitableModelIds: ['missing-model'],
|
|
284
|
+
}),
|
|
285
|
+
/confirmed suitable models must be available/,
|
|
286
|
+
);
|
|
287
|
+
} finally {
|
|
288
|
+
rmSync(preferencesDirectory, { recursive: true, force: true });
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test('an empty model set materializes allowed parent inheritance', () => {
|
|
293
|
+
const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-empty-parent-'));
|
|
294
|
+
try {
|
|
295
|
+
const selection = selectWithPreferences(preferencesDirectory, {
|
|
296
|
+
availableModelIds: [],
|
|
297
|
+
parentFallback: {
|
|
298
|
+
isAllowed: true,
|
|
299
|
+
hasMaterialCapabilityLoss: false,
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
assert.equal(selection.canDelegate, true);
|
|
303
|
+
assert.equal(selection.selectionSource, 'parent-inheritance');
|
|
304
|
+
assert.deepEqual(selection.panel.selectedModelIds, [null]);
|
|
305
|
+
assert.equal(selection.omitNativeModelArgument, true);
|
|
306
|
+
assert.deepEqual(selection.nativeSpawnArguments, {});
|
|
307
|
+
} finally {
|
|
308
|
+
rmSync(preferencesDirectory, { recursive: true, force: true });
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
test('delegation index and fallback flags require exact boundary types', () => {
|
|
313
|
+
const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-boundary-'));
|
|
314
|
+
try {
|
|
315
|
+
for (const invalidIndex of [undefined, Number.NaN, 0.5, -1]) {
|
|
316
|
+
assert.throws(
|
|
317
|
+
() => selectWithPreferences(preferencesDirectory, {
|
|
318
|
+
delegationIndex: invalidIndex,
|
|
319
|
+
}),
|
|
320
|
+
/delegation index must be a non-negative integer/,
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
for (const invalidRole of ['toString', ['feature, refactoring']]) {
|
|
324
|
+
assert.throws(
|
|
325
|
+
() => selectWithPreferences(preferencesDirectory, {
|
|
326
|
+
role: invalidRole,
|
|
327
|
+
}),
|
|
328
|
+
/role must name a portable pstack role/,
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
assert.throws(
|
|
332
|
+
() => selectWithPreferences(preferencesDirectory, {
|
|
333
|
+
parentFallback: {
|
|
334
|
+
isAllowed: 'yes',
|
|
335
|
+
hasMaterialCapabilityLoss: false,
|
|
336
|
+
},
|
|
337
|
+
}),
|
|
338
|
+
/parent fallback flags must be booleans/,
|
|
339
|
+
);
|
|
340
|
+
assert.throws(
|
|
341
|
+
() => selectWithPreferences(preferencesDirectory, {
|
|
342
|
+
parentFallback: {
|
|
343
|
+
isAllowed: true,
|
|
344
|
+
},
|
|
345
|
+
}),
|
|
346
|
+
/parent fallback flags must be booleans/,
|
|
347
|
+
);
|
|
348
|
+
} finally {
|
|
349
|
+
rmSync(preferencesDirectory, { recursive: true, force: true });
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test('an optional-diversity review panel reports repeated models', () => {
|
|
354
|
+
const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-review-panel-'));
|
|
355
|
+
try {
|
|
356
|
+
writeHostPreferences(preferencesDirectory, 'codex', {
|
|
357
|
+
'how critics': ['gpt-5.6-sol'],
|
|
358
|
+
});
|
|
359
|
+
const selection = selectWithPreferences(preferencesDirectory, {
|
|
360
|
+
role: 'how critics',
|
|
361
|
+
delegationIndex: 2,
|
|
362
|
+
panel: {
|
|
363
|
+
agentCount: 3,
|
|
364
|
+
requiresDistinctModels: false,
|
|
365
|
+
},
|
|
366
|
+
availableModelIds: ['gpt-5.6-sol'],
|
|
367
|
+
});
|
|
368
|
+
assert.equal(selection.canDelegate, true);
|
|
369
|
+
assert.deepEqual(selection.panel.selectedModelIds, [
|
|
370
|
+
'gpt-5.6-sol',
|
|
371
|
+
'gpt-5.6-sol',
|
|
372
|
+
'gpt-5.6-sol',
|
|
373
|
+
]);
|
|
374
|
+
assert.equal(selection.panel.isCrossModelDiverse, false);
|
|
375
|
+
} finally {
|
|
376
|
+
rmSync(preferencesDirectory, { recursive: true, force: true });
|
|
377
|
+
}
|
|
378
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Team-
|
|
1
|
+
# Team-advisor skill
|
|
2
2
|
|
|
3
|
-
`/team-advisor` binds one standing warm advisor for this session at the strongest reachable tier.
|
|
3
|
+
`/team-advisor` binds one standing warm advisor for this session at the strongest reachable tier. It sends explicit packets to a warm Agent/SendMessage advisor or the read-only `codex_astra_advisor.py` helper.
|
|
4
4
|
|
|
5
5
|
## Refs
|
|
6
6
|
|
|
@@ -17,4 +17,4 @@ Follow the call rules in `advisor-tool.md` (orientation first, then consult befo
|
|
|
17
17
|
|
|
18
18
|
## Selected path
|
|
19
19
|
|
|
20
|
-
`/team-advisor` is the repository's advisor implementation. It provides explicit first-consult packets, delta consults, a standing warm reviewer, an in-session
|
|
20
|
+
`/team-advisor` is the repository's advisor implementation. It provides explicit first-consult packets, delta consults, a standing warm reviewer, an in-session Astra spawn on Codex, and a read-only Astra CLI option.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Shared names for mutation and shell tools."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
BASH_TOOL_NAME: str = "Bash"
|
|
6
|
+
POWERSHELL_TOOL_NAME: str = "PowerShell"
|
|
7
|
+
WRITE_TOOL_NAME: str = "Write"
|
|
8
|
+
EDIT_TOOL_NAME: str = "Edit"
|
|
9
|
+
MULTI_EDIT_TOOL_NAME: str = "MultiEdit"
|
|
10
|
+
APPLY_PATCH_TOOL_NAME: str = "apply_patch"
|
|
11
|
+
|
|
12
|
+
ALL_BASH_AND_POWERSHELL_TOOL_NAMES: frozenset[str] = frozenset(
|
|
13
|
+
{BASH_TOOL_NAME, POWERSHELL_TOOL_NAME}
|
|
14
|
+
)
|
|
15
|
+
ALL_WRITE_EDIT_MULTI_EDIT_TOOL_NAMES: frozenset[str] = frozenset(
|
|
16
|
+
{WRITE_TOOL_NAME, EDIT_TOOL_NAME, MULTI_EDIT_TOOL_NAME}
|
|
17
|
+
)
|
|
18
|
+
ALL_WRITE_EDIT_MULTI_EDIT_APPLY_PATCH_TOOL_NAMES: frozenset[str] = frozenset(
|
|
19
|
+
ALL_WRITE_EDIT_MULTI_EDIT_TOOL_NAMES | {APPLY_PATCH_TOOL_NAME}
|
|
20
|
+
)
|
|
@@ -2,7 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
AST-based Python style checks for code quality enforcement.
|
|
4
4
|
|
|
5
|
-
The checks live in `python_style_checks.py
|
|
5
|
+
The checks live in `python_style_checks.py`. Shared source-line splitting, function discovery, and statement classification live in `python_style_helpers.py`. `python_style_import_bootstrap.py` recognizes the repo's sys.path bootstrap-guard idiom so the imports-at-top check does not penalize a file for following it. `fast_save_validators.py` runs the twelve non-Mypy, non-Ruff checks in-process for the Write/Edit save-path gate, with no per-check subprocess.
|
|
6
|
+
|
|
7
|
+
## Production modules
|
|
8
|
+
|
|
9
|
+
| File | Role |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `abbreviation_checks.py` | Abbreviation and single-letter name checks |
|
|
12
|
+
| `code_quality_checks.py` | Function, nesting, and file length checks |
|
|
13
|
+
| `comment_checks.py` | Comment detection for the opt-in diff-aware linter |
|
|
14
|
+
| `exempt_paths.py` | Shared config, test, and hook-infrastructure path exemptions |
|
|
15
|
+
| `fast_save_validators.py` | In-process Write/Edit save-path validator roster |
|
|
16
|
+
| `file_structure_checks.py` | File structure checks for pre-PR validation |
|
|
17
|
+
| `git_checks.py` | Git and GitHub checks for pre-push review |
|
|
18
|
+
| `health_check.py` | Validator availability, dependency, and version checks |
|
|
19
|
+
| `hook_timing_harness.py` | Hook runtime measurement and threshold report |
|
|
20
|
+
| `magic_value_checks.py` | Hardcoded magic-number checks |
|
|
21
|
+
| `mypy_integration.py` | Mypy static type checking integration |
|
|
22
|
+
| `output_formatter.py` | Colored, diff, progress, and JSON validator output |
|
|
23
|
+
| `pr_reference_checks.py` | PR and commit reference checks in comments |
|
|
24
|
+
| `project_roots.py` | Project-root resolution from a path |
|
|
25
|
+
| `pyproject_config_discovery.py` | Walk-up discovery of tool pyproject tables |
|
|
26
|
+
| `python_antipattern_checks.py` | Mutable defaults, bare except, and print checks |
|
|
27
|
+
| `python_style_checks.py` | Import placement, decorator spacing, blank lines, and view naming |
|
|
28
|
+
| `python_style_helpers.py` | Source-line splitting and function-discovery helpers |
|
|
29
|
+
| `python_style_import_bootstrap.py` | sys.path bootstrap-guard recognizer |
|
|
30
|
+
| `react_checks.py` | React class-component and error-boundary checks |
|
|
31
|
+
| `ruff_integration.py` | Ruff lint integration |
|
|
32
|
+
| `run_all_validators.py` | Pre-push validator orchestration and report |
|
|
33
|
+
| `security_checks.py` | Hardcoded-secret, SQL injection, and XSS checks |
|
|
34
|
+
| `system_temporary_roots.py` | System temp-root membership for staged copies |
|
|
35
|
+
| `todo_checks.py` | TODO/FIXME tracking with issue-reference requirement |
|
|
36
|
+
| `type_safety_checks.py` | Missing type hints and Any usage checks |
|
|
37
|
+
| `useless_test_checks.py` | Useless-test detection |
|
|
38
|
+
| `validator_base.py` | Shared validator dataclasses, source read, and parse |
|
|
39
|
+
| `validator_defaults.py` | Shared constants for the validators package |
|
|
6
40
|
|
|
7
41
|
## Checks Implemented
|
|
8
42
|
|
|
@@ -14,7 +14,7 @@ the CLI mode calls. A save-path finding matches the CLI mode's finding for
|
|
|
14
14
|
the same validator; only Mypy and the process-per-check overhead are gone.
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
|
-
from collections.abc import Callable, Sequence
|
|
17
|
+
from collections.abc import Callable, Collection, Sequence
|
|
18
18
|
from dataclasses import dataclass
|
|
19
19
|
from pathlib import Path
|
|
20
20
|
|
|
@@ -125,92 +125,61 @@ class _FastSaveValidatorSpecification:
|
|
|
125
125
|
no_matching_files_message: str
|
|
126
126
|
|
|
127
127
|
|
|
128
|
-
def
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
128
|
+
def _build_specification(
|
|
129
|
+
display_name: str,
|
|
130
|
+
checks: str,
|
|
131
|
+
applies_to_file: FilePredicate,
|
|
132
|
+
collect_violation_lines: FileViolationLineCollector,
|
|
133
|
+
no_matching_files_message: str,
|
|
134
|
+
) -> _FastSaveValidatorSpecification:
|
|
135
|
+
return _FastSaveValidatorSpecification(
|
|
136
|
+
display_name,
|
|
137
|
+
checks,
|
|
138
|
+
applies_to_file,
|
|
139
|
+
collect_violation_lines,
|
|
140
|
+
no_matching_files_message,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _python_specifications() -> tuple[_FastSaveValidatorSpecification, ...]:
|
|
145
|
+
return (
|
|
146
|
+
_build_specification("Python Style", "1,2,3,4", _is_python_file, _violation_lines_from(validate_python_style_file), NO_PYTHON_FILES_MESSAGE),
|
|
147
|
+
_build_specification("Abbreviations", "5", _is_python_file, _violation_lines_from(validate_abbreviation_file), NO_PYTHON_FILES_MESSAGE),
|
|
148
|
+
_build_specification("Magic Values", "7", _is_python_file, _violation_lines_from(validate_magic_literal_file), NO_PYTHON_FILES_MESSAGE),
|
|
149
|
+
_build_specification("Security", "27,28,29", _is_python_file, _violation_lines_from(validate_security_file), NO_PYTHON_FILES_MESSAGE),
|
|
150
|
+
_build_specification("Code Quality", "30,31,32", _is_python_file, _violation_lines_from(validate_code_quality_file), NO_PYTHON_FILES_MESSAGE),
|
|
151
|
+
_build_specification("Python Anti-patterns", "33,34,35", _is_python_file, _violation_lines_from(validate_python_antipattern_file), NO_PYTHON_FILES_MESSAGE),
|
|
152
|
+
_build_specification("Type Safety", "39,40", _is_python_file, _violation_lines_from(validate_type_safety_file), NO_PYTHON_FILES_MESSAGE),
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _test_specifications() -> tuple[_FastSaveValidatorSpecification, ...]:
|
|
157
|
+
return (
|
|
158
|
+
_build_specification("Test Safety", "11,21", _is_test_python_file, _collect_test_safety_violation_lines, NO_TEST_FILES_MESSAGE),
|
|
159
|
+
_build_specification("Useless Tests", "12", _is_test_python_file, _violation_lines_from(validate_useless_test_file), NO_TEST_FILES_MESSAGE),
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _non_python_specifications() -> tuple[_FastSaveValidatorSpecification, ...]:
|
|
132
164
|
return (
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
_is_python_file,
|
|
152
|
-
_violation_lines_from(validate_magic_literal_file),
|
|
153
|
-
NO_PYTHON_FILES_MESSAGE,
|
|
154
|
-
),
|
|
155
|
-
_FastSaveValidatorSpecification(
|
|
156
|
-
"Security",
|
|
157
|
-
"27,28,29",
|
|
158
|
-
_is_python_file,
|
|
159
|
-
_violation_lines_from(validate_security_file),
|
|
160
|
-
NO_PYTHON_FILES_MESSAGE,
|
|
161
|
-
),
|
|
162
|
-
_FastSaveValidatorSpecification(
|
|
163
|
-
"Code Quality",
|
|
164
|
-
"30,31,32",
|
|
165
|
-
_is_python_file,
|
|
166
|
-
_violation_lines_from(validate_code_quality_file),
|
|
167
|
-
NO_PYTHON_FILES_MESSAGE,
|
|
168
|
-
),
|
|
169
|
-
# Python anti-patterns and typing.
|
|
170
|
-
_FastSaveValidatorSpecification(
|
|
171
|
-
"Python Anti-patterns",
|
|
172
|
-
"33,34,35",
|
|
173
|
-
_is_python_file,
|
|
174
|
-
_violation_lines_from(validate_python_antipattern_file),
|
|
175
|
-
NO_PYTHON_FILES_MESSAGE,
|
|
176
|
-
),
|
|
177
|
-
_FastSaveValidatorSpecification(
|
|
178
|
-
"Type Safety",
|
|
179
|
-
"39,40",
|
|
180
|
-
_is_python_file,
|
|
181
|
-
_violation_lines_from(validate_type_safety_file),
|
|
182
|
-
NO_PYTHON_FILES_MESSAGE,
|
|
183
|
-
),
|
|
184
|
-
# Scoped to a test_*.py-shaped file.
|
|
185
|
-
_FastSaveValidatorSpecification(
|
|
186
|
-
"Test Safety",
|
|
187
|
-
"11,21",
|
|
188
|
-
_is_test_python_file,
|
|
189
|
-
_collect_test_safety_violation_lines,
|
|
190
|
-
NO_TEST_FILES_MESSAGE,
|
|
191
|
-
),
|
|
192
|
-
_FastSaveValidatorSpecification(
|
|
193
|
-
"Useless Tests",
|
|
194
|
-
"12",
|
|
195
|
-
_is_test_python_file,
|
|
196
|
-
_violation_lines_from(validate_useless_test_file),
|
|
197
|
-
NO_TEST_FILES_MESSAGE,
|
|
198
|
-
),
|
|
199
|
-
# Non-Python or multi-language files.
|
|
200
|
-
_FastSaveValidatorSpecification(
|
|
201
|
-
"React",
|
|
202
|
-
"17",
|
|
203
|
-
_is_react_file,
|
|
204
|
-
_collect_react_violation_lines,
|
|
205
|
-
NO_REACT_FILES_MESSAGE,
|
|
206
|
-
),
|
|
207
|
-
_FastSaveValidatorSpecification(
|
|
208
|
-
"PR References",
|
|
209
|
-
"6",
|
|
210
|
-
_is_code_reference_file,
|
|
211
|
-
_violation_lines_from(validate_pr_reference_file),
|
|
212
|
-
NO_CODE_REFERENCE_FILES_MESSAGE,
|
|
213
|
-
),
|
|
165
|
+
_build_specification("React", "17", _is_react_file, _collect_react_violation_lines, NO_REACT_FILES_MESSAGE),
|
|
166
|
+
_build_specification("PR References", "6", _is_code_reference_file, _violation_lines_from(validate_pr_reference_file), NO_CODE_REFERENCE_FILES_MESSAGE),
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _all_fast_save_validator_specifications(
|
|
171
|
+
excluded_validator_names: Collection[str] = (),
|
|
172
|
+
) -> tuple[_FastSaveValidatorSpecification, ...]:
|
|
173
|
+
"""Return the save-path roster: every non-Mypy, non-Ruff file-scoped check."""
|
|
174
|
+
all_specifications = (
|
|
175
|
+
*_python_specifications(),
|
|
176
|
+
*_test_specifications(),
|
|
177
|
+
*_non_python_specifications(),
|
|
178
|
+
)
|
|
179
|
+
return tuple(
|
|
180
|
+
each_specification
|
|
181
|
+
for each_specification in all_specifications
|
|
182
|
+
if each_specification.display_name not in excluded_validator_names
|
|
214
183
|
)
|
|
215
184
|
|
|
216
185
|
|
|
@@ -250,11 +219,14 @@ def _run_specification(
|
|
|
250
219
|
return _fast_save_outcome(specification, False, violation_report)
|
|
251
220
|
|
|
252
221
|
|
|
253
|
-
def run_fast_save_validators(
|
|
222
|
+
def run_fast_save_validators(
|
|
223
|
+
files: "list[Path]", excluded_validator_names: Collection[str] = ()
|
|
224
|
+
) -> "list[ValidatorResult]":
|
|
254
225
|
"""Run every save-path validator in-process, skipping Mypy and Ruff.
|
|
255
226
|
|
|
256
227
|
Args:
|
|
257
|
-
files:
|
|
228
|
+
files: Files under validation. Gate mode passes one reconstructed file.
|
|
229
|
+
excluded_validator_names: Display names omitted from this run.
|
|
258
230
|
|
|
259
231
|
Returns:
|
|
260
232
|
One result per in-process validator, in roster order. The caller adds
|
|
@@ -262,5 +234,7 @@ def run_fast_save_validators(files: "list[Path]") -> "list[ValidatorResult]":
|
|
|
262
234
|
"""
|
|
263
235
|
return [
|
|
264
236
|
_run_specification(each_specification, files)
|
|
265
|
-
for each_specification in _all_fast_save_validator_specifications(
|
|
237
|
+
for each_specification in _all_fast_save_validator_specifications(
|
|
238
|
+
excluded_validator_names
|
|
239
|
+
)
|
|
266
240
|
]
|