@pareto4/pp-core 1.7.1 → 1.8.1
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/.claude-plugin/plugin.json +1 -1
- package/bin/install.js +197 -81
- package/commands/pp-build-handoff.md +8 -2
- package/commands/pp-discovery-to-build.md +40 -0
- package/commands/pp-ns-project.md +3 -1
- package/gemini-extension.json +1 -1
- package/package.json +2 -1
- package/pareto-skills/_shared/create-project-board.sh +195 -0
- package/pareto-skills/_shared/pp-claude-md-protocol.md +306 -0
- package/pareto-skills/_shared/sync-phase-status.sh +139 -0
- package/pareto-skills/pp-brand/SKILL.md +644 -0
- package/pareto-skills/pp-brand/assets/crew/ParetoAgent.inline.html +120 -0
- package/pareto-skills/pp-brand/assets/crew/ParetoAgent.tsx +239 -0
- package/pareto-skills/pp-brand/assets/crew/build.svg +11 -0
- package/pareto-skills/pp-brand/assets/crew/deploy.svg +11 -0
- package/pareto-skills/pp-brand/assets/crew/discovery.svg +11 -0
- package/pareto-skills/pp-brand/assets/crew/plan.svg +11 -0
- package/pareto-skills/pp-brand/assets/crew/run.svg +11 -0
- package/pareto-skills/pp-brand/assets/crew/validate.svg +11 -0
- package/pareto-skills/pp-brand/assets/pareto-brand-preview.html +547 -0
- package/pareto-skills/pp-obsidian-notes/SKILL.md +127 -0
- package/pareto-skills/pp-obsidian-notes/templates/CLAUDE.md +29 -0
- package/pareto-skills/pp-obsidian-notes/templates/client-context.md +116 -0
- package/pareto-skills/pp-obsidian-notes/templates/meeting-note.md +66 -0
- package/pareto-skills/pp-obsidian-notes/templates/pareto-context.md +102 -0
- package/pareto-skills/pp-obsidian-notes/templates/proposal.md +117 -0
- package/pareto-skills/pp-project-setup/SKILL.md +1364 -0
- package/pp-core/bin/lib/clusters.cjs +4 -0
- package/pp-core/bin/lib/intel.cjs +1 -1
- package/pp-core/bin/lib/io.cjs +120 -0
- package/pp-core/bin/lib/profile-pipeline-command-router.cjs +1 -1
- package/pp-core/bin/lib/roadmap-parser.cjs +1 -1
- package/pp-core/bin/lib/runtime-artifact-conversion.cjs +2 -2
- package/pp-core/bin/lib/surface.cjs +1 -1
- package/pp-core/bin/lib/worktree-safety.cjs +2 -2
- package/pp-core/bin/pp-tools.cjs +23 -0
- package/pp-core/references/ui-brand.md +73 -11
- package/pp-core/templates/codebase/structure.md +5 -5
- package/pp-core/templates/spec.md +7 -7
- package/pp-core/workflows/add-backlog.md +1 -1
- package/pp-core/workflows/autonomous.md +4 -4
- package/pp-core/workflows/debug.md +1 -1
- package/pp-core/workflows/{build-handoff.md → discovery-to-build.md} +6 -3
- package/pp-core/workflows/execute-phase.md +9 -1
- package/pp-core/workflows/plan-phase.md +14 -12
- package/pp-core/workflows/pr-branch.md +5 -4
- package/pp-core/workflows/reapply-patches.md +1 -1
- package/pp-core/workflows/ship.md +1 -1
- package/pp-core/workflows/thread.md +1 -1
- package/pp-core/workflows/verify-work.md +6 -5
- package/scripts/lint-allow-test-rule-refs.allowlist.json +1 -0
package/bin/install.js
CHANGED
|
@@ -216,7 +216,7 @@ const GSD_CURSOR_POST_TOOL_HOOK_SCRIPT = 'pp-cursor-post-tool.js';
|
|
|
216
216
|
// Marker comment embedded in managed hook entries so GSD can find+remove them.
|
|
217
217
|
const GSD_CURSOR_HOOK_MARKER = 'gsd-managed';
|
|
218
218
|
|
|
219
|
-
//
|
|
219
|
+
// pp-core-managed files under hooks/lib/ (helpers required by gsd-*.sh hooks).
|
|
220
220
|
// git-cmd.js does not start with "gsd-" (shared classifier for #3129), pp-graphify-rebuild.sh does.
|
|
221
221
|
const GSD_HOOK_LIB_FILES = ['git-cmd.js', 'pp-graphify-rebuild.sh'];
|
|
222
222
|
|
|
@@ -297,7 +297,7 @@ function _getGsdEffortCatalog() {
|
|
|
297
297
|
} catch (_err) {
|
|
298
298
|
// Fail loudly — a missing manifest is a broken install, not a soft degradation.
|
|
299
299
|
throw new Error(
|
|
300
|
-
`
|
|
300
|
+
`pp-core install: cannot load config-defaults.manifest.json at ${manifestPath}: ${_err.message}`
|
|
301
301
|
);
|
|
302
302
|
}
|
|
303
303
|
|
|
@@ -601,7 +601,7 @@ if (hasUninstall) {
|
|
|
601
601
|
|
|
602
602
|
// Show help if requested
|
|
603
603
|
if (hasHelp) {
|
|
604
|
-
console.log(` ${yellow}Usage:${reset} npx ${pkg.name} [options]\n\n ${yellow}Options:${reset}\n ${cyan}-g, --global${reset} Install globally (to config directory)\n ${cyan}-l, --local${reset} Install locally (to current directory)\n ${cyan}--claude${reset} Install for Claude Code only\n ${cyan}--opencode${reset} Install for OpenCode only\n ${cyan}--gemini${reset} Install for Gemini only\n ${cyan}--kilo${reset} Install for Kilo only\n ${cyan}--codex${reset} Install for Codex only\n ${cyan}--kimi${reset} Install for Kimi CLI only\n ${cyan}--copilot${reset} Install for Copilot only\n ${cyan}--antigravity${reset} Install for Antigravity only\n ${cyan}--cursor${reset} Install for Cursor only\n ${cyan}--windsurf${reset} Install for Windsurf only\n ${cyan}--augment${reset} Install for Augment only\n ${cyan}--trae${reset} Install for Trae only\n ${cyan}--qwen${reset} Install for Qwen Code only\n ${cyan}--hermes${reset} Install for Hermes Agent only\n ${cyan}--cline${reset} Install for Cline only\n ${cyan}--codebuddy${reset} Install for CodeBuddy only\n ${cyan}--all${reset} Install for all runtimes\n ${cyan}-u, --uninstall${reset} Uninstall pp-core (remove all pp-core files)\n ${cyan}-c, --config-dir <path>${reset} Specify custom config directory\n ${cyan}-h, --help${reset} Show this help message\n ${cyan}--force-statusline${reset} Replace existing statusline config\n ${cyan}--portable-hooks${reset} Emit \$HOME-relative hook paths in settings.json\n (for WSL/Docker bind-mount setups; also GSD_PORTABLE_HOOKS=1)\n ${cyan}--profile=<name>${reset} Install a named skill profile. Profiles:\n core — ${PROFILES.core.length} main-loop skills incl. phase (~130 desc tokens)\n standard — ${PROFILES.standard.length} skills incl. phase, review, config (~700)\n full — all skills (default)\n Composable: --profile=core,audit installs union of closures.\n Profile is persisted and respected by \`
|
|
604
|
+
console.log(` ${yellow}Usage:${reset} npx ${pkg.name} [options]\n\n ${yellow}Options:${reset}\n ${cyan}-g, --global${reset} Install globally (to config directory)\n ${cyan}-l, --local${reset} Install locally (to current directory)\n ${cyan}--claude${reset} Install for Claude Code only\n ${cyan}--opencode${reset} Install for OpenCode only\n ${cyan}--gemini${reset} Install for Gemini only\n ${cyan}--kilo${reset} Install for Kilo only\n ${cyan}--codex${reset} Install for Codex only\n ${cyan}--kimi${reset} Install for Kimi CLI only\n ${cyan}--copilot${reset} Install for Copilot only\n ${cyan}--antigravity${reset} Install for Antigravity only\n ${cyan}--cursor${reset} Install for Cursor only\n ${cyan}--windsurf${reset} Install for Windsurf only\n ${cyan}--augment${reset} Install for Augment only\n ${cyan}--trae${reset} Install for Trae only\n ${cyan}--qwen${reset} Install for Qwen Code only\n ${cyan}--hermes${reset} Install for Hermes Agent only\n ${cyan}--cline${reset} Install for Cline only\n ${cyan}--codebuddy${reset} Install for CodeBuddy only\n ${cyan}--all${reset} Install for all runtimes\n ${cyan}-u, --uninstall${reset} Uninstall pp-core (remove all pp-core files)\n ${cyan}-c, --config-dir <path>${reset} Specify custom config directory\n ${cyan}-h, --help${reset} Show this help message\n ${cyan}--force-statusline${reset} Replace existing statusline config\n ${cyan}--portable-hooks${reset} Emit \$HOME-relative hook paths in settings.json\n (for WSL/Docker bind-mount setups; also GSD_PORTABLE_HOOKS=1)\n ${cyan}--profile=<name>${reset} Install a named skill profile. Profiles:\n core — ${PROFILES.core.length} main-loop skills incl. phase (~130 desc tokens)\n standard — ${PROFILES.standard.length} skills incl. phase, review, config (~700)\n full — all skills (default)\n Composable: --profile=core,audit installs union of closures.\n Profile is persisted and respected by \`pp update\`.\n ${cyan}--minimal${reset} Alias for --profile=core (back-compat).\n Cuts cold-start overhead from ~12k tokens to ~700.\n Alias: --core-only.\n\n ${yellow}Examples:${reset}\n ${dim}# Interactive install (prompts for runtime and location)${reset}\n npx ${pkg.name}\n\n ${dim}# Install for Claude Code globally${reset}\n npx ${pkg.name} --claude --global\n\n ${dim}# Install for Gemini globally${reset}\n npx ${pkg.name} --gemini --global\n\n ${dim}# Install for Kilo globally${reset}\n npx ${pkg.name} --kilo --global\n\n ${dim}# Install for Codex globally${reset}\n npx ${pkg.name} --codex --global\n\n ${dim}# Install for Kimi CLI globally${reset}\n npx ${pkg.name} --kimi --global\n\n ${dim}# Install for Kimi CLI under ~/.kimi-code${reset}\n npx ${pkg.name} --kimi --global --config-dir ~/.kimi-code\n\n ${dim}# Install for Copilot globally${reset}\n npx ${pkg.name} --copilot --global\n\n ${dim}# Install for Copilot locally${reset}\n npx ${pkg.name} --copilot --local\n\n ${dim}# Install for Antigravity globally${reset}\n npx ${pkg.name} --antigravity --global\n\n ${dim}# Install for Antigravity locally${reset}\n npx ${pkg.name} --antigravity --local\n\n ${dim}# Install for Cursor globally${reset}\n npx ${pkg.name} --cursor --global\n\n ${dim}# Install for Cursor locally${reset}\n npx ${pkg.name} --cursor --local\n\n ${dim}# Install for Windsurf globally${reset}\n npx ${pkg.name} --windsurf --global\n\n ${dim}# Install for Windsurf locally${reset}\n npx ${pkg.name} --windsurf --local\n\n ${dim}# Install for Augment globally${reset}\n npx ${pkg.name} --augment --global\n\n ${dim}# Install for Augment locally${reset}\n npx ${pkg.name} --augment --local\n\n ${dim}# Install for Trae globally${reset}\n npx ${pkg.name} --trae --global\n\n ${dim}# Install for Trae locally${reset}\n npx ${pkg.name} --trae --local\n\n ${dim}# Install for Hermes Agent globally${reset}\n npx ${pkg.name} --hermes --global\n\n ${dim}# Install for Hermes Agent locally${reset}\n npx ${pkg.name} --hermes --local\n\n ${dim}# Install for Cline globally${reset}\n npx ${pkg.name} --cline --global\n\n ${dim}# Install for Cline locally${reset}\n npx ${pkg.name} --cline --local\n\n ${dim}# Install for CodeBuddy globally${reset}\n npx ${pkg.name} --codebuddy --global\n\n ${dim}# Install for CodeBuddy locally${reset}\n npx ${pkg.name} --codebuddy --local\n\n ${dim}# Install for all runtimes globally${reset}\n npx ${pkg.name} --all --global\n\n ${dim}# Install to custom config directory${reset}\n npx ${pkg.name} --kilo --global --config-dir ~/.kilo-work\n\n ${dim}# Install to current project only${reset}\n npx ${pkg.name} --claude --local\n\n ${dim}# Uninstall pp-core from Cursor globally${reset}\n npx ${pkg.name} --cursor --global --uninstall\n\n ${yellow}Notes:${reset}\n The --config-dir option is useful when you have multiple configurations.\n It takes priority over CLAUDE_CONFIG_DIR / OPENCODE_CONFIG_DIR / GEMINI_CONFIG_DIR / KILO_CONFIG_DIR / CODEX_HOME / KIMI_CONFIG_DIR / COPILOT_CONFIG_DIR / COPILOT_HOME / ANTIGRAVITY_CONFIG_DIR / CURSOR_CONFIG_DIR / WINDSURF_CONFIG_DIR / AUGMENT_CONFIG_DIR / TRAE_CONFIG_DIR / QWEN_CONFIG_DIR / HERMES_HOME / CLINE_CONFIG_DIR / CODEBUDDY_CONFIG_DIR environment variables.\n Kimi CLI defaults to the first existing generic skills root: ${cyan}~/.config/agents/skills${reset}, then ${cyan}~/.agents/skills${reset}; if neither exists, pp-core creates ${cyan}~/.config/agents${reset}.\n Use ${cyan}--config-dir ~/.kimi-code${reset} or ${cyan}KIMI_CONFIG_DIR=~/.kimi-code${reset} for brand-specific Kimi installs.\n`);
|
|
605
605
|
process.exit(0);
|
|
606
606
|
}
|
|
607
607
|
|
|
@@ -640,7 +640,7 @@ const resolveBashRunner = hooksSurface.resolveBashRunner;
|
|
|
640
640
|
// referencesHook: pure predicate over hook entry objects, shared between
|
|
641
641
|
// install() and finishInstall() (ADR-857 phase 5f-1b).
|
|
642
642
|
const referencesHook = hooksSurface.referencesHook;
|
|
643
|
-
// applySettingsJsonHooks: mutates settings.hooks.* in place with all
|
|
643
|
+
// applySettingsJsonHooks: mutates settings.hooks.* in place with all pp-core-managed
|
|
644
644
|
// hook registrations for settings.json-surface runtimes (ADR-857 phase 5f-1b).
|
|
645
645
|
const applySettingsJsonHooks = hooksSurface.applySettingsJsonHooks;
|
|
646
646
|
|
|
@@ -649,7 +649,7 @@ function rewriteLegacyManagedNodeHookCommands(settings, absoluteRunner, opts) {
|
|
|
649
649
|
}
|
|
650
650
|
|
|
651
651
|
/**
|
|
652
|
-
* Build the
|
|
652
|
+
* Build the pp-core-managed Codex SessionStart hook block for config.toml.
|
|
653
653
|
*
|
|
654
654
|
* Issue #3017: the previous shape inlined `command = "node ${path}"` which
|
|
655
655
|
* fails under GUI/minimal-PATH runtimes where bare `node` doesn't resolve
|
|
@@ -715,7 +715,7 @@ function reconcileCodexHooksJsonEvent(targetDir, eventName, opts = {}) {
|
|
|
715
715
|
}
|
|
716
716
|
|
|
717
717
|
/**
|
|
718
|
-
* Reconcile the
|
|
718
|
+
* Reconcile the pp-core-managed SessionStart hook entry in hooks.json.
|
|
719
719
|
* Delegates to the generic reconcileCodexHooksJsonEvent helper.
|
|
720
720
|
*
|
|
721
721
|
* @param {string} targetDir
|
|
@@ -818,7 +818,7 @@ function ensureCodexHooksJsonEvent(targetDir, eventName, opts = {}) {
|
|
|
818
818
|
}
|
|
819
819
|
|
|
820
820
|
/**
|
|
821
|
-
* Remove a
|
|
821
|
+
* Remove a pp-core-managed event entry from hooks.json. Called during uninstall.
|
|
822
822
|
*
|
|
823
823
|
* @param {string} targetDir
|
|
824
824
|
* @param {string} eventName
|
|
@@ -1483,7 +1483,7 @@ const claudeToGeminiTools = {
|
|
|
1483
1483
|
TodoWrite: 'write_todos',
|
|
1484
1484
|
};
|
|
1485
1485
|
|
|
1486
|
-
// Tool name mapping from Claude/
|
|
1486
|
+
// Tool name mapping from Claude/pp-core agents to Kimi CLI module paths.
|
|
1487
1487
|
// Kimi custom agent YAML requires fully-qualified module paths.
|
|
1488
1488
|
const claudeToKimiTools = {
|
|
1489
1489
|
Read: 'kimi_cli.tools.file:ReadFile',
|
|
@@ -3025,7 +3025,7 @@ Typed mapping (agent_type-capable schema only):
|
|
|
3025
3025
|
to \`spawn_agent\` when the runtime/tool supports it. Omit missing, empty,
|
|
3026
3026
|
inherited, or unsupported values; do not invent one-off effort literals in
|
|
3027
3027
|
workflow prose.
|
|
3028
|
-
- \`fork_context: false\` by default —
|
|
3028
|
+
- \`fork_context: false\` by default — pp-core agents load their own context via \`<files_to_read>\` blocks
|
|
3029
3029
|
- \`Task(isolation="worktree")\` / \`Agent(isolation="worktree")\` → no direct Codex mapping.
|
|
3030
3030
|
Codex \`spawn_agent\` does not create or bind a git worktree automatically.
|
|
3031
3031
|
Workflows that require this isolation must fail closed or use an explicit
|
|
@@ -3035,7 +3035,7 @@ Generic-agent workaround (multi_agent_v1 schema — NO agent_type field):
|
|
|
3035
3035
|
When only the generic \`multi_agent_v1\` schema is available, typed GSD agent dispatch
|
|
3036
3036
|
(\`pp-planner\`, \`pp-executor\`, etc.) is NOT possible. This is a known Codex limitation
|
|
3037
3037
|
(openai/codex#15250). **This workaround is NOT equivalent to typed pp-planner/pp-executor
|
|
3038
|
-
execution** —
|
|
3038
|
+
execution** — pp-core agents carry project-aware prompts, audit logging, and workflow context
|
|
3039
3039
|
that a generic subagent lacks. Use the following fallback:
|
|
3040
3040
|
1. Resolve your active Codex config root — the directory that contains your \`config.toml\`.
|
|
3041
3041
|
This directory is determined in priority order: \`$CODEX_HOME\` (if set), the path given
|
|
@@ -3198,11 +3198,11 @@ function generateCodexAgentToml(agentName, agentContent, modelOverrides = null,
|
|
|
3198
3198
|
}
|
|
3199
3199
|
|
|
3200
3200
|
/**
|
|
3201
|
-
* Remove stale agents/openai.yaml sidecar files from
|
|
3201
|
+
* Remove stale agents/openai.yaml sidecar files from pp-core-managed Codex skill dirs.
|
|
3202
3202
|
*
|
|
3203
3203
|
* Prior to #1326, GSD's Codex install path wrote an agents/openai.yaml file
|
|
3204
3204
|
* alongside each gsd-* SKILL.md. Recent Codex builds index BOTH SKILL.md and
|
|
3205
|
-
* the sidecar, causing each
|
|
3205
|
+
* the sidecar, causing each pp-core skill to appear twice in autocomplete. This
|
|
3206
3206
|
* function removes those stale sidecars and — if the agents/ subdirectory is
|
|
3207
3207
|
* now empty — prunes it too.
|
|
3208
3208
|
*
|
|
@@ -4066,7 +4066,7 @@ function stripLeakedGsdCodexSections(content) {
|
|
|
4066
4066
|
}
|
|
4067
4067
|
|
|
4068
4068
|
/**
|
|
4069
|
-
* Strip
|
|
4069
|
+
* Strip pp-core-managed legacy Codex hook blocks from a config.toml string
|
|
4070
4070
|
* using the TOML AST already used elsewhere in this file
|
|
4071
4071
|
* (`getTomlTableSections` + `removeContentRanges`). The earlier regex-based
|
|
4072
4072
|
* implementation required a precise key order, exact single-space padding
|
|
@@ -4084,7 +4084,7 @@ function stripLeakedGsdCodexSections(content) {
|
|
|
4084
4084
|
* removal range backward through any single preceding comment line.
|
|
4085
4085
|
*
|
|
4086
4086
|
* Pure function, exported for test coverage. Returns the input unchanged
|
|
4087
|
-
* if no
|
|
4087
|
+
* if no pp-core-managed hook section is present.
|
|
4088
4088
|
*/
|
|
4089
4089
|
function stripStaleGsdHookBlocks(configContent) {
|
|
4090
4090
|
const sections = getTomlTableSections(configContent);
|
|
@@ -4096,7 +4096,7 @@ function stripStaleGsdHookBlocks(configContent) {
|
|
|
4096
4096
|
return configContent;
|
|
4097
4097
|
}
|
|
4098
4098
|
|
|
4099
|
-
// A section is
|
|
4099
|
+
// A section is pp-core-managed if any structural `command` key inside its
|
|
4100
4100
|
// body parses to a string whose basename matches `gsd-(check-update|
|
|
4101
4101
|
// update-check).js`. The TOML line parser already classified each line's
|
|
4102
4102
|
// `keySegments`, so we never inspect raw text — this handles arbitrary
|
|
@@ -4431,7 +4431,7 @@ function migrateCodexHooksMapFormat(content) {
|
|
|
4431
4431
|
/**
|
|
4432
4432
|
* Detect whether the user already uses the namespaced AoT hooks form
|
|
4433
4433
|
* (`[[hooks.<EVENT>]]`) for the given event in the config. When true,
|
|
4434
|
-
* the
|
|
4434
|
+
* the pp-core-managed hook block must be emitted in the same shape so it
|
|
4435
4435
|
* coexists cleanly — mixing `[[hooks]]` (flat) with `[[hooks.SessionStart]]`
|
|
4436
4436
|
* (namespaced) in the same file confuses round-trip writers and can
|
|
4437
4437
|
* produce a config that Codex rejects (#2760, defect 3).
|
|
@@ -5145,7 +5145,7 @@ function mergeCodexConfig(configPath, gsdBlock) {
|
|
|
5145
5145
|
if (markerIndex !== -1) {
|
|
5146
5146
|
let before = existing.substring(0, markerIndex).trimEnd();
|
|
5147
5147
|
if (before) {
|
|
5148
|
-
// Strip any
|
|
5148
|
+
// Strip any pp-core-managed sections that leaked above the marker from previous installs
|
|
5149
5149
|
before = stripLeakedGsdCodexSections(before).trimEnd();
|
|
5150
5150
|
|
|
5151
5151
|
atomicWriteFileSync(configPath, before + eol + eol + normalizedGsdBlock + eol);
|
|
@@ -5420,7 +5420,7 @@ function mergeCopilotInstructions(filePath, gsdContent) {
|
|
|
5420
5420
|
|
|
5421
5421
|
/**
|
|
5422
5422
|
* Strip GSD section from copilot-instructions.md content.
|
|
5423
|
-
* Returns cleaned content, or null if file should be deleted (was
|
|
5423
|
+
* Returns cleaned content, or null if file should be deleted (was pp-core-only).
|
|
5424
5424
|
* @param {string} content - File content
|
|
5425
5425
|
* @returns {string|null} - Cleaned content or null if empty
|
|
5426
5426
|
*/
|
|
@@ -5492,7 +5492,7 @@ function mergeGsdAgentsMd(filePath, gsdContent) {
|
|
|
5492
5492
|
|
|
5493
5493
|
/**
|
|
5494
5494
|
* Strip the GSD block from AGENTS.md content. Returns null if the file became
|
|
5495
|
-
* empty (was
|
|
5495
|
+
* empty (was pp-core-only), the unchanged content if no markers were found, or the
|
|
5496
5496
|
* cleaned content otherwise.
|
|
5497
5497
|
*/
|
|
5498
5498
|
function stripGsdFromAgentsMd(content) {
|
|
@@ -5551,7 +5551,7 @@ function buildCursorHookEntry(scriptPath) {
|
|
|
5551
5551
|
}
|
|
5552
5552
|
|
|
5553
5553
|
/**
|
|
5554
|
-
* Return true if a Cursor hook entry is
|
|
5554
|
+
* Return true if a Cursor hook entry is pp-core-managed.
|
|
5555
5555
|
* Detection: presence of the GSD_CURSOR_HOOK_MARKER sentinel field.
|
|
5556
5556
|
*
|
|
5557
5557
|
* @param {object} entry - A hooks array element from hooks.json
|
|
@@ -5562,7 +5562,7 @@ function isManagedCursorHookEntry(entry) {
|
|
|
5562
5562
|
}
|
|
5563
5563
|
|
|
5564
5564
|
/**
|
|
5565
|
-
* Reconcile the
|
|
5565
|
+
* Reconcile the pp-core-managed entries in a Cursor hooks.json file.
|
|
5566
5566
|
*
|
|
5567
5567
|
* Supports both known hooks.json shapes:
|
|
5568
5568
|
* 1) { "version": 1, "hooks": { "sessionStart": [...], "postToolUse": [...] } }
|
|
@@ -5583,10 +5583,10 @@ function reconcileCursorHooksJson(hooksJsonPath, managedEntries) {
|
|
|
5583
5583
|
}
|
|
5584
5584
|
|
|
5585
5585
|
/**
|
|
5586
|
-
* #777 — Write
|
|
5586
|
+
* #777 — Write pp-core-managed Cursor lifecycle hooks into <targetDir>/hooks.json.
|
|
5587
5587
|
*
|
|
5588
5588
|
* Both managed hook scripts (pp-cursor-session-start.js, pp-cursor-post-tool.js)
|
|
5589
|
-
* are copied from the
|
|
5589
|
+
* are copied from the pp-core hooks/ source to <targetDir>/hooks/ first, so the
|
|
5590
5590
|
* hooks.json entries never reference a script that wasn't installed.
|
|
5591
5591
|
*
|
|
5592
5592
|
* @param {string} targetDir - The Cursor config dir (global: ~/.cursor; local: .cursor)
|
|
@@ -5599,7 +5599,7 @@ function writeCursorHooksJson(targetDir, src, opts) {
|
|
|
5599
5599
|
}
|
|
5600
5600
|
|
|
5601
5601
|
/**
|
|
5602
|
-
* Remove all
|
|
5602
|
+
* Remove all pp-core-managed Cursor lifecycle hook entries from hooks.json.
|
|
5603
5603
|
* User-owned entries are preserved. If the file becomes empty, it is removed.
|
|
5604
5604
|
*
|
|
5605
5605
|
* @param {string} targetDir - The Cursor config dir
|
|
@@ -5610,7 +5610,7 @@ function removeCursorHooksJson(targetDir) {
|
|
|
5610
5610
|
}
|
|
5611
5611
|
|
|
5612
5612
|
/**
|
|
5613
|
-
* #786 — Build the
|
|
5613
|
+
* #786 — Build the pp-core-managed GitHub Copilot lifecycle hook config object.
|
|
5614
5614
|
*
|
|
5615
5615
|
* Returns the verbatim JSON shape Copilot CLI expects:
|
|
5616
5616
|
* { version: 1, hooks: { sessionStart: [ <hook entry> ] } }
|
|
@@ -5628,7 +5628,7 @@ function buildCopilotHookConfig() {
|
|
|
5628
5628
|
}
|
|
5629
5629
|
|
|
5630
5630
|
/**
|
|
5631
|
-
* #786 — Write the
|
|
5631
|
+
* #786 — Write the pp-core-managed Copilot lifecycle hook config under the runtime
|
|
5632
5632
|
* config dir (`<targetDir>/hooks/gsd-session.json`). For local installs
|
|
5633
5633
|
* targetDir is `.github` (→ `.github/hooks/`); for global installs it is
|
|
5634
5634
|
* `~/.copilot` (→ `~/.copilot/hooks/`) — both are valid Copilot hook locations.
|
|
@@ -5922,7 +5922,7 @@ function convertClaudeToGeminiAgent(content) {
|
|
|
5922
5922
|
// Escape ${VAR} patterns in agent body for Gemini CLI compatibility.
|
|
5923
5923
|
// Gemini's templateString() treats all ${word} patterns as template variables
|
|
5924
5924
|
// and throws "Template validation failed: Missing required input parameters"
|
|
5925
|
-
// when they can't be resolved.
|
|
5925
|
+
// when they can't be resolved. pp-core agents use ${PHASE}, ${PLAN}, etc. as
|
|
5926
5926
|
// shell variables in bash code blocks — convert to $VAR (no braces) which
|
|
5927
5927
|
// is equivalent bash and invisible to Gemini's /\$\{(\w+)\}/g regex.
|
|
5928
5928
|
const escapedBody = body.replace(/\$\{(\w+)\}/g, '$$$1');
|
|
@@ -6585,7 +6585,7 @@ function writeHermesCategoryDescription(categoryDir) {
|
|
|
6585
6585
|
* /gsd-profile-user) and must be preserved across reinstalls. Critically, they
|
|
6586
6586
|
* MUST be excluded from gsd-file-manifest.json — otherwise saveLocalPatches()
|
|
6587
6587
|
* will compare a refreshed file against a stale manifest hash and emit a
|
|
6588
|
-
* spurious "locally modified
|
|
6588
|
+
* spurious "locally modified pp-core file" warning (bug #2771).
|
|
6589
6589
|
*
|
|
6590
6590
|
* Invariant: a file is either distribution (manifest-tracked, diff'd against
|
|
6591
6591
|
* manifest) or user artifact (preserved across installs, never diff'd). Never
|
|
@@ -7251,7 +7251,7 @@ function _restoreDir(dir, snapshot) {
|
|
|
7251
7251
|
*
|
|
7252
7252
|
* The removal set is derived from the ACTUAL installed skill dirs (every
|
|
7253
7253
|
* entry starting with 'gsd-' that is a directory), so it covers ALL shipped
|
|
7254
|
-
*
|
|
7254
|
+
* pp-core skills — including 'dev-preferences' and future additions — without
|
|
7255
7255
|
* relying on readGsdCommandNames() which only enumerates the commands source
|
|
7256
7256
|
* tree and can miss skills that ship outside that directory.
|
|
7257
7257
|
*
|
|
@@ -7387,6 +7387,120 @@ function installRuntimeArtifacts(runtime, configDir, scope, resolvedProfile) {
|
|
|
7387
7387
|
const nestedGsdDirForCleanup = path.join(configDir, 'skills', 'gsd');
|
|
7388
7388
|
_removeHermesBareStemDirs(nestedGsdDirForCleanup);
|
|
7389
7389
|
}
|
|
7390
|
+
|
|
7391
|
+
// Verbatim Pareto skill payload — MUST be the LAST step (post-prune ordering
|
|
7392
|
+
// is load-bearing, Pitfall 1). The skills-kind loop above runs _removeGsdEntries,
|
|
7393
|
+
// which unconditionally deletes EVERY pp-* skill dir; running the verbatim copy
|
|
7394
|
+
// here (delete-then-recopy) makes the payload self-healing across repeated
|
|
7395
|
+
// `npx` installs. Copies are byte-for-byte with NO content-rewrite pass (D-03).
|
|
7396
|
+
installVerbatimSkills(runtime, configDir, scope);
|
|
7397
|
+
}
|
|
7398
|
+
|
|
7399
|
+
/**
|
|
7400
|
+
* Locate the bundled `pareto-skills/` source directory.
|
|
7401
|
+
*
|
|
7402
|
+
* Walks up from `__dirname` (up to 6 ancestors) looking for the sentinel
|
|
7403
|
+
* `pareto-skills/pp-brand/SKILL.md`, mirroring the source-root walk-up the
|
|
7404
|
+
* installer already uses to anchor `commands/`. Returns the absolute path to
|
|
7405
|
+
* the `pareto-skills/` directory. Throws a clear error if it cannot be found.
|
|
7406
|
+
*
|
|
7407
|
+
* @returns {string} absolute path to the bundled `pareto-skills/` directory
|
|
7408
|
+
*/
|
|
7409
|
+
function findParetoSkillsSrc() {
|
|
7410
|
+
let dir = __dirname;
|
|
7411
|
+
for (let i = 0; i < 6; i += 1) {
|
|
7412
|
+
const candidate = path.join(dir, 'pareto-skills');
|
|
7413
|
+
if (fs.existsSync(path.join(candidate, 'pp-brand', 'SKILL.md'))) return candidate;
|
|
7414
|
+
const parent = path.dirname(dir);
|
|
7415
|
+
if (parent === dir) break;
|
|
7416
|
+
dir = parent;
|
|
7417
|
+
}
|
|
7418
|
+
throw new Error(`findParetoSkillsSrc: could not locate pareto-skills/ from ${__dirname}`);
|
|
7419
|
+
}
|
|
7420
|
+
|
|
7421
|
+
/**
|
|
7422
|
+
* Place the verbatim Pareto skill payload into a runtime's resolved skills dir.
|
|
7423
|
+
*
|
|
7424
|
+
* This is the "second channel" for shipping skills: unlike command-derived
|
|
7425
|
+
* skills (generated from commands/pp-*.md via the converter), these three
|
|
7426
|
+
* `pp-*` skill dirs — plus a peer `_shared/` — are copied byte-for-byte with
|
|
7427
|
+
* NO content-rewrite pass (D-03/D-07). That keeps them out of docs/COMMANDS.md
|
|
7428
|
+
* and prevents the rewrite walker from corrupting templates or the protocol
|
|
7429
|
+
* file (Pitfall 3). The payload is binary-safe (Crew assets), so we use a plain
|
|
7430
|
+
* recursive fs.cpSync — never applyRuntimeContentRewritesInPlace,
|
|
7431
|
+
* stageSkillsForRuntimeAsSkills, or the skillsKind converter.
|
|
7432
|
+
*
|
|
7433
|
+
* The destination is resolved from the artifact layout's skills-kind
|
|
7434
|
+
* destSubpath (never a hardcoded `skills/`), so Hermes' nested `skills/gsd/`
|
|
7435
|
+
* base is honoured automatically. Runtimes with no skills kind (gemini has only
|
|
7436
|
+
* a commands kind; grok has no skills-layout descriptor) are skipped gracefully
|
|
7437
|
+
* with an early `return 0` — never a throw (Pitfall 5).
|
|
7438
|
+
*
|
|
7439
|
+
* The skill names are a FIXED, package-owned allowlist — never interpolated
|
|
7440
|
+
* input (V5 / T-16-03). No manifest entry is added, so `/pp-surface` pruning
|
|
7441
|
+
* leaves these non-manifest pp-* dirs untouched (Pitfall 2). `_shared/*.sh`
|
|
7442
|
+
* regain their exec bit after copy, because a content-level cpSync does not
|
|
7443
|
+
* reliably preserve the exec bit cross-platform.
|
|
7444
|
+
*
|
|
7445
|
+
* @param {string} runtime - target runtime id
|
|
7446
|
+
* @param {string} configDir - resolved runtime config directory
|
|
7447
|
+
* @param {string} scope - 'local' | 'global'
|
|
7448
|
+
* @returns {number} number of skill dirs placed (0 if the runtime has no skills kind)
|
|
7449
|
+
*/
|
|
7450
|
+
function installVerbatimSkills(runtime, configDir, scope) {
|
|
7451
|
+
// Resolve the runtime's artifact layout. Two graceful-skip cases (Pitfall 5,
|
|
7452
|
+
// no throw): gemini's layout has only a commands kind (skillsKindEntry
|
|
7453
|
+
// undefined below); grok is not in the layout registry at all, so
|
|
7454
|
+
// resolveRuntimeArtifactLayout throws 'Unknown runtime' — treat that as
|
|
7455
|
+
// "no skills surface" and skip. In the production call path
|
|
7456
|
+
// installRuntimeArtifacts has already resolved the layout, but this function
|
|
7457
|
+
// is exported and called standalone by test harnesses (Plan 16-03), so the
|
|
7458
|
+
// guard lives here too.
|
|
7459
|
+
let layout;
|
|
7460
|
+
try {
|
|
7461
|
+
layout = resolveRuntimeArtifactLayout(runtime, configDir, scope);
|
|
7462
|
+
} catch {
|
|
7463
|
+
return 0;
|
|
7464
|
+
}
|
|
7465
|
+
const skillsKindEntry = layout.kinds.find((k) => k.kind === 'skills');
|
|
7466
|
+
// gemini (commands-only): no skills surface — skip, don't throw.
|
|
7467
|
+
if (!skillsKindEntry) return 0;
|
|
7468
|
+
|
|
7469
|
+
const src = findParetoSkillsSrc();
|
|
7470
|
+
// destSubpath already encodes Hermes' nested `skills/gsd` — never hardcode `skills/`.
|
|
7471
|
+
const skillsBase = path.join(configDir, skillsKindEntry.destSubpath);
|
|
7472
|
+
fs.mkdirSync(skillsBase, { recursive: true });
|
|
7473
|
+
|
|
7474
|
+
// FIXED, package-owned allowlist — never interpolated input (V5 / T-16-03).
|
|
7475
|
+
const BUNDLED = ['pp-brand', 'pp-project-setup', 'pp-obsidian-notes'];
|
|
7476
|
+
let placed = 0;
|
|
7477
|
+
for (const name of BUNDLED) {
|
|
7478
|
+
const skillDest = path.join(skillsBase, name);
|
|
7479
|
+
// Plain recursive, binary-safe copy — NO content-rewrite pass (D-03/Pitfall 3).
|
|
7480
|
+
fs.cpSync(path.join(src, name), skillDest, { recursive: true });
|
|
7481
|
+
if (verifyInstalled(skillDest, `skill ${name}`)) {
|
|
7482
|
+
placed += 1;
|
|
7483
|
+
}
|
|
7484
|
+
}
|
|
7485
|
+
|
|
7486
|
+
// Copy _shared/ as a PEER of the skill dirs so `../_shared/` resolves from any
|
|
7487
|
+
// skill body (D-04). Verbatim copy, no rewrite pass.
|
|
7488
|
+
const sharedDest = path.join(skillsBase, '_shared');
|
|
7489
|
+
fs.cpSync(path.join(src, '_shared'), sharedDest, { recursive: true });
|
|
7490
|
+
verifyInstalled(sharedDest, 'skill _shared');
|
|
7491
|
+
|
|
7492
|
+
// Restore the exec bit on bundled *.sh — cpSync content copy does not reliably
|
|
7493
|
+
// preserve it cross-platform (PATTERNS "No Analog Found").
|
|
7494
|
+
try {
|
|
7495
|
+
for (const entry of fs.readdirSync(sharedDest)) {
|
|
7496
|
+
if (entry.endsWith('.sh')) {
|
|
7497
|
+
fs.chmodSync(path.join(sharedDest, entry), 0o755);
|
|
7498
|
+
}
|
|
7499
|
+
}
|
|
7500
|
+
} catch { /* best-effort: non-fatal if _shared has no scripts */ }
|
|
7501
|
+
|
|
7502
|
+
console.log(` ${green}✓${reset} Placed ${placed} verbatim Pareto skill(s) + _shared into ${skillsKindEntry.destSubpath}`);
|
|
7503
|
+
return placed;
|
|
7390
7504
|
}
|
|
7391
7505
|
|
|
7392
7506
|
/**
|
|
@@ -7496,7 +7610,7 @@ function uninstallRuntimeArtifacts(runtime, configDir, scope) {
|
|
|
7496
7610
|
}
|
|
7497
7611
|
|
|
7498
7612
|
// Hermes: after removing gsd-* skill dirs from skills/gsd/, also remove
|
|
7499
|
-
// the
|
|
7613
|
+
// the pp-core-managed DESCRIPTION.md and then the category dir itself if it
|
|
7500
7614
|
// contains no user content (#947). _removeGsdEntries removed gsd-* dirs
|
|
7501
7615
|
// but left the category container and DESCRIPTION.md intact.
|
|
7502
7616
|
if (runtime === 'hermes') {
|
|
@@ -7936,7 +8050,7 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
7936
8050
|
if (runtime === 'kimi') runtimeLabel = 'Kimi CLI';
|
|
7937
8051
|
if (runtime === 'codebuddy') runtimeLabel = 'CodeBuddy';
|
|
7938
8052
|
|
|
7939
|
-
console.log(` Uninstalling
|
|
8053
|
+
console.log(` Uninstalling pp-core from ${cyan}${runtimeLabel}${reset} at ${cyan}${locationLabel}${reset}\n`);
|
|
7940
8054
|
|
|
7941
8055
|
// #786: AGENTS.md lives at the repo root (outside targetDir) for local Copilot
|
|
7942
8056
|
// installs, so its cleanup must run even when .github (targetDir) was already
|
|
@@ -7948,10 +8062,10 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
7948
8062
|
const cleaned = stripGsdFromCopilotInstructions(content);
|
|
7949
8063
|
if (cleaned === null) {
|
|
7950
8064
|
fs.unlinkSync(agentsMdPath);
|
|
7951
|
-
console.log(` ${green}✓${reset} Removed AGENTS.md (was
|
|
8065
|
+
console.log(` ${green}✓${reset} Removed AGENTS.md (was pp-core-only)`);
|
|
7952
8066
|
} else if (cleaned !== content) {
|
|
7953
8067
|
fs.writeFileSync(agentsMdPath, cleaned);
|
|
7954
|
-
console.log(` ${green}✓${reset} Cleaned
|
|
8068
|
+
console.log(` ${green}✓${reset} Cleaned pp-core section from AGENTS.md`);
|
|
7955
8069
|
}
|
|
7956
8070
|
}
|
|
7957
8071
|
}
|
|
@@ -8002,11 +8116,11 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
8002
8116
|
if (cleaned === null) {
|
|
8003
8117
|
fs.unlinkSync(codexConfigPath);
|
|
8004
8118
|
removedCount++;
|
|
8005
|
-
console.log(` ${green}✓${reset} Removed config.toml (was
|
|
8119
|
+
console.log(` ${green}✓${reset} Removed config.toml (was pp-core-only)`);
|
|
8006
8120
|
} else if (cleaned !== content) {
|
|
8007
8121
|
fs.writeFileSync(codexConfigPath, cleaned);
|
|
8008
8122
|
removedCount++;
|
|
8009
|
-
console.log(` ${green}✓${reset} Cleaned
|
|
8123
|
+
console.log(` ${green}✓${reset} Cleaned pp-core sections from config.toml`);
|
|
8010
8124
|
}
|
|
8011
8125
|
}
|
|
8012
8126
|
|
|
@@ -8035,15 +8149,15 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
8035
8149
|
if (cleaned === null) {
|
|
8036
8150
|
fs.unlinkSync(instructionsPath);
|
|
8037
8151
|
removedCount++;
|
|
8038
|
-
console.log(` ${green}✓${reset} Removed copilot-instructions.md (was
|
|
8152
|
+
console.log(` ${green}✓${reset} Removed copilot-instructions.md (was pp-core-only)`);
|
|
8039
8153
|
} else if (cleaned !== content) {
|
|
8040
8154
|
fs.writeFileSync(instructionsPath, cleaned);
|
|
8041
8155
|
removedCount++;
|
|
8042
|
-
console.log(` ${green}✓${reset} Cleaned
|
|
8156
|
+
console.log(` ${green}✓${reset} Cleaned pp-core section from copilot-instructions.md`);
|
|
8043
8157
|
}
|
|
8044
8158
|
}
|
|
8045
8159
|
|
|
8046
|
-
// #786: remove the
|
|
8160
|
+
// #786: remove the pp-core-managed Copilot lifecycle hook config and prune the
|
|
8047
8161
|
// hooks dir if we left it empty.
|
|
8048
8162
|
const hookPath = path.join(targetDir, 'hooks', GSD_COPILOT_HOOK_FILE);
|
|
8049
8163
|
if (fs.existsSync(hookPath)) {
|
|
@@ -8099,24 +8213,24 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
8099
8213
|
if (cleaned === null) {
|
|
8100
8214
|
fs.unlinkSync(agentsPath);
|
|
8101
8215
|
removedCount++;
|
|
8102
|
-
console.log(` ${green}✓${reset} Removed ~/.agents/AGENTS.md (was
|
|
8216
|
+
console.log(` ${green}✓${reset} Removed ~/.agents/AGENTS.md (was pp-core-only)`);
|
|
8103
8217
|
} else if (cleaned !== content) {
|
|
8104
8218
|
fs.writeFileSync(agentsPath, cleaned);
|
|
8105
8219
|
removedCount++;
|
|
8106
|
-
console.log(` ${green}✓${reset} Cleaned
|
|
8220
|
+
console.log(` ${green}✓${reset} Cleaned pp-core section from ~/.agents/AGENTS.md`);
|
|
8107
8221
|
}
|
|
8108
8222
|
}
|
|
8109
8223
|
} catch { /* best-effort */ }
|
|
8110
8224
|
}
|
|
8111
8225
|
}
|
|
8112
8226
|
|
|
8113
|
-
// 1b-cursor. Non-layout Cursor side-effects (issue #777): remove
|
|
8227
|
+
// 1b-cursor. Non-layout Cursor side-effects (issue #777): remove pp-core-managed
|
|
8114
8228
|
// hook entries from hooks.json and clean up the managed hook scripts.
|
|
8115
8229
|
if (isCursor) {
|
|
8116
8230
|
const hooksJsonCleanup = removeCursorHooksJson(targetDir);
|
|
8117
8231
|
if (hooksJsonCleanup.changed) {
|
|
8118
8232
|
removedCount++;
|
|
8119
|
-
console.log(` ${green}✓${reset} Removed
|
|
8233
|
+
console.log(` ${green}✓${reset} Removed pp-core-managed Cursor hooks from hooks.json`);
|
|
8120
8234
|
}
|
|
8121
8235
|
// Remove the managed hook scripts (session-start + post-tool).
|
|
8122
8236
|
const hooksDir = path.join(targetDir, 'hooks');
|
|
@@ -8265,7 +8379,7 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
8265
8379
|
}
|
|
8266
8380
|
}
|
|
8267
8381
|
|
|
8268
|
-
// 3. Remove
|
|
8382
|
+
// 3. Remove pp-core agents (gsd-*.md files only)
|
|
8269
8383
|
const agentsDir = path.join(targetDir, 'agents');
|
|
8270
8384
|
if (fs.existsSync(agentsDir)) {
|
|
8271
8385
|
const files = fs.readdirSync(agentsDir);
|
|
@@ -8278,11 +8392,11 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
8278
8392
|
}
|
|
8279
8393
|
if (agentCount > 0) {
|
|
8280
8394
|
removedCount++;
|
|
8281
|
-
console.log(` ${green}✓${reset} Removed ${agentCount}
|
|
8395
|
+
console.log(` ${green}✓${reset} Removed ${agentCount} pp-core agents`);
|
|
8282
8396
|
}
|
|
8283
8397
|
}
|
|
8284
8398
|
|
|
8285
|
-
// 4. Remove
|
|
8399
|
+
// 4. Remove pp-core hooks
|
|
8286
8400
|
const hooksDir = path.join(targetDir, 'hooks');
|
|
8287
8401
|
if (fs.existsSync(hooksDir)) {
|
|
8288
8402
|
let hookCount = 0;
|
|
@@ -8295,10 +8409,10 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
8295
8409
|
}
|
|
8296
8410
|
if (hookCount > 0) {
|
|
8297
8411
|
removedCount++;
|
|
8298
|
-
console.log(` ${green}✓${reset} Removed ${hookCount}
|
|
8412
|
+
console.log(` ${green}✓${reset} Removed ${hookCount} pp-core hooks`);
|
|
8299
8413
|
}
|
|
8300
8414
|
|
|
8301
|
-
// Remove only the
|
|
8415
|
+
// Remove only the pp-core-managed files from hooks/lib/ (git-cmd.js + pp-graphify-rebuild.sh).
|
|
8302
8416
|
// hooks/lib/ lives inside the user's runtime hooks directory (shared space) and
|
|
8303
8417
|
// may contain user-owned custom helpers. We must not recursively delete the dir.
|
|
8304
8418
|
const hooksLibDir = path.join(hooksDir, 'lib');
|
|
@@ -8327,9 +8441,9 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
8327
8441
|
}
|
|
8328
8442
|
|
|
8329
8443
|
// 4a. Remove scripts/changeset/ and scripts/lib/ (#935)
|
|
8330
|
-
//
|
|
8444
|
+
// pp-core-managed files only: enumerate the exact set the installer writes.
|
|
8331
8445
|
// Any file NOT in this set is user-owned and must survive uninstall.
|
|
8332
|
-
// After removing
|
|
8446
|
+
// After removing pp-core files, attempt to rmdir — if the directory is still
|
|
8333
8447
|
// non-empty (user has custom helpers) it stays; otherwise it goes cleanly.
|
|
8334
8448
|
const GSD_CHANGESET_FILES = [
|
|
8335
8449
|
'cli.cjs', 'parse.cjs', 'render.cjs', 'serialize.cjs',
|
|
@@ -8349,7 +8463,7 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
8349
8463
|
try { fs.rmdirSync(changesetUninstallDir); } catch (_) { /* Not empty — user content present */ }
|
|
8350
8464
|
if (removedChangeset > 0) {
|
|
8351
8465
|
removedCount++;
|
|
8352
|
-
console.log(` ${green}✓${reset} Removed scripts/changeset/
|
|
8466
|
+
console.log(` ${green}✓${reset} Removed scripts/changeset/ pp-core files`);
|
|
8353
8467
|
}
|
|
8354
8468
|
}
|
|
8355
8469
|
const scriptsLibUninstallDir = path.join(targetDir, 'scripts', 'lib');
|
|
@@ -8363,7 +8477,7 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
8363
8477
|
try { fs.rmdirSync(scriptsLibUninstallDir); } catch (_) { /* Not empty — user content present */ }
|
|
8364
8478
|
if (removedScriptsLib > 0) {
|
|
8365
8479
|
removedCount++;
|
|
8366
|
-
console.log(` ${green}✓${reset} Removed scripts/lib/
|
|
8480
|
+
console.log(` ${green}✓${reset} Removed scripts/lib/ pp-core files`);
|
|
8367
8481
|
}
|
|
8368
8482
|
}
|
|
8369
8483
|
// Remove scripts/fix-slash-commands.cjs (#1223) — must come before the scripts/ rmdir
|
|
@@ -8376,7 +8490,7 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
8376
8490
|
try { fs.rmdirSync(scriptsUninstallDir); } catch (_) { /* Not empty — leave it */ }
|
|
8377
8491
|
}
|
|
8378
8492
|
|
|
8379
|
-
// 5. Remove
|
|
8493
|
+
// 5. Remove pp-core package.json (CommonJS mode marker)
|
|
8380
8494
|
const pkgJsonPath = path.join(targetDir, 'package.json');
|
|
8381
8495
|
if (fs.existsSync(pkgJsonPath)) {
|
|
8382
8496
|
try {
|
|
@@ -8385,14 +8499,14 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
8385
8499
|
if (content === '{"type":"commonjs"}') {
|
|
8386
8500
|
fs.unlinkSync(pkgJsonPath);
|
|
8387
8501
|
removedCount++;
|
|
8388
|
-
console.log(` ${green}✓${reset} Removed
|
|
8502
|
+
console.log(` ${green}✓${reset} Removed pp-core package.json`);
|
|
8389
8503
|
}
|
|
8390
8504
|
} catch (e) {
|
|
8391
8505
|
// Ignore read errors
|
|
8392
8506
|
}
|
|
8393
8507
|
}
|
|
8394
8508
|
|
|
8395
|
-
// 6. Clean up settings.json (remove
|
|
8509
|
+
// 6. Clean up settings.json (remove pp-core hooks and statusline)
|
|
8396
8510
|
const settingsPath = path.join(targetDir, 'settings.json');
|
|
8397
8511
|
if (fs.existsSync(settingsPath)) {
|
|
8398
8512
|
let settings = readSettings(settingsPath);
|
|
@@ -8402,15 +8516,15 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
8402
8516
|
}
|
|
8403
8517
|
let settingsModified = false;
|
|
8404
8518
|
|
|
8405
|
-
// Remove
|
|
8519
|
+
// Remove pp-core statusline if it references our hook
|
|
8406
8520
|
if (settings.statusLine && settings.statusLine.command &&
|
|
8407
8521
|
settings.statusLine.command.includes('pp-statusline')) {
|
|
8408
8522
|
delete settings.statusLine;
|
|
8409
8523
|
settingsModified = true;
|
|
8410
|
-
console.log(` ${green}✓${reset} Removed
|
|
8524
|
+
console.log(` ${green}✓${reset} Removed pp-core statusline from settings`);
|
|
8411
8525
|
}
|
|
8412
8526
|
|
|
8413
|
-
// Remove
|
|
8527
|
+
// Remove pp-core hooks from settings — per-hook granularity to preserve
|
|
8414
8528
|
// user hooks that share an entry with a GSD hook (#1755 followup).
|
|
8415
8529
|
// Includes the 3 Qwen-only events added in #788 (SubagentStop, Stop,
|
|
8416
8530
|
// PreCompact, also registered for Claude in #770), the 3 Gemini-only
|
|
@@ -8424,7 +8538,7 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
8424
8538
|
settings.hooks[eventName] = settings.hooks[eventName]
|
|
8425
8539
|
.map(entry => {
|
|
8426
8540
|
if (!entry || typeof entry !== 'object' || !Array.isArray(entry.hooks)) return entry;
|
|
8427
|
-
// Filter out individual
|
|
8541
|
+
// Filter out individual pp-core hooks, keep user hooks
|
|
8428
8542
|
entry.hooks = entry.hooks.filter((h) => {
|
|
8429
8543
|
if (!h || typeof h.command !== 'string') return true;
|
|
8430
8544
|
return !isManagedHookCommand(h.command, {
|
|
@@ -8443,7 +8557,7 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
8443
8557
|
}
|
|
8444
8558
|
}
|
|
8445
8559
|
if (settingsModified) {
|
|
8446
|
-
console.log(` ${green}✓${reset} Removed
|
|
8560
|
+
console.log(` ${green}✓${reset} Removed pp-core hooks from settings`);
|
|
8447
8561
|
}
|
|
8448
8562
|
|
|
8449
8563
|
// Clean up empty hooks object
|
|
@@ -8455,7 +8569,7 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
8455
8569
|
// Applies only to Claude uninstalls. Filter only the exact GSD-owned entries
|
|
8456
8570
|
// to preserve any user-added allow/deny entries.
|
|
8457
8571
|
// Uses a local flag to avoid the shared `settingsModified` producing a false
|
|
8458
|
-
// "Removed
|
|
8572
|
+
// "Removed pp-core permissions" message when only hooks/statusline changed.
|
|
8459
8573
|
if (runtime === 'claude' && settings.permissions) {
|
|
8460
8574
|
let permissionsModified = false;
|
|
8461
8575
|
if (Array.isArray(settings.permissions.allow)) {
|
|
@@ -8478,7 +8592,7 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
8478
8592
|
}
|
|
8479
8593
|
if (permissionsModified) {
|
|
8480
8594
|
settingsModified = true;
|
|
8481
|
-
console.log(` ${green}✓${reset} Removed
|
|
8595
|
+
console.log(` ${green}✓${reset} Removed pp-core permissions from settings.json`);
|
|
8482
8596
|
}
|
|
8483
8597
|
}
|
|
8484
8598
|
|
|
@@ -8521,7 +8635,7 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
8521
8635
|
if (modified) {
|
|
8522
8636
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
8523
8637
|
removedCount++;
|
|
8524
|
-
console.log(` ${green}✓${reset} Removed
|
|
8638
|
+
console.log(` ${green}✓${reset} Removed pp-core permissions from ${path.basename(configPath)}`);
|
|
8525
8639
|
}
|
|
8526
8640
|
} catch (e) {
|
|
8527
8641
|
// Ignore JSON parse errors
|
|
@@ -8562,7 +8676,7 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
8562
8676
|
if (modified) {
|
|
8563
8677
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
8564
8678
|
removedCount++;
|
|
8565
|
-
console.log(` ${green}✓${reset} Removed
|
|
8679
|
+
console.log(` ${green}✓${reset} Removed pp-core permissions from ${path.basename(configPath)}`);
|
|
8566
8680
|
}
|
|
8567
8681
|
} catch (e) {
|
|
8568
8682
|
// Ignore JSON parse errors
|
|
@@ -8580,7 +8694,7 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
8580
8694
|
}
|
|
8581
8695
|
|
|
8582
8696
|
if (removedCount === 0) {
|
|
8583
|
-
console.log(` ${yellow}⚠${reset} No
|
|
8697
|
+
console.log(` ${yellow}⚠${reset} No pp-core files found to remove.`);
|
|
8584
8698
|
}
|
|
8585
8699
|
|
|
8586
8700
|
console.log(`
|
|
@@ -8727,7 +8841,7 @@ function configureOpencodePermissions(isGlobal = true, configDir = null) {
|
|
|
8727
8841
|
|
|
8728
8842
|
// Write config back
|
|
8729
8843
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
8730
|
-
console.log(` ${green}✓${reset} Configured read permission for
|
|
8844
|
+
console.log(` ${green}✓${reset} Configured read permission for pp-core docs`);
|
|
8731
8845
|
}
|
|
8732
8846
|
|
|
8733
8847
|
/**
|
|
@@ -8801,7 +8915,7 @@ function configureKiloPermissions(isGlobal = true, configDir = null) {
|
|
|
8801
8915
|
|
|
8802
8916
|
// Write config back
|
|
8803
8917
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
8804
|
-
console.log(` ${green}✓${reset} Configured read permission for
|
|
8918
|
+
console.log(` ${green}✓${reset} Configured read permission for pp-core docs`);
|
|
8805
8919
|
}
|
|
8806
8920
|
|
|
8807
8921
|
/**
|
|
@@ -8949,7 +9063,7 @@ function writeManifest(configDir, runtime = 'claude', options = {}) {
|
|
|
8949
9063
|
// Flat-hyphen layout: pp-*.md files live directly in commands/ (no gsd/ subdir).
|
|
8950
9064
|
const commandsDir = path.join(configDir, 'commands');
|
|
8951
9065
|
const opencodeCommandDir = path.join(configDir, 'command');
|
|
8952
|
-
// Hermes nests
|
|
9066
|
+
// Hermes nests pp-core skills under skills/gsd/ as a single category (#2841).
|
|
8953
9067
|
// All other runtimes that use the Codex-style skills layout use a flat skills/ root.
|
|
8954
9068
|
const codexSkillsDir = isHermes
|
|
8955
9069
|
? path.join(configDir, 'skills', 'gsd')
|
|
@@ -9184,7 +9298,7 @@ function populatePristineDir({ packageSrc, pristineDir, modified, runtime, pathP
|
|
|
9184
9298
|
}
|
|
9185
9299
|
|
|
9186
9300
|
/**
|
|
9187
|
-
* Detect user-modified
|
|
9301
|
+
* Detect user-modified pp-core files by comparing against install manifest.
|
|
9188
9302
|
* Backs up modified files to gsd-local-patches/ for reapply after update.
|
|
9189
9303
|
* Also saves pristine copies (from manifest) to gsd-pristine/ to enable
|
|
9190
9304
|
* three-way merge during reapply-patches (pristine vs user vs new).
|
|
@@ -9252,7 +9366,7 @@ function saveLocalPatches(configDir, pristineCtx) {
|
|
|
9252
9366
|
meta.pristine_hashes[relPath] = pristineHashes[relPath];
|
|
9253
9367
|
}
|
|
9254
9368
|
fs.writeFileSync(path.join(patchesDir, 'backup-meta.json'), JSON.stringify(meta, null, 2));
|
|
9255
|
-
console.log(' ' + yellow + 'i' + reset + ' Found ' + modified.length + ' locally modified
|
|
9369
|
+
console.log(' ' + yellow + 'i' + reset + ' Found ' + modified.length + ' locally modified pp-core file(s) — backed up to ' + PATCHES_DIR_NAME + '/');
|
|
9256
9370
|
for (const f of modified) {
|
|
9257
9371
|
console.log(' ' + dim + f + reset);
|
|
9258
9372
|
}
|
|
@@ -9611,7 +9725,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
9611
9725
|
rollback();
|
|
9612
9726
|
};
|
|
9613
9727
|
|
|
9614
|
-
// Save any locally modified
|
|
9728
|
+
// Save any locally modified pp-core files before they get wiped.
|
|
9615
9729
|
// The pristine context lets saveLocalPatches populate gsd-pristine/ via
|
|
9616
9730
|
// the install transform pipeline, giving the reapply-patches Step 5
|
|
9617
9731
|
// verifier a real diff baseline (#2998).
|
|
@@ -9817,7 +9931,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
9817
9931
|
// #3541: non-interactive runs (typical /gsd-update via Claude Code) have
|
|
9818
9932
|
// no stdin TTY and therefore no way to answer prompt-user migration
|
|
9819
9933
|
// actions. Resolve safe categories by classification (stale SDK build
|
|
9820
|
-
// artifacts → remove; user-facing skills → keep; bundled
|
|
9934
|
+
// artifacts → remove; user-facing skills → keep; bundled pp-core hooks →
|
|
9821
9935
|
// remove [#3610]) and log every resolution; anything that cannot be
|
|
9822
9936
|
// safely defaulted falls through to assertInstallerMigrationsUnblocked,
|
|
9823
9937
|
// which now emits a grouped error with the documented resolution path.
|
|
@@ -9825,7 +9939,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
9825
9939
|
// #3610: the classifier-based resolution must run regardless of TTY.
|
|
9826
9940
|
// For unambiguous categories (e.g. `hooks/gsd-*` bundled hooks left
|
|
9827
9941
|
// behind by a previous version), there is no actual "user choice" to
|
|
9828
|
-
// make — the file is a known
|
|
9942
|
+
// make — the file is a known pp-core-managed artifact and the installer is
|
|
9829
9943
|
// about to write the fresh bundled version. Gating the resolver on
|
|
9830
9944
|
// `!isTTY` made `npx @opengsd/gsd-core@latest --codex` hard-abort with
|
|
9831
9945
|
// 12 blocked bundled hooks. The env-override branch (operator-supplied
|
|
@@ -9897,7 +10011,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
9897
10011
|
// #1326 — Codex only: remove stale agents/openai.yaml sidecars from managed
|
|
9898
10012
|
// gsd-* skill dirs. Prior installs wrote these files so Codex would show a
|
|
9899
10013
|
// display name and description in the /skills TUI popup. Recent Codex builds
|
|
9900
|
-
// index BOTH SKILL.md and the sidecar, causing each
|
|
10014
|
+
// index BOTH SKILL.md and the sidecar, causing each pp-core skill to appear twice
|
|
9901
10015
|
// in autocomplete. Cleaning them up fixes the duplication; SKILL.md alone is
|
|
9902
10016
|
// sufficient for Codex discovery. User-owned dirs are never touched.
|
|
9903
10017
|
if (isCodex) {
|
|
@@ -10036,7 +10150,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
10036
10150
|
// Global installs are handled above by _isSkillsRuntime (#782).
|
|
10037
10151
|
console.log(` ${green}✓${reset} Cline: commands will be available via .clinerules`);
|
|
10038
10152
|
} else if (isGemini) {
|
|
10039
|
-
// #3037: when running --local --gemini and a
|
|
10153
|
+
// #3037: when running --local --gemini and a pp-core-managed user-scope
|
|
10040
10154
|
// command directory already exists at ~/.gemini/commands/gsd/, skip
|
|
10041
10155
|
// the local copy. Gemini conflict-detects by command name across
|
|
10042
10156
|
// scopes and renames every overlapping /gsd:* command to
|
|
@@ -10112,7 +10226,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
10112
10226
|
fs.rmSync(path.join(staleSkillsDir, e.name), { recursive: true });
|
|
10113
10227
|
}
|
|
10114
10228
|
if (staleGsd.length > 0) {
|
|
10115
|
-
console.log(` ${green}✓${reset} Removed ${staleGsd.length} stale
|
|
10229
|
+
console.log(` ${green}✓${reset} Removed ${staleGsd.length} stale pp-core skill(s) from skills/`);
|
|
10116
10230
|
}
|
|
10117
10231
|
}
|
|
10118
10232
|
}
|
|
@@ -10835,7 +10949,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
10835
10949
|
let configContent = fs.existsSync(configPath) ? fs.readFileSync(configPath, 'utf-8') : '';
|
|
10836
10950
|
const eol = detectLineEnding(configContent);
|
|
10837
10951
|
|
|
10838
|
-
// Strip ALL prior
|
|
10952
|
+
// Strip ALL prior pp-core-managed hook blocks BEFORE migration so the migration
|
|
10839
10953
|
// only touches user-authored hooks, not GSD-owned stale entries. Running
|
|
10840
10954
|
// strip after migration causes Shape 1 (legacy gsd-update-check filename)
|
|
10841
10955
|
// to be converted by migration before the strip regex can match it (#2698).
|
|
@@ -10848,7 +10962,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
10848
10962
|
configContent = stripStaleGsdHookBlocks(configContent);
|
|
10849
10963
|
|
|
10850
10964
|
// Migrate legacy [hooks] map format and flat [[hooks]] AoT entries to the
|
|
10851
|
-
// namespaced [[hooks.<EVENT>]] form after stripping
|
|
10965
|
+
// namespaced [[hooks.<EVENT>]] form after stripping pp-core-managed stale blocks.
|
|
10852
10966
|
// Running migration after strip ensures only user-authored hooks are migrated
|
|
10853
10967
|
// (#2698 regression: migration before strip converts stale GSD blocks before
|
|
10854
10968
|
// the strip regexes can match their original shape).
|
|
@@ -10861,7 +10975,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
10861
10975
|
const codexHooksFeature = ensureCodexHooksFeature(configContent);
|
|
10862
10976
|
configContent = setManagedCodexHooksOwnership(codexHooksFeature.content, codexHooksFeature.ownership);
|
|
10863
10977
|
|
|
10864
|
-
//
|
|
10978
|
+
// pp-core-managed Codex hook payloads now live in hooks.json to avoid mixed
|
|
10865
10979
|
// representation warnings when a single layer contains both hooks.json
|
|
10866
10980
|
// and inline [hooks] entries. Keep config.toml focused on feature flags
|
|
10867
10981
|
// and agent metadata.
|
|
@@ -11129,7 +11243,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
11129
11243
|
}
|
|
11130
11244
|
}
|
|
11131
11245
|
writeSettings(sharedSettingsPath, sharedRaw);
|
|
11132
|
-
console.log(` ${green}✓${reset} Migrated
|
|
11246
|
+
console.log(` ${green}✓${reset} Migrated pp-core hook entries from settings.json to settings.local.json (#338)`);
|
|
11133
11247
|
}
|
|
11134
11248
|
}
|
|
11135
11249
|
}
|
|
@@ -11227,7 +11341,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
11227
11341
|
}
|
|
11228
11342
|
}
|
|
11229
11343
|
|
|
11230
|
-
// Register all
|
|
11344
|
+
// Register all pp-core-managed hook entries into settings.hooks.* for runtimes
|
|
11231
11345
|
// that use the settings.json hook surface (ADR-857 phase 5f-1b).
|
|
11232
11346
|
// settings is mutated in place by applySettingsJsonHooks.
|
|
11233
11347
|
applySettingsJsonHooks(settings, {
|
|
@@ -11562,7 +11676,7 @@ function handleStatusline(settings, isInteractive, callback) {
|
|
|
11562
11676
|
• Context window usage (color-coded)
|
|
11563
11677
|
|
|
11564
11678
|
${cyan}1${reset}) Keep existing
|
|
11565
|
-
${cyan}2${reset}) Replace with
|
|
11679
|
+
${cyan}2${reset}) Replace with pp-core statusline
|
|
11566
11680
|
`);
|
|
11567
11681
|
|
|
11568
11682
|
rl.question(` Choice ${dim}[1]${reset}: `, (answer) => {
|
|
@@ -12309,6 +12423,8 @@ module.exports = {
|
|
|
12309
12423
|
readGsdCommandNames,
|
|
12310
12424
|
installRuntimeArtifacts,
|
|
12311
12425
|
installOpencodeFamilySkills,
|
|
12426
|
+
installVerbatimSkills,
|
|
12427
|
+
findParetoSkillsSrc,
|
|
12312
12428
|
uninstallRuntimeArtifacts,
|
|
12313
12429
|
parseConfigDirFromArgs,
|
|
12314
12430
|
cleanupLegacyGsdCc,
|