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
@@ -1,142 +0,0 @@
1
- # Pstack delegation policy
2
-
3
- Pstack uses portable role requirements. Model IDs belong to host preference
4
- files. The active native subagent tool decides which model IDs the current host
5
- accepts.
6
-
7
- This policy replaces the fixed model defaults in pstack skills and setup-pstack's
8
- legacy step that writes one universal model rule. Setup updates only the selected
9
- host preference JSON. The installer keeps this portable policy current.
10
-
11
- ## Setup
12
-
13
- When setup-pstack runs, read the active native subagent tool metadata. Use the
14
- host name from that tool or the current host context. Do not infer the host from
15
- CODEX_HOME, CLAUDE_CONFIG_DIR, or another environment variable.
16
-
17
- Save choices in the agents home under
18
- `rules/pstack-model-preferences.<host>.json`. Keep each host in its own file.
19
- The file contains this shape:
20
-
21
- ```json
22
- {
23
- "host": "current-host",
24
- "defaultEffort": "medium",
25
- "modelsByRole": {
26
- "feature, refactoring": ["confirmed-host-model"]
27
- }
28
- }
29
- ```
30
-
31
- Automatic model and effort routing reads
32
- `rules/subagent-model-policy.json` from the same agents home. Edit that file to
33
- change the next routing decision. The installer creates it only when it is
34
- absent. The native hook blocks advisor role claims because the current Codex hook
35
- input has no host-provided role binding. The Python advisor helper passes trusted
36
- session metadata to the shared resolver. The native advisor hook path stays
37
- pending until the host supplies that binding.
38
-
39
- Offer only model IDs the active native tool accepts. Keep the user's order.
40
- Treat `inherit-parent` and `auto` as parent inheritance, not model IDs. A full
41
- install seeds the Claude file and the Codex file from the package when a host
42
- file is absent, and leaves an existing file untouched. A different host never
43
- copies another host's preference file.
44
-
45
- ## Selection before every delegation
46
-
47
- Refresh the accepted model IDs from the active native subagent tool immediately
48
- before each native delegation. Run the installed selector once for each agent.
49
- Use `~/.agents/scripts/select_pstack_models.mjs` for the main profile. For a
50
- named or explicit profile, use the selector under that profile's sibling agents
51
- home.
52
-
53
- Send one JSON object through standard input. Use the active host shell on
54
- other operating systems:
55
-
56
- ```powershell
57
- $selectorInput = @'
58
- {
59
- "host": "current-host",
60
- "inventoryHost": "current-host",
61
- "role": "how critics",
62
- "delegationIndex": 0,
63
- "panel": {
64
- "agentCount": 3,
65
- "requiresDistinctModels": false
66
- },
67
- "requestedEffort": "medium",
68
- "availableModelIds": ["confirmed-host-model"],
69
- "confirmedSuitableModelIds": [],
70
- "parentModelId": "current-parent-model",
71
- "parentFallback": {
72
- "isAllowed": true,
73
- "hasMaterialCapabilityLoss": false
74
- }
75
- }
76
- '@
77
- $selection = $selectorInput | node "$HOME/.agents/scripts/select_pstack_models.mjs"
78
- ```
79
-
80
- `availableModelIds` comes from the current native tool metadata.
81
- `confirmedSuitableModelIds` contains available alternatives whose suitability
82
- for this role the host metadata or user has confirmed. Do not rank or classify a
83
- model from its name.
84
- Keep the host's default effort in the preference file as `defaultEffort`, or
85
- pass it as trusted runtime data in `requestedEffort`. The selector applies that
86
- effort to a model-only entry before it picks a candidate. It holds the entry
87
- when neither source supplies an effort and the policy has no single approved
88
- effort for that model and role.
89
-
90
- Use the returned `nativeSpawnArguments` in the native subagent call. An empty
91
- object with `omitNativeModelArgument: true` means parent inheritance, so omit
92
- the native `model` argument. Stop when `canDelegate` is false. Ask the user
93
- when `requiresUserChoice` is true.
94
-
95
- When a preference entry includes `{ "model": "model-id", "effort": "medium" }`,
96
- the selector routes the pair before it deduplicates candidates and checks panel
97
- diversity. The selected pair appears in `panel.selectedModelPairs`, and the
98
- native argument uses `reasoning_effort`. The policy's `selectorExclusions` list
99
- removes matching routed pairs from panel contenders without blocking direct
100
- resolver or spawn-hook use.
101
-
102
- Run the selector again before the next agent call. Pass the same panel contract
103
- and the new delegation index with the refreshed inventory. Report
104
- `panel.selectedModelIds` and `panel.isCrossModelDiverse` as returned. A panel
105
- that repeats one model is a same-model panel.
106
-
107
- ## Portable role requirements
108
-
109
- feature, refactoring: reliable code execution
110
- bug-fix: careful diagnosis and code execution
111
- perf-issue: systems reasoning and performance analysis
112
- hillclimb: strong iterative judgment
113
- judgment and prose: strong judgment and clear prose
114
- hardest tasks: highest available reasoning capability
115
- how explorer: fast codebase exploration
116
- how explainer: clear technical synthesis
117
- how critics: independent technical criticism
118
- why investigators: fast evidence gathering
119
- why synthesizer: strong evidence synthesis
120
- reflect tooling: reliable tool and workflow analysis
121
- reflect judgment, divergent, synthesizer: strong judgment and divergent analysis
122
- arena runners: independent solution development
123
- arena cross-judge pool: independent comparative judgment
124
- swarm workers: fast bounded task execution
125
- architect runners: independent architecture design
126
- interrogate reviewers: independent adversarial review
127
-
128
- ## Panel requirements
129
-
130
- Panel size and model diversity are separate from model preferences.
131
-
132
- - How critics use three agents by default. Cross-model diversity is optional.
133
- - Arena runners use three agents by default and require distinct models.
134
- - The arena cross-judge pool selects one judge. Pass `parentModelId` so the
135
- selector chooses another available preference before the parent's model.
136
- - Swarm uses the requested worker count. Same-model workers are valid.
137
- - Architect uses two agents by default and requires distinct models.
138
- - Interrogate uses three agents by default and requires distinct models.
139
-
140
- A required-diversity panel stops when the current host lacks enough distinct
141
- confirmed models. Parent inheritance is the last fallback. When inheritance
142
- causes material capability loss, stop and ask the user before delegating.
@@ -1,5 +0,0 @@
1
- # Obsidian vault context
2
-
3
- An Obsidian vault holds session reports (`sessions/`), decisions (`decisions/`), and research (`Research/`) across projects.
4
-
5
- Before project work, search the vault with the obsidian MCP tools. Search by `project` frontmatter first, then by keyword. Search again when a component has known history, or when a task might repeat or reverse a prior decision.
@@ -1,17 +0,0 @@
1
- # Pstack compatibility
2
-
3
- Apply this mapping to the upstream workflow below. User instructions and host permissions stay in charge.
4
-
5
- Determine the active host from the session's native tools. Read host-claude.md, host-codex.md, or host-cursor.md beside this file for the active host. Read pstack-host-mapping.md and pstack-models.md beside this file before delegation. Those files are copied from claude-dev-env's existing rules into this immutable release. Run the selectorPath recorded in release.json and supply its preferencesDirectory in the selector input. This overrides the old global selector path. Model preferences remain outside the release.
6
-
7
- Read the installed inventory in release.json for exact skill identities. Pstack skills use the `pstack:<slug>` plugin namespace. A pstack request for how, /how, or pstack:how means pstack:how. Cursor-team-kit skills keep flat component-prefixed names, so a request for deslop means cursor-team-kit-deslop. Resolve all other skill names through the inventory. Read each leaf skill in full when it applies. Keep each skill's scripts, references, playbooks, and agent definitions at its recorded source root.
8
-
9
- Every delegation prompt must include this compatibility file's absolute path, the active host file's absolute path, the selected skill's absolute path, and the requested upstream agent definition's absolute path when present. Tell the child to read them before working and to put the complete findings and evidence in its final message. The child receives the same host and model mapping as the parent.
10
-
11
- Treat upstream model identifiers as role preferences. Use only models and arguments accepted by the native tool in this session. Preserve required independence and model diversity. Report a missing capability when the session cannot supply the required independent panel. Use the native tool's supported concurrency and wait operations.
12
-
13
- For setup-pstack, configure only the current host's model preference file described in pstack-models.md. Keep that file outside this release. The Cursor .mdc output is a Cursor rule, not a cross-host model configuration.
14
-
15
- For create-skill, use Cursor's built-in skill on Cursor and cde-create-skill on Claude or Codex. For control-cli and control-ui, load the corresponding installed cursor-team-kit skill and use native terminal or browser tools. Check that the required CLI, browser, credentials, and permissions exist before executing its steps. State an unavailable dependency explicitly. Never claim an unattended browser or remote session ran from a filesystem check.
16
-
17
- Updates are prepared outside the active release. Follow absolute paths into the release named in this entry point for the rest of this task. Preserve supporting files and scripts, including executable modes and licenses.
@@ -1,14 +0,0 @@
1
- ---
2
- name: cde-create-skill
3
- description: Write or update a portable SKILL.md workflow when pstack requests create-skill on a host without Cursor's built-in skill.
4
- ---
5
-
6
- # Create a portable skill
7
-
8
- State the user-visible outcome and the exact trigger first. Inspect applicable repository instructions and existing skills before adding a new skill.
9
-
10
- Create a lowercase, hyphenated skill directory under the project's .claude/skills. Give SKILL.md YAML frontmatter with a matching name and a one-line description that says when to use it. Put the workflow after the frontmatter. Keep supporting scripts and references together with the skill, and use relative links inside it. Add a Codex .agents/skills directory link where needed, preserving existing files.
11
-
12
- Use native host tools and capability checks. Give each step a concrete input, action, and observable result. Separate human choices from facts the agent can inspect. Read referenced instructions before using them.
13
-
14
- Verify metadata, links, and script behavior. Then invoke the installed skill in the intended host and record the actual result. Distinguish a filesystem check from a native invocation. Keep the scope to the requested workflow.
@@ -1,7 +0,0 @@
1
- # Claude Code
2
-
3
- Use Claude Code's native Skill mechanism to load installed skills. Use Agent for Task requests, with only arguments the session exposes. For an upstream poteto-agent or another unavailable agent type, use an available general-purpose agent and include the upstream definition and compatibility files in its prompt. A file on disk does not register a native agent type.
4
-
5
- Use AskUserQuestion for a required human choice. Use Read, Glob, Grep, Edit, Write, Bash, and the available browser tools for their corresponding work. Read native tool schemas rather than forwarding Cursor-only arguments. Use background execution only when the current tool supports it.
6
-
7
- Choose model aliases accepted by this session. Omit the model argument for permitted parent inheritance. Keep host preferences in the profile's agents-home rules/pstack-model-preferences.claude.json. Use the current session's transcript location, or state that transcripts are unavailable.
@@ -1,11 +0,0 @@
1
- # Codex
2
-
3
- Load SKILL.md with the native skill mechanism or read its complete file with the available file tool. Codex does not need a Claude Skill tool.
4
-
5
- Map Task or Agent to the native spawn_agent, wait, send_input, and close_agent operations that this session exposes. Field names vary by host version. Inspect the live schema. For poteto-agent, include the upstream agent definition, skill entry, and compatibility files in the child prompt. Select an available native agent type. Do not pass subagent_type, run_in_background, or a model argument unless that tool accepts it.
6
-
7
- Use the native shell for commands, apply_patch or the native editor for edits, and the available browser tool for UI work. Ask the user through the current session's supported interaction mechanism for a genuine preference decision.
8
-
9
- A multi-agent feature setting in a CLI profile does not establish subagent support in a cloud session. Check available tools. Required independent or cross-model review stops with a clear capability report when that session cannot provide it. Keep configured model preferences in the profile's agents-home rules/pstack-model-preferences.codex.json. Read the current transcript location from host context, not ~/.cursor/projects.
10
-
11
- The selector's availableModelIds contains only models selectable through this session's delegation tool. A tool without a model argument supplies an empty list and uses permitted parent inheritance. A provider's general model catalog is not proof of per-delegation model selection.
@@ -1,7 +0,0 @@
1
- # Cursor
2
-
3
- Use Cursor's available native skill and delegation tools. The installed component-prefixed names in release.json replace upstream plugin short names. Keep project skills under .claude/skills and use the .agents/skills links for shared discovery. The installer writes no skills under .cursor.
4
-
5
- For an upstream custom agent type absent from the live tool inventory, use an available native agent type and include that upstream agent definition, the skill entry, and compatibility files in the child prompt. Use the session's supported model identifiers and delegation fields. Preserve required panel independence and model diversity.
6
-
7
- Use Cursor's built-in create-skill when present. Otherwise read cde-create-skill. Keep Cursor-specific preferences separate from Claude and Codex preferences. Resolve transcripts from the current workspace and session context.
@@ -1,11 +0,0 @@
1
- {
2
- "schemaVersion": 1,
3
- "adapterVersion": 1,
4
- "repository": "https://github.com/cursor/plugins.git",
5
- "commit": "27e2a62ff94f9af4b5e68435e41cdceacadb840c",
6
- "components": ["pstack", "cursor-team-kit"],
7
- "requiredSkills": {
8
- "pstack": ["poteto-mode"],
9
- "cursor-team-kit": ["deslop", "control-cli", "control-ui"]
10
- }
11
- }
@@ -1,200 +0,0 @@
1
- /**
2
- * Keep a pstack skills-directory plugin manifest listing every skill folder.
3
- *
4
- * Claude Code loads a folder under `~/.claude/skills` as a plugin when the
5
- * folder holds `.claude-plugin/plugin.json`, and it namespaces that plugin's
6
- * skills as `pstack:how`. It does not scan a plugin root for skill folders, so
7
- * the manifest names each sub-skill path:
8
- *
9
- * plugin root holds: how/SKILL.md, why/SKILL.md, docs/, .claude-plugin/
10
- * manifest before: "skills": ["./how"]
11
- * manifest after: "skills": ["./how", "./why"] <- why/ picked up
12
- * second run: no write <- the list matches
13
- * docs/, .claude-plugin/ <- skipped, no SKILL.md
14
- *
15
- * The installer calls `refreshPstackPluginManifest` for a pstack folder the
16
- * user installed. Running this file directly refreshes the same manifest after
17
- * a pstack update.
18
- */
19
-
20
- import {
21
- existsSync,
22
- mkdirSync,
23
- readdirSync,
24
- readFileSync,
25
- realpathSync,
26
- writeFileSync,
27
- } from 'node:fs';
28
- import { homedir } from 'node:os';
29
- import { join } from 'node:path';
30
- import { fileURLToPath } from 'node:url';
31
-
32
- export const PSTACK_PLUGIN_DIRECTORY_NAME = 'pstack';
33
- export const PSTACK_PLUGIN_MANIFEST_RELATIVE_PATH = join('.claude-plugin', 'plugin.json');
34
- export const SKILL_MANIFEST_FILE_NAME = 'SKILL.md';
35
- export const SKILL_PATH_PREFIX = './';
36
-
37
- const CLAUDE_HOME_DIRECTORY_NAME = '.claude';
38
- const MANIFEST_SKILLS_KEY = 'skills';
39
- const MANIFEST_INDENT_SPACES = 2;
40
- const FILE_ENCODING = 'utf8';
41
- const HIDDEN_NAME_PREFIX = '.';
42
-
43
- /**
44
- * Fields a first write seeds from the published pstack plugin. An existing
45
- * manifest keeps its own values. This refresher changes only the skills list.
46
- */
47
- const SEEDED_MANIFEST_FIELDS = Object.freeze({
48
- $schema: 'https://anthropic.com/claude-code/plugin.schema.json',
49
- name: PSTACK_PLUGIN_DIRECTORY_NAME,
50
- version: '0.14.8',
51
- description:
52
- 'if you want to go fast, go deep first. pstack helps you write less, but higher '
53
- + 'quality code. rigorous agent workflows you can parallelize with confidence.',
54
- author: Object.freeze({ name: 'Lauren Tan' }),
55
- homepage: 'https://github.com/cursor/plugins/tree/main/pstack',
56
- repository: 'https://github.com/cursor/plugins',
57
- license: 'MIT',
58
- });
59
-
60
- /**
61
- * Name every sub-folder of a plugin root that Claude Code loads as a skill.
62
- *
63
- * @param {string} pluginRoot Plugin folder holding one sub-folder per skill.
64
- * @returns {string[]} The sorted names of each visible sub-folder holding a
65
- * skill manifest. A hidden folder and a folder without `SKILL.md` are left out.
66
- */
67
- export function findPstackSkillDirectoryNames(pluginRoot) {
68
- return readdirSync(pluginRoot, { withFileTypes: true })
69
- .filter((eachEntry) => eachEntry.isDirectory())
70
- .map((eachEntry) => eachEntry.name)
71
- .filter((eachName) => !eachName.startsWith(HIDDEN_NAME_PREFIX))
72
- .filter((eachName) => existsSync(join(pluginRoot, eachName, SKILL_MANIFEST_FILE_NAME)))
73
- .sort();
74
- }
75
-
76
- function readManifestSkillPaths(manifestPath) {
77
- if (!existsSync(manifestPath)) return { manifest: null, previousSkillPaths: [] };
78
- const manifest = JSON.parse(readFileSync(manifestPath, FILE_ENCODING));
79
- const listedSkillPaths = manifest[MANIFEST_SKILLS_KEY];
80
- if (typeof listedSkillPaths === 'string') {
81
- return { manifest, previousSkillPaths: [listedSkillPaths] };
82
- }
83
- if (Array.isArray(listedSkillPaths)) {
84
- return { manifest, previousSkillPaths: listedSkillPaths };
85
- }
86
- return { manifest, previousSkillPaths: [] };
87
- }
88
-
89
- /**
90
- * Rewrite a pstack plugin manifest's skills list to match its folder.
91
- *
92
- * Writes only when the list changes. A second call on an unchanged folder
93
- * leaves the file byte for byte. An existing manifest keeps every field but
94
- * the skills list.
95
- *
96
- * @param {string} pluginRoot Plugin folder holding the skill sub-folders.
97
- * @returns {{didWrite: boolean, reason: string, previousSkillPaths: string[],
98
- * currentSkillPaths: string[]}} Whether this call wrote, and the paths
99
- * before and after. `reason` names a skip, and is empty on a write or an
100
- * unchanged list.
101
- */
102
- export function refreshPstackPluginManifest(pluginRoot) {
103
- const declined = { didWrite: false, previousSkillPaths: [], currentSkillPaths: [] };
104
- if (!existsSync(pluginRoot)) {
105
- return { ...declined, reason: 'plugin root is absent' };
106
- }
107
- if (existsSync(join(pluginRoot, SKILL_MANIFEST_FILE_NAME))) {
108
- return { ...declined, reason: 'plugin root is itself a skill' };
109
- }
110
- const allSkillNames = findPstackSkillDirectoryNames(pluginRoot);
111
- if (allSkillNames.length === 0) {
112
- return { ...declined, reason: 'plugin root holds no skills' };
113
- }
114
- const manifestPath = join(pluginRoot, PSTACK_PLUGIN_MANIFEST_RELATIVE_PATH);
115
- const { manifest, previousSkillPaths } = readManifestSkillPaths(manifestPath);
116
- const currentSkillPaths = allSkillNames.map((eachName) => SKILL_PATH_PREFIX + eachName);
117
- const outcome = { reason: '', previousSkillPaths, currentSkillPaths };
118
- const isUnchanged = manifest !== null
119
- && previousSkillPaths.length === currentSkillPaths.length
120
- && previousSkillPaths.every((eachPath, eachIndex) => eachPath === currentSkillPaths[eachIndex]);
121
- if (isUnchanged) {
122
- return { ...outcome, didWrite: false };
123
- }
124
- const refreshedManifest = {
125
- ...(manifest === null ? SEEDED_MANIFEST_FIELDS : manifest),
126
- [MANIFEST_SKILLS_KEY]: currentSkillPaths,
127
- };
128
- mkdirSync(join(pluginRoot, '.claude-plugin'), { recursive: true });
129
- writeFileSync(
130
- manifestPath,
131
- JSON.stringify(refreshedManifest, null, MANIFEST_INDENT_SPACES) + '\n',
132
- FILE_ENCODING,
133
- );
134
- return { ...outcome, didWrite: true };
135
- }
136
-
137
- /**
138
- * Return the pstack plugin folder under a Claude home.
139
- *
140
- * @param {string} claudeHome The Claude home directory holding `skills`.
141
- * @returns {string} The pstack plugin folder path, present or not.
142
- */
143
- export function pstackPluginRootFor(claudeHome) {
144
- return join(claudeHome, 'skills', PSTACK_PLUGIN_DIRECTORY_NAME);
145
- }
146
-
147
- /**
148
- * Return the Claude home used when no folder argument is given.
149
- *
150
- * A named profile sets `CLAUDE_CONFIG_DIR`. The installer writes into that
151
- * root. The command-line form reads the same variable.
152
- *
153
- * @param {Record<string, string|undefined>} environment The process environment.
154
- * @returns {string} The Claude home directory holding `skills`.
155
- */
156
- export function claudeHomeFrom(environment) {
157
- return environment.CLAUDE_CONFIG_DIR || join(homedir(), CLAUDE_HOME_DIRECTORY_NAME);
158
- }
159
-
160
- function main() {
161
- const explicitRoot = process.argv[2];
162
- const pluginRoot = explicitRoot || pstackPluginRootFor(claudeHomeFrom(process.env));
163
- const outcome = refreshPstackPluginManifest(pluginRoot);
164
- if (!outcome.didWrite && outcome.reason) {
165
- console.log(`No manifest written: ${outcome.reason}.`);
166
- return;
167
- }
168
- if (!outcome.didWrite) {
169
- console.log(`No change. ${outcome.currentSkillPaths.length} skills listed.`);
170
- return;
171
- }
172
- console.log(`Updated. ${outcome.currentSkillPaths.length} skills listed.`);
173
- for (const eachPath of outcome.currentSkillPaths) {
174
- if (!outcome.previousSkillPaths.includes(eachPath)) console.log(` added ${eachPath}`);
175
- }
176
- for (const eachPath of outcome.previousSkillPaths) {
177
- if (!outcome.currentSkillPaths.includes(eachPath)) console.log(` removed ${eachPath}`);
178
- }
179
- }
180
-
181
- /**
182
- * Return whether node ran this module as its entry point.
183
- *
184
- * A directory link to this script gives node an invoked path that differs from
185
- * this module's own path. Both sides resolve to their real location before
186
- * the comparison.
187
- *
188
- * @param {string|undefined} invokedPath The path node reports as `argv[1]`.
189
- * @returns {boolean} True when node ran this module as its entry point.
190
- */
191
- function isEntryPoint(invokedPath) {
192
- if (!invokedPath) return false;
193
- try {
194
- return realpathSync(fileURLToPath(import.meta.url)) === realpathSync(invokedPath);
195
- } catch {
196
- return fileURLToPath(import.meta.url) === invokedPath;
197
- }
198
- }
199
-
200
- if (isEntryPoint(process.argv[1])) main();