claude-dev-env 8.2.0 → 8.4.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/agents/poteto-agent.md +6 -0
- package/.agents/agents/test_agent_frontmatter.py +9 -9
- package/.agents/agents/test_pr_description_writer_style.py +1 -1
- package/.agents/agents-archived/ARCHIVE-MANIFEST.md +15 -0
- package/.agents/{agents → agents-archived}/code-quality-agent.md +1 -1
- package/.agents/{agents → agents-archived}/session-advisor.md +1 -1
- package/.agents/skills/_shared/end-of-run-gotcha-recommendations.md +2 -2
- package/.agents/skills/grok-spawn/SKILL.md +9 -6
- package/.agents/skills/grok-spawn/reference/flag-profiles.md +12 -8
- package/.agents/skills/issue-tracker/SKILL.md +2 -2
- package/.agents/skills/orchestrator/SKILL.md +16 -15
- package/.agents/skills/orchestrator-refresh/SKILL.md +2 -0
- package/.agents/skills/pull-request/SKILL.md +14 -5
- package/.agents/skills/pull-request/reference/publication-tasks.md +1 -1
- package/.agents/skills/team-advisor/SKILL.md +13 -8
- package/_shared/advisor/reference/spawn-walk-log.md +2 -1
- package/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py +3 -0
- package/_shared/advisor/scripts/model_tier_run_validator.py +169 -14
- package/_shared/advisor/scripts/tests/test_codex_astra_advisor.py +20 -0
- package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +99 -0
- package/bin/install.agents-home.test.mjs +54 -94
- package/bin/install.mjs +31 -24
- package/bin/install.test.mjs +35 -0
- package/bin/resolve-package-managed-directory.test.mjs +1 -1
- package/bin/subagent-model-policy.test.mjs +15 -8
- package/docs/references/advisor-tool.md +32 -0
- package/docs/references/team-advisor-skill.md +1 -1
- package/docs/subagent-model-routing.md +6 -5
- package/hooks/blocking/tdd_enforcer.py +2 -3
- package/hooks/blocking/test_bash_post_call_dispatcher.py +7 -24
- package/hooks/hooks_constants/bash_post_call_dispatcher_constants.py +0 -3
- package/hooks/hooks_constants/hosted_hook_runner.py +1 -1
- package/hooks/hooks_constants/msys_path_conversion_advisor_constants.py +3 -35
- package/hooks/hooks_constants/test_bash_post_call_dispatcher_constants.py +2 -10
- package/hooks/routing/subagent_model_routing.mjs +15 -0
- package/hooks/routing/subagent_model_routing.test.mjs +58 -25
- package/hooks/validators/README.md +0 -1
- package/hooks/validators/test_files/EXECUTIVE_SUMMARY.md +1 -2
- package/package.json +1 -1
- package/rules/git-workflow.md +3 -3
- package/rules/shell-invocation.md +0 -2
- package/rules/subagent-model-policy.json +5 -13
- package/rules/testing.md +0 -2
- package/scripts/dev_env_scripts_constants/grok_worker_constants.py +3 -3
- package/scripts/grok_headless_runner.py +19 -1
- package/scripts/grok_worker_preflight.py +67 -1
- package/scripts/policy_lint/config/approved_test_pairs.py +9 -0
- package/scripts/resolve_advisor_model_route.mjs +2 -2
- package/scripts/subagent_model_policy.mjs +4 -1
- package/scripts/test_grok_headless_runner.py +35 -1
- package/scripts/test_grok_worker_preflight.py +73 -0
- package/scripts/tests/resolve_advisor_model_route.test.mjs +3 -3
- package/scripts/tests/test_policy_lint_rules_pairing.py +16 -0
- package/scripts/tests/test_rules.py +5 -5
- package/scripts/windows/pstack/pstack-command-shim.ps1 +41 -0
- package/scripts/windows/pstack/ship-pr.cmd +3 -0
- package/scripts/windows/pstack/watch-pr.cmd +3 -0
- package/hooks/advisory/msys_path_conversion_advisor.py +0 -200
- package/hooks/advisory/test_msys_path_conversion_advisor.py +0 -223
- package/hooks/hooks_constants/test_failure_recorder_constants.py +0 -34
- package/hooks/observability/test_failure_recorder.py +0 -221
- package/hooks/observability/test_test_failure_recorder.py +0 -173
- package/hooks/validators/VALIDATION_REPORT.md +0 -287
- package/hooks/validators/validator_defaults.py +0 -5
- /package/.agents/{agents → agents-archived}/clean-coder.md +0 -0
- /package/.agents/{agents → agents-archived}/git-commit-crafter.md +0 -0
- /package/.agents/{agents → agents-archived}/issue-tracker.md +0 -0
- /package/.agents/{agents → agents-archived}/plan-packet-validator.md +0 -0
- /package/.agents/{agents → agents-archived}/pr-description-writer.md +0 -0
- /package/.agents/{agents → agents-archived}/skill-writer-agent.md +0 -0
package/rules/testing.md
CHANGED
|
@@ -26,5 +26,3 @@ Tests exercise production behavior, production data, and production code paths.
|
|
|
26
26
|
## No Gate Holds the Test-First Order
|
|
27
27
|
|
|
28
28
|
`tdd_enforcer.py` once required a fresh, failing test before a production module was written. Nothing runs it now, and the staged policy lint carries no replacement. Hold the red-green-refactor order yourself, and let review check it on the diff.
|
|
29
|
-
|
|
30
|
-
`hooks/observability/test_failure_recorder.py` still runs on every Bash call. It records a single unchained pytest run that names a test file path and reports a failing exit status, storing the command, that exit status, and the path in the content-hash store. `content_hash_store.py`'s module docstring holds the full contract. No gate reads that store today, so the record serves as history rather than a precondition.
|
|
@@ -85,14 +85,14 @@ ROLE_BUGTEAM: str = "bugteam"
|
|
|
85
85
|
"""Role name whose agent definition set the preflight validates by default."""
|
|
86
86
|
|
|
87
87
|
ROLE_CLEAN_CODER: str = "clean-coder"
|
|
88
|
-
"""
|
|
88
|
+
"""Compatibility role for FIX and standards-fix using poteto-agent."""
|
|
89
89
|
|
|
90
90
|
DEFAULT_ROLE: str = ROLE_BUGTEAM
|
|
91
91
|
"""Role applied when the caller does not pass ``--role``."""
|
|
92
92
|
|
|
93
93
|
ALL_AGENT_FILENAMES_BY_ROLE: dict[str, tuple[str, ...]] = {
|
|
94
|
-
ROLE_BUGTEAM: ("
|
|
95
|
-
ROLE_CLEAN_CODER: ("
|
|
94
|
+
ROLE_BUGTEAM: ("poteto-agent.md",),
|
|
95
|
+
ROLE_CLEAN_CODER: ("poteto-agent.md",),
|
|
96
96
|
}
|
|
97
97
|
"""Agent definition filenames required under ``agents/`` for each known role."""
|
|
98
98
|
|
|
@@ -19,7 +19,7 @@ Import ``run_headless_worker`` for the outcome object::
|
|
|
19
19
|
working_directory=cwd,
|
|
20
20
|
run_state_directory=run_dir,
|
|
21
21
|
timeout_seconds=5400,
|
|
22
|
-
agent_name="
|
|
22
|
+
agent_name="poteto-agent",
|
|
23
23
|
)
|
|
24
24
|
"""
|
|
25
25
|
|
|
@@ -72,6 +72,7 @@ from dev_env_scripts_constants.grok_worker_constants import (
|
|
|
72
72
|
WORKER_SPEC_TIMEOUT_KEY,
|
|
73
73
|
)
|
|
74
74
|
from shared_tree_paths import resolve_shared_process_tree_scripts_directory
|
|
75
|
+
from grok_worker_preflight import poteto_mode_skill_path
|
|
75
76
|
|
|
76
77
|
_shared_process_tree_scripts_directory = resolve_shared_process_tree_scripts_directory(
|
|
77
78
|
__file__,
|
|
@@ -420,6 +421,23 @@ def run_headless_worker(
|
|
|
420
421
|
if leader_socket_path is not None
|
|
421
422
|
else _mint_leader_socket_path(run_state_directory)
|
|
422
423
|
)
|
|
424
|
+
if agent_name == "poteto-agent":
|
|
425
|
+
try:
|
|
426
|
+
skill_path = poteto_mode_skill_path().resolve()
|
|
427
|
+
skill_text = skill_path.read_text(encoding=UTF8_ENCODING)
|
|
428
|
+
task_text = prompt_file.read_text(encoding=UTF8_ENCODING)
|
|
429
|
+
run_state_directory.mkdir(parents=True, exist_ok=True)
|
|
430
|
+
prompt_file = run_state_directory / f"{resolved_leader_socket_path.stem}-poteto.txt"
|
|
431
|
+
prompt_file.write_text(
|
|
432
|
+
f"Poteto skill source: {skill_path}\n"
|
|
433
|
+
"Resolve companion skill paths against that source directory.\n"
|
|
434
|
+
"The task below defines your review, build, or translation duty. "
|
|
435
|
+
"Preserve its scope, write permissions, and report contract.\n\n"
|
|
436
|
+
f"{skill_text}\n\nTask:\n{task_text}",
|
|
437
|
+
encoding=UTF8_ENCODING,
|
|
438
|
+
)
|
|
439
|
+
except OSError as error:
|
|
440
|
+
return _launch_failure_outcome(error)
|
|
423
441
|
all_arguments = _build_invocation(
|
|
424
442
|
prompt_file=prompt_file,
|
|
425
443
|
working_directory=working_directory,
|
|
@@ -45,6 +45,7 @@ from __future__ import annotations
|
|
|
45
45
|
|
|
46
46
|
import argparse
|
|
47
47
|
import json
|
|
48
|
+
import os
|
|
48
49
|
import shutil
|
|
49
50
|
import subprocess
|
|
50
51
|
import sys
|
|
@@ -61,6 +62,7 @@ from dev_env_scripts_constants.grok_worker_constants import (
|
|
|
61
62
|
CLAUDE_HOME_SUBDIRECTORY,
|
|
62
63
|
CLI_PING_FLAG,
|
|
63
64
|
CLI_ROLE_FLAG,
|
|
65
|
+
CLAUDE_CONFIG_DIR_ENV_VAR,
|
|
64
66
|
CLI_RUN_STATE_DIR_FLAG,
|
|
65
67
|
DEFAULT_AUTH_TIMEOUT_SECONDS,
|
|
66
68
|
DEFAULT_PING_TIMEOUT_SECONDS,
|
|
@@ -111,7 +113,67 @@ class PreflightOutcome:
|
|
|
111
113
|
|
|
112
114
|
def claude_config_home() -> Path:
|
|
113
115
|
"""Return the per-user Claude configuration directory."""
|
|
114
|
-
|
|
116
|
+
configured_root = os.environ.get(CLAUDE_CONFIG_DIR_ENV_VAR)
|
|
117
|
+
return Path(configured_root) if configured_root else Path.home() / CLAUDE_HOME_SUBDIRECTORY
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _read_pstack_registry(registry_path: Path) -> dict[str, object]:
|
|
121
|
+
try:
|
|
122
|
+
registry = json.loads(registry_path.read_text(encoding=UTF8_ENCODING))
|
|
123
|
+
except (UnicodeError, json.JSONDecodeError) as error:
|
|
124
|
+
raise OSError(f"Invalid pstack installation registry {registry_path}: {error}") from error
|
|
125
|
+
if not isinstance(registry, dict):
|
|
126
|
+
raise OSError(f"Invalid pstack installation registry {registry_path}: root must be an object")
|
|
127
|
+
return registry
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _user_pstack_entries(all_registry: dict[str, object], registry_path: Path) -> list[dict[str, object]]:
|
|
131
|
+
plugins = all_registry.get("plugins")
|
|
132
|
+
if not isinstance(plugins, dict):
|
|
133
|
+
raise OSError(f"Invalid pstack installation registry {registry_path}: plugins must be an object")
|
|
134
|
+
entries = plugins.get("pstack@pstack-claude", [])
|
|
135
|
+
if not isinstance(entries, list) or any(not isinstance(entry, dict) for entry in entries):
|
|
136
|
+
raise OSError(f"Invalid pstack installation registry {registry_path}: pstack entries must be objects")
|
|
137
|
+
user_entries = [entry for entry in entries if entry.get("scope") == "user"]
|
|
138
|
+
if len(user_entries) > 1:
|
|
139
|
+
raise OSError(f"Invalid pstack installation registry {registry_path}: multiple user pstack installations")
|
|
140
|
+
return user_entries
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _native_pstack_skill_path(registry_path: Path, fallback_path: Path) -> Path:
|
|
144
|
+
if not registry_path.exists():
|
|
145
|
+
return fallback_path
|
|
146
|
+
user_entries = _user_pstack_entries(_read_pstack_registry(registry_path), registry_path)
|
|
147
|
+
if not user_entries:
|
|
148
|
+
return fallback_path
|
|
149
|
+
install_path = user_entries[0].get("installPath")
|
|
150
|
+
if not isinstance(install_path, str) or not Path(install_path).is_absolute():
|
|
151
|
+
raise OSError(f"Invalid pstack installation registry {registry_path}: pstack installPath must be absolute")
|
|
152
|
+
return Path(install_path) / "skills" / "poteto-mode" / "SKILL.md"
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def poteto_mode_skill_path() -> Path:
|
|
156
|
+
"""Return the selected installed pstack poteto-mode skill path.
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
The validated poteto-mode skill path.
|
|
160
|
+
|
|
161
|
+
Raises:
|
|
162
|
+
OSError: If the registry or selected skill is invalid.
|
|
163
|
+
"""
|
|
164
|
+
config_root = claude_config_home()
|
|
165
|
+
fallback_path = config_root / "skills" / "poteto-mode" / "SKILL.md"
|
|
166
|
+
skill_path = _native_pstack_skill_path(
|
|
167
|
+
config_root / "plugins" / "installed_plugins.json",
|
|
168
|
+
fallback_path,
|
|
169
|
+
)
|
|
170
|
+
try:
|
|
171
|
+
skill_text = skill_path.read_text(encoding=UTF8_ENCODING)
|
|
172
|
+
except UnicodeError as error:
|
|
173
|
+
raise OSError(f"Invalid UTF-8 poteto skill {skill_path}") from error
|
|
174
|
+
if not skill_text.strip():
|
|
175
|
+
raise OSError(f"Empty poteto skill {skill_path}")
|
|
176
|
+
return skill_path
|
|
115
177
|
|
|
116
178
|
|
|
117
179
|
def _install_manifest_path() -> Path:
|
|
@@ -218,6 +280,10 @@ def _is_grok_binary_resolvable() -> bool:
|
|
|
218
280
|
def _is_claude_dev_env_config_present(role: str) -> bool:
|
|
219
281
|
if not _install_manifest_path().is_file():
|
|
220
282
|
return False
|
|
283
|
+
try:
|
|
284
|
+
poteto_mode_skill_path()
|
|
285
|
+
except OSError:
|
|
286
|
+
return False
|
|
221
287
|
all_agent_filenames = ALL_AGENT_FILENAMES_BY_ROLE.get(role)
|
|
222
288
|
if all_agent_filenames is None:
|
|
223
289
|
return False
|
|
@@ -5,6 +5,15 @@ from pathlib import PurePosixPath
|
|
|
5
5
|
APPROVED_TEST_PATHS_BY_PRODUCTION_PATH: dict[
|
|
6
6
|
PurePosixPath, frozenset[PurePosixPath]
|
|
7
7
|
] = {
|
|
8
|
+
PurePosixPath(
|
|
9
|
+
".cursor/skills/verify-claude-dev-env/scripts/verify_installer_constants/constants.mjs"
|
|
10
|
+
): frozenset(
|
|
11
|
+
{
|
|
12
|
+
PurePosixPath(
|
|
13
|
+
".cursor/skills/verify-claude-dev-env/scripts/verify-installer.test.mjs"
|
|
14
|
+
),
|
|
15
|
+
}
|
|
16
|
+
),
|
|
8
17
|
PurePosixPath("packages/claude-dev-env/bin/install-constants.mjs"): frozenset(
|
|
9
18
|
{
|
|
10
19
|
PurePosixPath("packages/claude-dev-env/bin/install.cursor-rules.test.mjs"),
|
|
@@ -36,14 +36,14 @@ if (input !== undefined) {
|
|
|
36
36
|
routingDecision = routingInputIssue
|
|
37
37
|
? blockedRoute(routingInputIssue)
|
|
38
38
|
: resolveSubagentModelRoute({
|
|
39
|
-
agent_type: '
|
|
39
|
+
agent_type: 'advisor',
|
|
40
40
|
...(input.model !== undefined && input.model !== null ? { model: input.model } : {}),
|
|
41
41
|
...(input.reasoning_effort !== undefined ? { reasoning_effort: input.reasoning_effort } : {}),
|
|
42
42
|
}, {
|
|
43
43
|
policyPath: input.policyPath,
|
|
44
44
|
trustedSessionMetadata: {
|
|
45
45
|
authorized: true,
|
|
46
|
-
registeredAgentType: '
|
|
46
|
+
registeredAgentType: 'advisor',
|
|
47
47
|
},
|
|
48
48
|
});
|
|
49
49
|
}
|
|
@@ -298,7 +298,10 @@ function validateRoles(roles) {
|
|
|
298
298
|
const aliases = validateStringList(roles[role], `roles.${role}`).map(normalizeToken);
|
|
299
299
|
const allAliases = [role, ...aliases];
|
|
300
300
|
for (const alias of allAliases) {
|
|
301
|
-
|
|
301
|
+
const roleParts = alias.split(':');
|
|
302
|
+
if (roleParts.length > 2 || !roleParts.every(isIdentifier)) {
|
|
303
|
+
throw new SubagentModelPolicyError(`invalid role name: ${alias}`);
|
|
304
|
+
}
|
|
302
305
|
const previousRole = roleByAlias.get(alias);
|
|
303
306
|
if (previousRole && previousRole !== role) {
|
|
304
307
|
throw new SubagentModelPolicyError(`role alias is ambiguous: ${alias}`);
|
|
@@ -7,6 +7,7 @@ include the signature substrings the constants module lists.
|
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
+
import json
|
|
10
11
|
import subprocess
|
|
11
12
|
import sys
|
|
12
13
|
import time
|
|
@@ -336,7 +337,11 @@ def test_argv_includes_agent_when_named(
|
|
|
336
337
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
337
338
|
) -> None:
|
|
338
339
|
fake_process = _FakeProcess(returncode=0, stdout='{"ok":true}')
|
|
339
|
-
agent_name = "
|
|
340
|
+
agent_name = "poteto-agent"
|
|
341
|
+
skill_path = tmp_path / "poteto-mode" / "SKILL.md"
|
|
342
|
+
skill_path.parent.mkdir()
|
|
343
|
+
skill_path.write_text("Keep the requested duty.\n", encoding="utf-8")
|
|
344
|
+
monkeypatch.setattr(runner, "poteto_mode_skill_path", lambda: skill_path)
|
|
340
345
|
outcome, recorder, _, _, _ = _run_once(
|
|
341
346
|
monkeypatch, tmp_path, fake_process, agent_name=agent_name
|
|
342
347
|
)
|
|
@@ -345,6 +350,35 @@ def test_argv_includes_agent_when_named(
|
|
|
345
350
|
invocation = recorder.invocations[0]
|
|
346
351
|
assert AGENT_FLAG in invocation
|
|
347
352
|
assert agent_name in invocation
|
|
353
|
+
generated_prompt = Path(invocation[invocation.index(PROMPT_FILE_FLAG) + 1])
|
|
354
|
+
assert "Keep the requested duty." in generated_prompt.read_text(encoding="utf-8")
|
|
355
|
+
assert str(skill_path) in generated_prompt.read_text(encoding="utf-8")
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def test_native_plugin_skill_is_included_in_worker_prompt(
|
|
359
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
360
|
+
) -> None:
|
|
361
|
+
config_root = tmp_path / "profile"
|
|
362
|
+
registry_path = config_root / "plugins" / "installed_plugins.json"
|
|
363
|
+
registry_path.parent.mkdir(parents=True)
|
|
364
|
+
plugin_root = tmp_path / "plugin" / "version"
|
|
365
|
+
registry_path.write_text(json.dumps({"plugins": {"pstack@pstack-claude": [
|
|
366
|
+
{"scope": "user", "installPath": str(plugin_root)},
|
|
367
|
+
]}}), encoding="utf-8")
|
|
368
|
+
skill_path = plugin_root / "skills" / "poteto-mode" / "SKILL.md"
|
|
369
|
+
skill_path.parent.mkdir(parents=True)
|
|
370
|
+
skill_path.write_text("Use the native workflow.\n", encoding="utf-8")
|
|
371
|
+
monkeypatch.setenv("CLAUDE_CONFIG_DIR", str(config_root))
|
|
372
|
+
outcome, recorder, _, _, _ = _run_once(
|
|
373
|
+
monkeypatch, tmp_path, _FakeProcess(returncode=0, stdout="done"),
|
|
374
|
+
agent_name="poteto-agent",
|
|
375
|
+
)
|
|
376
|
+
assert outcome.is_ok
|
|
377
|
+
invocation = recorder.invocations[0]
|
|
378
|
+
prompt = Path(invocation[invocation.index(PROMPT_FILE_FLAG) + 1]).read_text(encoding="utf-8")
|
|
379
|
+
assert str(skill_path.resolve()) in prompt
|
|
380
|
+
assert "Use the native workflow." in prompt
|
|
381
|
+
assert "Resolve companion skill paths against that source directory." in prompt
|
|
348
382
|
|
|
349
383
|
|
|
350
384
|
def test_unique_leader_socket_path_per_call(
|
|
@@ -80,6 +80,9 @@ class _Recorder:
|
|
|
80
80
|
def _write_install_layout(claude_home: Path, *, role: str = ROLE_BUGTEAM) -> None:
|
|
81
81
|
claude_home.mkdir(parents=True, exist_ok=True)
|
|
82
82
|
(claude_home / MANIFEST_FILENAME).write_text("{}", encoding=UTF8_ENCODING)
|
|
83
|
+
skill_path = claude_home / "skills" / "poteto-mode" / "SKILL.md"
|
|
84
|
+
skill_path.parent.mkdir(parents=True, exist_ok=True)
|
|
85
|
+
skill_path.write_text("Poteto guidance\n", encoding=UTF8_ENCODING)
|
|
83
86
|
agents_directory = claude_home / AGENTS_SUBDIRECTORY
|
|
84
87
|
agents_directory.mkdir(parents=True, exist_ok=True)
|
|
85
88
|
for each_filename in ALL_AGENT_FILENAMES_BY_ROLE[role]:
|
|
@@ -88,6 +91,76 @@ def _write_install_layout(claude_home: Path, *, role: str = ROLE_BUGTEAM) -> Non
|
|
|
88
91
|
)
|
|
89
92
|
|
|
90
93
|
|
|
94
|
+
def test_missing_poteto_skill_rejects_installed_agent(
|
|
95
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
96
|
+
) -> None:
|
|
97
|
+
_write_install_layout(tmp_path)
|
|
98
|
+
monkeypatch.setattr(preflight, "claude_config_home", lambda: tmp_path)
|
|
99
|
+
assert preflight._is_claude_dev_env_config_present(ROLE_BUGTEAM)
|
|
100
|
+
preflight.poteto_mode_skill_path().unlink()
|
|
101
|
+
assert not preflight._is_claude_dev_env_config_present(ROLE_BUGTEAM)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _write_native_pstack(config_root: Path, plugin_root: Path) -> Path:
|
|
105
|
+
registry_path = config_root / "plugins" / "installed_plugins.json"
|
|
106
|
+
registry_path.parent.mkdir(parents=True, exist_ok=True)
|
|
107
|
+
registry_path.write_text(
|
|
108
|
+
json.dumps({"plugins": {"pstack@pstack-claude": [
|
|
109
|
+
{"scope": "user", "installPath": str(plugin_root)},
|
|
110
|
+
{"scope": "project", "installPath": str(plugin_root / "other")},
|
|
111
|
+
]}}),
|
|
112
|
+
encoding=UTF8_ENCODING,
|
|
113
|
+
)
|
|
114
|
+
skill_path = plugin_root / "skills" / "poteto-mode" / "SKILL.md"
|
|
115
|
+
skill_path.parent.mkdir(parents=True)
|
|
116
|
+
skill_path.write_text("Native poteto guidance\n", encoding=UTF8_ENCODING)
|
|
117
|
+
companion = skill_path.parent / "playbooks" / "bug-fix.md"
|
|
118
|
+
companion.parent.mkdir()
|
|
119
|
+
companion.write_text("Fix guidance\n", encoding=UTF8_ENCODING)
|
|
120
|
+
return skill_path
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _damage_native_plugin(config_root: Path, native_skill: Path, damage: str) -> None:
|
|
124
|
+
if damage == "missing":
|
|
125
|
+
native_skill.unlink()
|
|
126
|
+
return
|
|
127
|
+
if damage == "empty":
|
|
128
|
+
native_skill.write_text("", encoding=UTF8_ENCODING)
|
|
129
|
+
return
|
|
130
|
+
if damage == "encoding":
|
|
131
|
+
native_skill.write_bytes(b"\xff")
|
|
132
|
+
return
|
|
133
|
+
(config_root / "plugins" / "installed_plugins.json").write_text(
|
|
134
|
+
"{", encoding=UTF8_ENCODING
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def test_native_plugin_source_overrides_shared_skill(
|
|
139
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
140
|
+
) -> None:
|
|
141
|
+
config_root = tmp_path / "profile"
|
|
142
|
+
_write_install_layout(config_root)
|
|
143
|
+
native_skill = _write_native_pstack(config_root, tmp_path / "plugin-version")
|
|
144
|
+
monkeypatch.setenv("CLAUDE_CONFIG_DIR", str(config_root))
|
|
145
|
+
assert preflight.poteto_mode_skill_path() == native_skill
|
|
146
|
+
assert (native_skill.parent / "playbooks" / "bug-fix.md").read_text() == "Fix guidance\n"
|
|
147
|
+
assert preflight._is_claude_dev_env_config_present(ROLE_BUGTEAM)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@pytest.mark.parametrize("damage", ["missing", "empty", "encoding", "registry"])
|
|
151
|
+
def test_broken_native_plugin_does_not_fall_back_to_shared_skill(
|
|
152
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path, damage: str
|
|
153
|
+
) -> None:
|
|
154
|
+
config_root = tmp_path / "profile"
|
|
155
|
+
_write_install_layout(config_root)
|
|
156
|
+
native_skill = _write_native_pstack(config_root, tmp_path / "plugin-version")
|
|
157
|
+
monkeypatch.setenv("CLAUDE_CONFIG_DIR", str(config_root))
|
|
158
|
+
_damage_native_plugin(config_root, native_skill, damage)
|
|
159
|
+
with pytest.raises(OSError):
|
|
160
|
+
preflight.poteto_mode_skill_path()
|
|
161
|
+
assert not preflight._is_claude_dev_env_config_present(ROLE_BUGTEAM)
|
|
162
|
+
|
|
163
|
+
|
|
91
164
|
def _install_ok_static_seams(
|
|
92
165
|
monkeypatch: pytest.MonkeyPatch,
|
|
93
166
|
claude_home: Path,
|
|
@@ -12,12 +12,12 @@ function runBridge(input) {
|
|
|
12
12
|
});
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
test('advisor bridge
|
|
16
|
-
const result = runBridge(JSON.stringify({ reasoning_effort: '
|
|
15
|
+
test('advisor bridge uses the trusted advisor registration', () => {
|
|
16
|
+
const result = runBridge(JSON.stringify({ model: 'Astra', reasoning_effort: 'high' }));
|
|
17
17
|
assert.equal(result.status, 0);
|
|
18
18
|
assert.deepEqual(JSON.parse(result.stdout).selected, {
|
|
19
19
|
model: 'gpt-6-astra',
|
|
20
|
-
effort: '
|
|
20
|
+
effort: 'high',
|
|
21
21
|
});
|
|
22
22
|
});
|
|
23
23
|
|
|
@@ -182,6 +182,22 @@ def test_pairing_rejects_an_approved_module_without_its_suite(
|
|
|
182
182
|
assert all_paths == (_APPROVED_PRODUCTION_PATH,)
|
|
183
183
|
|
|
184
184
|
|
|
185
|
+
def test_cursor_verifier_constants_require_the_changed_driver_suite(
|
|
186
|
+
tmp_path: Path,
|
|
187
|
+
) -> None:
|
|
188
|
+
constants_path = PurePosixPath(
|
|
189
|
+
".cursor/skills/verify-claude-dev-env/scripts/verify_installer_constants/constants.mjs"
|
|
190
|
+
)
|
|
191
|
+
suite_path = PurePosixPath(
|
|
192
|
+
".cursor/skills/verify-claude-dev-env/scripts/verify-installer.test.mjs"
|
|
193
|
+
)
|
|
194
|
+
constants_change = _body_change_at(constants_path)
|
|
195
|
+
assert _diagnostic_paths(tmp_path, constants_change) == (constants_path,)
|
|
196
|
+
assert _diagnostic_paths(
|
|
197
|
+
tmp_path, constants_change, _body_change_at(suite_path)
|
|
198
|
+
) == ()
|
|
199
|
+
|
|
200
|
+
|
|
185
201
|
def test_pairing_rejects_an_unrelated_module_beside_a_changed_approved_suite(
|
|
186
202
|
tmp_path: Path,
|
|
187
203
|
) -> None:
|
|
@@ -94,8 +94,8 @@ def _comment_policy_surfaces(package_root: Path) -> tuple[Path, ...]:
|
|
|
94
94
|
package_root / "AGENTS.md",
|
|
95
95
|
package_root / "docs" / "CODE_RULES.md",
|
|
96
96
|
package_root / "system-prompts" / "software-engineer.xml",
|
|
97
|
-
package_root / ".agents" / "agents" / "clean-coder.md",
|
|
98
|
-
package_root / ".agents" / "agents" / "code-quality-agent.md",
|
|
97
|
+
package_root / ".agents" / "agents-archived" / "clean-coder.md",
|
|
98
|
+
package_root / ".agents" / "agents-archived" / "code-quality-agent.md",
|
|
99
99
|
package_root / "audit-rubrics" / "category_rubrics" / "category-j-code-rules-compliance.md",
|
|
100
100
|
package_root / "audit-rubrics" / "prompts" / "category-j-code-rules-compliance.md",
|
|
101
101
|
package_root / "audit-rubrics" / "category_rubrics" / "category-l-behavior-equivalence.md",
|
|
@@ -119,7 +119,7 @@ def _worker_policy_surfaces(package_root: Path) -> tuple[Path, ...]:
|
|
|
119
119
|
package_root / "AGENTS.md",
|
|
120
120
|
package_root / "docs" / "CODE_RULES.md",
|
|
121
121
|
package_root / "system-prompts" / "software-engineer.xml",
|
|
122
|
-
package_root / ".agents" / "agents" / "clean-coder.md",
|
|
122
|
+
package_root / ".agents" / "agents-archived" / "clean-coder.md",
|
|
123
123
|
package_root / ".agents" / "skills" / "grok-spawn" / "reference" / "worker-briefs.md",
|
|
124
124
|
package_root.parent.parent / ".github" / "copilot-instructions.md",
|
|
125
125
|
)
|
|
@@ -185,14 +185,14 @@ def _category_l_policy_texts() -> tuple[str, str, str]:
|
|
|
185
185
|
|
|
186
186
|
|
|
187
187
|
def _comment_policy_summary_texts() -> tuple[str, str, str]:
|
|
188
|
-
clean_coder = _PACKAGE_ROOT / ".agents" / "agents" / "clean-coder.md"
|
|
188
|
+
clean_coder = _PACKAGE_ROOT / ".agents" / "agents-archived" / "clean-coder.md"
|
|
189
189
|
category_j = (
|
|
190
190
|
_PACKAGE_ROOT
|
|
191
191
|
/ "audit-rubrics"
|
|
192
192
|
/ "category_rubrics"
|
|
193
193
|
/ "category-j-code-rules-compliance.md"
|
|
194
194
|
)
|
|
195
|
-
code_quality = _PACKAGE_ROOT / ".agents" / "agents" / "code-quality-agent.md"
|
|
195
|
+
code_quality = _PACKAGE_ROOT / ".agents" / "agents-archived" / "code-quality-agent.md"
|
|
196
196
|
return tuple(
|
|
197
197
|
each_path.read_text(encoding="utf-8").lower()
|
|
198
198
|
for each_path in (clean_coder, category_j, code_quality)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
$command = $args[0]
|
|
2
|
+
$commandArguments = @($args | Select-Object -Skip 1)
|
|
3
|
+
|
|
4
|
+
if (@("watch-pr", "ship-pr") -notcontains $command) {
|
|
5
|
+
Write-Error "The command must be watch-pr or ship-pr"
|
|
6
|
+
exit 1
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
$codexHome = if ([string]::IsNullOrWhiteSpace($env:CODEX_HOME)) {
|
|
10
|
+
Join-Path $env:USERPROFILE ".codex"
|
|
11
|
+
} else {
|
|
12
|
+
$env:CODEX_HOME
|
|
13
|
+
}
|
|
14
|
+
$cacheRoot = Join-Path $codexHome "plugins\cache\pstack-claude\pstack"
|
|
15
|
+
$pluginRoot = Get-ChildItem -LiteralPath $cacheRoot -Directory -ErrorAction SilentlyContinue |
|
|
16
|
+
Where-Object { $_.Name -match "^\d+\.\d+\.\d+$" } |
|
|
17
|
+
Sort-Object { [version]$_.Name } -Descending |
|
|
18
|
+
Select-Object -First 1
|
|
19
|
+
|
|
20
|
+
if ($null -eq $pluginRoot) {
|
|
21
|
+
Write-Error "No installed pstack-claude version was found under $cacheRoot"
|
|
22
|
+
exit 1
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
$entrypoint = Join-Path $pluginRoot.FullName "skills\poteto-mode\scripts\watch-pr\$command"
|
|
26
|
+
if (-not (Test-Path -LiteralPath $entrypoint -PathType Leaf)) {
|
|
27
|
+
Write-Error "The pstack entrypoint was not found: $entrypoint"
|
|
28
|
+
exit 1
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
$scriptsRoot = Split-Path -Parent (Split-Path -Parent $entrypoint)
|
|
32
|
+
$exitCode = 1
|
|
33
|
+
Push-Location $scriptsRoot
|
|
34
|
+
try {
|
|
35
|
+
& bun $entrypoint @commandArguments
|
|
36
|
+
$exitCode = $LASTEXITCODE
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
Pop-Location
|
|
40
|
+
}
|
|
41
|
+
exit $exitCode
|