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.
Files changed (99) hide show
  1. package/.agents/skills/_shared/advisor/scripts/README.md +1 -1
  2. package/.agents/skills/autoconverge/SKILL.md +19 -15
  3. package/.agents/skills/autoconverge/reference/gotchas.md +3 -3
  4. package/.agents/skills/autoconverge/reference/multi-pr.md +6 -2
  5. package/.agents/skills/autoconverge/workflow/converge.contract.test.mjs +11 -0
  6. package/.agents/skills/autoconverge/workflow/converge.mjs +26 -33
  7. package/.agents/skills/autoconverge/workflow/converge.run-input.test.mjs +26 -2
  8. package/.agents/skills/autoconverge/workflow/converge_multi.mjs +12 -28
  9. package/.agents/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +16 -0
  10. package/.agents/skills/team-advisor/SKILL.md +11 -11
  11. package/_shared/advisor/advisor-protocol.md +51 -51
  12. package/_shared/advisor/reference/advisor-block.md +9 -9
  13. package/_shared/advisor/reference/astra-rung.md +39 -0
  14. package/_shared/advisor/reference/identity.md +4 -4
  15. package/_shared/advisor/reference/lifecycle.md +6 -6
  16. package/_shared/advisor/reference/spawn-walk-log.md +9 -9
  17. package/_shared/advisor/reference/third-party-bind.md +11 -11
  18. package/_shared/advisor/reference/warm-up.md +13 -13
  19. package/_shared/advisor/scripts/codex_astra_advisor.py +308 -0
  20. package/_shared/advisor/scripts/codex_astra_preflight.py +115 -0
  21. package/_shared/advisor/scripts/codex_astra_reply.py +159 -0
  22. package/_shared/advisor/scripts/config/advisor_scripts_constants/advisor_route_constants.py +3 -3
  23. package/_shared/advisor/scripts/config/advisor_scripts_constants/astra_advisor_constants.py +38 -0
  24. package/_shared/advisor/scripts/model_tier_run_validator.py +17 -18
  25. package/_shared/advisor/scripts/tests/test_codex_astra_advisor.py +271 -0
  26. package/_shared/advisor/scripts/tests/test_codex_astra_preflight.py +24 -0
  27. package/_shared/advisor/scripts/tests/test_codex_astra_reply.py +42 -0
  28. package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +55 -30
  29. package/_shared/advisor/scripts/tests/test_tier_model_ids.py +14 -4
  30. package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +14 -0
  31. package/_shared/pr-loop/scripts/terminology_sweep.py +138 -50
  32. package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +13 -1
  33. package/bin/cde.mjs +175 -0
  34. package/bin/cde.test.mjs +222 -0
  35. package/bin/install-constants.mjs +4 -0
  36. package/bin/install.cursor-rules.test.mjs +233 -2
  37. package/bin/install.mjs +49 -3
  38. package/bin/pstack-model-preferences.codex.json +23 -0
  39. package/bin/select-pstack-models.test.mjs +378 -0
  40. package/docs/references/team-advisor-skill.md +3 -3
  41. package/hooks/hooks_constants/tool_names.py +20 -0
  42. package/hooks/validators/README.md +35 -1
  43. package/hooks/validators/fast_save_validators.py +63 -89
  44. package/hooks/validators/run_all_validators.py +16 -8
  45. package/hooks/validators/test_fast_save_validators_policy_lint.py +25 -0
  46. package/hooks/validators/test_run_all_validators_policy_lint.py +52 -0
  47. package/package.json +3 -2
  48. package/rules/pstack-models.md +119 -0
  49. package/scripts/cde_lint.py +258 -0
  50. package/scripts/check.ps1 +18 -6
  51. package/scripts/policy_lint/__init__.py +41 -0
  52. package/scripts/policy_lint/adapter_configuration.py +91 -0
  53. package/scripts/policy_lint/adapter_detectors.py +368 -0
  54. package/scripts/policy_lint/adapter_pairing.py +145 -0
  55. package/scripts/policy_lint/adapter_support.py +97 -0
  56. package/scripts/policy_lint/adapters.py +376 -0
  57. package/scripts/policy_lint/config/__init__.py +1 -0
  58. package/scripts/policy_lint/config/constants.py +77 -0
  59. package/scripts/policy_lint/engine.py +224 -0
  60. package/scripts/policy_lint/model.py +388 -0
  61. package/scripts/policy_lint/registry.py +171 -0
  62. package/scripts/policy_lint/render.py +91 -0
  63. package/scripts/policy_lint/selection.py +372 -0
  64. package/scripts/policy_lint/selection_git.py +147 -0
  65. package/scripts/repository_checks/__init__.py +1 -0
  66. package/scripts/repository_checks/claude_md.py +108 -0
  67. package/scripts/repository_checks/config/__init__.py +1 -0
  68. package/scripts/repository_checks/config/constants.py +150 -0
  69. package/scripts/repository_checks/env_var_documentation.py +243 -0
  70. package/scripts/repository_checks/hook_modules.py +34 -0
  71. package/scripts/repository_checks/models.py +37 -0
  72. package/scripts/repository_checks/package_inventory.py +118 -0
  73. package/scripts/repository_checks/pytest_testpaths.py +84 -0
  74. package/scripts/repository_checks/reporting.py +39 -0
  75. package/scripts/repository_checks/runner.py +93 -0
  76. package/scripts/repository_checks/tracked_secrets.py +147 -0
  77. package/scripts/repository_checks/tracked_tree.py +28 -0
  78. package/scripts/repository_policy.py +120 -0
  79. package/scripts/select_pstack_models.mjs +304 -0
  80. package/scripts/tests/repository_policy_test_support.py +89 -0
  81. package/scripts/tests/test_cde_lint.py +398 -0
  82. package/scripts/tests/test_claude_md.py +68 -0
  83. package/scripts/tests/test_env_var_documentation.py +133 -0
  84. package/scripts/tests/test_hook_modules.py +17 -0
  85. package/scripts/tests/test_models.py +26 -0
  86. package/scripts/tests/test_package_inventory.py +96 -0
  87. package/scripts/tests/test_policy_lint_rules_engine.py +381 -0
  88. package/scripts/tests/test_policy_lint_rules_registry.py +369 -0
  89. package/scripts/tests/test_policy_lint_selection.py +373 -0
  90. package/scripts/tests/test_pytest_testpaths.py +66 -0
  91. package/scripts/tests/test_reporting.py +54 -0
  92. package/scripts/tests/test_repository_policy.py +71 -0
  93. package/scripts/tests/test_runner.py +59 -0
  94. package/scripts/tests/test_tracked_secrets.py +175 -0
  95. package/scripts/tests/test_tracked_tree.py +28 -0
  96. package/_shared/advisor/reference/sol-rung.md +0 -39
  97. package/_shared/advisor/scripts/codex_sol_advisor.py +0 -581
  98. package/_shared/advisor/scripts/config/advisor_scripts_constants/sol_advisor_constants.py +0 -38
  99. package/_shared/advisor/scripts/tests/test_codex_sol_advisor.py +0 -740
@@ -0,0 +1,304 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { dirname, join, resolve } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ const inheritanceAliases = new Set(['inherit-parent', 'auto']);
6
+ const hostNamePattern = /^[a-z0-9][a-z0-9_-]*$/;
7
+ const defaultSingleDelegationPanel = Object.freeze({
8
+ agentCount: 1,
9
+ requiresDistinctModels: false,
10
+ });
11
+
12
+ export const allPstackRoleRequirements = Object.freeze({
13
+ 'feature, refactoring': 'reliable code execution',
14
+ 'bug-fix': 'careful diagnosis and code execution',
15
+ 'perf-issue': 'systems reasoning and performance analysis',
16
+ 'hillclimb': 'strong iterative judgment',
17
+ 'judgment and prose': 'strong judgment and clear prose',
18
+ 'hardest tasks': 'highest available reasoning capability',
19
+ 'how explorer': 'fast codebase exploration',
20
+ 'how explainer': 'clear technical synthesis',
21
+ 'how critics': 'independent technical criticism',
22
+ 'why investigators': 'fast evidence gathering',
23
+ 'why synthesizer': 'strong evidence synthesis',
24
+ 'reflect tooling': 'reliable tool and workflow analysis',
25
+ 'reflect judgment, divergent, synthesizer': 'strong judgment and divergent analysis',
26
+ 'arena runners': 'independent solution development',
27
+ 'arena cross-judge pool': 'independent comparative judgment',
28
+ 'swarm workers': 'fast bounded task execution',
29
+ 'architect runners': 'independent architecture design',
30
+ 'interrogate reviewers': 'independent adversarial review',
31
+ });
32
+
33
+ export const defaultPanelByPstackRole = Object.freeze({
34
+ 'how critics': Object.freeze({ agentCount: 3, requiresDistinctModels: false }),
35
+ 'arena runners': Object.freeze({ agentCount: 3, requiresDistinctModels: true }),
36
+ 'swarm workers': defaultSingleDelegationPanel,
37
+ 'architect runners': Object.freeze({ agentCount: 2, requiresDistinctModels: true }),
38
+ 'interrogate reviewers': Object.freeze({ agentCount: 3, requiresDistinctModels: true }),
39
+ });
40
+
41
+ export function selectPstackDelegation(input) {
42
+ validateSelectionInput(input);
43
+ const panel = resolvePanel(input);
44
+ validateDelegationIndex(input.delegationIndex, panel.agentCount);
45
+ const modelsByRole = readHostPreferences(input);
46
+ const allCandidates = collectCandidates(input, modelsByRole[input.role] ?? []);
47
+ const allSelectedCandidates = selectPanelCandidates(allCandidates, panel);
48
+ return buildSelection(input, panel, allSelectedCandidates);
49
+ }
50
+
51
+ function validateSelectionInput(input) {
52
+ if (!input || typeof input !== 'object' || Array.isArray(input)) {
53
+ throw new Error('selection input must be an object');
54
+ }
55
+ validateHost(input.host, input.inventoryHost);
56
+ validateDelegationIndex(input.delegationIndex);
57
+ requireStringArray(input.availableModelIds, 'available model ids');
58
+ requireStringArray(input.confirmedSuitableModelIds, 'confirmed suitable model ids');
59
+ validateRealModelIds(input.availableModelIds, 'available model ids');
60
+ validateParentFallback(input.parentFallback);
61
+ validateConfirmedModels(input);
62
+ if (typeof input.role !== 'string' || !Object.hasOwn(allPstackRoleRequirements, input.role)) {
63
+ throw new Error('role must name a portable pstack role');
64
+ }
65
+ }
66
+
67
+ function validateHost(host, inventoryHost) {
68
+ if (typeof host !== 'string' || !hostNamePattern.test(host)) {
69
+ throw new Error('host must be a lowercase host identifier');
70
+ }
71
+ if (inventoryHost !== host) {
72
+ throw new Error('inventory host must match selected host');
73
+ }
74
+ }
75
+
76
+ function validateDelegationIndex(delegationIndex, agentCount) {
77
+ if (!Number.isInteger(delegationIndex) || delegationIndex < 0) {
78
+ throw new Error('delegation index must be a non-negative integer');
79
+ }
80
+ if (agentCount !== undefined && delegationIndex >= agentCount) {
81
+ throw new Error('delegation index must identify an agent in the panel');
82
+ }
83
+ }
84
+
85
+ function requireStringArray(allEntries, label) {
86
+ if (!Array.isArray(allEntries) || allEntries.some(
87
+ eachEntry => typeof eachEntry !== 'string' || eachEntry === '',
88
+ )) {
89
+ throw new Error(label + ' must be an array of non-empty strings');
90
+ }
91
+ }
92
+
93
+ function validateRealModelIds(allModelIds, label) {
94
+ if (allModelIds.some(eachModelId => inheritanceAliases.has(eachModelId))) {
95
+ throw new Error(label + ' cannot contain inheritance aliases');
96
+ }
97
+ }
98
+
99
+ function validateParentFallback(parentFallback) {
100
+ const hasBooleanFlags = parentFallback
101
+ && typeof parentFallback === 'object'
102
+ && typeof parentFallback.isAllowed === 'boolean'
103
+ && typeof parentFallback.hasMaterialCapabilityLoss === 'boolean';
104
+ if (!hasBooleanFlags) {
105
+ throw new Error('parent fallback flags must be booleans');
106
+ }
107
+ }
108
+
109
+ function validateConfirmedModels(input) {
110
+ validateRealModelIds(input.confirmedSuitableModelIds, 'confirmed suitable model ids');
111
+ const allAvailableModelIds = new Set(input.availableModelIds);
112
+ if (input.confirmedSuitableModelIds.some(
113
+ eachModelId => !allAvailableModelIds.has(eachModelId),
114
+ )) {
115
+ throw new Error('confirmed suitable models must be available');
116
+ }
117
+ }
118
+
119
+ function resolvePanel(input) {
120
+ const panel = input.panel
121
+ ?? defaultPanelByPstackRole[input.role]
122
+ ?? defaultSingleDelegationPanel;
123
+ const hasValidShape = panel
124
+ && typeof panel === 'object'
125
+ && Number.isInteger(panel.agentCount)
126
+ && panel.agentCount > 0
127
+ && typeof panel.requiresDistinctModels === 'boolean';
128
+ if (!hasValidShape) {
129
+ throw new Error('panel requires a positive agent count and a diversity boolean');
130
+ }
131
+ return {
132
+ agentCount: panel.agentCount,
133
+ requiresDistinctModels: panel.requiresDistinctModels,
134
+ };
135
+ }
136
+
137
+ function readHostPreferences(input) {
138
+ const preferencesDirectory = input.preferencesDirectory
139
+ ?? resolve(dirname(fileURLToPath(import.meta.url)), '..', 'rules');
140
+ const preferencesPath = join(
141
+ preferencesDirectory,
142
+ 'pstack-model-preferences.' + input.host + '.json',
143
+ );
144
+ if (!existsSync(preferencesPath)) {
145
+ return {};
146
+ }
147
+ const preferences = JSON.parse(readFileSync(preferencesPath, 'utf8'));
148
+ if (preferences.host !== input.host) {
149
+ throw new Error('preference host must match selected host');
150
+ }
151
+ return preferences.modelsByRole ?? {};
152
+ }
153
+
154
+ function collectCandidates(input, allPreferredModelIds) {
155
+ requireStringArray(allPreferredModelIds, 'role preferences');
156
+ const allAvailableModelIds = new Set(input.availableModelIds);
157
+ const allPreferredCandidates = allPreferredModelIds
158
+ .map(eachModelId => preferenceCandidate(eachModelId, allAvailableModelIds, input))
159
+ .filter(Boolean);
160
+ const allAlternativeCandidates = input.confirmedSuitableModelIds.map(
161
+ eachModelId => modelCandidate(eachModelId, 'confirmed-host-alternative'),
162
+ );
163
+ const allCandidates = deduplicateCandidates([
164
+ ...allPreferredCandidates,
165
+ ...allAlternativeCandidates,
166
+ ]);
167
+ const allCandidatesWithFallback = allCandidates.length === 0
168
+ && input.parentFallback.isAllowed
169
+ ? [parentCandidate()]
170
+ : allCandidates;
171
+ return orderCrossJudgeCandidates(input, allCandidatesWithFallback);
172
+ }
173
+
174
+ function preferenceCandidate(modelId, allAvailableModelIds, input) {
175
+ if (inheritanceAliases.has(modelId)) {
176
+ return input.parentFallback.isAllowed ? parentCandidate() : null;
177
+ }
178
+ return allAvailableModelIds.has(modelId)
179
+ ? modelCandidate(modelId, 'host-preference')
180
+ : null;
181
+ }
182
+
183
+ function modelCandidate(modelId, source) {
184
+ return { kind: 'model', modelId, source };
185
+ }
186
+
187
+ function parentCandidate() {
188
+ return { kind: 'parent', modelId: null, source: 'parent-inheritance' };
189
+ }
190
+
191
+ function deduplicateCandidates(allCandidates) {
192
+ const seenCandidateKeys = new Set();
193
+ return allCandidates.filter(eachCandidate => {
194
+ const candidateKey = eachCandidate.kind + ':' + (eachCandidate.modelId ?? '');
195
+ if (seenCandidateKeys.has(candidateKey)) {
196
+ return false;
197
+ }
198
+ seenCandidateKeys.add(candidateKey);
199
+ return true;
200
+ });
201
+ }
202
+
203
+ function orderCrossJudgeCandidates(input, allCandidates) {
204
+ if (input.role !== 'arena cross-judge pool' || !input.parentModelId) {
205
+ return allCandidates;
206
+ }
207
+ const allOtherCandidates = allCandidates.filter(
208
+ eachCandidate => eachCandidate.kind === 'model'
209
+ && eachCandidate.modelId !== input.parentModelId,
210
+ );
211
+ const allParentCandidates = allCandidates.filter(
212
+ eachCandidate => eachCandidate.kind === 'parent'
213
+ || eachCandidate.modelId === input.parentModelId,
214
+ );
215
+ return [...allOtherCandidates, ...allParentCandidates];
216
+ }
217
+
218
+ function selectPanelCandidates(allCandidates, panel) {
219
+ if (panel.requiresDistinctModels) {
220
+ return allCandidates
221
+ .filter(eachCandidate => eachCandidate.kind === 'model')
222
+ .slice(0, panel.agentCount);
223
+ }
224
+ if (allCandidates.length === 0) {
225
+ return [];
226
+ }
227
+ return Array.from(
228
+ { length: panel.agentCount },
229
+ (_, index) => allCandidates[index % allCandidates.length],
230
+ );
231
+ }
232
+
233
+ function buildSelection(input, requestedPanel, allSelectedCandidates) {
234
+ const panel = reportPanel(requestedPanel, allSelectedCandidates);
235
+ const failure = selectionFailure(input, panel, allSelectedCandidates);
236
+ const selectedCandidate = allSelectedCandidates[input.delegationIndex];
237
+ return {
238
+ selectedHost: input.host,
239
+ role: input.role,
240
+ requiredCapability: allPstackRoleRequirements[input.role],
241
+ availabilityValidated: true,
242
+ canDelegate: failure === null,
243
+ requiresUserChoice: failure !== null,
244
+ failure,
245
+ selectionSource: selectedCandidate?.source ?? null,
246
+ panel,
247
+ nativeSpawnArguments: nativeSpawnArguments(selectedCandidate, failure),
248
+ omitNativeModelArgument: selectedCandidate?.kind === 'parent',
249
+ };
250
+ }
251
+
252
+ function reportPanel(requestedPanel, allSelectedCandidates) {
253
+ const allSelectedModelIds = allSelectedCandidates.map(
254
+ eachCandidate => eachCandidate.modelId,
255
+ );
256
+ const distinctRealModelCount = new Set(allSelectedModelIds.filter(Boolean)).size;
257
+ return {
258
+ agentCount: requestedPanel.agentCount,
259
+ requiresDistinctModels: requestedPanel.requiresDistinctModels,
260
+ selectedModelIds: allSelectedModelIds,
261
+ selectionSources: allSelectedCandidates.map(eachCandidate => eachCandidate.source),
262
+ isCrossModelDiverse: requestedPanel.agentCount > 1
263
+ && distinctRealModelCount === requestedPanel.agentCount,
264
+ };
265
+ }
266
+
267
+ function selectionFailure(input, panel, allSelectedCandidates) {
268
+ if (panel.requiresDistinctModels && !panel.isCrossModelDiverse) {
269
+ return 'distinct-models-unavailable';
270
+ }
271
+ if (allSelectedCandidates.length < panel.agentCount) {
272
+ return 'no-supported-model';
273
+ }
274
+ if (allSelectedCandidates.some(eachCandidate => eachCandidate.kind === 'parent')
275
+ && input.parentFallback.hasMaterialCapabilityLoss) {
276
+ return 'parent-capability-loss';
277
+ }
278
+ return null;
279
+ }
280
+
281
+ function nativeSpawnArguments(selectedCandidate, failure) {
282
+ if (failure || !selectedCandidate || selectedCandidate.kind === 'parent') {
283
+ return {};
284
+ }
285
+ return { model: selectedCandidate.modelId };
286
+ }
287
+
288
+ async function readSelectionInput() {
289
+ let inputText = '';
290
+ for await (const eachChunk of process.stdin) {
291
+ inputText += eachChunk;
292
+ }
293
+ return JSON.parse(inputText);
294
+ }
295
+
296
+ function isEntryPoint() {
297
+ return process.argv[1]
298
+ && resolve(process.argv[1]) === fileURLToPath(import.meta.url);
299
+ }
300
+
301
+ if (isEntryPoint()) {
302
+ const selectionInput = await readSelectionInput();
303
+ console.log(JSON.stringify(selectPstackDelegation(selectionInput)));
304
+ }
@@ -0,0 +1,89 @@
1
+ """Shared fixtures for repository-policy behavior tests."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import io
6
+ import subprocess
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ import pytest
11
+
12
+ SCRIPTS_DIRECTORY = Path(__file__).resolve().parents[1]
13
+ UTF8_ENCODING = "utf-8"
14
+
15
+ if str(SCRIPTS_DIRECTORY) not in sys.path:
16
+ sys.path.insert(0, str(SCRIPTS_DIRECTORY))
17
+
18
+ import repository_policy
19
+
20
+
21
+ def initialize_repository(repository_root: Path) -> None:
22
+ repository_root.mkdir(parents=True, exist_ok=True)
23
+ run_git(repository_root, ("init", "--initial-branch=main"))
24
+
25
+
26
+ def commit_tracked_files(repository_root: Path) -> None:
27
+ run_git(repository_root, ("add", "-A"))
28
+ run_git(repository_root, ("commit", "--quiet", "-m", "seed"))
29
+
30
+
31
+ def write_text(file_path: Path, content: str) -> None:
32
+ file_path.parent.mkdir(parents=True, exist_ok=True)
33
+ file_path.write_text(content, encoding=UTF8_ENCODING)
34
+
35
+
36
+ def patch_unreadable_named_file(
37
+ monkeypatch: pytest.MonkeyPatch, filename: str, error_text: str
38
+ ) -> None:
39
+ original_read_text = Path.read_text
40
+
41
+ def refuse_named_file(
42
+ file_path: Path,
43
+ encoding: str | None = None,
44
+ errors: str | None = None,
45
+ ) -> str:
46
+ if file_path.name == filename:
47
+ raise OSError(error_text)
48
+ return original_read_text(file_path, encoding=encoding, errors=errors)
49
+
50
+ monkeypatch.setattr(Path, "read_text", refuse_named_file)
51
+
52
+
53
+ def seed_clean_repository(repository_root: Path) -> Path:
54
+ initialize_repository(repository_root)
55
+ write_text(repository_root / "README.md", "clean tree\n")
56
+ commit_tracked_files(repository_root)
57
+ return repository_root
58
+
59
+
60
+ def run_policy(
61
+ repository_root: Path, all_arguments: list[str] | None = None
62
+ ) -> tuple[int, str, str]:
63
+ stdout = io.StringIO()
64
+ stderr = io.StringIO()
65
+ exit_code = repository_policy.main(
66
+ all_arguments or [],
67
+ repository_root=repository_root,
68
+ stdout=stdout,
69
+ stderr=stderr,
70
+ )
71
+ return exit_code, stdout.getvalue(), stderr.getvalue()
72
+
73
+
74
+ def run_git(repository_root: Path, all_arguments: tuple[str, ...]) -> None:
75
+ subprocess.run(
76
+ (
77
+ "git",
78
+ "-c",
79
+ "user.email=ci@example.com",
80
+ "-c",
81
+ "user.name=CI",
82
+ "-c",
83
+ "core.hooksPath=",
84
+ *all_arguments,
85
+ ),
86
+ cwd=repository_root,
87
+ check=True,
88
+ capture_output=True,
89
+ )