claude-dev-env 8.73.1 → 8.74.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/test_agent_frontmatter.py +23 -23
- package/.agents/agents-archived/clean-coder.md +5 -5
- package/.agents/skills/correction/playbooks/fix.md +1 -1
- package/.agents/skills/eli5/SKILL.md +3 -3
- package/.agents/skills/pr-lifecycle/SKILL.md +1 -1
- package/.agents/skills/recovering-codex-startup/SKILL.md +1 -1
- package/.agents/skills/recovering-codex-startup/reference/task-seeds.md +1 -1
- package/_shared/advisor/reference/third-party-bind.md +1 -1
- package/bin/cde.mjs +1 -0
- package/bin/cde.test.mjs +1 -0
- package/bin/install.mjs +1 -0
- package/bin/install.policy-lint.test.mjs +1 -0
- package/bin/install.profile-root.test.mjs +1 -1
- package/bin/install.settings-defaults.test.mjs +10 -2
- package/bin/install.test.mjs +1 -0
- package/bin/merge_settings_defaults.mjs +34 -1
- package/bin/merge_settings_defaults.test.mjs +53 -0
- package/commands/fix-followups.md +1 -1
- package/commands/sr-loop.md +1 -1
- package/docs/references/prose-style-enforcement.md +1 -1
- package/docs/references/working-style.md +1 -1
- package/docs/rule-guides/asd-ste100-language.md +9 -1
- package/docs/rule-guides/cleanup-temp-files.md +8 -2
- package/docs/rule-guides/destructive-commands.md +13 -3
- package/docs/rule-guides/explore-thoroughly.md +9 -1
- package/docs/rule-guides/features-start-with-an-eval.md +8 -2
- package/docs/rule-guides/filesystem-search.md +7 -1
- package/docs/rule-guides/flag-non-breaking-findings.md +3 -0
- package/docs/rule-guides/long-horizon-autonomy.md +1 -1
- package/docs/rule-guides/memory-stores-durable-facts.md +9 -3
- package/docs/rule-guides/no-contrast-framing.md +10 -4
- package/docs/rule-guides/prefer-existing-tools.md +2 -2
- package/docs/rule-guides/proof-before-pull-request.md +5 -1
- package/docs/rule-guides/request-scope.md +5 -1
- package/docs/rule-guides/research-mode.md +9 -1
- package/docs/rule-guides/session-title.md +7 -1
- package/docs/rule-guides/shell-invocation.md +9 -1
- package/docs/rule-guides/verify-before-asking.md +7 -1
- package/docs/rule-guides/verify-runtime-state.md +7 -1
- package/hooks/blocking/code_rules_enforcer.py +16 -6
- package/hooks/blocking/code_rules_shared.py +23 -4
- package/hooks/blocking/haiku_spawn_slim_gate.py +96 -0
- package/hooks/blocking/reply_length_gate.py +74 -14
- package/hooks/blocking/state_description_blocker.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_agent_home_tooling.py +54 -1
- package/hooks/blocking/test_code_rules_shared.py +33 -0
- package/hooks/blocking/test_haiku_spawn_slim_gate.py +65 -0
- package/hooks/blocking/test_reply_length_gate.py +184 -5
- package/hooks/blocking/test_state_description_blocker.py +1 -1
- package/hooks/features/conduct-gates.md +3 -6
- package/hooks/features/spawn-routing.md +5 -1
- package/hooks/followup_ledger.py +52 -2
- package/hooks/hooks.json +11 -6
- package/hooks/hooks_constants/code_rules_enforcer_constants.py +1 -1
- package/hooks/hooks_constants/haiku_spawn_slim_gate_constants.py +29 -0
- package/hooks/hooks_constants/reply_length_gate_constants.py +1 -0
- package/hooks/hooks_constants/session_env_cleanup_constants.py +0 -2
- package/hooks/hooks_constants/test_visual_reply_rules_constants.py +15 -0
- package/hooks/hooks_constants/test_windows_filesystem.py +57 -0
- package/hooks/hooks_constants/visual_reply_rules_constants.py +63 -0
- package/hooks/hooks_constants/windows_filesystem.py +51 -0
- package/hooks/session/session_env_cleanup.py +4 -30
- package/hooks/test_followup_ledger.py +58 -0
- package/hooks/test_visual_reply_rules.py +164 -0
- package/hooks/visual_reply_rules.py +268 -0
- package/hooks/workflow/test_auto_formatter.py +4 -25
- package/package.json +1 -1
- package/rules/index.md +30 -0
- package/rules/visual-reply-rules.json +41 -0
- package/scripts/Migrate-ShellPolicy.ps1 +1 -1
- package/scripts/conftest.py +13 -5
- package/scripts/dev_env_scripts_constants/contrast_framing_constants.py +2 -2
- package/scripts/dev_env_scripts_constants/followup_constants.py +4 -1
- package/scripts/dev_env_scripts_constants/test_contrast_framing_constants.py +12 -0
- package/scripts/followup_cli.py +25 -1
- package/scripts/policy_lint/adapter_configuration.py +0 -5
- package/scripts/policy_lint/config/constants.py +1 -1
- package/scripts/repository_checks/config/constants.py +6 -0
- package/scripts/repository_checks/followup_ledger_duplicates.py +55 -0
- package/scripts/repository_checks/runner.py +7 -0
- package/scripts/test_active_capability_references.py +0 -5
- package/scripts/test_broker_state_isolation.py +3 -3
- package/scripts/test_digest_check.py +0 -5
- package/scripts/test_durable_post_lint.py +1 -5
- package/scripts/test_followup_cli.py +18 -6
- package/scripts/test_resolve_worker_spawn.py +5 -10
- package/scripts/test_setup_project_paths.py +0 -12
- package/scripts/test_shared_tree_paths.py +2 -6
- package/scripts/test_validate_instruction_pairs.py +0 -7
- package/scripts/test_verify_installable_package.py +0 -5
- package/scripts/tests/test_contrast_framing.py +3 -3
- package/scripts/tests/test_followup_ledger_duplicates.py +58 -0
- package/scripts/tests/test_repository_policy.py +2 -0
- package/scripts/tests/test_rule_index_shape.py +59 -5
- package/scripts/tests/test_rule_load_scopes.py +1 -16
- package/settings.json +9 -0
- package/system-prompts/software-engineer.xml +4 -4
- package/hooks/blocking/artifact_look_gate.py +0 -223
- package/hooks/blocking/test_artifact_look_gate.py +0 -153
- package/hooks/hooks_constants/artifact_look_gate_constants.py +0 -32
- package/rules/asd-ste100-language.md +0 -9
- package/rules/cleanup-temp-files.md +0 -9
- package/rules/destructive-commands.md +0 -11
- package/rules/explore-thoroughly.md +0 -9
- package/rules/features-start-with-an-eval.md +0 -9
- package/rules/filesystem-search.md +0 -9
- package/rules/memory-stores-durable-facts.md +0 -9
- package/rules/no-contrast-framing.md +0 -9
- package/rules/proof-before-pull-request.md +0 -7
- package/rules/request-scope.md +0 -7
- package/rules/research-mode.md +0 -9
- package/rules/session-title.md +0 -9
- package/rules/shell-invocation.md +0 -9
- package/rules/skill-pointers.md +0 -3
- package/rules/verify-before-asking.md +0 -9
- package/rules/verify-runtime-state.md +0 -9
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Committed follow-up ledger check for exact repeated lines.
|
|
2
|
+
|
|
3
|
+
GitHub's server-side merge ignores ``merge=union``, so repeated lines arrive
|
|
4
|
+
only through a local union merge. This check stops them at the merge queue.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from collections.abc import Sequence
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from types import ModuleType
|
|
12
|
+
|
|
13
|
+
from policy_lint.config import constants as policy_constants
|
|
14
|
+
|
|
15
|
+
from repository_checks.config import constants as repository_constants
|
|
16
|
+
from repository_checks.hook_modules import load_hooks_module
|
|
17
|
+
from repository_checks.models import RepositoryFinding
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def collect_followup_ledger_duplicate_findings(
|
|
21
|
+
repository_root: Path, all_tracked_paths: Sequence[str]
|
|
22
|
+
) -> list[RepositoryFinding]:
|
|
23
|
+
"""Return a finding when the tracked ledger repeats a line.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
repository_root: Git repository root.
|
|
27
|
+
all_tracked_paths: Repository-relative tracked paths.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
One finding naming the repeated-line count, or no findings.
|
|
31
|
+
"""
|
|
32
|
+
ledger = load_hooks_module(repository_constants.FOLLOWUP_LEDGER_MODULE_NAME)
|
|
33
|
+
ledger_path = ledger.followup_ledger_path(repository_root)
|
|
34
|
+
relative_path = ledger_path.relative_to(repository_root).as_posix()
|
|
35
|
+
if relative_path not in all_tracked_paths:
|
|
36
|
+
return []
|
|
37
|
+
repeated_line_count = _repeated_line_count(ledger, ledger_path)
|
|
38
|
+
if not repeated_line_count:
|
|
39
|
+
return []
|
|
40
|
+
return [
|
|
41
|
+
RepositoryFinding(
|
|
42
|
+
repository_constants.CHECK_ID_FOLLOWUP_LEDGER_DUPLICATES,
|
|
43
|
+
relative_path,
|
|
44
|
+
repository_constants.FOLLOWUP_LEDGER_DUPLICATE_MESSAGE_TEMPLATE.format(
|
|
45
|
+
repeated_line_count=repeated_line_count
|
|
46
|
+
),
|
|
47
|
+
)
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _repeated_line_count(ledger: ModuleType, ledger_path: Path) -> int:
|
|
52
|
+
all_ledger_lines = ledger_path.read_text(
|
|
53
|
+
encoding=policy_constants.UTF8_ENCODING
|
|
54
|
+
).splitlines()
|
|
55
|
+
return len(all_ledger_lines) - len(ledger.deduplicated_lines(all_ledger_lines))
|
|
@@ -11,6 +11,9 @@ from repository_checks.config import constants as repository_constants
|
|
|
11
11
|
from repository_checks.env_var_documentation import (
|
|
12
12
|
collect_env_var_documentation_findings,
|
|
13
13
|
)
|
|
14
|
+
from repository_checks.followup_ledger_duplicates import (
|
|
15
|
+
collect_followup_ledger_duplicate_findings,
|
|
16
|
+
)
|
|
14
17
|
from repository_checks.models import RepositoryCheckReport, RepositoryFinding
|
|
15
18
|
from repository_checks.package_inventory import collect_package_inventory_findings
|
|
16
19
|
from repository_checks.pytest_testpaths import collect_pytest_testpath_findings
|
|
@@ -81,6 +84,10 @@ def _all_collectors() -> tuple[tuple[str, RepositoryCollector], ...]:
|
|
|
81
84
|
repository_constants.CHECK_ID_ENV_VAR_DOCUMENTATION,
|
|
82
85
|
collect_env_var_documentation_findings,
|
|
83
86
|
),
|
|
87
|
+
(
|
|
88
|
+
repository_constants.CHECK_ID_FOLLOWUP_LEDGER_DUPLICATES,
|
|
89
|
+
collect_followup_ledger_duplicate_findings,
|
|
90
|
+
),
|
|
84
91
|
(
|
|
85
92
|
repository_constants.CHECK_ID_PACKAGE_INVENTORY,
|
|
86
93
|
collect_package_inventory_findings,
|
|
@@ -2,13 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import sys
|
|
6
5
|
from pathlib import Path
|
|
7
6
|
|
|
8
|
-
_SCRIPTS = Path(__file__).resolve().parent
|
|
9
|
-
if str(_SCRIPTS) not in sys.path:
|
|
10
|
-
sys.path.insert(0, str(_SCRIPTS))
|
|
11
|
-
|
|
12
7
|
from active_capability_references import (
|
|
13
8
|
build_capability_inventory,
|
|
14
9
|
classify_capability_reference,
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import json
|
|
5
6
|
import subprocess
|
|
6
7
|
from datetime import datetime, timezone
|
|
7
8
|
from pathlib import Path
|
|
@@ -43,6 +44,5 @@ def test_meter_cache_write_lands_outside_the_home_directory(
|
|
|
43
44
|
|
|
44
45
|
home_state_path = fake_home / ".claude" / "account-broker" / "state.json"
|
|
45
46
|
assert not home_state_path.exists()
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
)
|
|
47
|
+
state = json.loads(isolated_broker_state_path.read_text(encoding="utf-8"))
|
|
48
|
+
assert f"claude:only:{account.home}" in state["meters"]
|
|
@@ -2,15 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import sys
|
|
6
5
|
from pathlib import Path
|
|
7
6
|
|
|
8
7
|
import pytest
|
|
9
8
|
|
|
10
|
-
_SCRIPTS_DIRECTORY = Path(__file__).resolve().parent
|
|
11
|
-
if str(_SCRIPTS_DIRECTORY) not in sys.path:
|
|
12
|
-
sys.path.insert(0, str(_SCRIPTS_DIRECTORY))
|
|
13
|
-
|
|
14
9
|
import digest_check
|
|
15
10
|
from dev_env_scripts_constants.digest_check_constants import (
|
|
16
11
|
CARD_WORD_BUDGET,
|
|
@@ -9,14 +9,10 @@ from pathlib import Path
|
|
|
9
9
|
|
|
10
10
|
import pytest
|
|
11
11
|
|
|
12
|
-
_SCRIPTS_DIRECTORY = Path(__file__).resolve().parent
|
|
13
|
-
if str(_SCRIPTS_DIRECTORY) not in sys.path:
|
|
14
|
-
sys.path.insert(0, str(_SCRIPTS_DIRECTORY))
|
|
15
|
-
|
|
16
12
|
import durable_post_lint
|
|
17
13
|
from dev_env_scripts_constants.private_term_constants import PrivateTermDigest
|
|
18
14
|
|
|
19
|
-
SCRIPT_PATH =
|
|
15
|
+
SCRIPT_PATH = Path(__file__).resolve().parent / "durable_post_lint.py"
|
|
20
16
|
VALID_PR_BODY = """## Why
|
|
21
17
|
|
|
22
18
|
Why text.
|
|
@@ -4,24 +4,20 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import io
|
|
6
6
|
import json
|
|
7
|
-
import sys
|
|
8
7
|
from pathlib import Path
|
|
9
8
|
|
|
10
9
|
from dev_env_scripts_constants.followup_constants import (
|
|
11
10
|
BACKLOG_EXCEEDED_EXIT_CODE,
|
|
12
11
|
BACKLOG_EXCEEDED_TEMPLATE,
|
|
13
12
|
BACKLOG_WITHIN_TEMPLATE,
|
|
13
|
+
DEDUPE_RESULT_TEMPLATE,
|
|
14
14
|
FOLLOWUP_BACKLOG_THRESHOLD,
|
|
15
15
|
)
|
|
16
16
|
from followup_cli import main
|
|
17
|
-
|
|
18
|
-
_hooks_directory = str(Path(__file__).resolve().parents[1] / "hooks")
|
|
19
|
-
if _hooks_directory not in sys.path:
|
|
20
|
-
sys.path.insert(0, _hooks_directory)
|
|
21
|
-
|
|
22
17
|
from followup_ledger import (
|
|
23
18
|
FollowupFinding,
|
|
24
19
|
all_recorded_findings,
|
|
20
|
+
followup_ledger_path,
|
|
25
21
|
record_followup_finding,
|
|
26
22
|
)
|
|
27
23
|
|
|
@@ -309,3 +305,19 @@ def test_list_names_the_check_behind_each_finding(tmp_path: Path) -> None:
|
|
|
309
305
|
|
|
310
306
|
assert "code-rules/constant-outside-config" in output_text
|
|
311
307
|
assert "f00dcafe" in output_text
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def test_dedupe_removes_repeated_ledger_lines_and_keeps_the_order(
|
|
311
|
+
tmp_path: Path,
|
|
312
|
+
) -> None:
|
|
313
|
+
ledger_path = followup_ledger_path(tmp_path)
|
|
314
|
+
ledger_path.parent.mkdir(parents=True)
|
|
315
|
+
ledger_path.write_text("first\nsecond\nfirst\n", encoding="utf-8")
|
|
316
|
+
|
|
317
|
+
exit_code, output_text = run_command(
|
|
318
|
+
["dedupe", "--repository-root", str(tmp_path)]
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
assert exit_code == 0
|
|
322
|
+
assert output_text == DEDUPE_RESULT_TEMPLATE.format(removed_count=1) + "\n"
|
|
323
|
+
assert ledger_path.read_text(encoding="utf-8") == "first\nsecond\n"
|
|
@@ -4,7 +4,6 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
6
|
import subprocess
|
|
7
|
-
import sys
|
|
8
7
|
import threading
|
|
9
8
|
from dataclasses import dataclass, field
|
|
10
9
|
from datetime import datetime, timezone
|
|
@@ -12,13 +11,9 @@ from pathlib import Path
|
|
|
12
11
|
|
|
13
12
|
import pytest
|
|
14
13
|
|
|
15
|
-
_SCRIPTS_DIR = Path(__file__).resolve().parent
|
|
16
|
-
if str(_SCRIPTS_DIR) not in sys.path:
|
|
17
|
-
sys.path.insert(0, str(_SCRIPTS_DIR))
|
|
18
|
-
|
|
19
14
|
import account_broker
|
|
20
15
|
import invoke_code_review as invoker
|
|
21
|
-
import resolve_worker_spawn as dispatcher
|
|
16
|
+
import resolve_worker_spawn as dispatcher
|
|
22
17
|
from dev_env_scripts_constants.account_broker_constants import (
|
|
23
18
|
Account,
|
|
24
19
|
BrokerConfigurationError,
|
|
@@ -28,7 +23,7 @@ from dev_env_scripts_constants.account_broker_constants import (
|
|
|
28
23
|
ProductAdapter,
|
|
29
24
|
WAIT_EXIT_CODE,
|
|
30
25
|
)
|
|
31
|
-
from dev_env_scripts_constants.grok_worker_constants import (
|
|
26
|
+
from dev_env_scripts_constants.grok_worker_constants import (
|
|
32
27
|
AGENT_FLAG,
|
|
33
28
|
CLI_AGENT_FLAG,
|
|
34
29
|
ALL_AGENT_FILENAMES_BY_ROLE,
|
|
@@ -67,12 +62,12 @@ from dev_env_scripts_constants.grok_worker_constants import ( # noqa: E402
|
|
|
67
62
|
TIER_CLAUDE_HEADLESS,
|
|
68
63
|
TIER_GROK,
|
|
69
64
|
)
|
|
70
|
-
import grok_headless_runner
|
|
71
|
-
from grok_headless_runner import (
|
|
65
|
+
import grok_headless_runner
|
|
66
|
+
from grok_headless_runner import (
|
|
72
67
|
GrokRunnerOutcome,
|
|
73
68
|
run_headless_worker,
|
|
74
69
|
)
|
|
75
|
-
from grok_worker_preflight import PreflightOutcome
|
|
70
|
+
from grok_worker_preflight import PreflightOutcome
|
|
76
71
|
|
|
77
72
|
HOST_PROFILE_CLAUDE = "Claude"
|
|
78
73
|
HOST_PROFILE_THIRD_PARTY = "ThirdParty"
|
|
@@ -3,23 +3,11 @@
|
|
|
3
3
|
import inspect
|
|
4
4
|
import json
|
|
5
5
|
import subprocess
|
|
6
|
-
import sys
|
|
7
6
|
from pathlib import Path
|
|
8
7
|
from unittest.mock import patch
|
|
9
8
|
|
|
10
9
|
import pytest
|
|
11
10
|
|
|
12
|
-
_SCRIPTS_DIR = Path(__file__).resolve().parent
|
|
13
|
-
_HOOKS_DIR = _SCRIPTS_DIR.parent / "hooks"
|
|
14
|
-
_SESSION_HOOKS_PACKAGE_DIR = _SCRIPTS_DIR.parent / "hooks" / "session"
|
|
15
|
-
for each_sys_path_entry in (
|
|
16
|
-
str(_SCRIPTS_DIR),
|
|
17
|
-
str(_HOOKS_DIR),
|
|
18
|
-
str(_SESSION_HOOKS_PACKAGE_DIR),
|
|
19
|
-
):
|
|
20
|
-
if each_sys_path_entry not in sys.path:
|
|
21
|
-
sys.path.insert(0, each_sys_path_entry)
|
|
22
|
-
|
|
23
11
|
import setup_project_paths as setup
|
|
24
12
|
import untracked_repo_detector as detector_module
|
|
25
13
|
from hooks_constants.project_paths_reader import registry_file_path
|
|
@@ -9,12 +9,8 @@ from pathlib import Path
|
|
|
9
9
|
|
|
10
10
|
import pytest
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
sys.path.insert(0, str(_SCRIPTS_DIR))
|
|
15
|
-
|
|
16
|
-
import shared_tree_paths # noqa: E402
|
|
17
|
-
from dev_env_scripts_constants.grok_worker_constants import ( # noqa: E402
|
|
12
|
+
import shared_tree_paths
|
|
13
|
+
from dev_env_scripts_constants.grok_worker_constants import (
|
|
18
14
|
CLAUDE_CONFIG_DIR_ENV_VAR,
|
|
19
15
|
PROCESS_TREE_DIRECTORY_NAME,
|
|
20
16
|
PROCESS_TREE_KILL_MODULE_FILENAME,
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import subprocess
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
|
|
4
|
-
import sys
|
|
5
|
-
|
|
6
4
|
from validate_instruction_pairs import (
|
|
7
5
|
GATE_FAILED_EXIT_CODE,
|
|
8
6
|
GATE_PASSED_EXIT_CODE,
|
|
@@ -10,11 +8,6 @@ from validate_instruction_pairs import (
|
|
|
10
8
|
run_gate,
|
|
11
9
|
validate_repository,
|
|
12
10
|
)
|
|
13
|
-
|
|
14
|
-
_hooks_directory = str(Path(__file__).resolve().parents[1] / "hooks")
|
|
15
|
-
if _hooks_directory not in sys.path:
|
|
16
|
-
sys.path.insert(0, _hooks_directory)
|
|
17
|
-
|
|
18
11
|
from followup_ledger import all_recorded_findings, head_commit
|
|
19
12
|
from hooks_constants.followup_ledger_constants import SEVERITY_SMELL
|
|
20
13
|
|
|
@@ -6,14 +6,9 @@ command walking without requiring a full npm pack on every case.
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
-
import sys
|
|
10
9
|
import tarfile
|
|
11
10
|
from pathlib import Path
|
|
12
11
|
|
|
13
|
-
_SCRIPTS_DIR = Path(__file__).resolve().parent
|
|
14
|
-
if str(_SCRIPTS_DIR) not in sys.path:
|
|
15
|
-
sys.path.insert(0, str(_SCRIPTS_DIR))
|
|
16
|
-
|
|
17
12
|
import verify_installable_package as mod
|
|
18
13
|
from dev_env_scripts_constants.verify_installable_package_constants import (
|
|
19
14
|
CLASS_PACKAGED,
|
|
@@ -89,12 +89,12 @@ def test_form_named_rejects_an_unknown_name() -> None:
|
|
|
89
89
|
form_named("no-such-form")
|
|
90
90
|
|
|
91
91
|
|
|
92
|
-
def
|
|
93
|
-
|
|
92
|
+
def test_every_form_is_named_in_the_rule_guide() -> None:
|
|
93
|
+
guide_text = (PACKAGE_ROOT / CONTRAST_FRAMING_RULE_DOCUMENT).read_text(
|
|
94
94
|
encoding="utf-8"
|
|
95
95
|
)
|
|
96
96
|
for each_form in ALL_CONTRAST_FRAMING_FORMS:
|
|
97
|
-
assert f"`{each_form.name}`" in
|
|
97
|
+
assert f"`{each_form.name}`" in guide_text
|
|
98
98
|
|
|
99
99
|
|
|
100
100
|
def test_a_list_of_not_items_stays_quiet() -> None:
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Behavior tests for the committed follow-up ledger duplicate check."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
_TESTS_DIRECTORY = Path(__file__).resolve().parent
|
|
9
|
+
if str(_TESTS_DIRECTORY) not in sys.path:
|
|
10
|
+
sys.path.insert(0, str(_TESTS_DIRECTORY))
|
|
11
|
+
|
|
12
|
+
from repository_checks.config.constants import (
|
|
13
|
+
CHECK_ID_FOLLOWUP_LEDGER_DUPLICATES,
|
|
14
|
+
FINDINGS_EXIT_CODE,
|
|
15
|
+
SUCCESS_EXIT_CODE,
|
|
16
|
+
)
|
|
17
|
+
from repository_policy_test_support import (
|
|
18
|
+
commit_tracked_files,
|
|
19
|
+
initialize_repository,
|
|
20
|
+
run_policy,
|
|
21
|
+
write_text,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
_LEDGER_RELATIVE_PATH = ".claude/followups/smells.jsonl"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_should_flag_a_committed_ledger_with_a_repeated_line(tmp_path: Path) -> None:
|
|
28
|
+
repository_root = tmp_path / "repo"
|
|
29
|
+
initialize_repository(repository_root)
|
|
30
|
+
write_text(
|
|
31
|
+
repository_root / _LEDGER_RELATIVE_PATH,
|
|
32
|
+
'{"rule_id": "a"}\n{"rule_id": "b"}\n{"rule_id": "a"}\n',
|
|
33
|
+
)
|
|
34
|
+
commit_tracked_files(repository_root)
|
|
35
|
+
|
|
36
|
+
exit_code, stdout_text, _stderr_text = run_policy(repository_root)
|
|
37
|
+
|
|
38
|
+
assert exit_code == FINDINGS_EXIT_CODE
|
|
39
|
+
assert (
|
|
40
|
+
f"{CHECK_ID_FOLLOWUP_LEDGER_DUPLICATES}: {_LEDGER_RELATIVE_PATH}:"
|
|
41
|
+
in stdout_text
|
|
42
|
+
)
|
|
43
|
+
assert "cde followup dedupe" in stdout_text
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_should_pass_a_committed_ledger_without_repeats(tmp_path: Path) -> None:
|
|
47
|
+
repository_root = tmp_path / "repo"
|
|
48
|
+
initialize_repository(repository_root)
|
|
49
|
+
write_text(
|
|
50
|
+
repository_root / _LEDGER_RELATIVE_PATH,
|
|
51
|
+
'{"rule_id": "a"}\n{"rule_id": "b"}\n',
|
|
52
|
+
)
|
|
53
|
+
commit_tracked_files(repository_root)
|
|
54
|
+
|
|
55
|
+
exit_code, stdout_text, _stderr_text = run_policy(repository_root)
|
|
56
|
+
|
|
57
|
+
assert exit_code == SUCCESS_EXIT_CODE
|
|
58
|
+
assert stdout_text == ""
|
|
@@ -17,6 +17,7 @@ import repository_policy
|
|
|
17
17
|
from repository_checks.config.constants import (
|
|
18
18
|
CHECK_ID_CLAUDE_MD_ORPHANS,
|
|
19
19
|
CHECK_ID_ENV_VAR_DOCUMENTATION,
|
|
20
|
+
CHECK_ID_FOLLOWUP_LEDGER_DUPLICATES,
|
|
20
21
|
CHECK_ID_PACKAGE_INVENTORY,
|
|
21
22
|
CHECK_ID_PYTEST_TESTPATHS,
|
|
22
23
|
CHECK_ID_TRACKED_PERSONAL_DATA,
|
|
@@ -50,6 +51,7 @@ def test_should_keep_stable_check_identifiers() -> None:
|
|
|
50
51
|
assert repository_policy.ALL_CHECK_IDS == (
|
|
51
52
|
CHECK_ID_CLAUDE_MD_ORPHANS,
|
|
52
53
|
CHECK_ID_ENV_VAR_DOCUMENTATION,
|
|
54
|
+
CHECK_ID_FOLLOWUP_LEDGER_DUPLICATES,
|
|
53
55
|
CHECK_ID_PACKAGE_INVENTORY,
|
|
54
56
|
CHECK_ID_PYTEST_TESTPATHS,
|
|
55
57
|
CHECK_ID_TRACKED_PERSONAL_DATA,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""The rules index and each rule entry keep their shape, and the always-loaded rules stay inside a byte budget."""
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
4
|
import sys
|
|
@@ -28,11 +28,16 @@ DOUBLED_WHEN_LEAD_IN_PATTERN = re.compile(
|
|
|
28
28
|
r"^\*\*When(?: this applies)?:\*\* When\b", re.MULTILINE
|
|
29
29
|
)
|
|
30
30
|
|
|
31
|
+
INDEX_LINE_PATTERN = re.compile(r"^- \*\*[^*]+\*\*(?: \(\[guide\]\((\.\./docs/rule-guides/[^)#\s]+\.md)\)\))?\. \S")
|
|
32
|
+
INDEX_SKILL_POINTER_PATTERN = re.compile(r"the `[a-z-]+` skill")
|
|
33
|
+
|
|
34
|
+
INDEX_FILE_NAME = "index.md"
|
|
35
|
+
INDEX_PATH = RULES_DIRECTORY / INDEX_FILE_NAME
|
|
31
36
|
MAXIMUM_ENTRY_BYTES = 1_500
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
MAXIMUM_INDEX_BYTES = 4_000
|
|
38
|
+
MAXIMUM_ALWAYS_ON_BYTES = 6_000
|
|
34
39
|
CODEX_VERBATIM_ENTRY_NAMES = frozenset({"question-presentation.md"})
|
|
35
|
-
INDEX_SHAPE_EXEMPT_ENTRY_NAMES =
|
|
40
|
+
INDEX_SHAPE_EXEMPT_ENTRY_NAMES = CODEX_VERBATIM_ENTRY_NAMES
|
|
36
41
|
CODEX_MATERIALIZED_GUIDE_NAMES = frozenset(
|
|
37
42
|
Path(each_path).name for each_path in codex_instruction_rule_relative_paths
|
|
38
43
|
)
|
|
@@ -43,6 +48,7 @@ def _entry_paths() -> list[Path]:
|
|
|
43
48
|
each_path
|
|
44
49
|
for each_path in RULES_DIRECTORY.glob("*.md")
|
|
45
50
|
if each_path.name not in instruction_alias_filenames
|
|
51
|
+
and each_path.name != INDEX_FILE_NAME
|
|
46
52
|
)
|
|
47
53
|
|
|
48
54
|
|
|
@@ -183,11 +189,59 @@ def test_each_entry_links_a_full_text_guide_that_exists() -> None:
|
|
|
183
189
|
assert all_problems == []
|
|
184
190
|
|
|
185
191
|
|
|
192
|
+
def _index_lines() -> list[str]:
|
|
193
|
+
return [
|
|
194
|
+
each_line
|
|
195
|
+
for each_line in INDEX_PATH.read_text(encoding="utf-8").splitlines()
|
|
196
|
+
if each_line.startswith("- ")
|
|
197
|
+
]
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _index_guide_names() -> list[str]:
|
|
201
|
+
return [
|
|
202
|
+
Path(each_match.group(1)).name
|
|
203
|
+
for each_line in _index_lines()
|
|
204
|
+
if (each_match := INDEX_LINE_PATTERN.match(each_line)) and each_match.group(1)
|
|
205
|
+
]
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def test_each_index_line_names_a_rule_and_a_trigger() -> None:
|
|
209
|
+
malformed = [
|
|
210
|
+
each_line for each_line in _index_lines() if not INDEX_LINE_PATTERN.match(each_line)
|
|
211
|
+
]
|
|
212
|
+
assert malformed == []
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def test_each_index_line_links_a_guide_or_names_a_skill() -> None:
|
|
216
|
+
unlinked = [
|
|
217
|
+
each_line
|
|
218
|
+
for each_line in _index_lines()
|
|
219
|
+
if not INDEX_LINE_PATTERN.match(each_line).group(1)
|
|
220
|
+
and not INDEX_SKILL_POINTER_PATTERN.search(each_line)
|
|
221
|
+
]
|
|
222
|
+
assert unlinked == []
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def test_each_index_guide_exists() -> None:
|
|
226
|
+
missing = [
|
|
227
|
+
each_guide_name
|
|
228
|
+
for each_guide_name in _index_guide_names()
|
|
229
|
+
if not (RULE_GUIDES_DIRECTORY / each_guide_name).is_file()
|
|
230
|
+
]
|
|
231
|
+
assert missing == []
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def test_index_stays_inside_its_byte_budget() -> None:
|
|
235
|
+
assert len(INDEX_PATH.read_bytes()) <= MAXIMUM_INDEX_BYTES
|
|
236
|
+
|
|
237
|
+
|
|
186
238
|
def test_each_guide_has_exactly_one_owner() -> None:
|
|
187
239
|
owners_by_guide: dict[str, list[str]] = {}
|
|
188
240
|
for each_path in _entry_paths():
|
|
189
241
|
for each_guide_name in _linked_guide_names(_entry_text(each_path)):
|
|
190
242
|
owners_by_guide.setdefault(each_guide_name, []).append(each_path.name)
|
|
243
|
+
for each_guide_name in _index_guide_names():
|
|
244
|
+
owners_by_guide.setdefault(each_guide_name, []).append(INDEX_FILE_NAME)
|
|
191
245
|
unowned = sorted(
|
|
192
246
|
each_guide.name
|
|
193
247
|
for each_guide in RULE_GUIDES_DIRECTORY.glob("*.md")
|
|
@@ -204,7 +258,7 @@ def test_each_guide_has_exactly_one_owner() -> None:
|
|
|
204
258
|
|
|
205
259
|
|
|
206
260
|
def test_always_loaded_entries_stay_inside_the_total_byte_budget() -> None:
|
|
207
|
-
always_on_bytes = sum(
|
|
261
|
+
always_on_bytes = len(INDEX_PATH.read_bytes()) + sum(
|
|
208
262
|
len(each_path.read_bytes())
|
|
209
263
|
for each_path in _entry_paths()
|
|
210
264
|
if _loads_in_every_session(_entry_text(each_path))
|
|
@@ -8,24 +8,9 @@ SKILLS_DIRECTORY = PACKAGE_ROOT / ".agents" / "skills"
|
|
|
8
8
|
|
|
9
9
|
ALWAYS_ON_RULE_NAMES = frozenset(
|
|
10
10
|
{
|
|
11
|
-
"asd-ste100-language.md",
|
|
12
|
-
"cleanup-temp-files.md",
|
|
13
11
|
"correction-lens.md",
|
|
14
|
-
"
|
|
15
|
-
"explore-thoroughly.md",
|
|
16
|
-
"features-start-with-an-eval.md",
|
|
17
|
-
"filesystem-search.md",
|
|
18
|
-
"memory-stores-durable-facts.md",
|
|
19
|
-
"no-contrast-framing.md",
|
|
20
|
-
"proof-before-pull-request.md",
|
|
12
|
+
"index.md",
|
|
21
13
|
"question-presentation.md",
|
|
22
|
-
"research-mode.md",
|
|
23
|
-
"request-scope.md",
|
|
24
|
-
"session-title.md",
|
|
25
|
-
"skill-pointers.md",
|
|
26
|
-
"shell-invocation.md",
|
|
27
|
-
"verify-before-asking.md",
|
|
28
|
-
"verify-runtime-state.md",
|
|
29
14
|
}
|
|
30
15
|
)
|
|
31
16
|
|
package/settings.json
CHANGED
|
@@ -7,6 +7,15 @@
|
|
|
7
7
|
]
|
|
8
8
|
},
|
|
9
9
|
"advisorModel": "fable",
|
|
10
|
+
"autoCompactWindow": 350000,
|
|
11
|
+
"modelSettings": {
|
|
12
|
+
"claude-haiku-5-5": {
|
|
13
|
+
"autoCompactWindow": 100000
|
|
14
|
+
},
|
|
15
|
+
"claude-haiku-4-5": {
|
|
16
|
+
"autoCompactWindow": 100000
|
|
17
|
+
}
|
|
18
|
+
},
|
|
10
19
|
"autoMode": {
|
|
11
20
|
"allow": [
|
|
12
21
|
"$defaults",
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
</role>
|
|
8
8
|
|
|
9
9
|
<task_scope>
|
|
10
|
-
Apply
|
|
10
|
+
Apply docs/rule-guides/asd-ste100-language.md for user-facing word choice, sentence style, tone, punctuation,
|
|
11
11
|
and prose form. Keep the behavior contracts below within their named scope.
|
|
12
12
|
Defer to the user's judgment when they indicate a task is too ambitious or complex to pursue.
|
|
13
13
|
Match the scope of every action to what the user explicitly requested: a bug fix delivers the
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
<output_style>
|
|
29
29
|
When a user-facing response needs beginner presentation, use ELI5: beginner framing, large
|
|
30
30
|
visuals, minimal text, one stable self-contained HTML artifact, update-in-place continuity,
|
|
31
|
-
and sharing. Apply
|
|
31
|
+
and sharing. Apply docs/rule-guides/asd-ste100-language.md to sentence-level prose inside that artifact.
|
|
32
32
|
Lead every response with the direct answer or result. Open with the substance — the
|
|
33
33
|
conclusion, the fix, the decision — before any supporting context or explanation. Expand
|
|
34
34
|
depth in proportion to the complexity of the ask: a direct question earns a sentence; a
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
Reserve user questions for preferences, missing context the user holds, judgment calls, and
|
|
68
68
|
scope decisions.
|
|
69
69
|
|
|
70
|
-
Source every claim as
|
|
70
|
+
Source every claim as docs/rule-guides/research-mode.md describes.
|
|
71
71
|
</investigation>
|
|
72
72
|
|
|
73
73
|
<problem_solving>
|
|
@@ -363,7 +363,7 @@
|
|
|
363
363
|
Visible output is concise by default. The first progress update is one sentence.
|
|
364
364
|
Later updates mark only important discoveries or direction changes. The final
|
|
365
365
|
response begins with the outcome.
|
|
366
|
-
Language policy:
|
|
366
|
+
Language policy: docs/rule-guides/asd-ste100-language.md. This section owns visible progress,
|
|
367
367
|
tool narration, and outcome-first final structure.
|
|
368
368
|
</communication_contract>
|
|
369
369
|
|