claude-dev-env 7.7.0 → 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.
- package/.agents/agents/clean-coder.md +5 -6
- package/.agents/agents/issue-tracker.md +1 -1
- package/.agents/agents/pr-description-writer.md +1 -1
- package/.agents/agents/skill-writer-agent.md +1 -12
- package/.agents/agents/test_agent_frontmatter.py +2 -19
- package/.agents/skills/e-code-review/reference/fix.md +4 -2
- package/.agents/skills/e-code-review/reference/loop.md +4 -2
- package/.agents/skills/issue-tracker/SKILL.md +1 -1
- package/.agents/skills/orchestrator/SKILL.md +4 -5
- package/.agents/skills/pull-request/SKILL.md +8 -7
- package/.agents/skills/pull-request/reference/publication-tasks.md +1 -1
- package/.agents/skills/team-advisor/SKILL.md +2 -3
- package/_shared/advisor/advisor-protocol.md +0 -1
- package/_shared/advisor/reference/lifecycle.md +0 -1
- package/_shared/advisor/reference/warm-up.md +1 -2
- package/audit-rubrics/prompts/category-e-dead-code.md +3 -3
- package/bin/install-constants.mjs +0 -5
- package/bin/install-pstack-plugin.mjs +192 -0
- package/bin/install-pstack-plugin.test.mjs +181 -0
- package/bin/install.agents-home.test.mjs +0 -4
- package/bin/install.codex-rules.test.mjs +6 -0
- package/bin/install.cursor-rules.test.mjs +3 -253
- package/bin/install.mjs +55 -160
- package/bin/install.test.mjs +110 -149
- package/bin/prune-retired-pstack-store.mjs +83 -0
- package/bin/prune-retired-pstack-store.test.mjs +105 -0
- package/bin/prune-session-continuity.mjs +57 -0
- package/bin/prune-session-continuity.test.mjs +70 -0
- package/bin/resolve-install-root.mjs +2 -0
- package/bin/resolve-install-root.test.mjs +69 -0
- package/bin/subagent-model-policy.test.mjs +56 -19
- package/docs/CODE_RULES.md +1 -1
- package/docs/agent-spawn-protocol.md +1 -1
- package/docs/codex-compatibility.md +4 -16
- package/docs/nas-ssh-invocation.md +1 -1
- package/docs/references/prose-style-enforcement.md +2 -2
- package/docs/subagent-model-routing.md +21 -2
- package/hooks/HOOK_SPECS_PROMPT_WORKFLOW.md +1 -3
- package/hooks/blocking/code_rules_constants_config.py +1 -108
- package/hooks/blocking/code_rules_enforcer.py +9 -5
- package/hooks/blocking/code_rules_test_layout.py +6 -7
- package/hooks/blocking/test_code_rules_constants_config.py +13 -0
- package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +0 -1
- package/hooks/blocking/test_code_rules_enforcer_dispatch_wiring.py +15 -0
- package/hooks/blocking/test_code_rules_enforcer_split_constants_config.py +0 -60
- package/hooks/blocking/test_context_control_policy_files.py +0 -11
- package/hooks/hooks_constants/blocking_check_limits.py +1 -1
- package/hooks/hooks_constants/code_rules_enforcer_constants.py +0 -1
- package/hooks/hooks_constants/setup_project_paths_constants.py +1 -2
- package/hooks/routing/subagent_model_routing.test.mjs +65 -28
- package/hooks/session/test_untracked_repo_detector.py +1 -1
- package/hooks/validators/code_quality_checks.py +15 -2
- package/hooks/validators/test_code_quality_checks.py +22 -0
- package/package.json +1 -2
- package/rules/ci-owns-the-gate.md +58 -0
- package/rules/code-standards.md +1 -2
- package/rules/docstring-prose-matches-implementation.md +2 -2
- package/rules/failure-blast-radius.md +1 -1
- package/rules/git-workflow.md +23 -11
- package/rules/orphan-css-class.md +1 -1
- package/rules/paired-test-coverage.md +1 -1
- package/rules/retired-hook-prose.md +1 -1
- package/rules/subagent-model-policy.json +1 -1
- package/rules/verify-before-asking.md +1 -1
- package/rules/windows-filesystem-safe.md +1 -1
- package/rules/workflow-substitution-slots.md +1 -1
- package/scripts/Sync-RepoMain.ps1 +71 -6
- package/scripts/policy_lint/adapter_detectors.py +116 -10
- package/scripts/policy_lint/config/approved_test_pairs.py +12 -0
- package/scripts/policy_lint/config/constants.py +3 -0
- package/scripts/tests/Sync-RepoMain.Tests.ps1 +108 -0
- package/scripts/tests/test_policy_lint_rules_validator_baseline.py +126 -0
- package/.agents/skills/session-continuity/AGENTS.md +0 -1
- package/.agents/skills/session-continuity/CLAUDE.md +0 -1
- package/.agents/skills/session-continuity/README.md +0 -223
- package/.agents/skills/session-continuity/SKILL.md +0 -125
- package/.agents/skills/session-continuity/continuity.mjs +0 -445
- package/.agents/skills/session-continuity/continuity.test.mjs +0 -381
- package/bin/install-session-continuity.mjs +0 -131
- package/bin/install-session-continuity.test.mjs +0 -106
- package/bin/install.pstack-plugin.test.mjs +0 -115
- package/bin/pstack-codex-discovery.test.mjs +0 -79
- package/bin/pstack-model-preferences.claude.json +0 -23
- package/bin/pstack-model-preferences.codex.json +0 -24
- package/bin/pstack.mjs +0 -392
- package/bin/pstack.test.mjs +0 -347
- package/bin/refresh-pstack-plugin-skills.test.mjs +0 -211
- package/bin/select-pstack-models.test.mjs +0 -727
- package/hooks/blocking/test_code_rules_enforcer_file_global_constants.py +0 -231
- package/rules/agent-spawn-protocol.md +0 -9
- package/rules/ask-user-question-required.md +0 -31
- package/rules/claims-as-quotes.md +0 -55
- package/rules/confirm-implementation-forks.md +0 -11
- package/rules/file-global-constants.md +0 -81
- package/rules/hedging-claims.md +0 -7
- package/rules/measurement-denominators.md +0 -39
- package/rules/prompt-workflow-context-controls.md +0 -19
- package/rules/pstack-host-mapping.md +0 -48
- package/rules/pstack-models.md +0 -142
- package/rules/vault-context.md +0 -5
- package/scripts/pstack-adapters/common.md +0 -17
- package/scripts/pstack-adapters/create-skill.md +0 -14
- package/scripts/pstack-adapters/host-claude.md +0 -7
- package/scripts/pstack-adapters/host-codex.md +0 -11
- package/scripts/pstack-adapters/host-cursor.md +0 -7
- package/scripts/pstack.lock.json +0 -11
- package/scripts/refresh_pstack_plugin_skills.mjs +0 -200
- package/scripts/select_pstack_models.mjs +0 -469
- /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
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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,8 +21,10 @@ every rule below.
|
|
|
21
21
|
Before returning, the resumed agent runs the code-rules gate with the same bare
|
|
22
22
|
call every other surface in this skill uses:
|
|
23
23
|
|
|
24
|
-
1. Run
|
|
25
|
-
root>` with no file paths and no `--only-under` prefix.
|
|
24
|
+
1. Run `python ~/.claude/_shared/pr-loop/scripts/code_rules_gate.py --repo-root
|
|
25
|
+
<repo root>` with no file paths and no `--only-under` prefix. The `python`
|
|
26
|
+
prefix is required. On Windows a bare `.py` path opens the file in Cursor,
|
|
27
|
+
which then crashes on the gate flags.
|
|
26
28
|
2. If the gate reports violations **on lines this fix already owns** (the files
|
|
27
29
|
and added lines the fix changed), fix them and re-run the exact same command.
|
|
28
30
|
3. A violation on a path or added line outside this fix's own work is reported
|
|
@@ -48,8 +48,10 @@ no advisor call reopens that.
|
|
|
48
48
|
## Required checks
|
|
49
49
|
|
|
50
50
|
"Run required checks" means: run
|
|
51
|
-
|
|
52
|
-
with no file paths and no `--only-under` prefix.
|
|
51
|
+
`python ~/.claude/_shared/pr-loop/scripts/code_rules_gate.py --repo-root <repo root>`
|
|
52
|
+
with no file paths and no `--only-under` prefix. The `python` prefix is
|
|
53
|
+
required. On Windows a bare `.py` path opens the file in Cursor, which then
|
|
54
|
+
crashes on the gate flags. On a violation on lines this
|
|
53
55
|
round already owns, fix it and re-run the exact same command until clean. A
|
|
54
56
|
violation outside the review target is reported or skipped, not force-fixed into
|
|
55
57
|
unrelated files, and the skip is logged with path and reason.
|
|
@@ -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
|
-
|
|
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
|
|
40
|
-
|
|
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
|
|
162
|
-
|
|
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` |
|
|
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,
|
|
101
|
-
`pr-description-writer
|
|
102
|
-
|
|
103
|
-
|
|
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
|
|
126
|
-
|
|
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.
|
|
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.
|
|
30
|
-
5.
|
|
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.
|
|
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
|
|
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;
|
|
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
|
|
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
|
+
}
|