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
|
@@ -11,7 +11,7 @@ Audit [REPO/ARTIFACT] [TARGET_ID] for **Category N only** (test-name scenario ve
|
|
|
11
11
|
|
|
12
12
|
ID prefix: `find`.
|
|
13
13
|
|
|
14
|
-
[ONE-PARAGRAPH FRAME: enumerate every test whose name includes a scenario claim (`_when_*`, `_at_*`, `_under_*`, `_with_*`, `_on_*`, `_after_*`, `_during_*`). State the audit goal: for each scenario-named test, verify the body sets up the named condition via fixture / monkeypatch / environment gate so the production code's scenario-named branch
|
|
14
|
+
[ONE-PARAGRAPH FRAME: enumerate every test whose name includes a scenario claim (`_when_*`, `_at_*`, `_under_*`, `_with_*`, `_on_*`, `_after_*`, `_during_*`). State the audit goal: for each scenario-named test, verify the body sets up the named condition via fixture / monkeypatch / environment gate so the production code's scenario-named branch runs during the act phase.]
|
|
15
15
|
|
|
16
16
|
## Source material ([N] files/sections, all lines in scope)
|
|
17
17
|
|
|
@@ -37,12 +37,12 @@ ID prefix: `find`.
|
|
|
37
37
|
|
|
38
38
|
**N5. Assertion shape mismatch**
|
|
39
39
|
- For every assertion, verify the assertion's shape can fail by construction. `assert <substring> not in result` where the substring is misspelled relative to the production output, or `assert result == ""` when the production function returns `None` on the negative case, or `len(result) > 0` when the production function returns an empty list on the no-feature path.
|
|
40
|
-
- Adversarial probes: (a) inspect each assertion's shape against the production function's
|
|
40
|
+
- Adversarial probes: (a) inspect each assertion's shape against the production function's return-value space; (b) check for assertions where the substring shape never appears in the production output by construction; (c) check for `assert x is True` where the production function returns truthy non-bool values.
|
|
41
41
|
|
|
42
42
|
**N6. Cross-platform scenario gating**
|
|
43
43
|
- For every test named `_on_windows` / `_on_linux` / `_on_macos`, verify the body gates on `sys.platform`, `monkeypatch.setattr(os, "name", ...)`, or `@pytest.mark.skipif`.
|
|
44
44
|
- Bare scenario names that run unchanged across platforms claim more than they prove.
|
|
45
|
-
- Adversarial probes: (a) does the production function's platform-specific branch get skipped on the CI runner's
|
|
45
|
+
- Adversarial probes: (a) does the production function's platform-specific branch get skipped on the CI runner's platform; (b) does the test pass against the platform fallback rather than the platform-specific code; (c) is the platform fixture installed and respected by the test runner.
|
|
46
46
|
|
|
47
47
|
**N7. Time / clock scenario gating**
|
|
48
48
|
- For every test named `_after_<duration>` / `_at_midnight` / `_during_business_hours`, verify the body injects a frozen clock (`freezegun.freeze_time`, `monkeypatch.setattr(time, "time", ...)`, `unittest.mock.patch("datetime.now")`).
|
|
@@ -62,8 +62,8 @@ ID prefix: `find`.
|
|
|
62
62
|
- For every test, verify the fixture / path / import wiring resolves to the artifact the test name claims.
|
|
63
63
|
- Path arithmetic: walk every `Path(__file__).parents[k]` chain symbolically and confirm it reaches the directory the assertion expects — a `parents[3]` that stops at `skills/` while the test expects the package root cannot fail for the right reason.
|
|
64
64
|
- Same-symbol dual imports: `from module import helper` plus `from module import helper as helper_alias` bind two names to the same function object, so any parity assertion between the two bound names is true by construction and proves nothing.
|
|
65
|
-
- Fixture file lookups: confirm every `open(Path(__file__).parent / "fixture.txt")` (or equivalent) reaches a file that
|
|
66
|
-
- Adversarial probes: (a) re-derive each `parents[k]` index against the
|
|
65
|
+
- Fixture file lookups: confirm every `open(Path(__file__).parent / "fixture.txt")` (or equivalent) reaches a file that exists in the repo.
|
|
66
|
+
- Adversarial probes: (a) re-derive each `parents[k]` index against the directory depth and flag any off-by-k; (b) check whether two imports in the test resolve to the same object before trusting a cross-name comparison; (c) confirm each referenced fixture path exists on disk at the depth the arithmetic produces.
|
|
67
67
|
|
|
68
68
|
## Cross-bucket questions to answer at the end
|
|
69
69
|
|
|
@@ -87,7 +87,7 @@ PR: refactor(hooks): cross-platform path resolution for windows-rmtree-blocker
|
|
|
87
87
|
Head SHA: (the commit that landed the platform-conditional logic)
|
|
88
88
|
ID prefix: `find`.
|
|
89
89
|
|
|
90
|
-
The PR adds platform-conditional path-resolution logic to `windows_rmtree_blocker.py` and ships 5 new tests named `test_*_on_windows` and `test_*_on_linux` across `test_windows_rmtree_blocker.py`. The audit goal: verify each scenario-named test sets up the named platform via monkeypatch or skipif gate so the production function's platform-specific branch
|
|
90
|
+
The PR adds platform-conditional path-resolution logic to `windows_rmtree_blocker.py` and ships 5 new tests named `test_*_on_windows` and `test_*_on_linux` across `test_windows_rmtree_blocker.py`. The audit goal: verify each scenario-named test sets up the named platform via monkeypatch or skipif gate so the production function's platform-specific branch runs during the act phase.
|
|
91
91
|
|
|
92
92
|
## Sub-buckets (each requires Shape A finding OR Shape B with ≥3 adversarial probes)
|
|
93
93
|
|
|
@@ -128,7 +128,7 @@ The PR adds platform-conditional path-resolution logic to `windows_rmtree_blocke
|
|
|
128
128
|
|
|
129
129
|
## Cross-bucket questions to answer at the end
|
|
130
130
|
|
|
131
|
-
Q1: Five scenario-named tests (F5, F21, F23, F26, F27) do not gate on `sys.platform` and pass against the Linux-fallback branch on the CI runner. The Windows-specific code path has zero
|
|
131
|
+
Q1: Five scenario-named tests (F5, F21, F23, F26, F27) do not gate on `sys.platform` and pass against the Linux-fallback branch on the CI runner. The Windows-specific code path has zero coverage despite the test names claiming it. Cite `test_windows_rmtree_blocker.py:42` (F5 first test) and `windows_rmtree_blocker.py:67` (the `if sys.platform == "win32":` branch) as the misclaim pair.
|
|
132
132
|
|
|
133
133
|
Q2: Worst false-coverage signal: F5 — the test's name `test_resolves_path_on_windows` reads as Windows-branch coverage in the PR review, but the act phase exercises the Linux fallback. A reviewer reading the test name during PR review would assume Windows coverage exists; it does not.
|
|
134
134
|
|
|
@@ -57,7 +57,7 @@ ID prefix: `find`.
|
|
|
57
57
|
|
|
58
58
|
**O6. Free-form `Args:`-adjacent claims**
|
|
59
59
|
- Judgment: thick rubric O6 (unions, suppressors, exclusion axis, delegation summaries, Returns/Raises/Note claims, run-mode field meaning). Gate inventory and free-form checklist live in the rubric.
|
|
60
|
-
- Adversarial probes: (a) check `Returns:` claims against every `return` statement in the body — is the documented return shape the
|
|
60
|
+
- Adversarial probes: (a) check `Returns:` claims against every `return` statement in the body — is the documented return shape the shape the body returns; (b) check `Raises:` claims against every `raise` and propagating callee — is every documented raise reachable; (c) check `Example:` snippets — does the snippet compile against the signature.
|
|
61
61
|
|
|
62
62
|
**O7. Module-doc-vs-split-module after refactor**
|
|
63
63
|
- Judgment: thick rubric O7.
|
|
@@ -89,7 +89,7 @@ Lead: `Total: N (P0=N, P1=N, P2=N)`. For each sub-bucket O1-O9, produce Shape A
|
|
|
89
89
|
|
|
90
90
|
Audit jl-cmd/claude-dev-env PR #522 for **Category O only** (docstring / fixture-prose vs implementation drift). Skip A-N, P. Sub-bucket forced-exhaustion mode: Category O is decomposed into 9 sub-buckets below.
|
|
91
91
|
|
|
92
|
-
PR #522 split `pr_description_command_parser.py` into two modules — the original parser and a new `pr_description_pr_number.py` — but the originating module's docstring still claims the PR-number recovery responsibility. A sibling change to `pr_description_body_audit.py` introduced a module docstring whose verb (`detects vague language`) overstates the module's
|
|
92
|
+
PR #522 split `pr_description_command_parser.py` into two modules — the original parser and a new `pr_description_pr_number.py` — but the originating module's docstring still claims the PR-number recovery responsibility. A sibling change to `pr_description_body_audit.py` introduced a module docstring whose verb (`detects vague language`) overstates the module's responsibility (it only exposes `_extract_vague_scan_text()`; detection runs elsewhere).
|
|
93
93
|
|
|
94
94
|
Expected findings on PR #522:
|
|
95
95
|
- **O1 finding:** `pr_description_body_audit.py:8` docstring uses verb `detects`, but the only exported symbol prepares input for a regex scan that fires in a different module. Body line(s) showing `_extract_vague_scan_text` returning normalized text without a detection call.
|
|
@@ -37,11 +37,11 @@ Two canonical failures to probe for:
|
|
|
37
37
|
|
|
38
38
|
**P1. Boolean / flag names assert state the body keeps**
|
|
39
39
|
- For every new `is_*` / `has_*` / `was_*` / `should_*` flag, trace the body. Every set site must be paired with a reset site that fires when the named condition becomes false.
|
|
40
|
-
- Adversarial probes: (a) grep every assignment to the flag — count set-true vs set-false; (b) for AST-driven flags, walk the visit method and confirm scope-exit semantics (def visited → flag set; dedent / function-exit → flag reset); (c) construct an input where the flag should be false after a
|
|
40
|
+
- Adversarial probes: (a) grep every assignment to the flag — count set-true vs set-false; (b) for AST-driven flags, walk the visit method and confirm scope-exit semantics (def visited → flag set; dedent / function-exit → flag reset); (c) construct an input where the flag should be false after a region that set it true — does the code agree.
|
|
41
41
|
|
|
42
42
|
**P2. Predicate-name breadth matches body coverage**
|
|
43
43
|
- For every new `_is_*` / `_has_*` / `_can_*` predicate function, list the body's `return True` branches. The named predicate must hold on the union of those branches and nothing else.
|
|
44
|
-
- Adversarial probes: (a) construct an input that satisfies the name but returns False — that is a P2 narrowness finding; (b) construct an input that does not satisfy the name but returns True — that is a P2 breadth finding; (c) check neighbor predicates — is the body's
|
|
44
|
+
- Adversarial probes: (a) construct an input that satisfies the name but returns False — that is a P2 narrowness finding; (b) construct an input that does not satisfy the name but returns True — that is a P2 breadth finding; (c) check neighbor predicates — is the body's contract the responsibility of a different helper.
|
|
45
45
|
|
|
46
46
|
**P3. Regex name vs regex shape** ⭐ canonical P case
|
|
47
47
|
- For every new `*_PATTERN` / `*_REGEX` constant, read the regex source. Confirm the anchors (`^`, `$`, `\b`, lookarounds) match the name's promised shape. `FILE_PATH_PATTERN` implies path shape; the regex must include path-shape anchors (slash count, segment shape, length bounds) enough to reject non-path inputs.
|
|
@@ -73,7 +73,7 @@ Q3: Which identifier most clearly shows the body should be renamed (the name is
|
|
|
73
73
|
|
|
74
74
|
## Output
|
|
75
75
|
|
|
76
|
-
Lead: `Total: N (P0=N, P1=N, P2=N)`. For each sub-bucket P1-P7, produce Shape A or Shape B (with ≥3 probes). Each Shape A finding must cite (a) the identifier file:line, (b) one concrete input that shows the name-vs-body gap, and (c) the recommended fix direction (rename vs body-tighten). Cross-bucket Q1-Q3 answers after the per-sub-bucket walk. Adversarial second pass: "assume your first pass missed at least 3 identifiers whose names overstate what the body
|
|
76
|
+
Lead: `Total: N (P0=N, P1=N, P2=N)`. For each sub-bucket P1-P7, produce Shape A or Shape B (with ≥3 probes). Each Shape A finding must cite (a) the identifier file:line, (b) one concrete input that shows the name-vs-body gap, and (c) the recommended fix direction (rename vs body-tighten). Cross-bucket Q1-Q3 answers after the per-sub-bucket walk. Adversarial second pass: "assume your first pass missed at least 3 identifiers whose names overstate what the body does — find them." Open Questions section for ambiguities. Read-only. No edits, no commits.
|
|
77
77
|
|
|
78
78
|
---
|
|
79
79
|
|
|
@@ -30,13 +30,13 @@ ID prefix: `find`.
|
|
|
30
30
|
|
|
31
31
|
**Q2. PR-description claim verification**
|
|
32
32
|
- Read the PR body. List each falsifiable claim — a dead-code removal, a performance or ceiling number, a "last caller" assertion, a "no remaining X" cleanup claim.
|
|
33
|
-
- Verify each claim two ways against the diff and the repo. A dead-code claim gets a reference search across the repo for the named symbol. A ceiling or performance claim gets checked against the
|
|
33
|
+
- Verify each claim two ways against the diff and the repo. A dead-code claim gets a reference search across the repo for the named symbol. A ceiling or performance claim gets checked against the config value the diff sets and the call sites it touches. Cite the PR-body line and the diff or repo line that confirms or contradicts it.
|
|
34
34
|
- Adversarial probes when the body looks correct: (a) search the repo for a surviving caller of any symbol the body calls dead; (b) compare each stated number against the config constant the diff sets; (c) check whether a "this finishes the cleanup" claim leaves a matching pattern in an unchanged sibling file.
|
|
35
35
|
|
|
36
36
|
**Q3. Message-vs-guard consistency**
|
|
37
37
|
- For every log, error, and evidence string the diff adds or edits, read the branch condition that gates it. The message text matches what that guard guarantees: a string asserting an outcome fires only on a branch that reaches the outcome.
|
|
38
38
|
- Check temporal wording against the lifetime of the state variable the message names: "this run" versus "this round", "is open" versus "was opened", "reached" versus "attempted". Cite the message line and the guard or state-variable line.
|
|
39
|
-
- Adversarial probes when messages look aligned: (a) trace each success message back to the return value or flag it claims and confirm the branch cannot reach it with the opposite value; (b) check every "missing" / "not found" error against the exception class the branch
|
|
39
|
+
- Adversarial probes when messages look aligned: (a) trace each success message back to the return value or flag it claims and confirm the branch cannot reach it with the opposite value; (b) check every "missing" / "not found" error against the exception class the branch catches; (c) check every counter-based message against where the counter resets.
|
|
40
40
|
|
|
41
41
|
## Cross-bucket questions to answer at the end
|
|
42
42
|
|
|
@@ -41,6 +41,10 @@ test('EVER_SHIPPED_SKILL_NAMES includes the windows scheduled task skill', () =>
|
|
|
41
41
|
assert.equal(EVER_SHIPPED_SKILL_NAMES.has('windows-scheduled-task'), true);
|
|
42
42
|
});
|
|
43
43
|
|
|
44
|
+
test('EVER_SHIPPED_SKILL_NAMES includes the plugin eval standalone skill', () => {
|
|
45
|
+
assert.equal(EVER_SHIPPED_SKILL_NAMES.has('plugin-eval-standalone-skill'), true);
|
|
46
|
+
});
|
|
47
|
+
|
|
44
48
|
test('EVER_SHIPPED_SKILL_NAMES includes the session continuity companion', () => {
|
|
45
49
|
assert.equal(EVER_SHIPPED_SKILL_NAMES.has('session-continuity'), true);
|
|
46
50
|
});
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
mkdirSync,
|
|
13
13
|
mkdtempSync,
|
|
14
14
|
readFileSync,
|
|
15
|
+
readdirSync,
|
|
15
16
|
realpathSync,
|
|
16
17
|
rmSync,
|
|
17
18
|
writeFileSync,
|
|
@@ -34,38 +35,8 @@ const SHIPPED_SKILL_NAME = 'privacy-hygiene';
|
|
|
34
35
|
const ELI5_SKILL_NAME = 'eli5';
|
|
35
36
|
const E_CODE_REVIEW_SKILL_NAME = 'e-code-review';
|
|
36
37
|
const REFERENCE_DIRECTORY_NAME = 'reference';
|
|
37
|
-
const SHIPPED_AGENT_FILE_NAME = '
|
|
38
|
-
const
|
|
39
|
-
SHIPPED_AGENT_FILE_NAME,
|
|
40
|
-
'code-quality-agent.md',
|
|
41
|
-
'pr-description-writer.md',
|
|
42
|
-
];
|
|
43
|
-
const CLEAN_CODER_POLICY_REFERENCES = [
|
|
44
|
-
[
|
|
45
|
-
'<managed-root>/docs/CODE_RULES.md',
|
|
46
|
-
'packages/claude-dev-env/docs/CODE_RULES.md',
|
|
47
|
-
],
|
|
48
|
-
[
|
|
49
|
-
'<managed-root>/hooks/blocking/code_rules_enforcer.py',
|
|
50
|
-
'packages/claude-dev-env/hooks/blocking/code_rules_enforcer.py',
|
|
51
|
-
],
|
|
52
|
-
[
|
|
53
|
-
'<managed-root>/rules/code-standards.md',
|
|
54
|
-
'packages/claude-dev-env/rules/code-standards.md',
|
|
55
|
-
],
|
|
56
|
-
[
|
|
57
|
-
'<managed-root>/rules/windows-filesystem-safe.md',
|
|
58
|
-
'packages/claude-dev-env/rules/windows-filesystem-safe.md',
|
|
59
|
-
],
|
|
60
|
-
[
|
|
61
|
-
'<managed-root>/rules/gh-cli-conventions.md',
|
|
62
|
-
'packages/claude-dev-env/rules/gh-cli-conventions.md',
|
|
63
|
-
],
|
|
64
|
-
[
|
|
65
|
-
'<managed-root>/rules/plain-illustrative-docstrings.md',
|
|
66
|
-
'packages/claude-dev-env/rules/plain-illustrative-docstrings.md',
|
|
67
|
-
],
|
|
68
|
-
];
|
|
38
|
+
const SHIPPED_AGENT_FILE_NAME = 'AGENTS.md';
|
|
39
|
+
const RETIRED_AGENT_FILE_NAMES = ['clean-coder.md', 'code-quality-agent.md', 'pr-description-writer.md'];
|
|
69
40
|
const PERSONAL_SKILL_NAME = 'my-notes';
|
|
70
41
|
const PREFLIGHT_PROPOSAL_FILE_NAME = 'preflight-proposal.md';
|
|
71
42
|
|
|
@@ -116,37 +87,48 @@ function assertProposalContractInstallation(installationPaths) {
|
|
|
116
87
|
assert.equal(readFileSync(installedContractPath, 'utf8'), readFileSync(sourceContractPath, 'utf8'));
|
|
117
88
|
}
|
|
118
89
|
|
|
119
|
-
/**
|
|
120
|
-
* @param {string} agentFilePath
|
|
121
|
-
* @param {string} layoutName
|
|
122
|
-
* @param {string} managedRoot
|
|
123
|
-
* @returns {string[]}
|
|
124
|
-
*/
|
|
125
|
-
function cleanCoderPolicyReferenceProblems(agentFilePath, layoutName, managedRoot) {
|
|
126
|
-
const agentBody = readFileSync(agentFilePath, 'utf8');
|
|
127
|
-
const missingReferences = [];
|
|
128
|
-
for (const [installedReference, sourceReference] of CLEAN_CODER_POLICY_REFERENCES) {
|
|
129
|
-
const eachReference = layoutName === 'source'
|
|
130
|
-
? sourceReference
|
|
131
|
-
: installedReference;
|
|
132
|
-
const eachTargetPath = sourceReference.replace('packages/claude-dev-env/', '');
|
|
133
|
-
if (!agentBody.includes(eachReference)) {
|
|
134
|
-
missingReferences.push(eachReference + ' is absent');
|
|
135
|
-
}
|
|
136
|
-
const resolvedPath = layoutName === 'source'
|
|
137
|
-
? join(PACKAGE_DIRECTORY, eachTargetPath)
|
|
138
|
-
: join(managedRoot, eachTargetPath);
|
|
139
|
-
if (!existsSync(resolvedPath)) {
|
|
140
|
-
missingReferences.push(layoutName + ': ' + resolvedPath);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
return missingReferences.map((reference) => layoutName + ': ' + reference);
|
|
144
|
-
}
|
|
145
90
|
|
|
146
91
|
test('CONTENT_DIRECTORIES omits agents because that tree installs to the agents home', () => {
|
|
147
92
|
assert.equal(CONTENT_DIRECTORIES.includes(MANAGED_AGENTS_DIRECTORY_NAME), false);
|
|
148
93
|
});
|
|
149
94
|
|
|
95
|
+
test('full install archives recorded retired agents and preserves third-party agents', () => {
|
|
96
|
+
const homeDirectory = mkdtempSync(join(tmpdir(), 'cdev-agent-retirement-'));
|
|
97
|
+
try {
|
|
98
|
+
runInstaller(homeDirectory, []);
|
|
99
|
+
const agentsDirectory = join(homeDirectory, '.agents', 'agents');
|
|
100
|
+
const retiredPath = join(agentsDirectory, 'clean-coder.md');
|
|
101
|
+
const pluginPath = join(agentsDirectory, 'poteto-agent.md');
|
|
102
|
+
const activePath = join(agentsDirectory, 'AGENTS.md');
|
|
103
|
+
const sourcePluginPath = join(PACKAGE_DIRECTORY, '.agents', 'agents', 'poteto-agent.md');
|
|
104
|
+
assert.equal(readFileSync(pluginPath, 'utf8'), readFileSync(sourcePluginPath, 'utf8'));
|
|
105
|
+
writeFileSync(retiredPath, 'recover this retired definition\n');
|
|
106
|
+
writeFileSync(pluginPath, 'preserve this plugin definition\n');
|
|
107
|
+
const manifestPath = join(homeDirectory, '.claude', '.claude-dev-env-manifest.json');
|
|
108
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
109
|
+
manifest.files.push(retiredPath);
|
|
110
|
+
manifest.files.push(activePath);
|
|
111
|
+
writeFileSync(manifestPath, JSON.stringify(manifest));
|
|
112
|
+
|
|
113
|
+
runInstaller(homeDirectory, []);
|
|
114
|
+
|
|
115
|
+
assert.equal(existsSync(retiredPath), false);
|
|
116
|
+
assert.ok(existsSync(activePath));
|
|
117
|
+
const backupRoot = join(homeDirectory, '.claude', '.claude-dev-env-pruned');
|
|
118
|
+
const backupNames = readdirSync(backupRoot, { recursive: true });
|
|
119
|
+
const retiredBackup = backupNames.find(name => name.endsWith('clean-coder.md'));
|
|
120
|
+
assert.ok(retiredBackup);
|
|
121
|
+
assert.equal(readFileSync(join(backupRoot, retiredBackup), 'utf8'), 'recover this retired definition\n');
|
|
122
|
+
assert.equal(readFileSync(pluginPath, 'utf8'), 'preserve this plugin definition\n');
|
|
123
|
+
const updatedManifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
124
|
+
assert.equal(updatedManifest.files.includes(retiredPath), false);
|
|
125
|
+
runInstaller(homeDirectory, []);
|
|
126
|
+
assert.equal(readFileSync(pluginPath, 'utf8'), 'preserve this plugin definition\n');
|
|
127
|
+
} finally {
|
|
128
|
+
rmSync(homeDirectory, { recursive: true, force: true });
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
150
132
|
test('a full install writes skills and agents under .agents and points .claude at them', () => {
|
|
151
133
|
const homeDirectory = mkdtempSync(join(tmpdir(), 'cdev-agents-home-'));
|
|
152
134
|
const claudeHome = join(homeDirectory, '.claude');
|
|
@@ -201,18 +183,6 @@ test('a full install writes skills and agents under .agents and points .claude a
|
|
|
201
183
|
readFileSync(lookupAgentFile, 'utf8'),
|
|
202
184
|
readFileSync(canonicalAgentFile, 'utf8'),
|
|
203
185
|
);
|
|
204
|
-
const sourceAgentFile = join(
|
|
205
|
-
resolvePackageManagedDirectory(
|
|
206
|
-
PACKAGE_DIRECTORY,
|
|
207
|
-
MANAGED_AGENTS_DIRECTORY_NAME,
|
|
208
|
-
),
|
|
209
|
-
SHIPPED_AGENT_FILE_NAME,
|
|
210
|
-
);
|
|
211
|
-
const brokenPolicyReferences = [
|
|
212
|
-
...cleanCoderPolicyReferenceProblems(sourceAgentFile, 'source', claudeHome),
|
|
213
|
-
...cleanCoderPolicyReferenceProblems(canonicalAgentFile, 'installed', claudeHome),
|
|
214
|
-
];
|
|
215
|
-
assert.deepEqual(brokenPolicyReferences, [], 'Clean Coder has broken policy references');
|
|
216
186
|
assert.equal(realpathSync(lookupAgentFile), realpathSync(canonicalAgentFile));
|
|
217
187
|
assert.equal(
|
|
218
188
|
lstatSync(skillsInstallDirectory).isSymbolicLink(),
|
|
@@ -229,7 +199,7 @@ test('a full install writes skills and agents under .agents and points .claude a
|
|
|
229
199
|
}
|
|
230
200
|
});
|
|
231
201
|
|
|
232
|
-
test('
|
|
202
|
+
test('installs omit retired agents in each active agents home', () => {
|
|
233
203
|
const runRoot = mkdtempSync(join(tmpdir(), 'cdev-active-roots-'));
|
|
234
204
|
const homeDirectory = join(runRoot, 'home');
|
|
235
205
|
const configRoot = join(runRoot, 'config-profile');
|
|
@@ -283,7 +253,7 @@ test('real installs place the Clean Coder in each active agents home', () => {
|
|
|
283
253
|
...eachInstallCase.environment,
|
|
284
254
|
},
|
|
285
255
|
);
|
|
286
|
-
for (const eachAgentFileName of
|
|
256
|
+
for (const eachAgentFileName of RETIRED_AGENT_FILE_NAMES) {
|
|
287
257
|
const installedAgentPath = join(
|
|
288
258
|
eachInstallCase.agentsHome,
|
|
289
259
|
MANAGED_AGENTS_DIRECTORY_NAME,
|
|
@@ -291,30 +261,10 @@ test('real installs place the Clean Coder in each active agents home', () => {
|
|
|
291
261
|
);
|
|
292
262
|
assert.equal(
|
|
293
263
|
existsSync(installedAgentPath),
|
|
294
|
-
|
|
295
|
-
`${eachInstallCase.name}: agent is
|
|
264
|
+
false,
|
|
265
|
+
`${eachInstallCase.name}: retired agent is absent`,
|
|
296
266
|
);
|
|
297
|
-
const installedAgentText = readFileSync(installedAgentPath, 'utf8');
|
|
298
|
-
assert.match(installedAgentText, /active managed root/i);
|
|
299
|
-
assert.match(installedAgentText, /active agents home/i);
|
|
300
|
-
assert.match(installedAgentText, /<managed-root>\//);
|
|
301
|
-
assert.match(installedAgentText, /<agents-home>\//);
|
|
302
267
|
}
|
|
303
|
-
const installedCleanCoderPath = join(
|
|
304
|
-
eachInstallCase.agentsHome,
|
|
305
|
-
MANAGED_AGENTS_DIRECTORY_NAME,
|
|
306
|
-
SHIPPED_AGENT_FILE_NAME,
|
|
307
|
-
);
|
|
308
|
-
const policyReferenceProblems = cleanCoderPolicyReferenceProblems(
|
|
309
|
-
installedCleanCoderPath,
|
|
310
|
-
'installed',
|
|
311
|
-
eachInstallCase.managedRoot,
|
|
312
|
-
);
|
|
313
|
-
assert.deepEqual(
|
|
314
|
-
policyReferenceProblems,
|
|
315
|
-
[],
|
|
316
|
-
`${eachInstallCase.name}: Clean Coder has broken policy references`,
|
|
317
|
-
);
|
|
318
268
|
assert.equal(
|
|
319
269
|
isDirectoryPointerTo(
|
|
320
270
|
join(eachInstallCase.managedRoot, MANAGED_AGENTS_DIRECTORY_NAME),
|
package/bin/install.mjs
CHANGED
|
@@ -1135,6 +1135,7 @@ function renameCaseOnlyMatchToShippedName(destinationFilePath, entryNamesByDirec
|
|
|
1135
1135
|
|
|
1136
1136
|
export function copyTree(sourceBase, destBase, options = {}) {
|
|
1137
1137
|
const excludedFileNames = new Set(options.excludeFileNames ?? []);
|
|
1138
|
+
const preservedFileNames = new Set(options.preserveExistingFileNames ?? []);
|
|
1138
1139
|
const files = collectFiles(sourceBase).filter(
|
|
1139
1140
|
sourceFile => !excludedFileNames.has(basename(sourceFile)),
|
|
1140
1141
|
);
|
|
@@ -1144,6 +1145,10 @@ export function copyTree(sourceBase, destBase, options = {}) {
|
|
|
1144
1145
|
const relativePath = relative(sourceBase, sourceFile);
|
|
1145
1146
|
const destFile = join(destBase, relativePath);
|
|
1146
1147
|
mkdirSync(dirname(destFile), { recursive: true });
|
|
1148
|
+
if (preservedFileNames.has(basename(sourceFile)) && existsSync(destFile)) {
|
|
1149
|
+
stats.paths.push(destFile);
|
|
1150
|
+
continue;
|
|
1151
|
+
}
|
|
1147
1152
|
const existed = existsSync(destFile);
|
|
1148
1153
|
renameCaseOnlyMatchToShippedName(destFile, entryNamesByDirectory, options);
|
|
1149
1154
|
copyFileSync(sourceFile, destFile);
|
|
@@ -1223,6 +1228,8 @@ export const POST_FOLDED_HOOK_RELATIVE_PATHS = new Set([
|
|
|
1223
1228
|
]);
|
|
1224
1229
|
|
|
1225
1230
|
export const RETIRED_HOOK_REGISTRATION_RELATIVE_PATHS = new Set([
|
|
1231
|
+
'observability/test_failure_recorder.py',
|
|
1232
|
+
'advisory/msys_path_conversion_advisor.py',
|
|
1226
1233
|
'session/untracked_repo_detector.py',
|
|
1227
1234
|
'session/gh_pr_author_session_cleanup.py',
|
|
1228
1235
|
'observability/pr_description_writer_spawn_tracker.py',
|
|
@@ -2326,29 +2333,6 @@ function pruneRetiredSkills(installedSkillNames, priorManifestSkills) {
|
|
|
2326
2333
|
return movedDirectoryCount;
|
|
2327
2334
|
}
|
|
2328
2335
|
|
|
2329
|
-
/**
|
|
2330
|
-
* Move every file a prior install wrote under a managed root that this run leaves
|
|
2331
|
-
* unwritten into the run's backup root, one call per root.
|
|
2332
|
-
*
|
|
2333
|
-
* `copyTree` adds and overwrites but never removes, so every managed root carries
|
|
2334
|
-
* the same drift the skills root does. One call per root hands the containment
|
|
2335
|
-
* guard and the emptied-parent walk the root that owns each file, and each root's
|
|
2336
|
-
* content lands under `<backupRoot>/<root-name>/<relative>`, so the recovery point
|
|
2337
|
-
* mirrors ~/.claude.
|
|
2338
|
-
*
|
|
2339
|
-
* Nothing moves unless a prior install recorded it. A user-authored file, a
|
|
2340
|
-
* runtime artifact, and a recorded path under no managed root — ~/.claude/CLAUDE.md,
|
|
2341
|
-
* settings.json, the manifest itself, and ~/.mypy.ini outside the home — all sit
|
|
2342
|
-
* outside every root's diff and stay where they are. ~/.claude/_shared and
|
|
2343
|
-
* ~/.claude/skills/_shared are distinct absolute paths, so the `_shared` root call
|
|
2344
|
-
* and the skills root call each see their own files and neither sees the other's.
|
|
2345
|
-
*
|
|
2346
|
-
* @param {string[]|null} priorInstalledFiles Files the prior manifest recorded, or null when unknown.
|
|
2347
|
-
* @param {string[]} currentInstalledFiles Every file this run copied.
|
|
2348
|
-
* @param {string} backupRoot The run's timestamped backup directory.
|
|
2349
|
-
* @returns {{prunedCount: number, skillsPrunedCount: number, failedPaths: string[]}}
|
|
2350
|
-
* The summed count, the skills root's own count, and every path whose move failed.
|
|
2351
|
-
*/
|
|
2352
2336
|
function pruneStaleFilesAcrossManagedRoots(priorInstalledFiles, currentInstalledFiles, backupRoot) {
|
|
2353
2337
|
let prunedCount = 0;
|
|
2354
2338
|
let skillsPrunedCount = 0;
|
|
@@ -2366,6 +2350,27 @@ function pruneStaleFilesAcrossManagedRoots(priorInstalledFiles, currentInstalled
|
|
|
2366
2350
|
skillsPrunedCount = rootOutcome.prunedCount;
|
|
2367
2351
|
}
|
|
2368
2352
|
}
|
|
2353
|
+
for (const rootName of [MANAGED_AGENTS_DIRECTORY_NAME, MANAGED_SKILLS_DIRECTORY_NAME]) {
|
|
2354
|
+
const lookupRoot = join(CLAUDE_HOME, rootName);
|
|
2355
|
+
const canonicalRoot = join(AGENTS_HOME, rootName);
|
|
2356
|
+
const canonicalCurrentFiles = currentInstalledFiles.map(filePath =>
|
|
2357
|
+
isInsideDirectory(filePath, lookupRoot)
|
|
2358
|
+
? join(canonicalRoot, relative(lookupRoot, filePath))
|
|
2359
|
+
: filePath,
|
|
2360
|
+
);
|
|
2361
|
+
const rootOutcome = pruneStaleInstalledFiles(
|
|
2362
|
+
priorInstalledFiles,
|
|
2363
|
+
canonicalCurrentFiles,
|
|
2364
|
+
canonicalRoot,
|
|
2365
|
+
join(backupRoot, 'agents-home', rootName),
|
|
2366
|
+
{ managedHomeDirectory: AGENTS_HOME },
|
|
2367
|
+
);
|
|
2368
|
+
prunedCount += rootOutcome.prunedCount;
|
|
2369
|
+
failedPaths.push(...rootOutcome.failedPaths);
|
|
2370
|
+
if (rootName === MANAGED_SKILLS_DIRECTORY_NAME) {
|
|
2371
|
+
skillsPrunedCount += rootOutcome.prunedCount;
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2369
2374
|
return { prunedCount, skillsPrunedCount, failedPaths };
|
|
2370
2375
|
}
|
|
2371
2376
|
|
|
@@ -2655,7 +2660,9 @@ function executeInstallPlanMutations(plan, transactionHelpers) {
|
|
|
2655
2660
|
);
|
|
2656
2661
|
if (!existsSync(agentsSource)) continue;
|
|
2657
2662
|
const agentsDestination = INSTALL_ROOT_RESOLUTION.agentsLookupDirectory;
|
|
2658
|
-
const stats = copyTree(agentsSource, agentsDestination
|
|
2663
|
+
const stats = copyTree(agentsSource, agentsDestination, {
|
|
2664
|
+
preserveExistingFileNames: ['poteto-agent.md'],
|
|
2665
|
+
});
|
|
2659
2666
|
if (!summary[MANAGED_AGENTS_DIRECTORY_NAME]) {
|
|
2660
2667
|
summary[MANAGED_AGENTS_DIRECTORY_NAME] = stats;
|
|
2661
2668
|
} else {
|
package/bin/install.test.mjs
CHANGED
|
@@ -1168,6 +1168,8 @@ test('FOLDED_HOOK_RELATIVE_PATHS contains folded hooks plus retired entries', ()
|
|
|
1168
1168
|
|
|
1169
1169
|
test('retired hook registrations stay managed so reinstall removes them', () => {
|
|
1170
1170
|
const retiredPaths = new Set([
|
|
1171
|
+
'observability/test_failure_recorder.py',
|
|
1172
|
+
'advisory/msys_path_conversion_advisor.py',
|
|
1171
1173
|
'session/untracked_repo_detector.py',
|
|
1172
1174
|
'session/gh_pr_author_session_cleanup.py',
|
|
1173
1175
|
'observability/pr_description_writer_spawn_tracker.py',
|
|
@@ -2023,6 +2025,25 @@ test('copyTree copies AGENTS.md with agent definitions', () => {
|
|
|
2023
2025
|
});
|
|
2024
2026
|
|
|
2025
2027
|
|
|
2028
|
+
test('copyTree preserves an existing file named in the preserve set', () => {
|
|
2029
|
+
const sourceRoot = mkdtempSync(join(tmpdir(), 'cdev-copy-preserve-source-'));
|
|
2030
|
+
const destinationRoot = mkdtempSync(join(tmpdir(), 'cdev-copy-preserve-destination-'));
|
|
2031
|
+
try {
|
|
2032
|
+
const sourceFilePath = join(sourceRoot, 'poteto-agent.md');
|
|
2033
|
+
const destinationFilePath = join(destinationRoot, 'poteto-agent.md');
|
|
2034
|
+
writeFileSync(sourceFilePath, 'package wrapper\n');
|
|
2035
|
+
writeFileSync(destinationFilePath, 'user wrapper\n');
|
|
2036
|
+
|
|
2037
|
+
copyTree(sourceRoot, destinationRoot, { preserveExistingFileNames: ['poteto-agent.md'] });
|
|
2038
|
+
|
|
2039
|
+
assert.equal(readFileSync(destinationFilePath, 'utf8'), 'user wrapper\n');
|
|
2040
|
+
} finally {
|
|
2041
|
+
rmSync(sourceRoot, { recursive: true, force: true });
|
|
2042
|
+
rmSync(destinationRoot, { recursive: true, force: true });
|
|
2043
|
+
}
|
|
2044
|
+
});
|
|
2045
|
+
|
|
2046
|
+
|
|
2026
2047
|
const SHIPPED_README_NAME = 'README.md';
|
|
2027
2048
|
const INSTALLED_README_NAME = 'Readme.md';
|
|
2028
2049
|
const RETIRED_HOOK_RELATIVE_PATH = 'blocking/retired_gate.py';
|
|
@@ -26,7 +26,7 @@ import { resolvePackageManagedDirectory } from './resolve-package-managed-direct
|
|
|
26
26
|
|
|
27
27
|
const PACKAGE_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
28
28
|
const CANONICAL_SKILL_NAME = 'privacy-hygiene';
|
|
29
|
-
const CANONICAL_AGENT_FILE_NAME = '
|
|
29
|
+
const CANONICAL_AGENT_FILE_NAME = 'AGENTS.md';
|
|
30
30
|
|
|
31
31
|
test('prefers the .agents tree when both layouts exist', () => {
|
|
32
32
|
const sourceRoot = mkdtempSync(join(tmpdir(), 'cdev-src-agents-'));
|
|
@@ -25,7 +25,7 @@ function advisorRoute(request, options = {}) {
|
|
|
25
25
|
return route(request, {
|
|
26
26
|
trustedSessionMetadata: {
|
|
27
27
|
authorized: true,
|
|
28
|
-
registeredAgentType: '
|
|
28
|
+
registeredAgentType: 'team-advisor',
|
|
29
29
|
},
|
|
30
30
|
...options,
|
|
31
31
|
});
|
|
@@ -55,6 +55,13 @@ test('the shipped policy parses and names native model ids', () => {
|
|
|
55
55
|
assert.deepEqual(policy.advisorDefault, { model: 'astra', effort: 'medium' });
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
+
test('poteto plugin aliases use worker routing without advisor privileges', () => {
|
|
59
|
+
for (const agent_type of ['poteto-agent', 'pstack:poteto-agent']) {
|
|
60
|
+
const result = route({ agent_type, model: 'Astra', reasoning_effort: 'high' });
|
|
61
|
+
assert.deepEqual(result.selected, { model: 'gpt-6-astra', effort: 'low' });
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
58
65
|
test('every approved pair returns its literal native pair', () => {
|
|
59
66
|
const expected = [
|
|
60
67
|
[['Luna', 'high'], ['gpt-5.6-luna', 'high']],
|
|
@@ -74,7 +81,7 @@ test('every approved pair returns its literal native pair', () => {
|
|
|
74
81
|
const advisor = advisorRoute({
|
|
75
82
|
model: 'Astra',
|
|
76
83
|
reasoning_effort: 'high',
|
|
77
|
-
agent_type: '
|
|
84
|
+
agent_type: 'team-advisor',
|
|
78
85
|
});
|
|
79
86
|
assert.equal(advisor.status, 'remapped');
|
|
80
87
|
assert.deepEqual(advisor.selected, { model: 'gpt-6-astra', effort: 'high' });
|
|
@@ -105,7 +112,7 @@ test('every automatic replacement returns its literal destination', () => {
|
|
|
105
112
|
|
|
106
113
|
test('advisor and worker Astra restrictions use role-specific replacements', () => {
|
|
107
114
|
assert.deepEqual(
|
|
108
|
-
advisorRoute({ model: 'Astra', reasoning_effort: 'xhigh', agent_type: '
|
|
115
|
+
advisorRoute({ model: 'Astra', reasoning_effort: 'xhigh', agent_type: 'team-advisor' }).selected,
|
|
109
116
|
{ model: 'gpt-6-astra', effort: 'medium' },
|
|
110
117
|
);
|
|
111
118
|
assert.deepEqual(
|
|
@@ -113,7 +120,7 @@ test('advisor and worker Astra restrictions use role-specific replacements', ()
|
|
|
113
120
|
{ model: 'gpt-6-astra', effort: 'low' },
|
|
114
121
|
);
|
|
115
122
|
assert.deepEqual(
|
|
116
|
-
advisorRoute({ model: 'Astra', reasoning_effort: 'high', agent_type: '
|
|
123
|
+
advisorRoute({ model: 'Astra', reasoning_effort: 'high', agent_type: 'team-advisor' }).selected,
|
|
117
124
|
{ model: 'gpt-6-astra', effort: 'high' },
|
|
118
125
|
);
|
|
119
126
|
});
|
|
@@ -122,7 +129,7 @@ test('registered advisor roles satisfy advisory authorization', () => {
|
|
|
122
129
|
const result = advisorRoute({
|
|
123
130
|
model: 'Astra',
|
|
124
131
|
reasoning_effort: 'high',
|
|
125
|
-
agent_type: '
|
|
132
|
+
agent_type: 'team-advisor',
|
|
126
133
|
});
|
|
127
134
|
|
|
128
135
|
assert.deepEqual(result.selected, { model: 'gpt-6-astra', effort: 'high' });
|
|
@@ -135,7 +142,7 @@ test('a policy from the first stack revision remains routable', () => {
|
|
|
135
142
|
{
|
|
136
143
|
model: 'Astra',
|
|
137
144
|
reasoning_effort: 'high',
|
|
138
|
-
agent_type: '
|
|
145
|
+
agent_type: 'team-advisor',
|
|
139
146
|
},
|
|
140
147
|
{ policy: legacyPolicy },
|
|
141
148
|
);
|
|
@@ -146,7 +153,7 @@ test('an advisor role claim cannot grant advisory authorization', () => {
|
|
|
146
153
|
const result = route({
|
|
147
154
|
model: 'Astra',
|
|
148
155
|
reasoning_effort: 'high',
|
|
149
|
-
agent_type: '
|
|
156
|
+
agent_type: 'team-advisor',
|
|
150
157
|
});
|
|
151
158
|
assert.equal(result.status, 'blocked');
|
|
152
159
|
assert.equal(result.diagnostic, 'advisor role is not trusted');
|
|
@@ -158,7 +165,7 @@ test('Astra Light resolves to Astra Low', () => {
|
|
|
158
165
|
});
|
|
159
166
|
|
|
160
167
|
test('missing advisor settings use the policy default', () => {
|
|
161
|
-
const result = advisorRoute({ agent_type: '
|
|
168
|
+
const result = advisorRoute({ agent_type: 'team-advisor' });
|
|
162
169
|
assert.deepEqual(result.selected, { model: 'gpt-6-astra', effort: 'medium' });
|
|
163
170
|
});
|
|
164
171
|
|
|
@@ -6,7 +6,7 @@ Source: Smart & Molak, *BDD in Action* 2e, Chapter 7.6 — scenario quality patt
|
|
|
6
6
|
|
|
7
7
|
## Declarative Focus
|
|
8
8
|
|
|
9
|
-
Scenarios work best when they name what users want to achieve in the language of the business. Lead with goals and recognizable domain tasks so readers grasp intent at a glance. Reserve step-level or UI detail for places where it
|
|
9
|
+
Scenarios work best when they name what users want to achieve in the language of the business. Lead with goals and recognizable domain tasks so readers grasp intent at a glance. Reserve step-level or UI detail for places where it clarifies behavior.
|
|
10
10
|
|
|
11
11
|
> "Good scenarios model business behavior, not system interactions." — Smart & Molak §7.6.3
|
|
12
12
|
|
|
@@ -42,7 +42,7 @@ Then I should be presented with the following hotels:
|
|
|
42
42
|
|
|
43
43
|
Personas ground scenarios in realistic goals and context. Use light soap-opera personas when you need depth before full UX research: introduce names and roles as needed and deepen them across scenarios.
|
|
44
44
|
|
|
45
|
-
> Smart & Molak §7.6.5 describe personas as rich, realistic descriptions: each persona captures goals, abilities, and background information that ground the test scenario in a
|
|
45
|
+
> Smart & Molak §7.6.5 describe personas as rich, realistic descriptions: each persona captures goals, abilities, and background information that ground the test scenario in a user context.
|
|
46
46
|
|
|
47
47
|
Good scenarios name who acts and what they need in plain language.
|
|
48
48
|
|