@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
|
@@ -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 {
|
|
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:
|
|
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 /
|
|
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
|
|
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 /
|
|
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('../
|
|
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.';
|
package/hooks/pp-statusline.js
CHANGED
|
@@ -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
|
}
|
|
@@ -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 {
|
|
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:
|
|
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 /
|
|
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
|
|
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 /
|
|
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.
|
|
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.
|
|
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"
|