claude-dev-env 7.7.1 → 8.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 (103) hide show
  1. package/.agents/agents/clean-coder.md +5 -6
  2. package/.agents/agents/issue-tracker.md +1 -1
  3. package/.agents/agents/pr-description-writer.md +1 -1
  4. package/.agents/agents/skill-writer-agent.md +1 -12
  5. package/.agents/agents/test_agent_frontmatter.py +2 -19
  6. package/.agents/skills/issue-tracker/SKILL.md +1 -1
  7. package/.agents/skills/orchestrator/SKILL.md +4 -5
  8. package/.agents/skills/pull-request/SKILL.md +8 -7
  9. package/.agents/skills/pull-request/reference/publication-tasks.md +1 -1
  10. package/.agents/skills/team-advisor/SKILL.md +2 -3
  11. package/_shared/advisor/advisor-protocol.md +0 -1
  12. package/_shared/advisor/reference/lifecycle.md +0 -1
  13. package/_shared/advisor/reference/warm-up.md +1 -2
  14. package/audit-rubrics/prompts/category-e-dead-code.md +3 -3
  15. package/bin/install-constants.mjs +0 -5
  16. package/bin/install-pstack-plugin.mjs +192 -0
  17. package/bin/install-pstack-plugin.test.mjs +181 -0
  18. package/bin/install.agents-home.test.mjs +0 -4
  19. package/bin/install.codex-rules.test.mjs +6 -0
  20. package/bin/install.cursor-rules.test.mjs +3 -253
  21. package/bin/install.mjs +55 -160
  22. package/bin/install.test.mjs +110 -149
  23. package/bin/prune-retired-pstack-store.mjs +83 -0
  24. package/bin/prune-retired-pstack-store.test.mjs +105 -0
  25. package/bin/prune-session-continuity.mjs +57 -0
  26. package/bin/prune-session-continuity.test.mjs +70 -0
  27. package/bin/resolve-install-root.mjs +2 -0
  28. package/bin/resolve-install-root.test.mjs +69 -0
  29. package/docs/CODE_RULES.md +1 -1
  30. package/docs/agent-spawn-protocol.md +1 -1
  31. package/docs/codex-compatibility.md +4 -16
  32. package/docs/nas-ssh-invocation.md +1 -1
  33. package/docs/references/prose-style-enforcement.md +2 -2
  34. package/hooks/HOOK_SPECS_PROMPT_WORKFLOW.md +1 -3
  35. package/hooks/blocking/code_rules_constants_config.py +1 -108
  36. package/hooks/blocking/code_rules_enforcer.py +9 -5
  37. package/hooks/blocking/code_rules_test_layout.py +6 -7
  38. package/hooks/blocking/test_code_rules_constants_config.py +13 -0
  39. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +0 -1
  40. package/hooks/blocking/test_code_rules_enforcer_dispatch_wiring.py +15 -0
  41. package/hooks/blocking/test_code_rules_enforcer_split_constants_config.py +0 -60
  42. package/hooks/blocking/test_context_control_policy_files.py +0 -11
  43. package/hooks/hooks_constants/blocking_check_limits.py +1 -1
  44. package/hooks/hooks_constants/code_rules_enforcer_constants.py +0 -1
  45. package/hooks/hooks_constants/setup_project_paths_constants.py +1 -2
  46. package/hooks/session/test_untracked_repo_detector.py +1 -1
  47. package/hooks/validators/code_quality_checks.py +15 -2
  48. package/hooks/validators/test_code_quality_checks.py +22 -0
  49. package/package.json +1 -2
  50. package/rules/ci-owns-the-gate.md +58 -0
  51. package/rules/code-standards.md +1 -2
  52. package/rules/docstring-prose-matches-implementation.md +2 -2
  53. package/rules/failure-blast-radius.md +1 -1
  54. package/rules/git-workflow.md +23 -11
  55. package/rules/orphan-css-class.md +1 -1
  56. package/rules/paired-test-coverage.md +1 -1
  57. package/rules/retired-hook-prose.md +1 -1
  58. package/rules/verify-before-asking.md +1 -1
  59. package/rules/windows-filesystem-safe.md +1 -1
  60. package/rules/workflow-substitution-slots.md +1 -1
  61. package/scripts/Sync-RepoMain.ps1 +71 -6
  62. package/scripts/policy_lint/adapter_detectors.py +116 -10
  63. package/scripts/policy_lint/config/approved_test_pairs.py +12 -0
  64. package/scripts/policy_lint/config/constants.py +3 -0
  65. package/scripts/tests/Sync-RepoMain.Tests.ps1 +108 -0
  66. package/scripts/tests/test_policy_lint_rules_validator_baseline.py +126 -0
  67. package/.agents/skills/session-continuity/AGENTS.md +0 -1
  68. package/.agents/skills/session-continuity/CLAUDE.md +0 -1
  69. package/.agents/skills/session-continuity/README.md +0 -223
  70. package/.agents/skills/session-continuity/SKILL.md +0 -125
  71. package/.agents/skills/session-continuity/continuity.mjs +0 -445
  72. package/.agents/skills/session-continuity/continuity.test.mjs +0 -381
  73. package/bin/install-session-continuity.mjs +0 -131
  74. package/bin/install-session-continuity.test.mjs +0 -106
  75. package/bin/install.pstack-plugin.test.mjs +0 -115
  76. package/bin/pstack-codex-discovery.test.mjs +0 -79
  77. package/bin/pstack-model-preferences.claude.json +0 -23
  78. package/bin/pstack-model-preferences.codex.json +0 -24
  79. package/bin/pstack.mjs +0 -392
  80. package/bin/pstack.test.mjs +0 -347
  81. package/bin/refresh-pstack-plugin-skills.test.mjs +0 -211
  82. package/bin/select-pstack-models.test.mjs +0 -776
  83. package/hooks/blocking/test_code_rules_enforcer_file_global_constants.py +0 -231
  84. package/rules/agent-spawn-protocol.md +0 -9
  85. package/rules/ask-user-question-required.md +0 -31
  86. package/rules/claims-as-quotes.md +0 -55
  87. package/rules/confirm-implementation-forks.md +0 -11
  88. package/rules/file-global-constants.md +0 -81
  89. package/rules/hedging-claims.md +0 -7
  90. package/rules/measurement-denominators.md +0 -39
  91. package/rules/prompt-workflow-context-controls.md +0 -19
  92. package/rules/pstack-host-mapping.md +0 -48
  93. package/rules/pstack-models.md +0 -142
  94. package/rules/vault-context.md +0 -5
  95. package/scripts/pstack-adapters/common.md +0 -17
  96. package/scripts/pstack-adapters/create-skill.md +0 -14
  97. package/scripts/pstack-adapters/host-claude.md +0 -7
  98. package/scripts/pstack-adapters/host-codex.md +0 -11
  99. package/scripts/pstack-adapters/host-cursor.md +0 -7
  100. package/scripts/pstack.lock.json +0 -11
  101. package/scripts/refresh_pstack_plugin_skills.mjs +0 -200
  102. package/scripts/select_pstack_models.mjs +0 -469
  103. /package/.agents/{skills → skills-archived}/source-command-logifix/SKILL.md +0 -0
@@ -22,7 +22,7 @@ Before writing a single line — **task-local discovery only** (no project-wide
22
22
  1. **Load scoped repository instructions first.** Starting at the repository root, read every applicable `AGENTS.md` on the path to the task file. Then read the applicable `CLAUDE.md` files. Apply nearer instructions after broader ones; the closest file wins.
23
23
  2. **Read the file you are about to edit** (when editing existing code). Note every existing comment so you can leave each one untouched on lines that remain otherwise unchanged.
24
24
  3. **Discover config only next to the task files.** From each file you will write or edit, walk up to the nearest package or repo root and inspect the target package's existing constants layout — such as `config/` or a sibling `*_constants` package. Keep this task-local constants search. Do **not** force a generic `config/` layout. Do **not** glob the whole tree for every config file. Do **not** glob or open `.env`, `.env.*`, or other secret files.
25
- 4. **Reuse constants from that local table.** Reuse first: exact value match → import the existing name. Semantic match → reuse it. Add a shared constant only when the value is shared policy or has multiple consumers. When no match exists, use the target package's existing constants layout; a one-use value follows `file-global-constants` rather than becoming a new shared constant.
25
+ 4. **Reuse constants from that local table.** Reuse first: exact value match → import the existing name. Semantic match → reuse it. Add a shared constant only when the value is shared policy or has multiple consumers. When no match exists, use the target package's existing constants layout, and keep a one-use value in the scope that reads it rather than making it a new shared constant.
26
26
  5. **Search callers.** When a symbol, name, or signature changes, search its full caller boundary and update every consumer. This search may be wider than the constants search.
27
27
 
28
28
  ## Generation mindset (9 laws)
@@ -50,17 +50,16 @@ Installed paths use the active managed root and agents home resolved above; sour
50
50
  | Write-time gates | `<managed-root>/hooks/blocking/code_rules_enforcer.py` (source fallback: `packages/claude-dev-env/hooks/blocking/code_rules_enforcer.py`) |
51
51
  | Naming and abbreviations | `<managed-root>/docs/CODE_RULES.md#5-no-abbreviations` (source fallback: `packages/claude-dev-env/docs/CODE_RULES.md#5-no-abbreviations`) |
52
52
  | Policy surface map | `<managed-root>/rules/code-standards.md` (source fallback: `packages/claude-dev-env/rules/code-standards.md`) |
53
- | File-global constants | `<managed-root>/rules/file-global-constants.md` (source fallback: `packages/claude-dev-env/rules/file-global-constants.md`) |
54
53
  | Windows rmtree / mkdir | `<managed-root>/rules/windows-filesystem-safe.md` (source fallback: `packages/claude-dev-env/rules/windows-filesystem-safe.md`) |
55
54
  | `gh` body files | `<managed-root>/rules/gh-cli-conventions.md` (source fallback: `packages/claude-dev-env/rules/gh-cli-conventions.md`) |
56
55
  | Plain illustrative docstrings | `<managed-root>/rules/plain-illustrative-docstrings.md` (source fallback: `packages/claude-dev-env/rules/plain-illustrative-docstrings.md`) |
57
56
  | Tests / TDD | `<managed-root>/rules/testing.md` (source fallback: `packages/claude-dev-env/rules/testing.md`), `<managed-root>/rules/paired-test-coverage.md` (source fallback: `packages/claude-dev-env/rules/paired-test-coverage.md`), `<managed-root>/rules/bdd.md` (source fallback: `packages/claude-dev-env/rules/bdd.md`) |
58
- | Questions / task tracking | `<managed-root>/rules/ask-user-question-required.md` (source fallback: `packages/claude-dev-env/rules/ask-user-question-required.md`), `<managed-root>/rules/verify-before-asking.md` (source fallback: `packages/claude-dev-env/rules/verify-before-asking.md`) |
57
+ | Questions / task tracking | `<managed-root>/rules/verify-before-asking.md` (source fallback: `packages/claude-dev-env/rules/verify-before-asking.md`) |
59
58
  | Runtime evidence | `<managed-root>/rules/verify-runtime-state.md` (source fallback: `packages/claude-dev-env/rules/verify-runtime-state.md`) |
60
59
  | Documentation / durable artifacts | `<managed-root>/rules/doc-inventory-integrity.md` (source fallback: `packages/claude-dev-env/rules/doc-inventory-integrity.md`), `<managed-root>/rules/durable-post-artifacts.md` (source fallback: `packages/claude-dev-env/rules/durable-post-artifacts.md`) |
61
60
  | Batch / failure blast radius | `<managed-root>/rules/failure-blast-radius.md` (source fallback: `packages/claude-dev-env/rules/failure-blast-radius.md`) |
62
61
  | Git / GitHub | `<managed-root>/rules/git-workflow.md` (source fallback: `packages/claude-dev-env/rules/git-workflow.md`), `<managed-root>/rules/gh-cli-conventions.md` (source fallback: `packages/claude-dev-env/rules/gh-cli-conventions.md`), `<managed-root>/rules/re-stage-before-commit.md` (source fallback: `packages/claude-dev-env/rules/re-stage-before-commit.md`) |
63
- | Workers / completion | `<managed-root>/rules/agent-spawn-protocol.md` (source fallback: `packages/claude-dev-env/rules/agent-spawn-protocol.md`), `<managed-root>/rules/workers-done-before-complete.md` (source fallback: `packages/claude-dev-env/rules/workers-done-before-complete.md`) |
62
+ | Workers / completion | `<managed-root>/rules/workers-done-before-complete.md` (source fallback: `packages/claude-dev-env/rules/workers-done-before-complete.md`) |
64
63
  | TDD / right-size | Review contract Tests + Design; `CODE_RULES.md` §7–§8 |
65
64
 
66
65
  ## Session policy map (canonical links)
@@ -70,13 +69,13 @@ Load only the group that matches the task. Keep session policy details in these
70
69
  | Group | Canonical refs |
71
70
  |---|---|
72
71
  | Tests | `<managed-root>/rules/testing.md` (source fallback: `packages/claude-dev-env/rules/testing.md`); `<managed-root>/rules/anti-corollary-tests.md` (source fallback: `packages/claude-dev-env/rules/anti-corollary-tests.md`) |
73
- | Questions | `<managed-root>/rules/ask-user-question-required.md` (source fallback: `packages/claude-dev-env/rules/ask-user-question-required.md`); `<managed-root>/rules/verify-before-asking.md` (source fallback: `packages/claude-dev-env/rules/verify-before-asking.md`) |
72
+ | Questions | `<managed-root>/rules/verify-before-asking.md` (source fallback: `packages/claude-dev-env/rules/verify-before-asking.md`) |
74
73
  | Search and shell | `<managed-root>/rules/filesystem-search.md` (source fallback: `packages/claude-dev-env/rules/filesystem-search.md`); `<managed-root>/rules/shell-invocation.md` (source fallback: `packages/claude-dev-env/rules/shell-invocation.md`) |
75
74
  | Runtime checks | `<managed-root>/rules/verify-runtime-state.md` (source fallback: `packages/claude-dev-env/rules/verify-runtime-state.md`) |
76
75
  | Documentation | `<managed-root>/rules/doc-inventory-integrity.md` (source fallback: `packages/claude-dev-env/rules/doc-inventory-integrity.md`); `<managed-root>/rules/docstring-prose-matches-implementation.md` (source fallback: `packages/claude-dev-env/rules/docstring-prose-matches-implementation.md`) |
77
76
  | Batch failures | `<managed-root>/rules/failure-blast-radius.md` (source fallback: `packages/claude-dev-env/rules/failure-blast-radius.md`) |
78
77
  | Git | `<managed-root>/rules/git-workflow.md` (source fallback: `packages/claude-dev-env/rules/git-workflow.md`); `<managed-root>/rules/re-stage-before-commit.md` (source fallback: `packages/claude-dev-env/rules/re-stage-before-commit.md`) |
79
- | Worker coordination | `<managed-root>/rules/agent-spawn-protocol.md` (source fallback: `packages/claude-dev-env/rules/agent-spawn-protocol.md`); `<managed-root>/rules/workers-done-before-complete.md` (source fallback: `packages/claude-dev-env/rules/workers-done-before-complete.md`) |
78
+ | Worker coordination | `<managed-root>/rules/workers-done-before-complete.md` (source fallback: `packages/claude-dev-env/rules/workers-done-before-complete.md`) |
80
79
 
81
80
  Material implementation questions must return to the caller for `AskUserQuestion` handling; do not ask in plain text or guess.
82
81
 
@@ -19,7 +19,7 @@ Keep the same warm `issue-tracker` agent for follow-up actions on the **same iss
19
19
 
20
20
  ## Voice
21
21
 
22
- Use the `plain-brief` output style (`output-styles/plain-brief.md`). Final message is issue number(s) and URL(s) only.
22
+ Keep the final message to the issue number(s) and URL(s) only.
23
23
 
24
24
  ## On each turn
25
25
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: pr-description-writer
3
- description: "Writes factual pull request titles, descriptions, and comments in plain illustrative language from the current diff. The `pr_description_writer_gate` PreToolUse hook requires a spawn of this agent before `gh pr create`."
3
+ description: "Writes factual pull request titles, descriptions, and comments in plain illustrative language from the current diff. Spawn it when a pull request needs a body written from the diff or rewritten in full."
4
4
  tools: Read,Grep,Glob,Bash
5
5
  ---
6
6
 
@@ -30,18 +30,7 @@ A caller may bundle three of these fields — `Composition plan`, `Description`,
30
30
 
31
31
  A refine packet instead carries: the current SKILL.md, what was observed, what to change, new gotchas to add, what to preserve, a description rewrite, a composition change, and a deterministic fix.
32
32
 
33
- **Direct request.** A looser prompt names the skill to build or the file to edit. Fill the packet yourself from the references below, and ask the caller for any field only they hold, such as domain context or constraints.
34
-
35
- ## Read first
36
-
37
- At the start of every job, read the live references at `~/.claude/skills/skill-builder/references/` so you author to current conventions:
38
-
39
- - `delegation-map.md` — the handoff packet and the Produce contract.
40
- - `deterministic-elements.md` — the rule that deterministic work ships as code, artifacts, or session tasks.
41
- - `description-field.md` — the trigger-catalog description shape.
42
- - `skill-modularity.md` — hub layout and compose-by-name.
43
-
44
- Read them fresh each run; they carry the rules you author to.
33
+ **Direct request.** A looser prompt names the skill to build or the file to edit. Fill the packet yourself from the standards below, and ask the caller for any field only they hold, such as domain context or constraints.
45
34
 
46
35
  ## Standards every file meets
47
36
 
@@ -13,14 +13,14 @@ model on every spawn, so no agent definition names one, concrete or
13
13
  `inherit`::
14
14
 
15
15
  ok: <no model key at all>
16
- flag: model: inherit <- caller can no longer choose the model
16
+ flag: model: inherit <- caller cannot choose the model
17
17
  flag: model: opus <- pinned concrete model, caller can't override
18
18
 
19
19
  Every block must also load through `yaml.safe_load`. An unquoted colon inside
20
20
  a plain scalar reads as a mapping key and makes the whole block unloadable::
21
21
 
22
22
  ok: description: ... constraints. Examples are below.
23
- flag: description: ... constraints. Examples: <- block no longer loads
23
+ flag: description: ... constraints. Examples: <- block does not load
24
24
 
25
25
  Two parsers read these files, and a block that satisfies one can still be
26
26
  unreadable by the other. `scripts/codex_compat_materializer.py` loads the
@@ -445,10 +445,6 @@ EXPECTED_SOURCE_LINK_PAIRS = {
445
445
  "<managed-root>/rules/code-standards.md",
446
446
  "packages/claude-dev-env/rules/code-standards.md",
447
447
  ),
448
- (
449
- "<managed-root>/rules/file-global-constants.md",
450
- "packages/claude-dev-env/rules/file-global-constants.md",
451
- ),
452
448
  (
453
449
  "<managed-root>/rules/windows-filesystem-safe.md",
454
450
  "packages/claude-dev-env/rules/windows-filesystem-safe.md",
@@ -477,10 +473,6 @@ EXPECTED_SOURCE_LINK_PAIRS = {
477
473
  "<managed-root>/rules/bdd.md",
478
474
  "packages/claude-dev-env/rules/bdd.md",
479
475
  ),
480
- (
481
- "<managed-root>/rules/ask-user-question-required.md",
482
- "packages/claude-dev-env/rules/ask-user-question-required.md",
483
- ),
484
476
  (
485
477
  "<managed-root>/rules/verify-before-asking.md",
486
478
  "packages/claude-dev-env/rules/verify-before-asking.md",
@@ -521,10 +513,6 @@ EXPECTED_SOURCE_LINK_PAIRS = {
521
513
  "<managed-root>/rules/re-stage-before-commit.md",
522
514
  "packages/claude-dev-env/rules/re-stage-before-commit.md",
523
515
  ),
524
- (
525
- "<managed-root>/rules/agent-spawn-protocol.md",
526
- "packages/claude-dev-env/rules/agent-spawn-protocol.md",
527
- ),
528
516
  (
529
517
  "<managed-root>/rules/workers-done-before-complete.md",
530
518
  "packages/claude-dev-env/rules/workers-done-before-complete.md",
@@ -797,7 +785,6 @@ def test_clean_coder_links_canonical_policy_areas() -> None:
797
785
  required_links = (
798
786
  "<managed-root>/docs/CODE_RULES.md#5-no-abbreviations",
799
787
  "<managed-root>/rules/testing.md",
800
- "<managed-root>/rules/ask-user-question-required.md",
801
788
  "<managed-root>/rules/verify-runtime-state.md",
802
789
  "<managed-root>/rules/doc-inventory-integrity.md",
803
790
  "<managed-root>/rules/failure-blast-radius.md",
@@ -992,7 +979,6 @@ def test_clean_coder_follows_target_package_constant_layouts() -> None:
992
979
  assert "Reuse first" in body
993
980
  assert "shared policy" in body
994
981
  assert "multiple consumers" in body
995
- assert "file-global-constants" in body
996
982
  assert "add the constant to the appropriate config file" not in body
997
983
 
998
984
 
@@ -1029,7 +1015,6 @@ def test_clean_coder_groups_session_policy_references() -> None:
1029
1015
  "Worker coordination",
1030
1016
  ):
1031
1017
  assert each_policy_group in body
1032
- assert "ask-user-question-required.md" in body
1033
1018
  assert "workers-done-before-complete.md" in body
1034
1019
  assert "verify-runtime-state.md" in body
1035
1020
  assert "Material implementation questions must return to the caller" in body
@@ -1039,7 +1024,6 @@ def test_clean_coder_groups_session_policy_references() -> None:
1039
1024
  expected_session_policy_files = (
1040
1025
  "testing.md",
1041
1026
  "anti-corollary-tests.md",
1042
- "ask-user-question-required.md",
1043
1027
  "verify-before-asking.md",
1044
1028
  "filesystem-search.md",
1045
1029
  "shell-invocation.md",
@@ -1049,7 +1033,6 @@ def test_clean_coder_groups_session_policy_references() -> None:
1049
1033
  "failure-blast-radius.md",
1050
1034
  "git-workflow.md",
1051
1035
  "re-stage-before-commit.md",
1052
- "agent-spawn-protocol.md",
1053
1036
  "workers-done-before-complete.md",
1054
1037
  )
1055
1038
  session_policy_map = body[body.index("## Session policy map") :]
@@ -21,7 +21,7 @@ Prefer the **same warm** `issue-tracker` agent for follow-ups on the same issue
21
21
 
22
22
  ## Voice
23
23
 
24
- Use the `plain-brief` output style (`output-styles/plain-brief.md`). End with the issue number(s) and URL(s) the caller must keep.
24
+ End with the issue number(s) and URL(s) the caller must keep, and nothing else.
25
25
 
26
26
  ## Gotchas
27
27
 
@@ -36,9 +36,8 @@ verification reads.
36
36
  ## status_gate (deterministic — not optional)
37
37
 
38
38
  **Prose does not keep the loop alive.** Re-arm and terminate are gated by
39
- `scripts/status_gate.py` (and, on Claude, the PreToolUse hook
40
- `orchestrator_refresh_reschedule_gate`). The gate is host-agnostic: a
41
- single pending re-arm latch in the status file, not host product names.
39
+ `scripts/status_gate.py`. The gate is host-agnostic: a single pending re-arm
40
+ latch in the status file, not host product names.
42
41
 
43
42
  ```
44
43
  python scripts/status_gate.py set --status active|done [--run-slug SLUG] [--status-file PATH]
@@ -158,8 +157,8 @@ in the repo the run works on (working files, not committed):
158
157
  - **One assignment file per task** — scope, file list, constraints, the
159
158
  acceptance check, baseline command output. The thick context goes
160
159
  here. `/prompt-generator` authors the assignment once at plan time,
161
- and every ticket for that task reuses it — that reuse is what
162
- satisfies the agent-spawn-protocol context check at each spawn.
160
+ and every ticket for that task reuses it, so each spawn starts from
161
+ the same named files, constraints, and acceptance check.
163
162
  - **Results merge into run state.** An executor's product is its
164
163
  artifact — the branch diff, the test output, the report its agent type
165
164
  may write — and its reply is thin: status, artifact paths, blockers.
@@ -73,7 +73,7 @@ current process environment.
73
73
 
74
74
  | Skill | When | Produces | If missing |
75
75
  |---|---|---|---|
76
- | `pr-description-writer` | Before create or a full body rewrite | A reviewed title and body file | Stop the create or rewrite and report that authoring is required |
76
+ | `pr-description-writer` | Create or a full body rewrite, when you want the body drafted for you | A reviewed title and body file | Write the title and body yourself |
77
77
  | `privacy-hygiene` | Before any durable GitHub post | A clean body and repository privacy sweep | Stop before publication and report the missing gate |
78
78
  | `issue-tracker` | Issue create, edit, or comment requests | Issue state and issue URLs | Route the request there. |
79
79
  | `e-code-review` | Review or review/fix loop requests | Structured findings or a clean review result | Route the request there. |
@@ -97,10 +97,10 @@ request target. Create actions publish drafts. Record the target before any writ
97
97
 
98
98
  ### 2. Author the title and body
99
99
 
100
- For create and full body rewrite, invoke the installed
101
- `pr-description-writer`. Require its title and body file as the authoring
102
- output. A comment or review may use a supplied body file when it does not
103
- rewrite the pull request description.
100
+ For create and full body rewrite, write the title and a body file, or invoke
101
+ the installed `pr-description-writer` to draft them from the diff. A comment or
102
+ review may use a supplied body file when it does not rewrite the pull request
103
+ description.
104
104
 
105
105
  ### 3. Run the local linter
106
106
 
@@ -122,8 +122,9 @@ permanent URLs. Rerun the linter after changing the body.
122
122
 
123
123
  ### 5. Resolve the process-local author
124
124
 
125
- Run `.agents/skills/pull-request/scripts/pull_request.py` with one structured
126
- action and the selected author. The command runs the linter again, resolves
125
+ Run `~/.agents/skills/pull-request/scripts/pull_request.py` (under the agents
126
+ home, not the repository; a worktree holds no `.agents/` copy) with one
127
+ structured action and the selected author. The command runs the linter again, resolves
127
128
  the author value, and passes it only to the child `gh` process. It never
128
129
  changes the parent environment or calls `gh auth switch`.
129
130
 
@@ -6,7 +6,7 @@ reason when the task is conditional. This file is a task seed catalog, not a
6
6
  markdown progress board.
7
7
 
8
8
  1. Resolve one repository, action, and selected author. For create, resolve the source branch, base, and head. For other actions, resolve one existing pull request target. Create actions publish drafts.
9
- 2. Invoke the required `pr-description-writer` for create or full body rewrite, then record its title and body file. Mark `N/A` for comment or review bodies that do not rewrite the pull request description.
9
+ 2. Record the title and body file for create or full body rewrite, written by you or drafted by `pr-description-writer`. Mark `N/A` for comment or review bodies that do not rewrite the pull request description.
10
10
  3. Validate every title, body, artifact, and local path input. Reject inline body input.
11
11
  4. Resolve the active managed root and run `<managed-root>/scripts/durable_post_lint.py` for the matching action. Record exit code `0` before credential lookup or network work.
12
12
  5. Run `privacy-hygiene` before the GitHub post and record its clean result.
@@ -26,9 +26,8 @@ One warm advisor at the strongest tier this session can reach. This session is t
26
26
  1. Name the session identity first (protocol **Host profiles**), then walk the model floor.
27
27
  2. Claude: Fable first in-session at `ADVISOR_EFFORT` (default low). When Fable is out of usage, bind Astra at the same effort. Codex: Astra in-session. Third-party: headless Fable then Astra. When the host's walk fails, fail closed.
28
28
  3. Name: `team-advisor-agent` on Claude (Agent spawn of `session-advisor`); a native Astra subagent on Codex; one CLI `session_id` on a third-party host via the protocol Claude-chain or Astra helper.
29
- 4. A Fable-tier spawn or re-spawn carries the exact token `FABLE-SPAWN-AUTHORIZED` in its prompt (protocol warm-up; `fable_spawn_gate` requires it).
30
- 5. Skip the multi-consumer "who you are" opener. This session is the sole consumer.
31
- 6. When the bind or reply path fails, fail closed and report to the user. On a third-party host, only the bound advisor issues ENDORSE / CORRECTION / PLAN / STOP.
29
+ 4. Skip the multi-consumer "who you are" opener. This session is the sole consumer.
30
+ 5. When the bind or reply path fails, fail closed and report to the user. On a third-party host, only the bound advisor issues ENDORSE / CORRECTION / PLAN / STOP.
32
31
 
33
32
  **GOTCHA (Cursor / ThirdParty + Astra):** when the walk reaches Astra or the user asks for Astra, first tool call is `python ~/.claude/_shared/advisor/scripts/codex_astra_advisor.py --bind --enable-astra --cwd <repo-root>` with the charter on stdin. Do not use Agent or Task. Do not search for a probe path. Details: [`third-party-bind.md`](../../_shared/advisor/reference/third-party-bind.md) GOTCHA and [`astra-rung.md`](../../_shared/advisor/reference/astra-rung.md).
34
33
 
@@ -104,7 +104,6 @@ The validator checks ladder shape only; host policy sits on top.
104
104
  ## Warm-up (once per session)
105
105
 
106
106
  On a **Claude host**, spawn `session-advisor` at the Fable alias with the charter as its prompt. When Fable is out of usage and the Astra rung is open, bind Astra through the Codex helper instead.
107
- A **Fable**-tier try carries the exact token `FABLE-SPAWN-AUTHORIZED` in its prompt. `hooks/blocking/fable_spawn_gate.py` denies a Fable-tier spawn without it.
108
107
  Full spawn fields and the charter template: [`reference/warm-up.md`](reference/warm-up.md).
109
108
 
110
109
  On a **Codex host**, spawn a native in-session Astra subagent with the same charter. Bind fields: [`reference/identity.md`](reference/identity.md) and [`reference/warm-up.md`](reference/warm-up.md).
@@ -10,7 +10,6 @@ Every other consumer (executors, or any other consulting session) reaches it by
10
10
  One shared advisor exists per orchestrated session, owned by the session that spawned it.
11
11
 
12
12
  **Re-spawn on drift.** If a reply shows the agent working from a stale picture, or the session pivots to an unrelated task, the owning session ends that agent and spawns a fresh one with a new charter.
13
- A **Fable**-tier re-spawn carries the exact token `FABLE-SPAWN-AUTHORIZED` in that fresh prompt, as a Fable-tier warm-up try does.
14
13
 
15
14
  ## Codex host
16
15
 
@@ -11,14 +11,13 @@ The consuming skill's session tries Fable first. Spawn with:
11
11
  - `model`: the short alias via `resolve_cli_model_id("Fable")` (alias table: [`cli-chain.md`](cli-chain.md)): `fable`. When Fable is out of usage and the Astra rung is open, continue with the Codex helper in [`astra-rung.md`](astra-rung.md) as the Astra fallback.
12
12
  - `name`: a name the session and every consumer will use to reach it (e.g. `team-advisor-agent`).
13
13
  - `run_in_background: true`.
14
- - `prompt`: the charter below. A **Fable**-tier try carries the exact token `FABLE-SPAWN-AUTHORIZED` in that prompt. `hooks/blocking/fable_spawn_gate.py` denies every `Agent` or `Task` spawn at `model: fable` whose prompt lacks that token. A try at any other tier needs no token.
14
+ - `prompt`: the charter below.
15
15
 
16
16
  Stop at the first successful bind. That try's tier is `selected_tier`; the warm advisor lives at that tier for the rest of the session.
17
17
 
18
18
  ## Charter (the spawn prompt)
19
19
 
20
20
  The charter states the agent's role, the repo path, and the session's current goal in two or three sentences. The agent is a standing reviewer. It answers only via SendMessage. File edits and commands are out of its scope.
21
- On a Fable-tier try, include the exact token `FABLE-SPAWN-AUTHORIZED` as plain text in this prompt (substring match; the gate reads the token alone, wherever it came from).
22
21
  State plainly:
23
22
 
24
23
  - Every consult carries: who is asking (name and assignment), what changed since their last consult, the live decision or question, and any load-bearing paths or excerpts.
@@ -59,7 +59,7 @@ Inline the artifact under this section using the section types defined in the ch
59
59
 
60
60
  **E7. Test fixtures / helpers defined but never used**
61
61
  - For every test file in scope, enumerate `@pytest.fixture`, `@pytest.fixture(scope=...)`, `setUp` / `tearDown` methods, factory helpers, mock builders, and module-level test constants. Verify each has at least one consumer test.
62
- - Module-level constants in test files: each must satisfy the file-global-constants use-count rule (≥2 references) OR be referenced by exactly one test plus one helper that the tests call.
62
+ - Module-level constants in test files: each must be referenced by at least two callers, OR by exactly one test plus one helper that the tests call.
63
63
  - Helpers defined inside a single test body that are never called within that body are dead.
64
64
  - Adversarial probes for proof-of-absence: (a) any test that defines a local helper (e.g., `def make_dir(...):`) and never calls it? (b) any imported test name from the production module that no test exercises? (c) any module-level constant whose call graph collapses to zero references after the diff?
65
65
 
@@ -156,7 +156,7 @@ ID prefix: `find`.
156
156
  **E7. Test fixtures / helpers defined but never used**
157
157
  - `test_sweep_empty_dirs.py` defines no `@pytest.fixture` decorators. Nothing to flag under "fixture with no consumers".
158
158
  - `_set_creation_time_windows` is the only test helper; it has three call sites (E2 above). Used.
159
- - `_SCRIPTS_DIR` (line 12) is a module-level constant used at line 13 (`if str(_SCRIPTS_DIR) not in sys.path:`) and line 14 (`sys.path.insert(0, str(_SCRIPTS_DIR))`). Two references; satisfies the file-global-constants use-count rule.
159
+ - `_SCRIPTS_DIR` (line 12) is a module-level constant used at line 13 (`if str(_SCRIPTS_DIR) not in sys.path:`) and line 14 (`sys.path.insert(0, str(_SCRIPTS_DIR))`). Two references; the constant earns its module scope.
160
160
  - No test data builders or mock factories are defined in this PR.
161
161
  - Adversarial probes for proof-of-absence: (a) does any of the five tests define a local helper inside its body that is then never called? (e.g., a `def make_dir(...):` defined but unused) — scan each test body. (b) does any test import a name from `sweep_empty_dirs` that it never uses? — only `sweep` is imported, and every test calls it. (c) does the `_SCRIPTS_DIR` block survive if pytest is invoked with `sweep_empty_dirs.py` already on `sys.path`? — yes, the membership-test guards the insert, so the constant is still meaningfully consumed even on the second run.
162
162
 
@@ -178,7 +178,7 @@ ID prefix: `find`.
178
178
 
179
179
  Q1: Are there imports unused locally but consumed by a re-export pattern in another file? Cite the cross-file pair if found. (Hypothesis: none — neither `sweep_empty_dirs.py` nor `test_sweep_empty_dirs.py` defines `__all__`, so re-export is not in play. `config/sweep_config.py` declares two constants that ARE consumed by `sweep_empty_dirs.py` lines 10-11; this is normal cross-file consumption, not a re-export.)
180
180
  Q2: What's the worst unused-code hazard introduced by this PR? Cite `<file>:<line>`. Candidates to evaluate: `arguments.interval` is parsed but unreachable on the `--once` path (line 57 short-circuits before the watch loop at line 67); the scheduled task always uses `--once`, so the `--interval` argparse declaration at line 45 is dead code on the only invocation path the installer creates. Decide: P2 (style) because the manual watch-mode path still uses it, vs P1 if you treat "the only installer path never exercises this branch" as functionally dead.
181
- Q3: Which symbol most likely will *become* dead code after a near-future refactor? Candidates: `_log_walk_error` (sole call site is the `os.walk(..., onerror=...)` kwarg — if a future refactor switches to `pathlib.Path.rglob` for walking, this helper has no other consumer and silently becomes orphaned); `DEFAULT_POLL_INTERVAL` (sole consumer is `_build_parser`'s `--interval` default — if Q2's hazard is resolved by removing `--interval` from the `--once`-only installer flow, this constant has zero consumers in the script and the file-global-constants use-count rule is broken).
181
+ Q3: Which symbol most likely will *become* dead code after a near-future refactor? Candidates: `_log_walk_error` (sole call site is the `os.walk(..., onerror=...)` kwarg — if a future refactor switches to `pathlib.Path.rglob` for walking, this helper has no other consumer and silently becomes orphaned); `DEFAULT_POLL_INTERVAL` (sole consumer is `_build_parser`'s `--interval` default — if Q2's hazard is resolved by removing `--interval` from the `--once`-only installer flow, this constant has zero consumers in the script).
182
182
 
183
183
  ## Output
184
184
 
@@ -55,12 +55,7 @@ export const RUN_BACKUP_DIRECTORY_NAME_PATTERN =
55
55
  */
56
56
  export const PACKAGE_AGENTS_HOME_DIRECTORY_NAME = '.agents';
57
57
 
58
- export const PSTACK_MODEL_RULE_FILE_NAME = 'pstack-models.mdc';
59
58
  export const SUBAGENT_MODEL_POLICY_FILE_NAME = 'subagent-model-policy.json';
60
- export const PSTACK_SEEDED_MODEL_PREFERENCES_FILE_NAMES = Object.freeze([
61
- 'pstack-model-preferences.claude.json',
62
- 'pstack-model-preferences.codex.json',
63
- ]);
64
59
 
65
60
  /**
66
61
  * The directory name skill directories carry in a package source and under
@@ -0,0 +1,192 @@
1
+ import { spawnSync } from 'node:child_process';
2
+
3
+ export const PSTACK_MARKETPLACE_REPOSITORY = 'michael-denyer/pstack-claude';
4
+ export const PSTACK_MARKETPLACE_NAME = 'pstack-claude';
5
+ export const PSTACK_PLUGIN_IDENTIFIER = `pstack@${PSTACK_MARKETPLACE_NAME}`;
6
+ export const PSTACK_PLUGIN_HOSTS = Object.freeze(['claude', 'codex']);
7
+
8
+ export const PSTACK_PLUGIN_OPT_OUT_FLAG = '--no-pstack';
9
+ export const PSTACK_PLUGIN_OPT_OUT_VARIABLE = 'CDE_INSTALL_PSTACK';
10
+
11
+ const HOST_PLANS = Object.freeze({
12
+ claude: Object.freeze({
13
+ executable: 'claude',
14
+ executableVariable: 'CDE_CLAUDE_EXECUTABLE',
15
+ homeVariable: 'CLAUDE_CONFIG_DIR',
16
+ commands: Object.freeze([
17
+ Object.freeze(['plugin', 'marketplace', 'add', PSTACK_MARKETPLACE_REPOSITORY]),
18
+ Object.freeze(['plugin', 'install', PSTACK_PLUGIN_IDENTIFIER]),
19
+ ]),
20
+ }),
21
+ codex: Object.freeze({
22
+ executable: 'codex',
23
+ executableVariable: 'CDE_CODEX_EXECUTABLE',
24
+ homeVariable: 'CODEX_HOME',
25
+ commands: Object.freeze([
26
+ Object.freeze(['plugin', 'marketplace', 'add', PSTACK_MARKETPLACE_REPOSITORY]),
27
+ Object.freeze(['plugin', 'add', PSTACK_PLUGIN_IDENTIFIER]),
28
+ ]),
29
+ }),
30
+ });
31
+
32
+ /**
33
+ * Read the marketplace and plugin commands one host installs pstack with.
34
+ *
35
+ * Claude Code and Codex publish the same marketplace under different plugin
36
+ * verbs, so the command list belongs to the host rather than to the caller.
37
+ *
38
+ * @param {string} host Either `claude` or `codex`.
39
+ * @returns {{executable: string, executableVariable: string, homeVariable: string,
40
+ * commands: ReadonlyArray<ReadonlyArray<string>>}} The host's install plan.
41
+ */
42
+ export function pstackPluginPlan(host) {
43
+ const plan = HOST_PLANS[host];
44
+ if (!plan) {
45
+ throw new Error(
46
+ `Unsupported pstack plugin host ${host}: this installer knows ${PSTACK_PLUGIN_HOSTS.join(', ')}.`,
47
+ );
48
+ }
49
+ return plan;
50
+ }
51
+
52
+ /**
53
+ * Decide whether a full install also installs the pstack plugin.
54
+ *
55
+ * The step reaches the network for the marketplace repository. `--no-pstack`
56
+ * on the command line, or `CDE_INSTALL_PSTACK=0` in the environment, turns it
57
+ * off for an air-gapped or offline run.
58
+ *
59
+ * @param {string[]} [argumentList] The command-line arguments after the script.
60
+ * @param {Record<string, string|undefined>} [environment] The process environment.
61
+ * @returns {boolean} True when this run installs the pstack plugin.
62
+ */
63
+ export function shouldInstallPstackPlugin(
64
+ argumentList = process.argv.slice(2),
65
+ environment = process.env,
66
+ ) {
67
+ if (argumentList.includes(PSTACK_PLUGIN_OPT_OUT_FLAG)) return false;
68
+ return environment[PSTACK_PLUGIN_OPT_OUT_VARIABLE] !== '0';
69
+ }
70
+
71
+ /**
72
+ * Build the process launch one platform needs to run a host command.
73
+ *
74
+ * Both hosts ship on Windows as a `.cmd` shim, and Node's synchronous spawn
75
+ * rejects a batch file with `EINVAL` unless a shell runs it, so Windows goes
76
+ * through `cmd.exe /d /s /c` with the whole command line as one verbatim
77
+ * argument. The executable carries its own quotes inside that line, which is
78
+ * why the line is passed verbatim rather than quoted again by Node. Every
79
+ * other platform launches the executable directly.
80
+ *
81
+ * @param {string} executable The host command, a bare name or a path.
82
+ * @param {string[]} commandArguments The arguments after the executable.
83
+ * @param {string} [platform] The platform to build for.
84
+ * @param {Record<string, string|undefined>} [environment] Supplies `ComSpec`.
85
+ * @returns {{file: string, args: string[], windowsVerbatimArguments: boolean}} The launch.
86
+ */
87
+ export function hostCommandInvocation(
88
+ executable,
89
+ commandArguments,
90
+ platform = process.platform,
91
+ environment = process.env,
92
+ ) {
93
+ if (platform !== 'win32') {
94
+ return { file: executable, args: [...commandArguments], windowsVerbatimArguments: false };
95
+ }
96
+ const commandLine = [`"${executable}"`, ...commandArguments].join(' ');
97
+ return {
98
+ file: environment.ComSpec || 'cmd.exe',
99
+ args: ['/d', '/s', '/c', `"${commandLine}"`],
100
+ windowsVerbatimArguments: true,
101
+ };
102
+ }
103
+
104
+ function runHostCommand(executable, commandArguments, options) {
105
+ const invocation = hostCommandInvocation(executable, commandArguments);
106
+ const spawned = spawnSync(invocation.file, invocation.args, {
107
+ encoding: 'utf8',
108
+ env: { ...process.env, ...options.environment },
109
+ windowsVerbatimArguments: invocation.windowsVerbatimArguments,
110
+ });
111
+ return { status: spawned.status, stderr: spawned.stderr ?? '', error: spawned.error };
112
+ }
113
+
114
+ const COMMAND_NOT_FOUND_EXIT_CODE = 9009;
115
+ const COMMAND_NOT_FOUND_PATTERN = /is not recognized as an internal or external command/;
116
+
117
+ function namesAnAbsentCommand(outcome) {
118
+ if (outcome.error?.code === 'ENOENT') return true;
119
+ return outcome.status === COMMAND_NOT_FOUND_EXIT_CODE
120
+ && COMMAND_NOT_FOUND_PATTERN.test(outcome.stderr ?? '');
121
+ }
122
+
123
+ function firstLine(text) {
124
+ const trimmed = (text ?? '').trim();
125
+ if (!trimmed) return '';
126
+ return trimmed.split(/\r?\n/).filter(Boolean).at(-1);
127
+ }
128
+
129
+ function installForHost(host, homeDirectory, environment, runCommand) {
130
+ const plan = pstackPluginPlan(host);
131
+ const executable = environment[plan.executableVariable] || plan.executable;
132
+ const commandEnvironment = { [plan.homeVariable]: homeDirectory };
133
+ for (const commandArguments of plan.commands) {
134
+ const outcome = runCommand(executable, [...commandArguments], {
135
+ environment: commandEnvironment,
136
+ });
137
+ if (namesAnAbsentCommand(outcome)) {
138
+ return {
139
+ host,
140
+ executable,
141
+ status: 'skipped',
142
+ warning: `${executable} is not on PATH, so pstack was not installed for ${host}.`,
143
+ };
144
+ }
145
+ if (outcome.status !== 0 || outcome.error) {
146
+ const detail = firstLine(outcome.stderr) || outcome.error?.message || `exit ${outcome.status}`;
147
+ return {
148
+ host,
149
+ executable,
150
+ status: 'failed',
151
+ warning: `${executable} ${commandArguments.join(' ')} failed: ${detail}`,
152
+ };
153
+ }
154
+ }
155
+ return { host, executable, status: 'installed', warning: null };
156
+ }
157
+
158
+ /**
159
+ * Install the pstack plugin from its marketplace into each host's own home.
160
+ *
161
+ * Each host is one member of the batch. A host without its command-line tool
162
+ * is skipped and a host whose command fails is reported, so the rules, hooks,
163
+ * and skills this run already wrote still reach their durable places. An
164
+ * absent tool arrives as a spawn `ENOENT` on other platforms and as
165
+ * `cmd.exe`'s command-not-found exit code on Windows, and both read as
166
+ * skipped.
167
+ *
168
+ * @param {object} [options] Install targets.
169
+ * @param {string} [options.claudeRoot] The managed Claude root to install into.
170
+ * @param {string} [options.codexHome] The Codex home to install into.
171
+ * @param {string[]} [options.hosts] The hosts to install. Defaults to both.
172
+ * @param {Record<string, string|undefined>} [options.environment] The process environment.
173
+ * @param {object} [dependencies] Seams for the command runner.
174
+ * @returns {{status: string, hosts: object[], warning: string|null}} The outcome per host.
175
+ */
176
+ export function installPstackPlugin(options = {}, dependencies = {}) {
177
+ const environment = options.environment ?? process.env;
178
+ const runCommand = dependencies.runCommand ?? runHostCommand;
179
+ const homeDirectories = { claude: options.claudeRoot, codex: options.codexHome };
180
+ const hosts = options.hosts ?? PSTACK_PLUGIN_HOSTS;
181
+ const hostOutcomes = hosts.map(host => installForHost(
182
+ host,
183
+ homeDirectories[host],
184
+ environment,
185
+ runCommand,
186
+ ));
187
+ const failedCount = hostOutcomes.filter(outcome => outcome.status === 'failed').length;
188
+ const installedCount = hostOutcomes.filter(outcome => outcome.status === 'installed').length;
189
+ const status = failedCount > 0 ? 'failed' : (installedCount > 0 ? 'installed' : 'skipped');
190
+ const warnings = hostOutcomes.map(outcome => outcome.warning).filter(Boolean);
191
+ return { status, hosts: hostOutcomes, warning: warnings.join(' ') || null };
192
+ }