@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.
Files changed (136) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/bin/install.js +205 -28
  3. package/commands/pp-help.md +2 -1
  4. package/gemini-extension.json +1 -1
  5. package/hooks/dist/pp-check-update-worker.js +1 -1
  6. package/hooks/dist/pp-context-monitor.js +3 -3
  7. package/hooks/dist/pp-statusline.js +88 -14
  8. package/hooks/dist/pp-update-banner.js +44 -4
  9. package/hooks/dist/pp-workflow-guard.js +3 -3
  10. package/hooks/pp-check-update-worker.js +1 -1
  11. package/hooks/pp-context-monitor.js +3 -3
  12. package/hooks/pp-statusline.js +88 -14
  13. package/hooks/pp-update-banner.js +44 -4
  14. package/hooks/pp-workflow-guard.js +3 -3
  15. package/package.json +2 -2
  16. package/pp-core/bin/lib/audience-command.cjs +271 -0
  17. package/pp-core/bin/lib/audience.cjs +293 -0
  18. package/pp-core/bin/lib/commands.cjs +26 -17
  19. package/pp-core/bin/lib/discovery-charter.cjs +13 -1
  20. package/pp-core/bin/lib/discovery-flow-page.cjs +191 -0
  21. package/pp-core/bin/lib/discovery-for-phase.cjs +33 -5
  22. package/pp-core/bin/lib/discovery-input-check.cjs +11 -11
  23. package/pp-core/bin/lib/discovery-processes.cjs +97 -4
  24. package/pp-core/bin/lib/discovery-session-check.cjs +7 -7
  25. package/pp-core/bin/lib/discovery-status.cjs +419 -0
  26. package/pp-core/bin/lib/discovery-stories.cjs +36 -5
  27. package/pp-core/bin/lib/discovery-tests.cjs +398 -0
  28. package/pp-core/bin/lib/explain-command.cjs +48 -0
  29. package/pp-core/bin/lib/gate-card.cjs +97 -0
  30. package/pp-core/bin/lib/hook-messages.cjs +132 -0
  31. package/pp-core/bin/lib/init-contract.cjs +116 -76
  32. package/pp-core/bin/lib/init-interactive.cjs +110 -49
  33. package/pp-core/bin/lib/init.cjs +44 -3
  34. package/pp-core/bin/lib/io.cjs +38 -0
  35. package/pp-core/bin/lib/message-labels.cjs +75 -0
  36. package/pp-core/bin/lib/messages/core.cjs +20 -0
  37. package/pp-core/bin/lib/messages/discovery.cjs +267 -0
  38. package/pp-core/bin/lib/messages/gate.cjs +257 -0
  39. package/pp-core/bin/lib/messages/hook.cjs +114 -0
  40. package/pp-core/bin/lib/messages/init.cjs +513 -0
  41. package/pp-core/bin/lib/messages/progress.cjs +110 -0
  42. package/pp-core/bin/lib/messages.cjs +208 -0
  43. package/pp-core/bin/lib/vault-command-router.cjs +16 -3
  44. package/pp-core/bin/lib/violation-render.cjs +78 -0
  45. package/pp-core/bin/pp-tools.cjs +92 -23
  46. package/pp-core/bin/shared/config-schema.manifest.json +1 -0
  47. package/pp-core/references/audience-moments.md +119 -0
  48. package/pp-core/references/audience.md +114 -0
  49. package/pp-core/references/discovery-build-consumption.md +5 -2
  50. package/pp-core/references/discovery-charter.md +2 -2
  51. package/pp-core/references/discovery-id-conventions.md +1 -1
  52. package/pp-core/references/discovery-processes.md +31 -7
  53. package/pp-core/references/discovery-session-output.md +1 -1
  54. package/pp-core/references/discovery-tests.md +186 -0
  55. package/pp-core/templates/project-charter.md +3 -4
  56. package/pp-core/workflows/add-phase.md +2 -0
  57. package/pp-core/workflows/add-tests.md +2 -0
  58. package/pp-core/workflows/add-todo.md +2 -0
  59. package/pp-core/workflows/ai-integration-phase.md +2 -0
  60. package/pp-core/workflows/audit-milestone.md +2 -0
  61. package/pp-core/workflows/autonomous.md +2 -0
  62. package/pp-core/workflows/check-todos.md +2 -0
  63. package/pp-core/workflows/code-review-fix.md +2 -0
  64. package/pp-core/workflows/code-review.md +2 -0
  65. package/pp-core/workflows/complete-milestone.md +10 -1
  66. package/pp-core/workflows/discovery-sprint/charter.md +3 -2
  67. package/pp-core/workflows/discovery-sprint/prefill.md +3 -0
  68. package/pp-core/workflows/discovery-sprint/prototype.md +14 -0
  69. package/pp-core/workflows/discovery-sprint/session-13-gates.md +2 -0
  70. package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +4 -1
  71. package/pp-core/workflows/discovery-sprint/sessions/03-to-be.md +135 -76
  72. package/pp-core/workflows/discovery-sprint/sessions/09-qa.md +94 -64
  73. package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +3 -2
  74. package/pp-core/workflows/discovery-sprint/vault-context.md +15 -0
  75. package/pp-core/workflows/discovery-sprint.md +16 -2
  76. package/pp-core/workflows/discovery-to-build/steps/handoff-gates.md +16 -9
  77. package/pp-core/workflows/discovery-to-build/templates/discovery-references.md +1 -1
  78. package/pp-core/workflows/discovery-to-build/templates/pp-handoff.md +9 -1
  79. package/pp-core/workflows/discovery-to-build.md +10 -4
  80. package/pp-core/workflows/discuss-phase/discovery.md +2 -2
  81. package/pp-core/workflows/discuss-phase-assumptions.md +2 -0
  82. package/pp-core/workflows/discuss-phase.md +1 -1
  83. package/pp-core/workflows/edit-phase.md +2 -0
  84. package/pp-core/workflows/eval-review.md +2 -0
  85. package/pp-core/workflows/execute-phase/steps/check-query-gate.md +25 -1
  86. package/pp-core/workflows/execute-phase/steps/vault-fetch.md +1 -0
  87. package/pp-core/workflows/execute-phase/steps/vault-load-gate.md +2 -0
  88. package/pp-core/workflows/execute-phase.md +1 -1
  89. package/pp-core/workflows/execute-plan.md +2 -0
  90. package/pp-core/workflows/extract-learnings.md +2 -0
  91. package/pp-core/workflows/help/modes/business.md +56 -0
  92. package/pp-core/workflows/help.md +12 -2
  93. package/pp-core/workflows/import.md +10 -1
  94. package/pp-core/workflows/ingest-docs.md +2 -0
  95. package/pp-core/workflows/init.md +17 -1
  96. package/pp-core/workflows/insert-phase.md +2 -0
  97. package/pp-core/workflows/list-workspaces.md +2 -0
  98. package/pp-core/workflows/manager.md +2 -0
  99. package/pp-core/workflows/map-codebase.md +2 -0
  100. package/pp-core/workflows/milestone-summary.md +2 -0
  101. package/pp-core/workflows/new-milestone.md +10 -1
  102. package/pp-core/workflows/new-project.md +12 -2
  103. package/pp-core/workflows/new-workspace.md +2 -0
  104. package/pp-core/workflows/next.md +8 -0
  105. package/pp-core/workflows/plan-milestone-gaps.md +10 -1
  106. package/pp-core/workflows/plan-phase.md +2 -1
  107. package/pp-core/workflows/plan-review-convergence.md +1 -1
  108. package/pp-core/workflows/progress.md +10 -0
  109. package/pp-core/workflows/quick.md +11 -1
  110. package/pp-core/workflows/remove-phase.md +2 -0
  111. package/pp-core/workflows/remove-workspace.md +2 -0
  112. package/pp-core/workflows/resume-project.md +25 -0
  113. package/pp-core/workflows/review.md +2 -0
  114. package/pp-core/workflows/scan.md +2 -0
  115. package/pp-core/workflows/secure-phase.md +2 -0
  116. package/pp-core/workflows/settings.md +41 -0
  117. package/pp-core/workflows/ship.md +2 -0
  118. package/pp-core/workflows/spec-phase.md +1 -1
  119. package/pp-core/workflows/ui-phase.md +2 -0
  120. package/pp-core/workflows/ui-review.md +2 -0
  121. package/pp-core/workflows/ultraplan-phase.md +2 -0
  122. package/pp-core/workflows/validate-phase.md +2 -0
  123. package/pp-core/workflows/vault-review.md +2 -0
  124. package/pp-core/workflows/verify-phase.md +2 -0
  125. package/pp-core/workflows/verify-work.md +2 -0
  126. package/scripts/affected-tests-lib.cjs +26 -12
  127. package/scripts/ci-release-only.cjs +158 -0
  128. package/scripts/live-runs/driver.cjs +326 -0
  129. package/scripts/live-runs/run.cjs +906 -0
  130. package/scripts/live-runs/scenarios.cjs +326 -0
  131. package/scripts/live-runs/scratch-project.cjs +132 -0
  132. package/scripts/live-runs/scrub.cjs +291 -0
  133. package/scripts/live-runs/turn-guard.cjs +107 -0
  134. package/scripts/live-runs/walk-setup.cjs +198 -0
  135. package/scripts/run-tests.cjs +151 -132
  136. package/scripts/test-env.cjs +64 -0
@@ -17,6 +17,36 @@ const path = require('path');
17
17
  const os = require('os');
18
18
  const { PACKAGE_NAME, updateCacheFileName } = require('../pp-core/bin/lib/package-identity.cjs');
19
19
 
20
+ // Audience wording (MSG-06, D-18, D-20): the light catalog entry. When it cannot
21
+ // load, the banner prints today's developer text.
22
+ let hookMessages = null;
23
+ try {
24
+ hookMessages = require('../pp-core/bin/lib/hook-messages.cjs');
25
+ } catch (e) {
26
+ hookMessages = null;
27
+ }
28
+
29
+ /** The audience for this process; developer when the catalog is unavailable or throws. */
30
+ function hookAudience() {
31
+ if (!hookMessages) return 'developer';
32
+ try {
33
+ return hookMessages.audienceOf(process.env);
34
+ } catch (e) {
35
+ return 'developer';
36
+ }
37
+ }
38
+
39
+ /** The catalog message for `code`, or `literal` (today's developer string) on any failure. */
40
+ function hookText(code, params, audience, literal) {
41
+ if (!hookMessages || audience === 'developer') return literal;
42
+ try {
43
+ const out = hookMessages.msg(code, params, audience, literal);
44
+ return typeof out === 'string' && out !== '' ? out : literal;
45
+ } catch (e) {
46
+ return literal;
47
+ }
48
+ }
49
+
20
50
  // Suppress repeat parse-error banners for 24 hours so a genuinely broken
21
51
  // cache file doesn't nag the user every session.
22
52
  const RATE_LIMIT_SECONDS = 24 * 60 * 60;
@@ -29,13 +59,18 @@ const RATE_LIMIT_SECONDS = 24 * 60 * 60;
29
59
  * @param {object|null} state.cache Parsed cache, or null if missing/unreadable.
30
60
  * @param {boolean} state.parseError True iff cache file existed but JSON.parse failed.
31
61
  * @param {boolean} state.suppressFailureWarning True when a recent failure warning already fired.
62
+ * @param {object} [opts]
63
+ * @param {string} [opts.audience='developer'] Whose wording to use; the developer text is today's string.
32
64
  * @returns {{systemMessage: string}|null} JSON envelope, or null for silent exit.
33
65
  */
34
- function buildBannerOutput(state) {
66
+ function buildBannerOutput(state, opts) {
67
+ const audience = (opts && opts.audience) || 'developer';
35
68
  const { cache, parseError, suppressFailureWarning } = state || {};
36
69
  if (parseError) {
37
70
  if (suppressFailureWarning) return null;
38
- return { systemMessage: 'GSD update check failed.' };
71
+ return {
72
+ systemMessage: hookText('hook.banner.check_failed', {}, audience, 'pp-core update check failed.'),
73
+ };
39
74
  }
40
75
  if (!cache) return null;
41
76
  // Lineage guard: package_name must be present and match this package.
@@ -45,7 +80,12 @@ function buildBannerOutput(state) {
45
80
  const installed = cache.installed || 'unknown';
46
81
  const latest = cache.latest || 'unknown';
47
82
  return {
48
- systemMessage: `GSD update available: ${installed} → ${latest}. Run /gsd:update.`,
83
+ systemMessage: hookText(
84
+ 'hook.banner.update_available',
85
+ { installed, latest },
86
+ audience,
87
+ `pp-core update available: ${installed} → ${latest}. Run /pp-update.`
88
+ ),
49
89
  };
50
90
  }
51
91
 
@@ -108,7 +148,7 @@ function main() {
108
148
  const suppressFailureWarning = parseError
109
149
  ? shouldSuppressFailureWarning(sentinelFile, now)
110
150
  : false;
111
- const output = buildBannerOutput({ cache, parseError, suppressFailureWarning });
151
+ const output = buildBannerOutput({ cache, parseError, suppressFailureWarning }, { audience: hookAudience() });
112
152
 
113
153
  if (parseError && !suppressFailureWarning) {
114
154
  // Ensure cache dir exists before writing the sentinel — first-run case
@@ -5,7 +5,7 @@
5
5
  // (no active /gsd- skill or Task subagent) and injects an advisory warning.
6
6
  //
7
7
  // This is a SOFT guard — it advises, not blocks. The edit still proceeds.
8
- // The warning nudges Claude to use /gsd:quick or /gsd:fast instead of
8
+ // The warning nudges Claude to use /pp-quick or /pp-fast instead of
9
9
  // making direct edits that bypass state tracking.
10
10
  //
11
11
  // Enable via config: hooks.workflow_guard: true (default: false)
@@ -151,9 +151,9 @@ process.stdin.on('end', () => {
151
151
  const output = {
152
152
  hookSpecificOutput: {
153
153
  hookEventName: "PreToolUse",
154
- additionalContext: `⚠️ WORKFLOW ADVISORY: You're editing ${path.basename(filePath)} directly without a GSD command. ` +
154
+ additionalContext: `⚠️ WORKFLOW ADVISORY: You're editing ${path.basename(filePath)} directly without a pp-core command. ` +
155
155
  'This edit will not be tracked in STATE.md or produce a SUMMARY.md. ' +
156
- 'Consider using /gsd:fast for trivial fixes or /gsd:quick for larger changes ' +
156
+ 'Consider using /pp-fast for trivial fixes or /pp-quick for larger changes ' +
157
157
  'to maintain project state tracking. ' +
158
158
  'If this is intentional (e.g., user explicitly asked for a direct edit), proceed normally.'
159
159
  }
@@ -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('../gsd-core/bin/check-latest-version.cjs');
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 /gsd:resume-work (#1974). Fire-and-forget subprocess —
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
- 'GSD state is already tracked in STATE.md. Inform the user so they can run ' +
169
- '/gsd:pause-work at the next natural stopping point.'
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 ? `Phase ${s.activePhase} ${stage}` : `Phase ${s.activePhase}`);
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(`next ${s.nextAction} ${phasesStr}`);
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
- ? `${s.phaseName} (${s.phaseNum}/${s.phaseTotal})`
281
- : `ph ${s.phaseNum}/${s.phaseTotal}`;
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
- const gsdStateStr = task ? '' : formatGsdState(readGsdState(dir) || {});
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⬆ /gsd:update\x1b[0m │ ';
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 /gsd:update\x1b[0m │ ';
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
  }
@@ -17,6 +17,36 @@ const path = require('path');
17
17
  const os = require('os');
18
18
  const { PACKAGE_NAME, updateCacheFileName } = require('../pp-core/bin/lib/package-identity.cjs');
19
19
 
20
+ // Audience wording (MSG-06, D-18, D-20): the light catalog entry. When it cannot
21
+ // load, the banner prints today's developer text.
22
+ let hookMessages = null;
23
+ try {
24
+ hookMessages = require('../pp-core/bin/lib/hook-messages.cjs');
25
+ } catch (e) {
26
+ hookMessages = null;
27
+ }
28
+
29
+ /** The audience for this process; developer when the catalog is unavailable or throws. */
30
+ function hookAudience() {
31
+ if (!hookMessages) return 'developer';
32
+ try {
33
+ return hookMessages.audienceOf(process.env);
34
+ } catch (e) {
35
+ return 'developer';
36
+ }
37
+ }
38
+
39
+ /** The catalog message for `code`, or `literal` (today's developer string) on any failure. */
40
+ function hookText(code, params, audience, literal) {
41
+ if (!hookMessages || audience === 'developer') return literal;
42
+ try {
43
+ const out = hookMessages.msg(code, params, audience, literal);
44
+ return typeof out === 'string' && out !== '' ? out : literal;
45
+ } catch (e) {
46
+ return literal;
47
+ }
48
+ }
49
+
20
50
  // Suppress repeat parse-error banners for 24 hours so a genuinely broken
21
51
  // cache file doesn't nag the user every session.
22
52
  const RATE_LIMIT_SECONDS = 24 * 60 * 60;
@@ -29,13 +59,18 @@ const RATE_LIMIT_SECONDS = 24 * 60 * 60;
29
59
  * @param {object|null} state.cache Parsed cache, or null if missing/unreadable.
30
60
  * @param {boolean} state.parseError True iff cache file existed but JSON.parse failed.
31
61
  * @param {boolean} state.suppressFailureWarning True when a recent failure warning already fired.
62
+ * @param {object} [opts]
63
+ * @param {string} [opts.audience='developer'] Whose wording to use; the developer text is today's string.
32
64
  * @returns {{systemMessage: string}|null} JSON envelope, or null for silent exit.
33
65
  */
34
- function buildBannerOutput(state) {
66
+ function buildBannerOutput(state, opts) {
67
+ const audience = (opts && opts.audience) || 'developer';
35
68
  const { cache, parseError, suppressFailureWarning } = state || {};
36
69
  if (parseError) {
37
70
  if (suppressFailureWarning) return null;
38
- return { systemMessage: 'GSD update check failed.' };
71
+ return {
72
+ systemMessage: hookText('hook.banner.check_failed', {}, audience, 'pp-core update check failed.'),
73
+ };
39
74
  }
40
75
  if (!cache) return null;
41
76
  // Lineage guard: package_name must be present and match this package.
@@ -45,7 +80,12 @@ function buildBannerOutput(state) {
45
80
  const installed = cache.installed || 'unknown';
46
81
  const latest = cache.latest || 'unknown';
47
82
  return {
48
- systemMessage: `GSD update available: ${installed} → ${latest}. Run /gsd:update.`,
83
+ systemMessage: hookText(
84
+ 'hook.banner.update_available',
85
+ { installed, latest },
86
+ audience,
87
+ `pp-core update available: ${installed} → ${latest}. Run /pp-update.`
88
+ ),
49
89
  };
50
90
  }
51
91
 
@@ -108,7 +148,7 @@ function main() {
108
148
  const suppressFailureWarning = parseError
109
149
  ? shouldSuppressFailureWarning(sentinelFile, now)
110
150
  : false;
111
- const output = buildBannerOutput({ cache, parseError, suppressFailureWarning });
151
+ const output = buildBannerOutput({ cache, parseError, suppressFailureWarning }, { audience: hookAudience() });
112
152
 
113
153
  if (parseError && !suppressFailureWarning) {
114
154
  // Ensure cache dir exists before writing the sentinel — first-run case
@@ -5,7 +5,7 @@
5
5
  // (no active /gsd- skill or Task subagent) and injects an advisory warning.
6
6
  //
7
7
  // This is a SOFT guard — it advises, not blocks. The edit still proceeds.
8
- // The warning nudges Claude to use /gsd:quick or /gsd:fast instead of
8
+ // The warning nudges Claude to use /pp-quick or /pp-fast instead of
9
9
  // making direct edits that bypass state tracking.
10
10
  //
11
11
  // Enable via config: hooks.workflow_guard: true (default: false)
@@ -151,9 +151,9 @@ process.stdin.on('end', () => {
151
151
  const output = {
152
152
  hookSpecificOutput: {
153
153
  hookEventName: "PreToolUse",
154
- additionalContext: `⚠️ WORKFLOW ADVISORY: You're editing ${path.basename(filePath)} directly without a GSD command. ` +
154
+ additionalContext: `⚠️ WORKFLOW ADVISORY: You're editing ${path.basename(filePath)} directly without a pp-core command. ` +
155
155
  'This edit will not be tracked in STATE.md or produce a SUMMARY.md. ' +
156
- 'Consider using /gsd:fast for trivial fixes or /gsd:quick for larger changes ' +
156
+ 'Consider using /pp-fast for trivial fixes or /pp-quick for larger changes ' +
157
157
  'to maintain project state tracking. ' +
158
158
  'If this is intentional (e.g., user explicitly asked for a direct edit), proceed normally.'
159
159
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pareto4/pp-core",
3
- "version": "1.21.1",
3
+ "version": "1.23.1",
4
4
  "description": "Pareto pp-core — The Pareto Project's AI-assisted engineering framework: meta-prompting, context engineering, and spec-driven development for AI coding agents.",
5
5
  "bin": {
6
6
  "pp-core": "bin/install.js",
@@ -71,7 +71,7 @@
71
71
  },
72
72
  "overrides": {
73
73
  "qs": ">=6.16.0",
74
- "@hono/node-server": ">=2.0.5",
74
+ "@hono/node-server": ">=2.1.3",
75
75
  "body-parser": "^2.4.0",
76
76
  "fast-uri": "^3.1.9",
77
77
  "zod": "^4.6.6"