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,39 @@
1
+ """Stable text reports for committed-tree repository checks."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from repository_checks.config import constants as repository_constants
6
+ from repository_checks.models import RepositoryCheckReport
7
+
8
+
9
+ def render_report(report: RepositoryCheckReport) -> str:
10
+ """Render findings and failed checks as newline-terminated text.
11
+
12
+ Args:
13
+ report: Findings and failed check identifiers.
14
+
15
+ Returns:
16
+ The report body, or an empty string when the tree is clean.
17
+ """
18
+ all_lines = [
19
+ repository_constants.FINDING_LINE_TEMPLATE.format(
20
+ check_id=each_finding.check_id,
21
+ relative_path=each_finding.relative_path,
22
+ message=each_finding.message,
23
+ )
24
+ for each_finding in report.all_findings
25
+ ]
26
+ all_lines.extend(_failed_check_lines(report))
27
+ if not all_lines:
28
+ return repository_constants.EMPTY_REPORT_TEXT
29
+ return (
30
+ repository_constants.REPORT_LINE_SEPARATOR.join(all_lines)
31
+ + repository_constants.REPORT_LINE_SEPARATOR
32
+ )
33
+
34
+
35
+ def _failed_check_lines(report: RepositoryCheckReport) -> list[str]:
36
+ return [
37
+ repository_constants.RULE_FAILED_LINE_TEMPLATE.format(check_id=each_check_id)
38
+ for each_check_id in report.all_failed_check_ids
39
+ ]
@@ -0,0 +1,93 @@
1
+ """Fail-closed runner for committed-tree repository checks."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable, Sequence
6
+ from pathlib import Path
7
+
8
+ from repository_checks import tracked_tree
9
+ from repository_checks.claude_md import collect_claude_md_orphan_findings
10
+ from repository_checks.config import constants as repository_constants
11
+ from repository_checks.env_var_documentation import (
12
+ collect_env_var_documentation_findings,
13
+ )
14
+ from repository_checks.models import RepositoryCheckReport, RepositoryFinding
15
+ from repository_checks.package_inventory import collect_package_inventory_findings
16
+ from repository_checks.pytest_testpaths import collect_pytest_testpath_findings
17
+ from repository_checks.tracked_secrets import collect_tracked_secret_findings
18
+
19
+ RepositoryCollector = Callable[[Path, Sequence[str]], list[RepositoryFinding]]
20
+
21
+
22
+ def run_repository_checks(repository_root: Path) -> RepositoryCheckReport:
23
+ """Run every committed-tree check and fail closed on collector errors.
24
+
25
+ Args:
26
+ repository_root: Git repository root whose tracked files are scanned.
27
+
28
+ Returns:
29
+ Sorted findings and any check identifiers that failed closed.
30
+ """
31
+ try:
32
+ all_tracked_paths = tracked_tree.tracked_relative_paths(repository_root)
33
+ except repository_constants.ALL_FAIL_CLOSED_EXCEPTION_TYPES:
34
+ return RepositoryCheckReport((), repository_constants.ALL_CHECK_IDS)
35
+ all_findings, all_failed_check_ids = _run_collectors(
36
+ repository_root, all_tracked_paths
37
+ )
38
+ return RepositoryCheckReport(
39
+ _sort_findings(all_findings), tuple(all_failed_check_ids)
40
+ )
41
+
42
+
43
+ def _run_collectors(
44
+ repository_root: Path, all_tracked_paths: Sequence[str]
45
+ ) -> tuple[list[RepositoryFinding], list[str]]:
46
+ all_findings: list[RepositoryFinding] = []
47
+ all_failed_check_ids: list[str] = []
48
+ for each_check_id, each_collector in _all_collectors():
49
+ try:
50
+ all_findings.extend(each_collector(repository_root, all_tracked_paths))
51
+ except repository_constants.ALL_FAIL_CLOSED_EXCEPTION_TYPES:
52
+ all_failed_check_ids.append(each_check_id)
53
+ return all_findings, all_failed_check_ids
54
+
55
+
56
+ def _sort_findings(
57
+ all_findings: list[RepositoryFinding],
58
+ ) -> tuple[RepositoryFinding, ...]:
59
+ return tuple(
60
+ sorted(
61
+ all_findings,
62
+ key=lambda each_finding: (
63
+ each_finding.check_id,
64
+ each_finding.relative_path,
65
+ each_finding.message,
66
+ ),
67
+ )
68
+ )
69
+
70
+
71
+ def _all_collectors() -> tuple[tuple[str, RepositoryCollector], ...]:
72
+ return (
73
+ (
74
+ repository_constants.CHECK_ID_CLAUDE_MD_ORPHANS,
75
+ collect_claude_md_orphan_findings,
76
+ ),
77
+ (
78
+ repository_constants.CHECK_ID_ENV_VAR_DOCUMENTATION,
79
+ collect_env_var_documentation_findings,
80
+ ),
81
+ (
82
+ repository_constants.CHECK_ID_PACKAGE_INVENTORY,
83
+ collect_package_inventory_findings,
84
+ ),
85
+ (
86
+ repository_constants.CHECK_ID_PYTEST_TESTPATHS,
87
+ collect_pytest_testpath_findings,
88
+ ),
89
+ (
90
+ repository_constants.CHECK_ID_TRACKED_PERSONAL_DATA,
91
+ collect_tracked_secret_findings,
92
+ ),
93
+ )
@@ -0,0 +1,147 @@
1
+ """Tracked-secret checks for committed trees."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ from collections.abc import Sequence
7
+ from pathlib import Path
8
+ from types import ModuleType
9
+
10
+ from policy_lint.config import constants as policy_constants
11
+
12
+ from repository_checks.config import constants as repository_constants
13
+ from repository_checks.hook_modules import load_hooks_module
14
+ from repository_checks.models import RepositoryFinding
15
+
16
+
17
+ def collect_tracked_secret_findings(
18
+ repository_root: Path, all_tracked_paths: Sequence[str]
19
+ ) -> list[RepositoryFinding]:
20
+ """Return non-exempt secrets found in tracked UTF-8 files.
21
+
22
+ Args:
23
+ repository_root: Git repository root.
24
+ all_tracked_paths: Repository-relative tracked paths.
25
+
26
+ Returns:
27
+ Findings with redacted previews for non-exempt secrets.
28
+ """
29
+ scanner = load_hooks_module(repository_constants.PII_SCANNER_MODULE_NAME)
30
+ exemption = load_hooks_module(repository_constants.REPOSITORY_EXEMPTION_MODULE_NAME)
31
+ all_allowlisted_literals = exemption.repository_allowlisted_values(repository_root)
32
+ all_findings: list[RepositoryFinding] = []
33
+ for each_relative_path in all_tracked_paths:
34
+ all_findings.extend(
35
+ _find_secrets_for_path(
36
+ repository_root,
37
+ each_relative_path,
38
+ scanner,
39
+ all_allowlisted_literals,
40
+ )
41
+ )
42
+ return all_findings
43
+
44
+
45
+ def _find_secrets_for_path(
46
+ repository_root: Path,
47
+ relative_path: str,
48
+ scanner: ModuleType,
49
+ all_allowlisted_literals: frozenset[str],
50
+ ) -> list[RepositoryFinding]:
51
+ posix_relative_path = relative_path.replace(
52
+ repository_constants.WINDOWS_PATH_SEPARATOR,
53
+ policy_constants.PATH_SEPARATOR,
54
+ )
55
+ if scanner.is_path_exempt_from_pii_scan(posix_relative_path):
56
+ return []
57
+ maybe_content = _read_utf8_text(
58
+ repository_root,
59
+ repository_root / relative_path,
60
+ )
61
+ if maybe_content is None:
62
+ return []
63
+ return _find_secret_matches(
64
+ posix_relative_path,
65
+ maybe_content,
66
+ scanner,
67
+ all_allowlisted_literals,
68
+ )
69
+
70
+
71
+ def _find_secret_matches(
72
+ posix_relative_path: str,
73
+ content: str,
74
+ scanner: ModuleType,
75
+ all_allowlisted_literals: frozenset[str],
76
+ ) -> list[RepositoryFinding]:
77
+ return [
78
+ _build_finding(
79
+ posix_relative_path,
80
+ category=each_match.category,
81
+ preview=each_match.preview,
82
+ )
83
+ for each_match in scanner.scan_text_for_pii(content)
84
+ if _should_report_match(
85
+ posix_relative_path,
86
+ each_match.category,
87
+ each_match.matched_text,
88
+ all_allowlisted_literals,
89
+ )
90
+ ]
91
+
92
+
93
+ def _read_utf8_text(repository_root: Path, absolute_path: Path) -> str | None:
94
+ if not absolute_path.is_file():
95
+ return None
96
+ _require_path_inside_repository(repository_root, absolute_path)
97
+ try:
98
+ return absolute_path.read_text(encoding=policy_constants.UTF8_ENCODING)
99
+ except UnicodeDecodeError:
100
+ return None
101
+
102
+
103
+ def _require_path_inside_repository(repository_root: Path, absolute_path: Path) -> None:
104
+ resolved_repository_root = repository_root.resolve(strict=True)
105
+ resolved_absolute_path = absolute_path.resolve(strict=True)
106
+ try:
107
+ resolved_absolute_path.relative_to(resolved_repository_root)
108
+ except ValueError as e:
109
+ raise OSError(absolute_path) from e
110
+
111
+
112
+ def _build_finding(
113
+ posix_relative_path: str, category: str, preview: str
114
+ ) -> RepositoryFinding:
115
+ return RepositoryFinding(
116
+ repository_constants.CHECK_ID_TRACKED_PERSONAL_DATA,
117
+ posix_relative_path,
118
+ repository_constants.TRACKED_MATCH_MESSAGE_TEMPLATE.format(
119
+ category=category,
120
+ preview=preview,
121
+ ),
122
+ )
123
+
124
+
125
+ def _should_report_match(
126
+ posix_relative_path: str,
127
+ category: str,
128
+ matched_text: str,
129
+ all_allowlisted_literals: frozenset[str],
130
+ ) -> bool:
131
+ exact_exemption_identity = (
132
+ posix_relative_path,
133
+ category,
134
+ _secret_digest(matched_text),
135
+ )
136
+ if (
137
+ exact_exemption_identity
138
+ in repository_constants.ALL_TRACKED_SECRET_EXACT_EXEMPTIONS
139
+ ):
140
+ return False
141
+ return matched_text not in all_allowlisted_literals
142
+
143
+
144
+ def _secret_digest(matched_text: str) -> str:
145
+ return hashlib.sha256(
146
+ matched_text.encode(policy_constants.UTF8_ENCODING)
147
+ ).hexdigest()
@@ -0,0 +1,28 @@
1
+ """List tracked worktree paths for the committed-tree checker."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from policy_lint.selection_git import git_bytes_for, split_nul_tokens
8
+
9
+ from repository_checks.config.constants import ALL_GIT_LS_FILES_ARGUMENTS
10
+
11
+
12
+ def tracked_relative_paths(repository_root: Path) -> tuple[str, ...]:
13
+ """Return tracked paths as repository-relative posix strings.
14
+
15
+ ::
16
+
17
+ git ls-files -z -> ("notes/CLAUDE.md", "src/app.py")
18
+ ok: paths are relative
19
+ flag: an absolute Windows path
20
+
21
+ Args:
22
+ repository_root: Git repository root.
23
+
24
+ Returns:
25
+ Tracked relative paths in Git order.
26
+ """
27
+ raw_paths = git_bytes_for(repository_root, ALL_GIT_LS_FILES_ARGUMENTS)
28
+ return split_nul_tokens(raw_paths)
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env python3
2
+ """Public command for the committed-tree repository checker."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import sys
8
+ from collections.abc import Sequence
9
+ from pathlib import Path
10
+ from typing import Never, TextIO
11
+
12
+ from policy_lint.config.constants import ALL_GIT_ROOT_ARGUMENTS, UTF8_ENCODING
13
+ from policy_lint.selection_git import GitSelectionError, git_bytes_for
14
+ from repository_checks.config.constants import (
15
+ ALL_CHECK_IDS,
16
+ REPOSITORY_ROOT_FLAG,
17
+ USAGE_EXIT_CODE,
18
+ )
19
+ from repository_checks.reporting import render_report
20
+ from repository_checks.runner import run_repository_checks
21
+
22
+ __all__ = ["ALL_CHECK_IDS", "main"]
23
+
24
+
25
+ class _RepositoryPolicyUsageError(ValueError):
26
+ """Raised when command arguments cannot form a repository scan."""
27
+
28
+
29
+ class _RepositoryPolicyArgumentParser(argparse.ArgumentParser):
30
+ def error(self, message: str) -> Never:
31
+ """Raise a usage error for the caller.
32
+
33
+ Args:
34
+ message: Parser error text.
35
+
36
+ Raises:
37
+ _RepositoryPolicyUsageError: Always.
38
+ """
39
+ raise _RepositoryPolicyUsageError(message)
40
+
41
+
42
+ def _discover_repository_root(starting_directory: Path) -> Path:
43
+ """Find the Git root that contains the command's current directory.
44
+
45
+ Args:
46
+ starting_directory: Directory where the command started.
47
+
48
+ Returns:
49
+ The resolved Git repository root.
50
+
51
+ Raises:
52
+ _RepositoryPolicyUsageError: If the directory is not inside a Git repository.
53
+ """
54
+ try:
55
+ root_bytes = git_bytes_for(starting_directory.resolve(), ALL_GIT_ROOT_ARGUMENTS)
56
+ except (GitSelectionError, OSError) as error:
57
+ raise _RepositoryPolicyUsageError(
58
+ "Current directory is not inside a Git repository"
59
+ ) from error
60
+ root_text = root_bytes.decode(UTF8_ENCODING).strip()
61
+ if not root_text:
62
+ raise _RepositoryPolicyUsageError("Git returned an empty repository root")
63
+ return Path(root_text).resolve()
64
+
65
+
66
+ def _build_parser() -> argparse.ArgumentParser:
67
+ parser = _RepositoryPolicyArgumentParser(prog="repository_policy")
68
+ parser.add_argument(
69
+ REPOSITORY_ROOT_FLAG,
70
+ dest="repository_root",
71
+ default=None,
72
+ help="Git repository root to scan.",
73
+ )
74
+ return parser
75
+
76
+
77
+ def main(
78
+ all_arguments: Sequence[str],
79
+ *,
80
+ repository_root: Path,
81
+ stdout: TextIO = sys.stdout,
82
+ stderr: TextIO = sys.stderr,
83
+ ) -> int:
84
+ """Run committed-tree repository checks.
85
+
86
+ Args:
87
+ all_arguments: Command arguments.
88
+ repository_root: Repository root used when the flag is omitted.
89
+ stdout: Output stream.
90
+ stderr: Error stream.
91
+
92
+ Returns:
93
+ Zero when the tree is clean, one for findings, two for usage errors,
94
+ and three when a check fails closed.
95
+ """
96
+ try:
97
+ namespace = _build_parser().parse_args(list(all_arguments))
98
+ except _RepositoryPolicyUsageError as usage_error:
99
+ stderr.write(f"{usage_error}\n")
100
+ return USAGE_EXIT_CODE
101
+ selected_root = repository_root
102
+ if namespace.repository_root is not None:
103
+ selected_root = Path(namespace.repository_root).resolve()
104
+ report = run_repository_checks(selected_root)
105
+ stdout.write(render_report(report))
106
+ return report.exit_code
107
+
108
+
109
+ if __name__ == "__main__":
110
+ try:
111
+ shell_directory = Path.cwd()
112
+ parsed_namespace = _build_parser().parse_args(sys.argv[1:])
113
+ if parsed_namespace.repository_root is not None:
114
+ shell_repository_root = Path(parsed_namespace.repository_root).resolve()
115
+ else:
116
+ shell_repository_root = _discover_repository_root(shell_directory)
117
+ except _RepositoryPolicyUsageError as usage_error:
118
+ sys.stderr.write(f"{usage_error}\n")
119
+ raise SystemExit(USAGE_EXIT_CODE) from usage_error
120
+ raise SystemExit(main(sys.argv[1:], repository_root=shell_repository_root))