claude-dev-env 8.1.0 → 8.3.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}/clean-coder.md +4 -4
- package/.agents/{agents → agents-archived}/code-quality-agent.md +5 -5
- package/.agents/{agents → agents-archived}/plan-packet-validator.md +2 -2
- package/.agents/{agents → agents-archived}/session-advisor.md +2 -2
- package/.agents/skills/_shared/end-of-run-gotcha-recommendations.md +2 -2
- package/.agents/skills/cert-classification-rule/SKILL.md +3 -3
- package/.agents/skills/e-code-review/SKILL.md +2 -2
- package/.agents/skills/e-code-review/reference/fix.md +1 -1
- package/.agents/skills/e-code-review/reference/loop.md +1 -1
- package/.agents/skills/e-code-review/reference/medium.md +4 -4
- package/.agents/skills/e-code-review/reference/xhigh.md +5 -5
- package/.agents/skills/everything-search/SKILL.md +3 -3
- 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 +3 -3
- package/.agents/skills/orchestrator/SKILL.md +19 -18
- package/.agents/skills/orchestrator/reference/consult-the-orchestrator.md +1 -1
- package/.agents/skills/orchestrator/reference/executor-consult-block.md +1 -1
- package/.agents/skills/orchestrator-refresh/SKILL.md +4 -2
- package/.agents/skills/plugin-eval-standalone-skill/SKILL.md +96 -0
- package/.agents/skills/plugin-eval-standalone-skill/reference/graders-and-commands.md +108 -0
- package/.agents/skills/privacy-hygiene/SKILL.md +5 -6
- package/.agents/skills/privacy-hygiene/reference/sweep-procedure.md +5 -6
- package/.agents/skills/pull-request/SKILL.md +14 -5
- package/.agents/skills/pull-request/reference/publication-tasks.md +1 -1
- package/.agents/skills/repairing-hook-boundaries/SKILL.md +1 -1
- package/.agents/skills/syncing-submodules/SKILL.md +1 -1
- package/.agents/skills/team-advisor/SKILL.md +11 -9
- package/.agents/skills/team-advisor/reference/advisor-docs-review.md +28 -1
- package/.agents/skills/usage-pause/SKILL.md +3 -3
- package/_shared/advisor/advisor-protocol.md +7 -3
- package/_shared/advisor/reference/advisor-block.md +8 -1
- package/audit-rubrics/category_rubrics/category-f-silent-failures.md +2 -2
- package/audit-rubrics/category_rubrics/category-i-concurrency.md +1 -1
- package/audit-rubrics/category_rubrics/category-m-producer-consumer-cardinality.md +1 -1
- package/audit-rubrics/category_rubrics/category-n-test-name-scenario-verifier.md +5 -5
- package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +5 -5
- package/audit-rubrics/category_rubrics/category-p-name-vs-behavior-contract.md +3 -3
- package/audit-rubrics/category_rubrics/category-q-cross-surface-claims.md +1 -1
- package/audit-rubrics/prompts/category-a-api-contracts.md +2 -2
- package/audit-rubrics/prompts/category-b-selector-engine-compat.md +3 -3
- package/audit-rubrics/prompts/category-c-resource-cleanup.md +3 -3
- package/audit-rubrics/prompts/category-d-scoping-and-ordering.md +1 -1
- package/audit-rubrics/prompts/category-e-dead-code.md +3 -3
- package/audit-rubrics/prompts/category-f-silent-failures.md +12 -12
- package/audit-rubrics/prompts/category-g-bounds-and-overflow.md +2 -2
- package/audit-rubrics/prompts/category-h-security-boundaries.md +4 -4
- package/audit-rubrics/prompts/category-i-concurrency.md +6 -6
- package/audit-rubrics/prompts/category-j-code-rules-compliance.md +5 -5
- package/audit-rubrics/prompts/category-m-producer-consumer-cardinality.md +1 -1
- package/audit-rubrics/prompts/category-n-test-name-scenario-verifier.md +7 -7
- package/audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md +2 -2
- package/audit-rubrics/prompts/category-p-name-vs-behavior-contract.md +3 -3
- package/audit-rubrics/prompts/category-q-cross-surface-claims.md +2 -2
- package/bin/ever-shipped-skills.mjs +1 -0
- package/bin/ever-shipped-skills.test.mjs +4 -0
- package/bin/install.agents-home.test.mjs +44 -94
- package/bin/install.mjs +31 -24
- package/bin/install.test.mjs +21 -0
- package/bin/resolve-package-managed-directory.test.mjs +1 -1
- package/bin/subagent-model-policy.test.mjs +15 -8
- package/docs/BDD_SCENARIO_QUALITY.md +2 -2
- package/docs/CODE_RULES.md +7 -7
- package/docs/TEST_QUALITY.md +1 -1
- package/docs/nas-ssh-invocation.md +1 -1
- package/docs/references/advisor-tool.md +2 -2
- package/docs/references/prose-style-enforcement.md +1 -1
- package/docs/references/team-advisor-skill.md +2 -2
- package/docs/references/weak-executor-advisor.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/blocking/test_code_rules_enforcer_magic_allowlist.py +1 -1
- 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/package.json +1 -1
- package/rules/archiving-agent-config.md +5 -8
- package/rules/git-workflow.md +3 -3
- package/rules/long-horizon-autonomy.md +1 -1
- package/rules/shell-invocation.md +0 -2
- package/rules/subagent-model-policy.json +5 -13
- package/rules/testing.md +0 -2
- package/scripts/active_capability_references.py +74 -19
- package/scripts/banned_prose_words.py +159 -0
- package/scripts/dev_env_scripts_constants/active_capability_constants.py +12 -10
- package/scripts/dev_env_scripts_constants/banned_prose_word_constants.py +95 -0
- 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/adapter_retired_hook_prose.py +17 -7
- package/scripts/policy_lint/adapters.py +6 -6
- package/scripts/policy_lint/config/constants.py +8 -0
- package/scripts/policy_lint/registry.py +1 -1
- package/scripts/resolve_advisor_model_route.mjs +2 -2
- package/scripts/subagent_model_policy.mjs +4 -1
- package/scripts/sync_to_cursor/rules.py +1 -1
- package/scripts/test_active_capability_references.py +100 -33
- 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_banned_prose_words.py +103 -0
- package/scripts/tests/test_engine.py +1 -1
- package/scripts/tests/test_policy_lint_rules_retired_hook_prose.py +46 -0
- package/scripts/tests/test_rules.py +17 -6
- package/system-prompts/software-engineer.xml +7 -7
- 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/.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}/pr-description-writer.md +0 -0
- /package/.agents/{agents → agents-archived}/skill-writer-agent.md +0 -0
package/docs/CODE_RULES.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The canonical review-criteria instruction set for every AI agent that audits pull requests in this repository, loaded on demand. [`.cursor/BUGBOT.md`](../../../.cursor/BUGBOT.md) is the checked-in pointer file Cursor BugBot reads; it points here.
|
|
4
4
|
|
|
5
|
-
⚡ marks rules
|
|
5
|
+
⚡ marks rules the hand-maintained `code_rules_enforcer.py` carries. The staged policy lint runs it over each changed file and returns the corrective detail, so this document lists those rules by name only. Session policy (question routing, task tracking) lives in `rules/*.md`; see [`code-standards.md`](../rules/code-standards.md).
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -25,9 +25,9 @@ Production and tests follow one rule. Changed directive, TODO, FIXME, HACK, XXX,
|
|
|
25
25
|
|
|
26
26
|
---
|
|
27
27
|
|
|
28
|
-
## ⚡
|
|
28
|
+
## ⚡ LINT-ENFORCED RULES
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
The staged policy lint reports each of these through `code_rules_enforcer.py` and names the specific breach; exact patterns and exemption lists live in that module:
|
|
31
31
|
|
|
32
32
|
no new comments · imports at top · logging format args (`log_*("...", arg)`) · no `%s`/`%d` printf tokens in a `str.format`-logger message (`log_*` imported from `automation_logging`; `str.format` drops the args — use `{}`) · no magic values in production bodies (0, 1, -1 exempt) · UPPER_SNAKE constants only in `config/` (exempt: `config/*`; `/migrations/`; Workflow registries: path contains any of these substrings — `/workflow/`, `_tab.py`, `/states.py`, or `/modules.py`, each matching independently as a substring, so `pkg/states.py` qualifies while a top-level `states.py` follows the standard `config/` rule; test files — path or filename matches `test_`, `_test.`, `.spec.`, `conftest`, or `/tests/`) · no hardcoded user home paths · guarded `sys.path.insert` · banned identifiers (`ctx`, `cfg`, `msg`, `btn`, `idx`, `cnt`, `tmp`, `elem`, `val`) · banned function prefixes (`handle_`, `process_`, `manage_`, `do_`) · no type escape hatches (`Any` import, `cast()`, inline `Any`, a parameter typed bare `object` whose body reads `param.attribute`) outside boundary files · no bare/broad `except` · no `Any` in signatures or class attributes · no stub bodies (`pass`/`...`/`raise NotImplementedError`) outside abstract/Protocol · TypedDict `_encode_*`/`_decode_*` companions in the same module · no test-mode branching in production (use dependency injection) · no thin wrapper modules · Google-style docstrings on public functions with `Args:` matching the signature · boolean names prefixed `is_`/`has_`/`should_`/`can_`/`was_`/`did_` (assignments AND bool-typed parameters) · must-check returns (`find_and_click`, `write_outcome`) assigned and checked · known pytest fixture parameters in test files annotated with their single documented type (`tmp_path: Path`, `monkeypatch: pytest.MonkeyPatch`, `capsys`, `caplog`, `request`, …) · known pytest fixture parameters a test function declares but never references (drop the unused parameter — pytest still pays its setup cost) · JavaScript/TypeScript boolean declarations (`const`/`let`/`var` bound to a boolean literal or negation) and `@param {boolean}` JSDoc names prefixed `is`/`has`/`should`/`can`/`was`/`did` (camelCase forms) · banned identifiers as `.mjs`/`.js` declaration names (`result`, `data`, `ctx`, `msg`, …), scoped to changed lines · in test files, banned identifiers fire on changed lines, and pytest-collectable `test_*` functions need a return annotation · unused module-level imports and unsorted import blocks are ruff's job (F401, isort I001), not this hook's · a `hooks/blocking/` command classifier anchors its multi-word command regex to the command start (`^`/`\A`) or tokenizes the first word (`shlex.split`), never matching a command as a bare substring
|
|
33
33
|
|
|
@@ -53,7 +53,7 @@ The banned-noun check applies to public function definitions, parameters, and bo
|
|
|
53
53
|
|
|
54
54
|
## 6. COMPLETE TYPE HINTS
|
|
55
55
|
|
|
56
|
-
ALL parameters typed, ALL returns typed. No `Any`. Avoid `# type: ignore`; remove it and use a typed boundary or
|
|
56
|
+
ALL parameters typed, ALL returns typed. No `Any`. Avoid `# type: ignore`; remove it and use a typed boundary or the type. Prefer fixing the type over an ignore when an annotation is available.
|
|
57
57
|
|
|
58
58
|
## 6.5 FILE LENGTH GUIDANCE
|
|
59
59
|
|
|
@@ -83,7 +83,7 @@ Components own their complete feature (state, modals, overlays, toasts). Parents
|
|
|
83
83
|
|
|
84
84
|
## 9.5 NO THIN WRAPPER MODULES
|
|
85
85
|
|
|
86
|
-
A non-`__init__.py` module whose body is only imports (optionally `__all__`) is indirection without payload — callers import the
|
|
86
|
+
A non-`__init__.py` module whose body is only imports (optionally `__all__`) is indirection without payload — callers import the module. `__init__.py` is the canonical re-export surface and is exempt.
|
|
87
87
|
|
|
88
88
|
## 9.6 NO BACKWARDS-COMPATIBILITY SHIMS
|
|
89
89
|
|
|
@@ -111,7 +111,7 @@ If you already have the data, don't fetch it again.
|
|
|
111
111
|
|
|
112
112
|
## 11.5 VALIDATION-PHASE PRECEDENCE
|
|
113
113
|
|
|
114
|
-
`code_rules_enforcer.py`
|
|
114
|
+
A staged policy lint run of `code_rules_enforcer.py` selects what it checks and reports along three independent axes. Each axis filters a narrower scope than the one before it; none widens what the axis before it already decided.
|
|
115
115
|
|
|
116
116
|
1. **Phase selects the roster.** `EDIT_LANE_PHASE` or `FULL_GATE_PHASE` decides which checks exist in the lane at all. `validate_content_for_phase` takes `phase` keyword-only with no default, so every caller names its lane explicitly.
|
|
117
117
|
2. **Target classification filters within a lane.** The hook-infrastructure patterns and the ephemeral-path check decide whether a target is validated, and with which subset. Classification narrows a lane; it never adds a check the phase already excluded.
|
|
@@ -127,6 +127,6 @@ Hook-infrastructure targets run three checks in the edit lane — `check_same_fi
|
|
|
127
127
|
|
|
128
128
|
Three surfaces report on the roster, and each reports a specific thing:
|
|
129
129
|
|
|
130
|
-
- `hooks/validators/hook_timing_harness.py` builds a `Write` payload against a target that already holds content. `_contents_for_validation` returns `None` for that payload, so the harness times interpreter start and hook dispatch. Time an `Edit` payload against a
|
|
130
|
+
- `hooks/validators/hook_timing_harness.py` builds a `Write` payload against a target that already holds content. `_contents_for_validation` returns `None` for that payload, so the harness times interpreter start and hook dispatch. Time an `Edit` payload against a file to measure the checks.
|
|
131
131
|
- `~/.claude/logs/hook-blocks.log` records the denials raised by fixtures in `test_code_rules_enforcer_*.py` and by the timing harness's default target.
|
|
132
132
|
- `hooks/validators/run_all_validators.py` stages the target under a temporary root and rebuilds the shortest path tail that carries every exemption signal. The walk starts at the target's own project root and skips a directory pytest generated for its own scratch tree, so the staged path reads the same wherever `--basetemp` places that tree.
|
package/docs/TEST_QUALITY.md
CHANGED
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
|
|
86
86
|
**Mock too much** - Prefer integration tests
|
|
87
87
|
- Bad: Mock every hook and function
|
|
88
|
-
- Good: Render with
|
|
88
|
+
- Good: Render with unmocked hooks, mock only API calls
|
|
89
89
|
|
|
90
90
|
**Test implementation details** - Test what user experiences
|
|
91
91
|
- Bad: Assert on state values, hook return values
|
|
@@ -66,7 +66,7 @@ running anything.
|
|
|
66
66
|
The NAS is a Linux box and this machine is Windows, so two classes of difference show up only there:
|
|
67
67
|
syntax newer than the NAS interpreter accepts, and tests that assert Windows paths.
|
|
68
68
|
|
|
69
|
-
**Always separate a
|
|
69
|
+
**Always separate a break in the code from a platform-only one.** Run the same test file at the branch head
|
|
70
70
|
and at the commit the work started from. Identical failures at both mean the environment, not the
|
|
71
71
|
change. Report both numbers rather than the head alone.
|
|
72
72
|
|
|
@@ -6,7 +6,7 @@ Source bones: [Anthropic Advisor tool](https://platform.claude.com/docs/en/agent
|
|
|
6
6
|
|
|
7
7
|
## What it is
|
|
8
8
|
|
|
9
|
-
`/team-advisor` is the repository's advisor implementation.
|
|
9
|
+
`/team-advisor` is the repository's advisor implementation. On a Claude host with the built-in `advisor` tool on, it calls `advisor()`, which reads the whole transcript. Otherwise it carries the full first packet explicitly, sends later deltas, and owns the warm Agent/SendMessage or read-only Astra CLI lifecycle.
|
|
10
10
|
|
|
11
11
|
See `team-advisor-skill.md` for the bind and lifecycle map.
|
|
12
12
|
|
|
@@ -57,7 +57,7 @@ When the consult path supports a free-text brief, append:
|
|
|
57
57
|
|
|
58
58
|
`(Advisor: please keep your guidance under 80 words — I need a focused starting point, not a comprehensive plan.)`
|
|
59
59
|
|
|
60
|
-
Size the ask at roughly 80 percent of the
|
|
60
|
+
Size the ask at roughly 80 percent of the ceiling; direct address to the advisor lands more reliably than a third-person description.
|
|
61
61
|
|
|
62
62
|
## Related
|
|
63
63
|
|
|
@@ -10,7 +10,7 @@ The flag is `PROSE_STYLE_ENFORCEMENT_ENABLED` in `hooks/blocking/config/prose_st
|
|
|
10
10
|
|
|
11
11
|
## Always on
|
|
12
12
|
|
|
13
|
-
Historical
|
|
13
|
+
Historical and comparative language detection lives in `state_description_blocker.py`, which the staged policy lint applies under its state-description rule whatever this flag holds. Apply `rules/asd-ste100-language.md` for user-facing word choice and sentence style. Code-rules and security blockers are out of scope for this switch.
|
|
14
14
|
|
|
15
15
|
## Hedging claims
|
|
16
16
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Team-advisor skill
|
|
2
2
|
|
|
3
|
-
`/team-advisor` binds one
|
|
3
|
+
`/team-advisor` binds one advisor for this session. On a Claude host with the built-in `advisor` tool on, it calls `advisor()`. Otherwise it binds a standing warm advisor at the strongest reachable tier and sends explicit packets to a warm Agent/SendMessage advisor or the read-only `codex_astra_advisor.py` helper.
|
|
4
4
|
|
|
5
5
|
## Refs
|
|
6
6
|
|
|
@@ -17,4 +17,4 @@ Follow the call rules in `advisor-tool.md` (orientation first, then consult befo
|
|
|
17
17
|
|
|
18
18
|
## Selected path
|
|
19
19
|
|
|
20
|
-
`/team-advisor` is the repository's advisor implementation. It provides explicit first-consult packets, delta consults, a standing warm reviewer, an in-session Astra spawn on Codex, and a read-only Astra CLI option.
|
|
20
|
+
`/team-advisor` is the repository's advisor implementation. It uses the built-in advisor tool when the session has it. Without the tool, it provides explicit first-consult packets, delta consults, a standing warm reviewer, an in-session Astra spawn on Codex, and a read-only Astra CLI option.
|
|
@@ -30,7 +30,7 @@ carries its own packet:
|
|
|
30
30
|
|
|
31
31
|
- **First consult** — use the complete packet in
|
|
32
32
|
`~/.claude/_shared/advisor/reference/consult-format.md`: assignment,
|
|
33
|
-
desired outcome, constraints and exclusions, actions taken in order,
|
|
33
|
+
desired outcome, constraints and exclusions, actions taken in order,
|
|
34
34
|
output and current state, decision or blocker, validation evidence,
|
|
35
35
|
unresolved risks, and load-bearing paths or excerpts.
|
|
36
36
|
- **Later consults** — the delta since the last consult.
|
|
@@ -53,15 +53,16 @@ The matching replacement is:
|
|
|
53
53
|
}
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
Unknown values, invalid policy data,
|
|
57
|
-
spawn with a short diagnostic.
|
|
56
|
+
Unknown values, invalid policy data, unavailable replacement models, and
|
|
57
|
+
unresolved parent inheritance stop the spawn with a short diagnostic.
|
|
58
58
|
|
|
59
59
|
The direct spawn hook and advisor bridge use the same shared resolver. A Sol
|
|
60
60
|
Medium remap changes only the model and effort fields in the hook's tool input.
|
|
61
61
|
The hook returns an allow decision with the updated input and emits no remap
|
|
62
|
-
notice
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
notice when the selected model matches `gpt-*-luna`. Any route that resolves to
|
|
63
|
+
another model is denied unless the tool input carries the explicit
|
|
64
|
+
`flags: ["--advisor"]` bypass. Parent inheritance is denied because the hook
|
|
65
|
+
cannot verify the inherited model before the spawn.
|
|
65
66
|
|
|
66
67
|
The hook blocks advisor role claims because the current Codex hook input has no
|
|
67
68
|
host-provided role binding. The Python advisor bridge passes trusted session
|
|
@@ -4,9 +4,8 @@ Blocks a write to a production source file when no matching test was modified
|
|
|
4
4
|
within the freshness window, enforcing "TDD IS NON-NEGOTIABLE" from CLAUDE.md.
|
|
5
5
|
The gate records each candidate test's content hash at every sighting and
|
|
6
6
|
compares that hash on the next write. A first sighting requires content that
|
|
7
|
-
differs from HEAD. A
|
|
8
|
-
|
|
9
|
-
and applies while the candidate's content still matches what failed.
|
|
7
|
+
differs from HEAD. A stored failing pytest run is read before that fallback
|
|
8
|
+
and applies while the candidate's content still matches the stored hash.
|
|
10
9
|
Each concern lives in a ``tdd_enforcer_parts`` submodule; this entry wires them
|
|
11
10
|
into one PreToolUse gate and re-exports their surface for the test suite.
|
|
12
11
|
"""
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
One interpreter start runs every hosted PostToolUse hook a Bash call fires.
|
|
4
4
|
The unit tests pin selection and call order with a fake
|
|
5
|
-
runner; the end-to-end test
|
|
6
|
-
payload and confirms the recorder's own real side effect lands.
|
|
5
|
+
runner; the end-to-end test checks that an ordinary Bash call stays silent.
|
|
7
6
|
``hooks/conftest.py`` already puts the hooks and blocking directories on
|
|
8
7
|
sys.path for test collection, so this file needs no bootstrap of its own.
|
|
9
8
|
"""
|
|
@@ -23,12 +22,9 @@ from bash_post_call_dispatcher import (
|
|
|
23
22
|
)
|
|
24
23
|
from hooks_constants.bash_pre_tool_use_dispatcher_constants import BASH_TOOL_NAME
|
|
25
24
|
from hooks_constants.hosted_hook_runner import HostedHookRun
|
|
26
|
-
from tdd_enforcer_parts import content_hash_store
|
|
27
25
|
|
|
28
26
|
_ALL_EXPECTED_ROSTER_PATHS = [
|
|
29
|
-
"observability/test_failure_recorder.py",
|
|
30
27
|
"advisory/pr_done_reminder.py",
|
|
31
|
-
"advisory/msys_path_conversion_advisor.py",
|
|
32
28
|
]
|
|
33
29
|
|
|
34
30
|
|
|
@@ -63,11 +59,9 @@ def test_dispatch_forwards_hosted_hook_additional_context_as_one_payload(
|
|
|
63
59
|
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
64
60
|
) -> None:
|
|
65
61
|
context_by_hook = {
|
|
66
|
-
"test_failure_recorder.py": "",
|
|
67
62
|
"pr_done_reminder.py": json.dumps(
|
|
68
63
|
{"hookSpecificOutput": {"hookEventName": "PostToolUse", "additionalContext": "PR #42"}}
|
|
69
64
|
),
|
|
70
|
-
"msys_path_conversion_advisor.py": "",
|
|
71
65
|
}
|
|
72
66
|
|
|
73
67
|
def _fake_run_hook(script_path: str, payload_text: str) -> HostedHookRun:
|
|
@@ -124,29 +118,18 @@ def test_main_exits_zero_and_writes_nothing_to_stdout(
|
|
|
124
118
|
assert capsys.readouterr().out == ""
|
|
125
119
|
|
|
126
120
|
|
|
127
|
-
def
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
test_file.write_text("def test_fulfill(): assert False\n")
|
|
131
|
-
session_id = "post-dispatcher-session"
|
|
132
|
-
failing_output = (
|
|
133
|
-
"Error: Exit code 1\n"
|
|
134
|
-
"FAILED test_orders.py::test_fulfill - AssertionError\n"
|
|
135
|
-
"1 failed in 0.02s"
|
|
136
|
-
)
|
|
121
|
+
def test_end_to_end_hosted_hooks_leave_an_ordinary_call_silent(
|
|
122
|
+
tmp_path: Path, capsys: pytest.CaptureFixture[str]
|
|
123
|
+
) -> None:
|
|
137
124
|
payload = json.dumps(
|
|
138
125
|
{
|
|
139
|
-
"session_id": session_id,
|
|
140
126
|
"cwd": str(tmp_path),
|
|
141
127
|
"tool_name": "Bash",
|
|
142
|
-
"tool_input": {"command": "
|
|
143
|
-
"tool_response":
|
|
128
|
+
"tool_input": {"command": "git status"},
|
|
129
|
+
"tool_response": {"stdout": "clean", "stderr": ""},
|
|
144
130
|
}
|
|
145
131
|
)
|
|
146
132
|
|
|
147
133
|
dispatch(payload, BASH_TOOL_NAME)
|
|
148
134
|
|
|
149
|
-
|
|
150
|
-
state = json.loads(state_file.read_text())
|
|
151
|
-
entry = state[content_hash_store._state_key_for(test_file)]
|
|
152
|
-
assert entry[content_hash_store.STORED_FAILURE_EXIT_STATUS_KEY] == 1
|
|
135
|
+
assert capsys.readouterr().out == ""
|
|
@@ -15,7 +15,6 @@ from __future__ import annotations
|
|
|
15
15
|
|
|
16
16
|
from hooks_constants.bash_pre_tool_use_dispatcher_constants import (
|
|
17
17
|
ALL_BASH_AND_POWERSHELL_TOOL_NAMES,
|
|
18
|
-
ALL_BASH_ONLY_TOOL_NAMES,
|
|
19
18
|
BashHostedHookEntry,
|
|
20
19
|
)
|
|
21
20
|
|
|
@@ -30,9 +29,7 @@ __all__ = [
|
|
|
30
29
|
]
|
|
31
30
|
|
|
32
31
|
ALL_BASH_POST_TOOL_USE_HOSTED_HOOK_ENTRIES: tuple[BashHostedHookEntry, ...] = (
|
|
33
|
-
BashHostedHookEntry("observability/test_failure_recorder.py", ALL_BASH_ONLY_TOOL_NAMES),
|
|
34
32
|
BashHostedHookEntry("advisory/pr_done_reminder.py", ALL_BASH_AND_POWERSHELL_TOOL_NAMES),
|
|
35
|
-
BashHostedHookEntry("advisory/msys_path_conversion_advisor.py", ALL_BASH_ONLY_TOOL_NAMES),
|
|
36
33
|
)
|
|
37
34
|
|
|
38
35
|
EXIT_CODE_ERROR_PREFIX: str = "Error: Exit code "
|
|
@@ -89,7 +89,7 @@ def resolved_hook_script_path(relative_path: str) -> str:
|
|
|
89
89
|
|
|
90
90
|
::
|
|
91
91
|
|
|
92
|
-
"
|
|
92
|
+
"advisory/pr_done_reminder.py" -> <hooks>/advisory/pr_done_reminder.py
|
|
93
93
|
|
|
94
94
|
The root comes from this module's own location, so every dispatcher
|
|
95
95
|
resolves against the same directory however deep it sits.
|
|
@@ -1,48 +1,16 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
The advisory is a PostToolUse observer on Bash. It never blocks. When a git
|
|
4
|
-
call fails because Git Bash rewrote a ``<rev>:<path>`` argument before git saw
|
|
5
|
-
it, the advisory adds one loud note naming the two exports that turn the
|
|
6
|
-
rewriting off.
|
|
7
|
-
"""
|
|
1
|
+
"""Shared environment variable names for the MSYS path-conversion rewriter."""
|
|
8
2
|
|
|
9
3
|
from __future__ import annotations
|
|
10
4
|
|
|
11
5
|
__all__ = [
|
|
12
|
-
"ALL_MANGLED_ARGUMENT_MARKERS",
|
|
13
|
-
"MANGLED_ARGUMENT_CLOSING_QUOTE",
|
|
14
|
-
"ALL_MSYS_MANGLING_CHARACTERS",
|
|
15
6
|
"ALL_MSYS_PATH_CONVERSION_VARIABLE_NAMES",
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"MANGLING_EXPLANATION_LINE",
|
|
19
|
-
"FIX_LINE",
|
|
20
|
-
"SAME_COMMAND_LINE",
|
|
21
|
-
"ADVISORY_LINE_SEPARATOR",
|
|
7
|
+
"MSYS_PATH_CONVERSION_VARIABLE_NAME",
|
|
8
|
+
"MSYS_ARGUMENT_CONVERSION_EXCLUSION_VARIABLE_NAME",
|
|
22
9
|
]
|
|
23
10
|
|
|
24
|
-
AMBIGUOUS_ARGUMENT_MARKER: str = "fatal: ambiguous argument '"
|
|
25
|
-
INVALID_OBJECT_NAME_MARKER: str = "fatal: invalid object name '"
|
|
26
|
-
ALL_MANGLED_ARGUMENT_MARKERS: tuple[str, ...] = (
|
|
27
|
-
AMBIGUOUS_ARGUMENT_MARKER,
|
|
28
|
-
INVALID_OBJECT_NAME_MARKER,
|
|
29
|
-
)
|
|
30
|
-
MANGLED_ARGUMENT_CLOSING_QUOTE: str = "'"
|
|
31
|
-
ALL_MSYS_MANGLING_CHARACTERS: frozenset[str] = frozenset({"\\", ";"})
|
|
32
11
|
MSYS_PATH_CONVERSION_VARIABLE_NAME: str = "MSYS_NO_PATHCONV"
|
|
33
12
|
MSYS_ARGUMENT_CONVERSION_EXCLUSION_VARIABLE_NAME: str = "MSYS2_ARG_CONV_EXCL"
|
|
34
13
|
ALL_MSYS_PATH_CONVERSION_VARIABLE_NAMES: tuple[str, ...] = (
|
|
35
14
|
MSYS_PATH_CONVERSION_VARIABLE_NAME,
|
|
36
15
|
MSYS_ARGUMENT_CONVERSION_EXCLUSION_VARIABLE_NAME,
|
|
37
16
|
)
|
|
38
|
-
MSYS_EXPORT_FIX_LINE: str = "export MSYS_NO_PATHCONV=1 MSYS2_ARG_CONV_EXCL='*'"
|
|
39
|
-
|
|
40
|
-
ADVISORY_HEADER: str = "=== MSYS PATH CONVERSION (context reminder, never a block) ==="
|
|
41
|
-
MANGLED_ARGUMENT_LINE_TEMPLATE: str = "Git saw: {argument}"
|
|
42
|
-
MANGLING_EXPLANATION_LINE: str = (
|
|
43
|
-
"Git Bash rewrote a <rev>:<path> argument before git saw it, turning the "
|
|
44
|
-
"colon into a semicolon and the slashes into backslashes."
|
|
45
|
-
)
|
|
46
|
-
FIX_LINE: str = "Fix: " + MSYS_EXPORT_FIX_LINE
|
|
47
|
-
SAME_COMMAND_LINE: str = "Put that export and the git command in the same command."
|
|
48
|
-
ADVISORY_LINE_SEPARATOR: str = "\n"
|
|
@@ -7,29 +7,21 @@ from hooks_constants.bash_post_call_dispatcher_constants import (
|
|
|
7
7
|
from hooks_constants.bash_pre_tool_use_dispatcher_constants import BASH_TOOL_NAME
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
def
|
|
10
|
+
def test_roster_runs_the_pr_reminder() -> None:
|
|
11
11
|
all_script_paths = [
|
|
12
12
|
each_entry.script_relative_path for each_entry in ALL_BASH_POST_TOOL_USE_HOSTED_HOOK_ENTRIES
|
|
13
13
|
]
|
|
14
14
|
assert all_script_paths == [
|
|
15
|
-
"observability/test_failure_recorder.py",
|
|
16
15
|
"advisory/pr_done_reminder.py",
|
|
17
|
-
"advisory/msys_path_conversion_advisor.py",
|
|
18
16
|
]
|
|
19
17
|
|
|
20
18
|
|
|
21
19
|
def test_pr_done_reminder_also_serves_the_powershell_tool() -> None:
|
|
22
|
-
reminder_entry = ALL_BASH_POST_TOOL_USE_HOSTED_HOOK_ENTRIES[
|
|
20
|
+
reminder_entry = ALL_BASH_POST_TOOL_USE_HOSTED_HOOK_ENTRIES[0]
|
|
23
21
|
assert reminder_entry.script_relative_path == "advisory/pr_done_reminder.py"
|
|
24
22
|
assert "PowerShell" in reminder_entry.applicable_tool_names
|
|
25
23
|
|
|
26
24
|
|
|
27
|
-
def test_msys_advisor_serves_the_bash_tool_only() -> None:
|
|
28
|
-
advisor_entry = ALL_BASH_POST_TOOL_USE_HOSTED_HOOK_ENTRIES[-1]
|
|
29
|
-
assert advisor_entry.script_relative_path == "advisory/msys_path_conversion_advisor.py"
|
|
30
|
-
assert advisor_entry.applicable_tool_names == frozenset({"Bash"})
|
|
31
|
-
|
|
32
|
-
|
|
33
25
|
def test_context_forwarding_keys_match_the_hook_output_contract() -> None:
|
|
34
26
|
assert constants.POST_TOOL_USE_HOOK_EVENT_NAME == "PostToolUse"
|
|
35
27
|
assert constants.HOOK_SPECIFIC_OUTPUT_KEY == "hookSpecificOutput"
|
|
@@ -16,6 +16,13 @@ export function buildSubagentModelRoutingResponse(hookPayload, routingOptions =
|
|
|
16
16
|
if (routingDecision.status === 'blocked') {
|
|
17
17
|
return buildBlockedResponse(routingDecision.diagnostic);
|
|
18
18
|
}
|
|
19
|
+
if (routingDecision.status === 'inherited') {
|
|
20
|
+
return buildBlockedResponse('subagent model must resolve to gpt-*-luna');
|
|
21
|
+
}
|
|
22
|
+
if (!hasAdvisorBypass(hookPayload.tool_input)
|
|
23
|
+
&& !isLunaModel(routingDecision.selected?.model)) {
|
|
24
|
+
return buildBlockedResponse('only gpt-*-luna subagents are permitted');
|
|
25
|
+
}
|
|
19
26
|
if (routingDecision.status !== 'remapped') return {};
|
|
20
27
|
|
|
21
28
|
return buildAllowResponse(routingDecision.updatedInput);
|
|
@@ -52,6 +59,14 @@ function isTargetSubagentTool(hookPayload) {
|
|
|
52
59
|
return hookPayload.tool_name === targetToolName;
|
|
53
60
|
}
|
|
54
61
|
|
|
62
|
+
function isLunaModel(model) {
|
|
63
|
+
return typeof model === 'string' && /^gpt-.+-luna$/i.test(model);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function hasAdvisorBypass(toolInput) {
|
|
67
|
+
return Array.isArray(toolInput?.flags) && toolInput.flags.includes('--advisor');
|
|
68
|
+
}
|
|
69
|
+
|
|
55
70
|
function buildAllowResponse(updatedInput) {
|
|
56
71
|
return {
|
|
57
72
|
hookSpecificOutput: {
|
|
@@ -122,32 +122,65 @@ test('blocks malformed policy, unknown input, unavailable replacement, and untru
|
|
|
122
122
|
}
|
|
123
123
|
});
|
|
124
124
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
{
|
|
137
|
-
trustedSessionMetadata: {
|
|
138
|
-
authorized: true,
|
|
139
|
-
registeredAgentType: 'session-advisor',
|
|
140
|
-
},
|
|
125
|
+
test('blocks a trusted session advisor route that resolves to Astra', () => {
|
|
126
|
+
const hookResponse = buildSubagentModelRoutingResponse(
|
|
127
|
+
buildHookPayload({
|
|
128
|
+
agent_type: 'team-advisor',
|
|
129
|
+
model: 'Astra',
|
|
130
|
+
reasoning_effort: 'high',
|
|
131
|
+
}),
|
|
132
|
+
{
|
|
133
|
+
trustedSessionMetadata: {
|
|
134
|
+
authorized: true,
|
|
135
|
+
registeredAgentType: 'team-advisor',
|
|
141
136
|
},
|
|
142
|
-
|
|
137
|
+
},
|
|
138
|
+
);
|
|
143
139
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
140
|
+
assert.equal(hookResponse.hookSpecificOutput.permissionDecision, 'deny');
|
|
141
|
+
assert.equal(
|
|
142
|
+
hookResponse.hookSpecificOutput.permissionDecisionReason,
|
|
143
|
+
'only gpt-*-luna subagents are permitted',
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('allows an Astra route when the explicit advisor flag is present', () => {
|
|
148
|
+
const toolInput = {
|
|
149
|
+
model: 'Astra',
|
|
150
|
+
reasoning_effort: 'high',
|
|
151
|
+
flags: ['--advisor'],
|
|
152
|
+
};
|
|
153
|
+
const hookResponse = buildSubagentModelRoutingResponse(buildHookPayload(toolInput));
|
|
154
|
+
assert.deepEqual(hookResponse.hookSpecificOutput.updatedInput, {
|
|
155
|
+
model: 'gpt-6-astra',
|
|
156
|
+
reasoning_effort: 'low',
|
|
157
|
+
flags: ['--advisor'],
|
|
149
158
|
});
|
|
150
|
-
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test('does not treat unrelated flags as an advisor bypass', () => {
|
|
162
|
+
const hookResponse = buildSubagentModelRoutingResponse(buildHookPayload({
|
|
163
|
+
model: 'Astra',
|
|
164
|
+
reasoning_effort: 'high',
|
|
165
|
+
flags: ['--worker'],
|
|
166
|
+
}));
|
|
167
|
+
assert.equal(hookResponse.hookSpecificOutput.permissionDecision, 'deny');
|
|
168
|
+
assert.equal(
|
|
169
|
+
hookResponse.hookSpecificOutput.permissionDecisionReason,
|
|
170
|
+
'only gpt-*-luna subagents are permitted',
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('blocks parent inheritance because the selected model is unresolved', () => {
|
|
175
|
+
const hookResponse = buildSubagentModelRoutingResponse(buildHookPayload({
|
|
176
|
+
model: 'inherit-parent',
|
|
177
|
+
}));
|
|
178
|
+
assert.equal(hookResponse.hookSpecificOutput.permissionDecision, 'deny');
|
|
179
|
+
assert.equal(
|
|
180
|
+
hookResponse.hookSpecificOutput.permissionDecisionReason,
|
|
181
|
+
'subagent model must resolve to gpt-*-luna',
|
|
182
|
+
);
|
|
183
|
+
});
|
|
151
184
|
|
|
152
185
|
test('the standalone hook silently remaps native Sol Medium and preserves other fields', () => {
|
|
153
186
|
const hookExecution = spawnSync(
|
|
@@ -183,7 +216,7 @@ test('the standalone hook silently remaps native Sol Medium and preserves other
|
|
|
183
216
|
|
|
184
217
|
test('a session advisor name without trusted session metadata is blocked', () => {
|
|
185
218
|
const hookResponse = buildSubagentModelRoutingResponse(buildHookPayload({
|
|
186
|
-
agent_type: '
|
|
219
|
+
agent_type: 'team-advisor',
|
|
187
220
|
model: 'Astra',
|
|
188
221
|
reasoning_effort: 'high',
|
|
189
222
|
}));
|
|
@@ -222,7 +255,7 @@ test('the standalone hook command blocks an advisor without host metadata', () =
|
|
|
222
255
|
{
|
|
223
256
|
encoding: 'utf8',
|
|
224
257
|
input: JSON.stringify(buildHookPayload({
|
|
225
|
-
agent_type: '
|
|
258
|
+
agent_type: 'team-advisor',
|
|
226
259
|
model: 'Astra',
|
|
227
260
|
reasoning_effort: 'high',
|
|
228
261
|
})),
|
package/package.json
CHANGED
|
@@ -45,18 +45,15 @@ to be reconstructed from the diff is not recoverable in any useful sense.
|
|
|
45
45
|
|
|
46
46
|
## Make the archive stick
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
Two things do not follow the file on their own.
|
|
49
49
|
|
|
50
50
|
**A skill's name stays in `bin/ever-shipped-skills.mjs`.** The prune computes
|
|
51
51
|
the retired set as every name ever shipped minus the names installed now, and
|
|
52
52
|
that is how a stale copy is moved out of a host's agents home. Delete the name
|
|
53
|
-
and the copy is stranded on every machine that has it.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
capability against the shipped inventory; it reports a name only when that name
|
|
58
|
-
sits on `ALL_BANNED_ACTIVE_CAPABILITY_NAMES`. Until the archived name is on that
|
|
59
|
-
list, every leftover mention of it passes.
|
|
53
|
+
and the copy is stranded on every machine that has it. The reference checker in
|
|
54
|
+
`scripts/active_capability_references.py` derives its retired set the same way,
|
|
55
|
+
so the registry entry is also what makes a leftover mention of the archived
|
|
56
|
+
name report. No second list needs the name.
|
|
60
57
|
|
|
61
58
|
**A renamed skill directory takes its tests out of the run.** The node test
|
|
62
59
|
command globs `.agents/skills/**/*.test.mjs`, so a suite under an archived skill
|
package/rules/git-workflow.md
CHANGED
|
@@ -25,12 +25,12 @@ the next run. The merge stays in the repository. No tag is cut and the package n
|
|
|
25
25
|
|
|
26
26
|
Spot one by its head branch, which starts `release-please--branches--`, or by a body that
|
|
27
27
|
opens with the bot's own marker line. The description rules in this file, the
|
|
28
|
-
`
|
|
28
|
+
`pstack:poteto-agent` writing brief, and the house wording style all step aside for it. The
|
|
29
29
|
failure signature in the release job log reads
|
|
30
30
|
`could not parse pull request body as a release PR`.
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
`pstack:poteto-agent` writes a title and body from the diff when you want one.
|
|
33
|
+
Publish the title and body file through
|
|
34
34
|
`~/.agents/skills/pull-request/scripts/pull_request.py`. That path is under the
|
|
35
35
|
agents home, not the repository. A worktree holds no `.agents/` copy.
|
|
36
36
|
|
|
@@ -12,7 +12,7 @@ This is the autonomous-run partner to the ambiguous case: research and recommend
|
|
|
12
12
|
|
|
13
13
|
## Do not end a turn on a promise
|
|
14
14
|
|
|
15
|
-
Pause for the user only when the work
|
|
15
|
+
Pause for the user only when the work needs them: a destructive or irreversible action, a scope change, or input only they can give. When you hit one, ask through `AskUserQuestion` and end the turn. Do not end on a promise about work you have not done.
|
|
16
16
|
|
|
17
17
|
A pause is a request you deliver, not a state you enter. Put it in the channel the user reads, name the one input you need and what resumes the moment it arrives, and shape it so a single word answers it. A decision recorded in a status line, a checklist, a working document, or a side thread is a note; the user never learns they are the bottleneck, and the work sits. Keep everything that does not depend on the answer moving while it is outstanding.
|
|
18
18
|
|
|
@@ -33,5 +33,3 @@ export MSYS2_ARG_CONV_EXCL='origin/main:'; git show origin/main:.claude/settings
|
|
|
33
33
|
`MSYS2_ARG_CONV_EXCL` takes a semicolon-separated list of argument prefixes. Naming one prefix per detected token leaves every other argument in the command converting as before. The blanket pair `MSYS_NO_PATHCONV=1 MSYS2_ARG_CONV_EXCL='*'` turns conversion off for the whole command, which changes a path the command meant to convert, so the rewriter does not emit it.
|
|
34
34
|
|
|
35
35
|
A quoted token is not a revision. `git commit -m "fix a/b:.py"` carries a slash and a dot in one token, and a rewrite there would break the message, so the detection skips a token holding whitespace or a quote.
|
|
36
|
-
|
|
37
|
-
`advisory/msys_path_conversion_advisor.py` (PostToolUse on Bash, hosted by `bash_post_call_dispatcher`) stays behind it. It reads a failed git call, looks for the mark MSYS leaves, and names the fix. It covers a shape the rewriter misses.
|
|
@@ -29,25 +29,17 @@
|
|
|
29
29
|
"default": "worker",
|
|
30
30
|
"worker": [
|
|
31
31
|
"worker",
|
|
32
|
+
"poteto-agent",
|
|
33
|
+
"pstack:poteto-agent",
|
|
32
34
|
"default",
|
|
33
35
|
"explorer",
|
|
34
36
|
"caveman",
|
|
35
|
-
"clean-coder",
|
|
36
|
-
"code-quality-agent",
|
|
37
37
|
"code-verifier",
|
|
38
38
|
"deep-research",
|
|
39
|
-
"docs-agent"
|
|
40
|
-
"git-commit-crafter",
|
|
41
|
-
"issue-tracker",
|
|
42
|
-
"plan-packet-validator",
|
|
43
|
-
"pr-description-writer",
|
|
44
|
-
"skill-writer-agent",
|
|
45
|
-
"theme-prompt-agent",
|
|
46
|
-
"theme-prompt-generator",
|
|
47
|
-
"translator"
|
|
39
|
+
"docs-agent"
|
|
48
40
|
],
|
|
49
|
-
"advisor": ["advisor", "
|
|
50
|
-
"trustedAdvisor": ["
|
|
41
|
+
"advisor": ["advisor", "team-advisor", "code-advisor"],
|
|
42
|
+
"trustedAdvisor": ["advisor", "team-advisor", "code-advisor"]
|
|
51
43
|
},
|
|
52
44
|
"approvedPairs": [
|
|
53
45
|
{"model": "luna", "effort": "high"},
|
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.
|