@pareto4/pp-core 1.21.1 → 1.23.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 +205 -28
- package/commands/pp-help.md +2 -1
- package/gemini-extension.json +1 -1
- package/hooks/dist/pp-check-update-worker.js +1 -1
- package/hooks/dist/pp-context-monitor.js +3 -3
- package/hooks/dist/pp-statusline.js +88 -14
- package/hooks/dist/pp-update-banner.js +44 -4
- package/hooks/dist/pp-workflow-guard.js +3 -3
- package/hooks/pp-check-update-worker.js +1 -1
- package/hooks/pp-context-monitor.js +3 -3
- package/hooks/pp-statusline.js +88 -14
- package/hooks/pp-update-banner.js +44 -4
- package/hooks/pp-workflow-guard.js +3 -3
- package/package.json +2 -2
- package/pp-core/bin/lib/audience-command.cjs +271 -0
- package/pp-core/bin/lib/audience.cjs +293 -0
- package/pp-core/bin/lib/commands.cjs +26 -17
- package/pp-core/bin/lib/discovery-charter.cjs +13 -1
- package/pp-core/bin/lib/discovery-flow-page.cjs +191 -0
- package/pp-core/bin/lib/discovery-for-phase.cjs +33 -5
- package/pp-core/bin/lib/discovery-input-check.cjs +11 -11
- package/pp-core/bin/lib/discovery-processes.cjs +97 -4
- package/pp-core/bin/lib/discovery-session-check.cjs +7 -7
- package/pp-core/bin/lib/discovery-status.cjs +419 -0
- package/pp-core/bin/lib/discovery-stories.cjs +36 -5
- package/pp-core/bin/lib/discovery-tests.cjs +398 -0
- package/pp-core/bin/lib/explain-command.cjs +48 -0
- package/pp-core/bin/lib/gate-card.cjs +97 -0
- package/pp-core/bin/lib/hook-messages.cjs +132 -0
- package/pp-core/bin/lib/init-contract.cjs +116 -76
- package/pp-core/bin/lib/init-interactive.cjs +110 -49
- package/pp-core/bin/lib/init.cjs +44 -3
- package/pp-core/bin/lib/io.cjs +38 -0
- package/pp-core/bin/lib/message-labels.cjs +75 -0
- package/pp-core/bin/lib/messages/core.cjs +20 -0
- package/pp-core/bin/lib/messages/discovery.cjs +267 -0
- package/pp-core/bin/lib/messages/gate.cjs +257 -0
- package/pp-core/bin/lib/messages/hook.cjs +114 -0
- package/pp-core/bin/lib/messages/init.cjs +513 -0
- package/pp-core/bin/lib/messages/progress.cjs +110 -0
- package/pp-core/bin/lib/messages.cjs +208 -0
- package/pp-core/bin/lib/vault-command-router.cjs +16 -3
- package/pp-core/bin/lib/violation-render.cjs +78 -0
- package/pp-core/bin/pp-tools.cjs +92 -23
- package/pp-core/bin/shared/config-schema.manifest.json +1 -0
- package/pp-core/references/audience-moments.md +119 -0
- package/pp-core/references/audience.md +114 -0
- package/pp-core/references/discovery-build-consumption.md +5 -2
- package/pp-core/references/discovery-charter.md +2 -2
- package/pp-core/references/discovery-id-conventions.md +1 -1
- package/pp-core/references/discovery-processes.md +31 -7
- package/pp-core/references/discovery-session-output.md +1 -1
- package/pp-core/references/discovery-tests.md +186 -0
- package/pp-core/templates/project-charter.md +3 -4
- package/pp-core/workflows/add-phase.md +2 -0
- package/pp-core/workflows/add-tests.md +2 -0
- package/pp-core/workflows/add-todo.md +2 -0
- package/pp-core/workflows/ai-integration-phase.md +2 -0
- package/pp-core/workflows/audit-milestone.md +2 -0
- package/pp-core/workflows/autonomous.md +2 -0
- package/pp-core/workflows/check-todos.md +2 -0
- package/pp-core/workflows/code-review-fix.md +2 -0
- package/pp-core/workflows/code-review.md +2 -0
- package/pp-core/workflows/complete-milestone.md +10 -1
- package/pp-core/workflows/discovery-sprint/charter.md +3 -2
- package/pp-core/workflows/discovery-sprint/prefill.md +3 -0
- package/pp-core/workflows/discovery-sprint/prototype.md +14 -0
- package/pp-core/workflows/discovery-sprint/session-13-gates.md +2 -0
- package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +4 -1
- package/pp-core/workflows/discovery-sprint/sessions/03-to-be.md +135 -76
- package/pp-core/workflows/discovery-sprint/sessions/09-qa.md +94 -64
- package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +3 -2
- package/pp-core/workflows/discovery-sprint/vault-context.md +15 -0
- package/pp-core/workflows/discovery-sprint.md +16 -2
- package/pp-core/workflows/discovery-to-build/steps/handoff-gates.md +16 -9
- package/pp-core/workflows/discovery-to-build/templates/discovery-references.md +1 -1
- package/pp-core/workflows/discovery-to-build/templates/pp-handoff.md +9 -1
- package/pp-core/workflows/discovery-to-build.md +10 -4
- package/pp-core/workflows/discuss-phase/discovery.md +2 -2
- package/pp-core/workflows/discuss-phase-assumptions.md +2 -0
- package/pp-core/workflows/discuss-phase.md +1 -1
- package/pp-core/workflows/edit-phase.md +2 -0
- package/pp-core/workflows/eval-review.md +2 -0
- package/pp-core/workflows/execute-phase/steps/check-query-gate.md +25 -1
- package/pp-core/workflows/execute-phase/steps/vault-fetch.md +1 -0
- package/pp-core/workflows/execute-phase/steps/vault-load-gate.md +2 -0
- package/pp-core/workflows/execute-phase.md +1 -1
- package/pp-core/workflows/execute-plan.md +2 -0
- package/pp-core/workflows/extract-learnings.md +2 -0
- package/pp-core/workflows/help/modes/business.md +56 -0
- package/pp-core/workflows/help.md +12 -2
- package/pp-core/workflows/import.md +10 -1
- package/pp-core/workflows/ingest-docs.md +2 -0
- package/pp-core/workflows/init.md +17 -1
- package/pp-core/workflows/insert-phase.md +2 -0
- package/pp-core/workflows/list-workspaces.md +2 -0
- package/pp-core/workflows/manager.md +2 -0
- package/pp-core/workflows/map-codebase.md +2 -0
- package/pp-core/workflows/milestone-summary.md +2 -0
- package/pp-core/workflows/new-milestone.md +10 -1
- package/pp-core/workflows/new-project.md +12 -2
- package/pp-core/workflows/new-workspace.md +2 -0
- package/pp-core/workflows/next.md +8 -0
- package/pp-core/workflows/plan-milestone-gaps.md +10 -1
- package/pp-core/workflows/plan-phase.md +2 -1
- package/pp-core/workflows/plan-review-convergence.md +1 -1
- package/pp-core/workflows/progress.md +10 -0
- package/pp-core/workflows/quick.md +11 -1
- package/pp-core/workflows/remove-phase.md +2 -0
- package/pp-core/workflows/remove-workspace.md +2 -0
- package/pp-core/workflows/resume-project.md +25 -0
- package/pp-core/workflows/review.md +2 -0
- package/pp-core/workflows/scan.md +2 -0
- package/pp-core/workflows/secure-phase.md +2 -0
- package/pp-core/workflows/settings.md +41 -0
- package/pp-core/workflows/ship.md +2 -0
- package/pp-core/workflows/spec-phase.md +1 -1
- package/pp-core/workflows/ui-phase.md +2 -0
- package/pp-core/workflows/ui-review.md +2 -0
- package/pp-core/workflows/ultraplan-phase.md +2 -0
- package/pp-core/workflows/validate-phase.md +2 -0
- package/pp-core/workflows/vault-review.md +2 -0
- package/pp-core/workflows/verify-phase.md +2 -0
- package/pp-core/workflows/verify-work.md +2 -0
- package/scripts/affected-tests-lib.cjs +26 -12
- package/scripts/ci-release-only.cjs +158 -0
- package/scripts/live-runs/driver.cjs +326 -0
- package/scripts/live-runs/run.cjs +906 -0
- package/scripts/live-runs/scenarios.cjs +326 -0
- package/scripts/live-runs/scratch-project.cjs +132 -0
- package/scripts/live-runs/scrub.cjs +291 -0
- package/scripts/live-runs/turn-guard.cjs +107 -0
- package/scripts/live-runs/walk-setup.cjs +198 -0
- package/scripts/run-tests.cjs +151 -132
- package/scripts/test-env.cjs +64 -0
package/bin/install.js
CHANGED
|
@@ -349,6 +349,9 @@ const {
|
|
|
349
349
|
const {
|
|
350
350
|
updateCacheFileName,
|
|
351
351
|
} = require(path.join(__dirname, '..', 'pp-core', 'bin', 'lib', 'package-identity.cjs'));
|
|
352
|
+
// Phase 39 (AUD-02): the per-person audience leaf is the only reader/writer of the
|
|
353
|
+
// person file; the installer never builds that path itself (it honours GSD_HOME).
|
|
354
|
+
const audienceLib = require(path.join(__dirname, '..', 'pp-core', 'bin', 'lib', 'audience.cjs'));
|
|
352
355
|
|
|
353
356
|
// Parse args
|
|
354
357
|
const args = process.argv.slice(2);
|
|
@@ -375,6 +378,9 @@ const _profileArgRaw = (() => {
|
|
|
375
378
|
const _profileIsCore = _profileArgRaw === 'core';
|
|
376
379
|
const _requestedProfileName = (hasMinimal || _profileIsCore) ? 'core' : (_profileArgRaw || null);
|
|
377
380
|
|
|
381
|
+
// Phase 39 AUD-02: pure parse here; the main block validates it before any install branch runs.
|
|
382
|
+
const audienceArg = parseAudienceArg(args);
|
|
383
|
+
|
|
378
384
|
if (hasMinimal && _profileArgRaw) {
|
|
379
385
|
console.error(` ${yellow}Cannot specify both --minimal/--core-only and --profile${reset}`);
|
|
380
386
|
process.exit(1);
|
|
@@ -585,7 +591,7 @@ if (hasUninstall) {
|
|
|
585
591
|
|
|
586
592
|
// Show help if requested
|
|
587
593
|
if (hasHelp) {
|
|
588
|
-
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`);
|
|
594
|
+
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 ${cyan}--audience <who>${reset} Who you are: business, engineer or developer (default developer).\n Saved once for you, not per project; skips the first-install question.\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`);
|
|
589
595
|
process.exit(0);
|
|
590
596
|
}
|
|
591
597
|
|
|
@@ -9842,17 +9848,13 @@ function reportLocalPatches(configDir, runtime = 'claude') {
|
|
|
9842
9848
|
try { meta = JSON.parse(fs.readFileSync(metaPath, 'utf8')); } catch { return []; }
|
|
9843
9849
|
|
|
9844
9850
|
if (meta.files && meta.files.length > 0) {
|
|
9845
|
-
const reapplyCommand =
|
|
9846
|
-
? '
|
|
9847
|
-
: runtime === '
|
|
9848
|
-
? '
|
|
9849
|
-
: runtime === 'codex'
|
|
9850
|
-
? '$gsd-update --reapply'
|
|
9851
|
-
: runtime === 'cursor'
|
|
9852
|
-
? 'gsd-update --reapply (mention the skill name)'
|
|
9851
|
+
const reapplyCommand = runtime === 'codex'
|
|
9852
|
+
? '$pp-update --reapply'
|
|
9853
|
+
: runtime === 'cursor'
|
|
9854
|
+
? 'pp-update --reapply (mention the skill name)'
|
|
9853
9855
|
: runtime === 'kimi'
|
|
9854
|
-
? '/skill:
|
|
9855
|
-
: '/
|
|
9856
|
+
? '/skill:pp-update --reapply'
|
|
9857
|
+
: '/pp-update --reapply';
|
|
9856
9858
|
console.log('');
|
|
9857
9859
|
console.log(' ' + yellow + 'Local patches detected' + reset + ' (from v' + meta.from_version + '):');
|
|
9858
9860
|
for (const f of meta.files) {
|
|
@@ -11471,7 +11473,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
11471
11473
|
}
|
|
11472
11474
|
|
|
11473
11475
|
persistActiveProfileMarker();
|
|
11474
|
-
return { settingsPath: null, settings: null, statuslineCommand: null, updateBannerCommand: null, runtime, configDir: targetDir };
|
|
11476
|
+
return { settingsPath: null, settings: null, statuslineCommand: null, updateBannerCommand: null, runtime, configDir: targetDir, priorInstallExisted };
|
|
11475
11477
|
}
|
|
11476
11478
|
|
|
11477
11479
|
if (plan.installSurface === 'copilot-instructions') {
|
|
@@ -11501,7 +11503,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
11501
11503
|
writeCopilotHookConfig(targetDir);
|
|
11502
11504
|
console.log(` ${green}✓${reset} Configured Copilot lifecycle hook (sessionStart)`);
|
|
11503
11505
|
persistActiveProfileMarker();
|
|
11504
|
-
return { settingsPath: null, settings: null, statuslineCommand: null, updateBannerCommand: null, runtime, configDir: targetDir };
|
|
11506
|
+
return { settingsPath: null, settings: null, statuslineCommand: null, updateBannerCommand: null, runtime, configDir: targetDir, priorInstallExisted };
|
|
11505
11507
|
}
|
|
11506
11508
|
|
|
11507
11509
|
if (plan.installSurface === 'cursor-hooks-json') {
|
|
@@ -11516,13 +11518,13 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
11516
11518
|
// Re-run the manifest pass so the hook scripts + hooks.json are hash-tracked.
|
|
11517
11519
|
writeManifest(targetDir, runtime, { mode: _effectiveInstallMode });
|
|
11518
11520
|
persistActiveProfileMarker();
|
|
11519
|
-
return { settingsPath: null, settings: null, statuslineCommand: null, updateBannerCommand: null, runtime, configDir: targetDir };
|
|
11521
|
+
return { settingsPath: null, settings: null, statuslineCommand: null, updateBannerCommand: null, runtime, configDir: targetDir, priorInstallExisted };
|
|
11520
11522
|
}
|
|
11521
11523
|
|
|
11522
11524
|
if (plan.installSurface === 'profile-marker-only') {
|
|
11523
11525
|
// Windsurf/Trae/Kimi use artifact-only surfaces — no config.toml or settings.json hooks needed.
|
|
11524
11526
|
persistActiveProfileMarker();
|
|
11525
|
-
return { settingsPath: null, settings: null, statuslineCommand: null, updateBannerCommand: null, runtime, configDir: targetDir };
|
|
11527
|
+
return { settingsPath: null, settings: null, statuslineCommand: null, updateBannerCommand: null, runtime, configDir: targetDir, priorInstallExisted };
|
|
11526
11528
|
}
|
|
11527
11529
|
|
|
11528
11530
|
if (plan.installSurface === 'cline-rules') {
|
|
@@ -11534,7 +11536,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
11534
11536
|
// writeManifest() call, so a second pass is needed to hash-track them.
|
|
11535
11537
|
writeManifest(targetDir, runtime, { mode: _effectiveInstallMode });
|
|
11536
11538
|
persistActiveProfileMarker();
|
|
11537
|
-
return { settingsPath: null, settings: null, statuslineCommand: null, updateBannerCommand: null, runtime, configDir: targetDir };
|
|
11539
|
+
return { settingsPath: null, settings: null, statuslineCommand: null, updateBannerCommand: null, runtime, configDir: targetDir, priorInstallExisted };
|
|
11538
11540
|
}
|
|
11539
11541
|
|
|
11540
11542
|
// Configure statusline and hooks in settings.json (or settings.local.json for local Claude installs).
|
|
@@ -11836,6 +11838,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
11836
11838
|
updateBannerCommand,
|
|
11837
11839
|
runtime,
|
|
11838
11840
|
configDir: targetDir,
|
|
11841
|
+
priorInstallExisted,
|
|
11839
11842
|
rollbackInstallerMigrations,
|
|
11840
11843
|
};
|
|
11841
11844
|
}
|
|
@@ -12177,6 +12180,163 @@ function promptRuntime(callback) {
|
|
|
12177
12180
|
});
|
|
12178
12181
|
}
|
|
12179
12182
|
|
|
12183
|
+
// ─── Audience (Phase 39 AUD-02, D-07, D-09) ─────────────────────────────────
|
|
12184
|
+
|
|
12185
|
+
/**
|
|
12186
|
+
* Parse `--audience <value>` / `--audience=<value>` from argv. Pure.
|
|
12187
|
+
* @param {string[]} argv
|
|
12188
|
+
* @returns {{ value: string|null, error: string|null }}
|
|
12189
|
+
*/
|
|
12190
|
+
function parseAudienceArg(argv) {
|
|
12191
|
+
const list = Array.isArray(argv) ? argv : [];
|
|
12192
|
+
const allowed = 'business, engineer, developer';
|
|
12193
|
+
let raw = null;
|
|
12194
|
+
for (let i = 0; i < list.length; i++) {
|
|
12195
|
+
const arg = list[i];
|
|
12196
|
+
if (typeof arg !== 'string') continue;
|
|
12197
|
+
if (arg === '--audience') {
|
|
12198
|
+
const next = list[i + 1];
|
|
12199
|
+
if (typeof next !== 'string' || next.startsWith('--')) {
|
|
12200
|
+
return { value: null, error: `--audience needs a value: ${allowed}` };
|
|
12201
|
+
}
|
|
12202
|
+
raw = next;
|
|
12203
|
+
break;
|
|
12204
|
+
}
|
|
12205
|
+
if (arg.startsWith('--audience=')) {
|
|
12206
|
+
raw = arg.slice('--audience='.length);
|
|
12207
|
+
break;
|
|
12208
|
+
}
|
|
12209
|
+
}
|
|
12210
|
+
if (raw === null) return { value: null, error: null };
|
|
12211
|
+
const value = raw.trim().toLowerCase();
|
|
12212
|
+
if (!audienceLib.isAudience(value)) {
|
|
12213
|
+
return { value: null, error: `--audience "${raw.slice(0, 40)}" is not one of ${allowed}` };
|
|
12214
|
+
}
|
|
12215
|
+
return { value, error: null };
|
|
12216
|
+
}
|
|
12217
|
+
|
|
12218
|
+
/** The first-run question, identical to the one the workflows show (D-09). Pure. */
|
|
12219
|
+
function buildAudiencePromptText() {
|
|
12220
|
+
return audienceLib.FIRST_RUN_PROMPT;
|
|
12221
|
+
}
|
|
12222
|
+
|
|
12223
|
+
/**
|
|
12224
|
+
* Turn a typed answer into an audience. Enter keeps developer (D-07); an
|
|
12225
|
+
* unknown answer gives no audience (`recognized: false`), so a typo is never
|
|
12226
|
+
* saved as a permanent developer (WR-02). Pure.
|
|
12227
|
+
*/
|
|
12228
|
+
function resolveInstallAudienceAnswer(answer) {
|
|
12229
|
+
const parsed = audienceLib.parseAudienceInput(answer == null ? '' : answer);
|
|
12230
|
+
if (parsed === null) return { audience: null, recognized: false };
|
|
12231
|
+
return { audience: parsed, recognized: true };
|
|
12232
|
+
}
|
|
12233
|
+
|
|
12234
|
+
const AUDIENCE_RETRY_NOTICE = ' Not recognised — choose 1, 2 or 3.';
|
|
12235
|
+
const AUDIENCE_GIVE_UP_NOTICE = ' Not recognised — nothing saved; /pp-init and /pp-discovery will ask again.';
|
|
12236
|
+
const AUDIENCE_NO_ANSWER_NOTICE = ' No answer — nothing saved; /pp-init and /pp-discovery will ask again.';
|
|
12237
|
+
|
|
12238
|
+
/**
|
|
12239
|
+
* True only on an interactive first install with nothing set (D-07): all four
|
|
12240
|
+
* conditions must hold. Pure.
|
|
12241
|
+
*/
|
|
12242
|
+
function shouldAskAudience({ flagValue, isInteractive, hasValidPersonSetting, priorInstallExisted }) {
|
|
12243
|
+
return !flagValue && !!isInteractive && !hasValidPersonSetting && !priorInstallExisted;
|
|
12244
|
+
}
|
|
12245
|
+
|
|
12246
|
+
function saveInstallAudience(audience, env) {
|
|
12247
|
+
// CR-01 (D-10): no language key, so a stored language override is kept.
|
|
12248
|
+
const result = audienceLib.writePerson({ audience }, env);
|
|
12249
|
+
if (result && result.ok) {
|
|
12250
|
+
console.log(` ${green}✓${reset} Audience set to ${audience} ${dim}(saved in ${result.path})${reset}`);
|
|
12251
|
+
} else {
|
|
12252
|
+
const why = result && result.error ? `: ${result.error}` : '';
|
|
12253
|
+
console.log(` ${yellow}Could not save the audience${why}. Set it later: pp-tools audience set <business|engineer|developer>${reset}`);
|
|
12254
|
+
}
|
|
12255
|
+
}
|
|
12256
|
+
|
|
12257
|
+
/**
|
|
12258
|
+
* Provision the per-person audience during install. `--audience` always
|
|
12259
|
+
* writes and never asks; otherwise ask once, on a true first install only.
|
|
12260
|
+
* Never throws. The callback runs unless the user presses Ctrl-C at the
|
|
12261
|
+
* prompt, which exits 130 before finalize (WR-09); a re-run completes it.
|
|
12262
|
+
*/
|
|
12263
|
+
function handleAudience({ flagValue, isInteractive, priorInstallExisted, env = process.env, ask,
|
|
12264
|
+
createInterface = () => readline.createInterface({ input: process.stdin, output: process.stdout }) }, callback) {
|
|
12265
|
+
if (flagValue) {
|
|
12266
|
+
saveInstallAudience(flagValue, env);
|
|
12267
|
+
callback();
|
|
12268
|
+
return;
|
|
12269
|
+
}
|
|
12270
|
+
let hasValidPersonSetting = false;
|
|
12271
|
+
try {
|
|
12272
|
+
hasValidPersonSetting = audienceLib.readPerson(env).audience !== null;
|
|
12273
|
+
} catch {
|
|
12274
|
+
hasValidPersonSetting = false;
|
|
12275
|
+
}
|
|
12276
|
+
if (!shouldAskAudience({ flagValue, isInteractive, hasValidPersonSetting, priorInstallExisted })) {
|
|
12277
|
+
callback();
|
|
12278
|
+
return;
|
|
12279
|
+
}
|
|
12280
|
+
// An unknown answer is asked once more; still unknown, nothing is saved and
|
|
12281
|
+
// the first-run question is left for /pp-init and /pp-discovery (WR-02).
|
|
12282
|
+
// One readline interface serves both attempts: a second interface on the
|
|
12283
|
+
// same stdin would lose lines the first one already buffered.
|
|
12284
|
+
//
|
|
12285
|
+
// WR-03: on Ctrl-D or a closed stdin the question callback never fires;
|
|
12286
|
+
// without the close handler the event loop drains, the process exits 0 and
|
|
12287
|
+
// finalize (settings.json: hooks, permissions) never runs. Treat it as
|
|
12288
|
+
// "save nothing, continue". `done` is set before rl.close(), which emits
|
|
12289
|
+
// 'close' synchronously, so the callback runs exactly once.
|
|
12290
|
+
let attempts = 0;
|
|
12291
|
+
let done = false;
|
|
12292
|
+
let rl = null;
|
|
12293
|
+
const askOnce = () => {
|
|
12294
|
+
attempts += 1;
|
|
12295
|
+
if (typeof ask === 'function') {
|
|
12296
|
+
ask(buildAudiencePromptText(), finish);
|
|
12297
|
+
return;
|
|
12298
|
+
}
|
|
12299
|
+
if (!rl) {
|
|
12300
|
+
rl = createInterface();
|
|
12301
|
+
// WR-09: on a TTY, readline turns Ctrl-C into rl.close() unless a
|
|
12302
|
+
// 'SIGINT' listener exists, which the close handler below would read as
|
|
12303
|
+
// "no answer, continue". Ctrl-C cancels, as at the other prompts.
|
|
12304
|
+
rl.on('SIGINT', () => {
|
|
12305
|
+
done = true;
|
|
12306
|
+
console.log(`\n ${yellow}Installation cancelled${reset}\n`);
|
|
12307
|
+
rl.close();
|
|
12308
|
+
process.exit(130);
|
|
12309
|
+
});
|
|
12310
|
+
rl.on('close', () => {
|
|
12311
|
+
if (done) return;
|
|
12312
|
+
done = true;
|
|
12313
|
+
console.log('\n' + AUDIENCE_NO_ANSWER_NOTICE);
|
|
12314
|
+
callback();
|
|
12315
|
+
});
|
|
12316
|
+
}
|
|
12317
|
+
rl.question(' > ', finish);
|
|
12318
|
+
};
|
|
12319
|
+
const finish = (answer) => {
|
|
12320
|
+
const { audience, recognized } = resolveInstallAudienceAnswer(answer);
|
|
12321
|
+
if (!recognized && attempts < 2) {
|
|
12322
|
+
console.log(AUDIENCE_RETRY_NOTICE);
|
|
12323
|
+
askOnce();
|
|
12324
|
+
return;
|
|
12325
|
+
}
|
|
12326
|
+
done = true;
|
|
12327
|
+
if (rl) rl.close();
|
|
12328
|
+
if (!recognized) {
|
|
12329
|
+
console.log(AUDIENCE_GIVE_UP_NOTICE);
|
|
12330
|
+
callback();
|
|
12331
|
+
return;
|
|
12332
|
+
}
|
|
12333
|
+
saveInstallAudience(audience, env);
|
|
12334
|
+
callback();
|
|
12335
|
+
};
|
|
12336
|
+
console.log(buildAudiencePromptText());
|
|
12337
|
+
askOnce();
|
|
12338
|
+
}
|
|
12339
|
+
|
|
12180
12340
|
// ─── Update banner (#2795) ──────────────────────────────────────────────────
|
|
12181
12341
|
|
|
12182
12342
|
/**
|
|
@@ -12575,6 +12735,8 @@ function installAllRuntimes(runtimes, isGlobal, isInteractive) {
|
|
|
12575
12735
|
throw error;
|
|
12576
12736
|
}
|
|
12577
12737
|
|
|
12738
|
+
const priorInstallExisted = results.some((r) => r && r.priorInstallExisted === true);
|
|
12739
|
+
|
|
12578
12740
|
const statuslineRuntimes = ['claude', 'gemini'];
|
|
12579
12741
|
const primaryStatuslineResult = results.find(r => statuslineRuntimes.includes(r.runtime));
|
|
12580
12742
|
|
|
@@ -12640,18 +12802,24 @@ function installAllRuntimes(runtimes, isGlobal, isInteractive) {
|
|
|
12640
12802
|
});
|
|
12641
12803
|
};
|
|
12642
12804
|
|
|
12643
|
-
|
|
12644
|
-
|
|
12645
|
-
|
|
12646
|
-
|
|
12647
|
-
|
|
12648
|
-
|
|
12649
|
-
|
|
12650
|
-
|
|
12651
|
-
|
|
12652
|
-
|
|
12653
|
-
|
|
12654
|
-
|
|
12805
|
+
const runRemainingPrompts = () => {
|
|
12806
|
+
if (primaryStatuslineResult) {
|
|
12807
|
+
handleStatusline(primaryStatuslineResult.settings, isInteractive, continueAfterStatusline);
|
|
12808
|
+
} else if (canInstallBanner) {
|
|
12809
|
+
// No statusline-capable runtime, but at least one runtime can host the
|
|
12810
|
+
// banner — still offer it.
|
|
12811
|
+
handleUpdateBanner(isInteractive, (shouldInstallBanner) => {
|
|
12812
|
+
finalize(false, shouldInstallBanner);
|
|
12813
|
+
});
|
|
12814
|
+
} else {
|
|
12815
|
+
// Nothing to prompt about — no statusline, no banner-capable runtime.
|
|
12816
|
+
finalize(false, false);
|
|
12817
|
+
}
|
|
12818
|
+
};
|
|
12819
|
+
|
|
12820
|
+
// Phase 39 AUD-02 (D-07): provision the per-person audience before the
|
|
12821
|
+
// statusline/banner prompts. Non-interactive paths call back immediately.
|
|
12822
|
+
handleAudience({ flagValue: audienceArg.value, isInteractive, priorInstallExisted }, runRemainingPrompts);
|
|
12655
12823
|
}
|
|
12656
12824
|
|
|
12657
12825
|
// Always export so runtime-artifact-layout.cjs's lazy loader can access
|
|
@@ -12793,6 +12961,11 @@ module.exports = {
|
|
|
12793
12961
|
buildRuntimePromptText,
|
|
12794
12962
|
buildUpdateBannerPromptText,
|
|
12795
12963
|
parseUpdateBannerInput,
|
|
12964
|
+
parseAudienceArg,
|
|
12965
|
+
buildAudiencePromptText,
|
|
12966
|
+
resolveInstallAudienceAnswer,
|
|
12967
|
+
shouldAskAudience,
|
|
12968
|
+
handleAudience,
|
|
12796
12969
|
buildUpdateBannerHookEntry,
|
|
12797
12970
|
buildHookCommand,
|
|
12798
12971
|
normalizeNodePath,
|
|
@@ -12824,6 +12997,10 @@ module.exports = {
|
|
|
12824
12997
|
|
|
12825
12998
|
// Main logic — only run when not loaded as a module for testing
|
|
12826
12999
|
if (require.main === module && !process.env.GSD_TEST_MODE) {
|
|
13000
|
+
if (audienceArg.error) {
|
|
13001
|
+
console.error(' ' + yellow + audienceArg.error + reset);
|
|
13002
|
+
process.exit(1);
|
|
13003
|
+
}
|
|
12827
13004
|
if (hasDryRun) {
|
|
12828
13005
|
// --dry-run: preview legacy cleanup and exit without installing.
|
|
12829
13006
|
if (hasUninstall) {
|
package/commands/pp-help.md
CHANGED
|
@@ -4,11 +4,12 @@ description: Show available pp-core commands and usage guide
|
|
|
4
4
|
argument-hint: "[--brief | --full | <topic> | --brief <topic>]"
|
|
5
5
|
allowed-tools:
|
|
6
6
|
- Read
|
|
7
|
+
- Bash
|
|
7
8
|
---
|
|
8
9
|
<objective>
|
|
9
10
|
Display pp-core help at the tier the user asked for: brief (one-line refresher), default (one-page tour), full (complete reference), a single topic section, or a compact scoped lookup of one topic (`--brief <topic>`: signature + one-line summary).
|
|
10
11
|
|
|
11
|
-
Output ONLY the reference content of the chosen tier. Do NOT add:
|
|
12
|
+
Output ONLY the reference content of the chosen tier. For a business user or a business engineer, help.md prints one audience header line first; that line is part of the output. Do NOT add:
|
|
12
13
|
- Project-specific analysis
|
|
13
14
|
- Git status or file context
|
|
14
15
|
- Next-step suggestions
|
package/gemini-extension.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pp-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.23.1",
|
|
4
4
|
"description": "PP Core — a meta-prompting, context engineering, and spec-driven development system for AI coding agents. Loads pp-core's operating context into every Gemini CLI session.",
|
|
5
5
|
"contextFileName": "GEMINI.md"
|
|
6
6
|
}
|
|
@@ -18,7 +18,7 @@ const { isSemverNewer } = require('../pp-core/bin/lib/semver-compare.cjs');
|
|
|
18
18
|
// The previous `require('../package.json').name` (#378) resolved to undefined
|
|
19
19
|
// in the installed tree (only a {"type":"commonjs"} marker ships), so the
|
|
20
20
|
// background check never reported updates.
|
|
21
|
-
const { checkLatestVersion } = require('../
|
|
21
|
+
const { checkLatestVersion } = require('../pp-core/bin/check-latest-version.cjs');
|
|
22
22
|
const { PACKAGE_NAME } = require('../pp-core/bin/lib/package-identity.cjs');
|
|
23
23
|
// Authoritative list of managed hooks — shared with tests to retire source-grep
|
|
24
24
|
// assertions (pending-migration-to-typed-ir [#455]).
|
|
@@ -133,7 +133,7 @@ process.stdin.on('end', () => {
|
|
|
133
133
|
const isGsdActive = fs.existsSync(path.join(cwd, '.planning', 'STATE.md'));
|
|
134
134
|
|
|
135
135
|
// On CRITICAL with active GSD project, auto-record session state as a
|
|
136
|
-
// breadcrumb for /
|
|
136
|
+
// breadcrumb for /pp-resume-work (#1974). Fire-and-forget subprocess —
|
|
137
137
|
// doesn't block the hook or the agent. Fires ONCE per CRITICAL session,
|
|
138
138
|
// guarded by warnData.criticalRecorded to prevent repeated overwrites
|
|
139
139
|
// of the "crash moment" record on every debounce cycle.
|
|
@@ -165,8 +165,8 @@ process.stdin.on('end', () => {
|
|
|
165
165
|
message = isGsdActive
|
|
166
166
|
? `CONTEXT CRITICAL: Usage at ${usedPct}%. Remaining: ${remaining}%. ` +
|
|
167
167
|
'Context is nearly exhausted. Do NOT start new complex work or write handoff files — ' +
|
|
168
|
-
'
|
|
169
|
-
'/
|
|
168
|
+
'pp-core state is already tracked in STATE.md. Inform the user so they can run ' +
|
|
169
|
+
'/pp-pause-work at the next natural stopping point.'
|
|
170
170
|
: `CONTEXT CRITICAL: Usage at ${usedPct}%. Remaining: ${remaining}%. ` +
|
|
171
171
|
'Context is nearly exhausted. Inform the user that context is low and ask how they ' +
|
|
172
172
|
'want to proceed. Do NOT autonomously save state or write handoff files unless the user asks.';
|
|
@@ -9,6 +9,59 @@ const os = require('os');
|
|
|
9
9
|
const { isSemverNewer } = require('../pp-core/bin/lib/semver-compare.cjs');
|
|
10
10
|
const { PACKAGE_NAME, updateCacheFileName } = require('../pp-core/bin/lib/package-identity.cjs');
|
|
11
11
|
|
|
12
|
+
// --- Audience wording (MSG-06, D-18, D-20) ----------------------------------
|
|
13
|
+
// The light catalog entry. When it cannot load (missing, broken, or its slice
|
|
14
|
+
// missing) every text below stays today's developer string.
|
|
15
|
+
let hookMessages = null;
|
|
16
|
+
try {
|
|
17
|
+
hookMessages = require('../pp-core/bin/lib/hook-messages.cjs');
|
|
18
|
+
} catch (e) {
|
|
19
|
+
hookMessages = null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The audience for this process; developer when the catalog is unavailable or throws. */
|
|
23
|
+
function hookAudience() {
|
|
24
|
+
if (!hookMessages) return 'developer';
|
|
25
|
+
try {
|
|
26
|
+
return hookMessages.audienceOf(process.env);
|
|
27
|
+
} catch (e) {
|
|
28
|
+
return 'developer';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The catalog text for `code`, or `literal` (today's developer string) on any failure. */
|
|
33
|
+
function hookText(code, params, audience, literal) {
|
|
34
|
+
if (!hookMessages || audience === 'developer') return literal;
|
|
35
|
+
try {
|
|
36
|
+
const out = hookMessages.msg(code, params, audience, literal);
|
|
37
|
+
return typeof out === 'string' && out !== '' ? out : literal;
|
|
38
|
+
} catch (e) {
|
|
39
|
+
return literal;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The display word for a canonical value, or the value itself on any failure. */
|
|
44
|
+
function hookLabel(domain, value, audience) {
|
|
45
|
+
if (!hookMessages || audience === 'developer') return value;
|
|
46
|
+
try {
|
|
47
|
+
const out = hookMessages.label(domain, value, audience);
|
|
48
|
+
return typeof out === 'string' && out !== '' ? out : value;
|
|
49
|
+
} catch (e) {
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The display text for a STATE.md status, or the status itself on any failure. */
|
|
55
|
+
function hookStatusWord(status, audience) {
|
|
56
|
+
if (!hookMessages || audience === 'developer') return status;
|
|
57
|
+
try {
|
|
58
|
+
const out = hookMessages.statusWord(status, audience);
|
|
59
|
+
return typeof out === 'string' && out !== '' ? out : status;
|
|
60
|
+
} catch (e) {
|
|
61
|
+
return status;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
12
65
|
// --- Config + last-command readers ------------------------------------------
|
|
13
66
|
|
|
14
67
|
/**
|
|
@@ -241,7 +294,8 @@ function renderProgressBar(percent) {
|
|
|
241
294
|
* Progress bar is opt-in: appended to the milestone segment only when
|
|
242
295
|
* progress.percent is present in frontmatter; absent → empty string.
|
|
243
296
|
*/
|
|
244
|
-
function formatGsdState(s) {
|
|
297
|
+
function formatGsdState(s, opts) {
|
|
298
|
+
const audience = (opts && opts.audience) || 'developer';
|
|
245
299
|
const parts = [];
|
|
246
300
|
|
|
247
301
|
// Milestone segment: version + name + (opt-in) progress bar
|
|
@@ -261,24 +315,41 @@ function formatGsdState(s) {
|
|
|
261
315
|
// Scene 1: an orchestrator is mid-flight on this phase.
|
|
262
316
|
// stage = whichever lifecycle status was written by the orchestrator
|
|
263
317
|
// (discussing / planning / executing / verifying)
|
|
264
|
-
const stage = s.status
|
|
265
|
-
parts.push(stage
|
|
318
|
+
const stage = s.status ? hookStatusWord(s.status, audience) : '';
|
|
319
|
+
parts.push(stage
|
|
320
|
+
? hookText('hook.statusline.phase_stage', { number: s.activePhase, stage }, audience, `Phase ${s.activePhase} ${s.status}`)
|
|
321
|
+
: hookText('hook.statusline.phase', { number: s.activePhase }, audience, `Phase ${s.activePhase}`));
|
|
266
322
|
} else if (s.nextAction && phasesStr) {
|
|
267
323
|
// Scene 2: idle + a recommended next command is visible to the user.
|
|
268
324
|
// Surfaces "what to run next" without the user opening STATE.md.
|
|
269
|
-
parts.push(
|
|
325
|
+
parts.push(hookText(
|
|
326
|
+
'hook.statusline.next',
|
|
327
|
+
{ action: hookLabel('state.next_action', s.nextAction, audience), numbers: phasesStr },
|
|
328
|
+
audience,
|
|
329
|
+
`next ${s.nextAction} ${phasesStr}`
|
|
330
|
+
));
|
|
270
331
|
} else if (Number(s.percent) === 100 || (s.completedPhases && s.totalPhases && s.completedPhases === s.totalPhases)) {
|
|
271
332
|
// Scene 3: milestone complete (every phase done).
|
|
272
|
-
parts.push('milestone complete');
|
|
333
|
+
parts.push(hookText('hook.statusline.milestone_complete', {}, audience, 'milestone complete'));
|
|
273
334
|
} else {
|
|
274
335
|
// Backward-compatible default — preserved EXACTLY for STATE.md files that
|
|
275
336
|
// don't carry the new lifecycle fields. Identical output to v1.38.x and
|
|
276
337
|
// earlier so no existing project's status-line changes shape.
|
|
277
|
-
if (s.status) parts.push(s.status);
|
|
338
|
+
if (s.status) parts.push(hookStatusWord(s.status, audience));
|
|
278
339
|
if (s.phaseNum && s.phaseTotal) {
|
|
279
340
|
const phase = s.phaseName
|
|
280
|
-
?
|
|
281
|
-
|
|
341
|
+
? hookText(
|
|
342
|
+
'hook.statusline.phase_named',
|
|
343
|
+
{ name: s.phaseName, num: s.phaseNum, total: s.phaseTotal },
|
|
344
|
+
audience,
|
|
345
|
+
`${s.phaseName} (${s.phaseNum}/${s.phaseTotal})`
|
|
346
|
+
)
|
|
347
|
+
: hookText(
|
|
348
|
+
'hook.statusline.phase_count',
|
|
349
|
+
{ num: s.phaseNum, total: s.phaseTotal },
|
|
350
|
+
audience,
|
|
351
|
+
`ph ${s.phaseNum}/${s.phaseTotal}`
|
|
352
|
+
);
|
|
282
353
|
parts.push(phase);
|
|
283
354
|
}
|
|
284
355
|
}
|
|
@@ -393,7 +464,9 @@ function runStatusline() {
|
|
|
393
464
|
}
|
|
394
465
|
|
|
395
466
|
// GSD state (milestone · status · phase) — shown when no todo task
|
|
396
|
-
|
|
467
|
+
// The audience is resolved once per run, never inside the formatter (Pitfall 7).
|
|
468
|
+
const audience = hookAudience();
|
|
469
|
+
const gsdStateStr = task ? '' : formatGsdState(readGsdState(dir) || {}, { audience });
|
|
397
470
|
|
|
398
471
|
// GSD update available?
|
|
399
472
|
// Read only the per-package shared cache file (#607). The legacy
|
|
@@ -406,12 +479,12 @@ function runStatusline() {
|
|
|
406
479
|
const cache = JSON.parse(fs.readFileSync(cacheFile, 'utf8'));
|
|
407
480
|
const { showUpdate, staleWarning } = evaluateUpdateCache(cache);
|
|
408
481
|
if (showUpdate) {
|
|
409
|
-
gsdUpdate = '\x1b[33m⬆ /
|
|
482
|
+
gsdUpdate = '\x1b[33m' + hookText('hook.statusline.update', {}, audience, '⬆ /pp-update') + '\x1b[0m │ ';
|
|
410
483
|
}
|
|
411
484
|
if (staleWarning === 'dev') {
|
|
412
|
-
gsdUpdate += '\x1b[33m⚠ dev install — re-run installer to sync hooks\x1b[0m │ ';
|
|
485
|
+
gsdUpdate += '\x1b[33m' + hookText('hook.statusline.dev_install', {}, audience, '⚠ dev install — re-run installer to sync hooks') + '\x1b[0m │ ';
|
|
413
486
|
} else if (staleWarning === 'stale') {
|
|
414
|
-
gsdUpdate += '\x1b[31m⚠ stale hooks — run /
|
|
487
|
+
gsdUpdate += '\x1b[31m' + hookText('hook.statusline.stale', {}, audience, '⚠ stale hooks — run /pp-update') + '\x1b[0m │ ';
|
|
415
488
|
}
|
|
416
489
|
} catch (e) {}
|
|
417
490
|
}
|
|
@@ -556,7 +629,8 @@ module.exports = {
|
|
|
556
629
|
* Render the statusline from an already-parsed hook input object. Exported for
|
|
557
630
|
* testing without feeding stdin. Returns the rendered string.
|
|
558
631
|
*/
|
|
559
|
-
function renderStatusline(data) {
|
|
632
|
+
function renderStatusline(data, opts) {
|
|
633
|
+
const audience = (opts && opts.audience) || hookAudience();
|
|
560
634
|
const model = data.model?.display_name || 'Claude';
|
|
561
635
|
const dir = data.workspace?.current_dir || process.cwd();
|
|
562
636
|
const dirname = path.basename(dir);
|
|
@@ -575,7 +649,7 @@ function renderStatusline(data) {
|
|
|
575
649
|
if (cfgPos != null) position = cfgPos;
|
|
576
650
|
} catch (e) { /* swallow */ }
|
|
577
651
|
|
|
578
|
-
const gsdStateStr = formatGsdState(readGsdState(dir) || {});
|
|
652
|
+
const gsdStateStr = formatGsdState(readGsdState(dir) || {}, { audience });
|
|
579
653
|
const middle = gsdStateStr ? `\x1b[2m${gsdStateStr}\x1b[0m` : null;
|
|
580
654
|
return formatDevBadge(readDevBuild()) + composeStatusline({ model, ctx: '', middle, dirname, lastCmdSuffix, position });
|
|
581
655
|
}
|