@pareto4/pp-core 1.22.0 → 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 (119) 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 +1 -1
  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-input-check.cjs +11 -11
  21. package/pp-core/bin/lib/discovery-session-check.cjs +7 -7
  22. package/pp-core/bin/lib/discovery-status.cjs +419 -0
  23. package/pp-core/bin/lib/explain-command.cjs +48 -0
  24. package/pp-core/bin/lib/gate-card.cjs +97 -0
  25. package/pp-core/bin/lib/hook-messages.cjs +132 -0
  26. package/pp-core/bin/lib/init-contract.cjs +116 -76
  27. package/pp-core/bin/lib/init-interactive.cjs +110 -49
  28. package/pp-core/bin/lib/init.cjs +44 -3
  29. package/pp-core/bin/lib/io.cjs +33 -0
  30. package/pp-core/bin/lib/message-labels.cjs +75 -0
  31. package/pp-core/bin/lib/messages/core.cjs +20 -0
  32. package/pp-core/bin/lib/messages/discovery.cjs +267 -0
  33. package/pp-core/bin/lib/messages/gate.cjs +257 -0
  34. package/pp-core/bin/lib/messages/hook.cjs +114 -0
  35. package/pp-core/bin/lib/messages/init.cjs +513 -0
  36. package/pp-core/bin/lib/messages/progress.cjs +110 -0
  37. package/pp-core/bin/lib/messages.cjs +208 -0
  38. package/pp-core/bin/lib/vault-command-router.cjs +16 -3
  39. package/pp-core/bin/lib/violation-render.cjs +78 -0
  40. package/pp-core/bin/pp-tools.cjs +68 -23
  41. package/pp-core/bin/shared/config-schema.manifest.json +1 -0
  42. package/pp-core/references/audience-moments.md +119 -0
  43. package/pp-core/references/audience.md +114 -0
  44. package/pp-core/references/discovery-charter.md +2 -2
  45. package/pp-core/templates/project-charter.md +3 -4
  46. package/pp-core/workflows/add-phase.md +2 -0
  47. package/pp-core/workflows/add-tests.md +2 -0
  48. package/pp-core/workflows/add-todo.md +2 -0
  49. package/pp-core/workflows/ai-integration-phase.md +2 -0
  50. package/pp-core/workflows/audit-milestone.md +2 -0
  51. package/pp-core/workflows/autonomous.md +2 -0
  52. package/pp-core/workflows/check-todos.md +2 -0
  53. package/pp-core/workflows/code-review-fix.md +2 -0
  54. package/pp-core/workflows/code-review.md +2 -0
  55. package/pp-core/workflows/complete-milestone.md +10 -1
  56. package/pp-core/workflows/discovery-sprint/charter.md +3 -2
  57. package/pp-core/workflows/discovery-sprint/prefill.md +3 -0
  58. package/pp-core/workflows/discovery-sprint/prototype.md +14 -0
  59. package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +4 -1
  60. package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +1 -1
  61. package/pp-core/workflows/discovery-sprint/vault-context.md +15 -0
  62. package/pp-core/workflows/discovery-sprint.md +14 -0
  63. package/pp-core/workflows/discovery-to-build.md +8 -2
  64. package/pp-core/workflows/discuss-phase-assumptions.md +2 -0
  65. package/pp-core/workflows/discuss-phase.md +1 -1
  66. package/pp-core/workflows/edit-phase.md +2 -0
  67. package/pp-core/workflows/eval-review.md +2 -0
  68. package/pp-core/workflows/execute-phase/steps/check-query-gate.md +25 -1
  69. package/pp-core/workflows/execute-phase/steps/vault-fetch.md +1 -0
  70. package/pp-core/workflows/execute-phase/steps/vault-load-gate.md +2 -0
  71. package/pp-core/workflows/execute-phase.md +1 -1
  72. package/pp-core/workflows/execute-plan.md +2 -0
  73. package/pp-core/workflows/extract-learnings.md +2 -0
  74. package/pp-core/workflows/help/modes/business.md +56 -0
  75. package/pp-core/workflows/help.md +12 -2
  76. package/pp-core/workflows/import.md +10 -1
  77. package/pp-core/workflows/ingest-docs.md +2 -0
  78. package/pp-core/workflows/init.md +17 -1
  79. package/pp-core/workflows/insert-phase.md +2 -0
  80. package/pp-core/workflows/list-workspaces.md +2 -0
  81. package/pp-core/workflows/manager.md +2 -0
  82. package/pp-core/workflows/map-codebase.md +2 -0
  83. package/pp-core/workflows/milestone-summary.md +2 -0
  84. package/pp-core/workflows/new-milestone.md +10 -1
  85. package/pp-core/workflows/new-project.md +12 -2
  86. package/pp-core/workflows/new-workspace.md +2 -0
  87. package/pp-core/workflows/next.md +8 -0
  88. package/pp-core/workflows/plan-milestone-gaps.md +10 -1
  89. package/pp-core/workflows/plan-phase.md +2 -1
  90. package/pp-core/workflows/plan-review-convergence.md +1 -1
  91. package/pp-core/workflows/progress.md +10 -0
  92. package/pp-core/workflows/quick.md +11 -1
  93. package/pp-core/workflows/remove-phase.md +2 -0
  94. package/pp-core/workflows/remove-workspace.md +2 -0
  95. package/pp-core/workflows/resume-project.md +25 -0
  96. package/pp-core/workflows/review.md +2 -0
  97. package/pp-core/workflows/scan.md +2 -0
  98. package/pp-core/workflows/secure-phase.md +2 -0
  99. package/pp-core/workflows/settings.md +41 -0
  100. package/pp-core/workflows/ship.md +2 -0
  101. package/pp-core/workflows/spec-phase.md +1 -1
  102. package/pp-core/workflows/ui-phase.md +2 -0
  103. package/pp-core/workflows/ui-review.md +2 -0
  104. package/pp-core/workflows/ultraplan-phase.md +2 -0
  105. package/pp-core/workflows/validate-phase.md +2 -0
  106. package/pp-core/workflows/vault-review.md +2 -0
  107. package/pp-core/workflows/verify-phase.md +2 -0
  108. package/pp-core/workflows/verify-work.md +2 -0
  109. package/scripts/affected-tests-lib.cjs +26 -12
  110. package/scripts/ci-release-only.cjs +158 -0
  111. package/scripts/live-runs/driver.cjs +326 -0
  112. package/scripts/live-runs/run.cjs +906 -0
  113. package/scripts/live-runs/scenarios.cjs +326 -0
  114. package/scripts/live-runs/scratch-project.cjs +132 -0
  115. package/scripts/live-runs/scrub.cjs +291 -0
  116. package/scripts/live-runs/turn-guard.cjs +107 -0
  117. package/scripts/live-runs/walk-setup.cjs +198 -0
  118. package/scripts/run-tests.cjs +151 -135
  119. package/scripts/test-env.cjs +64 -0
@@ -40,10 +40,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
40
40
  return (mod && mod.__esModule) ? mod : { "default": mod };
41
41
  };
42
42
  Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.printsResultJson = printsResultJson;
43
44
  exports.runInitInteractive = runInitInteractive;
44
45
  const node_fs_1 = __importDefault(require("node:fs"));
45
46
  const node_path_1 = __importDefault(require("node:path"));
46
47
  const init_contract_cjs_1 = require("./init-contract.cjs");
48
+ const messages_cjs_1 = require("./messages.cjs");
47
49
  // ─── Constants ───────────────────────────────────────────────────────────────
48
50
  /** Defensive circuit breaker against a runaway loop (a misbehaving
49
51
  * prompter or an unexpected session state that never reaches `done` or
@@ -71,29 +73,63 @@ function readBindingsContent(cwd) {
71
73
  return null;
72
74
  }
73
75
  }
76
+ /** A value shown in the compose summary or an echo, worded for business and engineer through the catalog
77
+ * (`init.word.<kind>.<value>`). A developer, and any value the catalog does not know, keeps the raw value. */
78
+ function word(kind, value, audience) {
79
+ if (audience === 'developer')
80
+ return value;
81
+ const code = `init.word.${kind}.${value.replace(/-/g, '_')}`;
82
+ return (0, messages_cjs_1.has)(code) ? (0, messages_cjs_1.text)(code, {}, { audience }) : value;
83
+ }
84
+ /** A parameterless message: the developer's catalog text, or the business/engineer wording with its Ref line. */
85
+ function plainOrMsg(code, audience) {
86
+ return audience === 'developer' ? (0, messages_cjs_1.msgDeveloper)(code) : (0, messages_cjs_1.msg)(code, {}, { audience });
87
+ }
88
+ /** One unfinished-session problem as a block: its message with its own `Ref:` line, the first line prefixed `- ` and
89
+ * every following line indented two spaces (the shape `renderViolation` gives discovery problems). */
90
+ function violationBlock(ref, audience) {
91
+ const lines = (0, messages_cjs_1.msg)(ref.code, ref.params, { audience }).split('\n');
92
+ return lines.map((line, i) => (i === 0 ? `- ${line}` : ` ${line}`)).join('\n');
93
+ }
94
+ /** Whether the CLI prints the trailing `{ "ok": … }` JSON after the human result. Autonomous Decision 7: it is a
95
+ * TTY-only dump that no workflow parses (the command refuses a non-TTY), so business users and engineers see only
96
+ * the human result; developers and `--raw` keep it. */
97
+ function printsResultJson(audience, raw) {
98
+ return raw || audience === 'developer';
99
+ }
74
100
  /** Render `compose_confirm`'s provenance table, drift list and action to
75
101
  * the prompter, BEFORE the caller asks for approval — plan Task 1's own
76
- * ordering requirement. */
77
- function renderComposeConfirm(prompter, info) {
102
+ * ordering requirement. The text is the catalog's (`init.compose.*`): a
103
+ * developer sees what the code always printed. */
104
+ function renderComposeConfirm(prompter, info, audience) {
78
105
  if (!isRecord(info))
79
106
  return;
80
107
  const typed = info;
81
- prompter.print('A binding can be composed from the org profile and project answers:');
108
+ const say = (code, params) => audience === 'developer' ? (0, messages_cjs_1.msgDeveloper)(code, params) : (0, messages_cjs_1.text)(code, params, { audience });
109
+ prompter.print(say('init.compose.header', {}));
82
110
  for (const entry of typed.provenance ?? []) {
83
- prompter.print(` ${entry.field} = ${JSON.stringify(entry.value)} (from ${entry.source})`);
111
+ prompter.print(say('init.compose.provenance', {
112
+ field: entry.field,
113
+ value: JSON.stringify(entry.value),
114
+ source: word('source', entry.source, audience),
115
+ }));
84
116
  }
85
117
  const drift = typed.drift ?? [];
86
118
  if (drift.length > 0) {
87
- prompter.print('Drift (the already-committed file wins — nothing here is a proposed change):');
119
+ prompter.print(say('init.compose.drift_header', {}));
88
120
  for (const d of drift) {
89
- prompter.print(` the profile would have supplied ${JSON.stringify(d.profileValue)} for "${d.key}"; ` +
90
- `the file already says ${JSON.stringify(d.effectiveValue)}, and ${JSON.stringify(d.effectiveValue)} wins.`);
121
+ prompter.print(say('init.compose.drift', {
122
+ key: d.key,
123
+ profile_value: JSON.stringify(d.profileValue),
124
+ file_value: JSON.stringify(d.effectiveValue),
125
+ }));
91
126
  }
92
127
  }
93
128
  for (const warning of typed.warnings ?? []) {
94
- prompter.print(` warning: ${warning}`);
129
+ prompter.print(say('init.compose.warning', { warning }));
95
130
  }
96
- prompter.print(`Action: ${typed.action ?? 'unknown'}`);
131
+ const action = typed.action ?? 'unknown';
132
+ prompter.print(say('init.compose.action', { action: word('action', action, audience) }));
97
133
  }
98
134
  // ─── runInitInteractive ─────────────────────────────────────────────────────
99
135
  /**
@@ -117,6 +153,9 @@ function renderComposeConfirm(prompter, info) {
117
153
  */
118
154
  async function runInitInteractive(cwd, prompter, opts = {}) {
119
155
  const sessionOpts = { offline: opts.offline === true, _globalRoot: opts._globalRoot };
156
+ const audience = opts.audience ?? (0, messages_cjs_1.audienceOf)(process.env);
157
+ /** The step's wording for this audience: the catalog text for business and engineer, the session's own text for a developer. */
158
+ const say = (ref, developerText) => audience === 'developer' || ref === undefined ? developerText : (0, messages_cjs_1.text)(ref.code, ref.params, { audience });
120
159
  let wroteBinding = false;
121
160
  // PBI 74299: answers the session filled in itself are shown once each, as
122
161
  // soon as they are decided, so the operator sees every value before the
@@ -125,15 +164,21 @@ async function runInitInteractive(cwd, prompter, opts = {}) {
125
164
  for (let steps = 1; steps <= MAX_SESSION_STEPS; steps++) {
126
165
  const state = (0, init_contract_cjs_1.initSessionNext)(cwd, sessionOpts);
127
166
  if (state.error !== undefined) {
128
- prompter.print(`Error: ${state.message ?? state.error}`);
167
+ const errorRef = state.display?.message;
168
+ prompter.print(audience !== 'developer' && errorRef !== undefined
169
+ ? (0, messages_cjs_1.msg)(errorRef.code, errorRef.params, { audience })
170
+ : (0, messages_cjs_1.msgDeveloper)('init.error_line', { message: state.message ?? state.error }));
129
171
  return { ok: false, wroteBinding, steps, violations: [] };
130
172
  }
131
173
  for (const auto of state.autoAnswered ?? []) {
132
174
  if (shownAutoAnswers.has(auto.id))
133
175
  continue;
134
176
  shownAutoAnswers.add(auto.id);
135
- const why = auto.source === 'derived' ? 'derived' : 'set by the org profile';
136
- prompter.print(` ${auto.id} = ${auto.value} (${why})`);
177
+ const code = auto.source === 'derived' ? 'init.auto_answer.derived' : 'init.auto_answer.org_profile';
178
+ const id = word('step', auto.id, audience);
179
+ prompter.print(audience === 'developer'
180
+ ? (0, messages_cjs_1.msgDeveloper)(code, { id, value: auto.value })
181
+ : (0, messages_cjs_1.text)(code, { id, value: auto.value }, { audience }));
137
182
  }
138
183
  if (state.done) {
139
184
  if (wroteBinding) {
@@ -148,35 +193,20 @@ async function runInitInteractive(cwd, prompter, opts = {}) {
148
193
  // line `pp-tools init` prints contradict the config file it just
149
194
  // finished writing, which is precisely the
150
195
  // tool-reports-a-state-that-isn't-real failure this phase exists to
151
- // end.
196
+ // end. Each reading has its own whole-sentence catalog entry.
152
197
  const vaultState = (0, init_contract_cjs_1.readVaultEnabledState)(cwd).state;
198
+ let code;
153
199
  if (vaultState === 'off') {
154
- prompter.print('Done — .platform-bindings.json was composed and written; vault.enabled was left at false ' +
155
- 'as this project set it, so the vault capability stays off and /pp-init Step 2.6 stays ' +
156
- 'inert (arm it with pp-tools config-set vault.enabled true).');
200
+ code = 'init.done.vault_off';
157
201
  }
158
202
  else if (vaultState === 'malformed') {
159
- prompter.print('Done — .platform-bindings.json was composed and written; vault.enabled holds a non-boolean ' +
160
- 'value and was left untouched — pp-tools query init-contract --verify reports it as ' +
161
- 'INIT_VAULT_ENABLED_MALFORMED; store the boolean with pp-tools config-set vault.enabled true.');
203
+ code = 'init.done.vault_malformed';
162
204
  }
163
205
  else if (vaultState === 'armed') {
164
206
  // Quick 260916-s5n: vault.required is named only when the READING
165
207
  // says armed, by the same rule — an explicit false is the project's
166
208
  // choice and the last line printed must not contradict it.
167
- const requiredArmed = (0, init_contract_cjs_1.readVaultRequiredState)(cwd).state === 'armed';
168
- // Built as a head plus clauses carrying no terminator of their own,
169
- // with exactly one full stop appended at the end.
170
- const head = 'Done — .platform-bindings.json was composed and written; vault.enabled is now true ' +
171
- '(the vault capability is armed — /pp-init Step 2.6 probes reachability next)';
172
- // 15-REVIEW WR-09: the clause names what is recorded and checked.
173
- const standardsClause = '; each plan records which client standards documents governed it ' +
174
- '(<vault_standards>), checked by the vault.trace gate';
175
- const requiredClause = requiredArmed
176
- ? '; vault.required is true, so a client vault miss, a failed fetch or an unwritable vault summary ' +
177
- 'stops the build at plan:pre and execute:pre'
178
- : '';
179
- prompter.print(head + standardsClause + requiredClause + '.');
209
+ code = (0, init_contract_cjs_1.readVaultRequiredState)(cwd).state === 'armed' ? 'init.done.vault_armed_required' : 'init.done.vault_armed';
180
210
  }
181
211
  else {
182
212
  // 'absent' — unreachable after an approved compose (the arming
@@ -184,34 +214,49 @@ async function runInitInteractive(cwd, prompter, opts = {}) {
184
214
  // where that write itself failed. It says only what is certain:
185
215
  // the binding was written, and it does NOT claim the capability is
186
216
  // on, because the key is not there to say so.
187
- prompter.print('Done — .platform-bindings.json was composed and written.');
217
+ code = 'init.done.written';
188
218
  }
219
+ prompter.print(plainOrMsg(code, audience));
189
220
  }
190
221
  else if (typeof state.info === 'string') {
191
- prompter.print(state.info);
222
+ const infoRef = state.display?.info;
223
+ prompter.print(audience === 'developer' || infoRef === undefined ? state.info : (0, messages_cjs_1.msg)(infoRef.code, infoRef.params, { audience }));
192
224
  }
193
225
  else {
194
- prompter.print('Done — nothing was composed (declined, or nothing was composable).');
226
+ prompter.print(plainOrMsg('init.done.nothing_composed', audience));
195
227
  }
196
228
  return { ok: true, wroteBinding, steps, violations: [] };
197
229
  }
198
230
  if (state.step === 'verify') {
199
231
  const violations = state.violations ?? [];
200
- prompter.print('The session could not reach a clean end state:');
201
- for (const v of violations) {
202
- prompter.print(` - [${v.code}] ${v.message}`);
232
+ if (audience === 'developer') {
233
+ prompter.print((0, messages_cjs_1.msgDeveloper)('init.verify'));
234
+ for (const v of violations) {
235
+ prompter.print((0, messages_cjs_1.msgDeveloper)('init.verify.line', { code: v.code, message: v.message }));
236
+ }
237
+ }
238
+ else {
239
+ prompter.print((0, messages_cjs_1.text)('init.verify', {}, { audience }));
240
+ violations.forEach((v, i) => {
241
+ const ref = state.display?.violations?.[i];
242
+ // A code the catalog has no wording for keeps the developer line.
243
+ prompter.print(ref !== undefined && (0, messages_cjs_1.has)(ref.code)
244
+ ? violationBlock(ref, audience)
245
+ : (0, messages_cjs_1.msgDeveloper)('init.verify.line', { code: v.code, message: v.message }));
246
+ });
247
+ prompter.print((0, messages_cjs_1.refLine)('init.verify'));
203
248
  }
204
249
  return { ok: false, wroteBinding, steps, violations };
205
250
  }
206
251
  if (state.step === null) {
207
252
  // Defensive: the session contract never returns a null step outside
208
253
  // `done`/`verify`/`error` above. Fail closed rather than crash.
209
- prompter.print('Unexpected session state: no step id was returned.');
254
+ prompter.print(plainOrMsg('init.unexpected_state', audience));
210
255
  return { ok: false, wroteBinding, steps, violations: [] };
211
256
  }
212
257
  const stepId = state.step;
213
258
  if (stepId === 'compose_confirm') {
214
- renderComposeConfirm(prompter, state.info);
259
+ renderComposeConfirm(prompter, state.info, audience);
215
260
  }
216
261
  // Ask, re-asking on rejection — never swallowed (T-34-73).
217
262
  for (;;) {
@@ -220,13 +265,23 @@ async function runInitInteractive(cwd, prompter, opts = {}) {
220
265
  // pre-existing file that predates this run (a bare post-hoc
221
266
  // `existsSync` cannot make that distinction).
222
267
  const preBindings = stepId === 'compose_confirm' ? readBindingsContent(cwd) : null;
223
- const answer = await prompter.ask({
224
- prompt: state.prompt ?? '',
225
- options: state.options,
226
- preselected: state.preselected,
227
- preselectedLabel: state.preselectedLabel,
228
- optionalAnswer: state.optionalAnswer,
229
- });
268
+ const answer = await prompter.ask(audience === 'developer'
269
+ ? {
270
+ prompt: state.prompt ?? '',
271
+ options: state.options,
272
+ preselected: state.preselected,
273
+ preselectedLabel: state.preselectedLabel,
274
+ optionalAnswer: state.optionalAnswer,
275
+ }
276
+ : {
277
+ prompt: say(state.display?.prompt, state.prompt ?? ''),
278
+ options: state.options,
279
+ preselected: state.preselected,
280
+ preselectedLabel: state.preselectedLabel === undefined ? undefined : say(state.display?.preselectedLabel, state.preselectedLabel),
281
+ optionalAnswer: state.optionalAnswer,
282
+ code: state.display?.prompt?.code,
283
+ audience,
284
+ });
230
285
  // UAT-13-F2: the driver has no default-acceptance path, on purpose — the
231
286
  // answer goes to initSessionAnswer verbatim, which owns the none/blank decline.
232
287
  const result = (0, init_contract_cjs_1.initSessionAnswer)(cwd, stepId, answer, sessionOpts);
@@ -236,9 +291,15 @@ async function runInitInteractive(cwd, prompter, opts = {}) {
236
291
  }
237
292
  break;
238
293
  }
239
- prompter.print(result.message ?? `Rejected (${result.reason ?? 'unknown reason'}) — please answer again.`);
294
+ const rejectedRef = result.display?.message;
295
+ const fallback = { code: 'init.rejected_fallback', params: { reason: result.reason ?? 'unknown reason' } };
296
+ prompter.print(audience === 'developer'
297
+ ? (result.message ?? (0, messages_cjs_1.msgDeveloper)(fallback.code, fallback.params))
298
+ : rejectedRef !== undefined
299
+ ? (0, messages_cjs_1.msg)(rejectedRef.code, rejectedRef.params, { audience })
300
+ : (result.message ?? (0, messages_cjs_1.msg)(fallback.code, fallback.params, { audience })));
240
301
  }
241
302
  }
242
- prompter.print('init session exceeded the maximum step count — aborting to avoid an infinite loop.');
303
+ prompter.print(plainOrMsg('init.step_overflow', audience));
243
304
  return { ok: false, wroteBinding, steps: MAX_SESSION_STEPS, violations: [] };
244
305
  }
@@ -36,6 +36,7 @@ const plan_sizing_cjs_1 = require("./plan-sizing.cjs");
36
36
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- plan-scan.cjs is an export= CommonJS module
37
37
  const scanPhasePlans = require("./plan-scan.cjs");
38
38
  const state_document_cjs_1 = require("./state-document.cjs");
39
+ const audience_cjs_1 = require("./audience.cjs");
39
40
  const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
40
41
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- commands.cjs is an export= CommonJS module
41
42
  const commandsMod = require("./commands.cjs");
@@ -113,7 +114,45 @@ function withProjectRoot(cwd, result) {
113
114
  result['project_title'] = h1Match[1].trim();
114
115
  }
115
116
  }
116
- return result;
117
+ return withAudience(result, typeof config.response_language === 'string' ? config.response_language : null);
118
+ }
119
+ /**
120
+ * Phase 40 (D-01..D-04, AUD-03): carry the person's audience in the init payload.
121
+ *
122
+ * Resolved once per init call through the Phase 39 leaf. For `developer` (set or
123
+ * defaulted) the result is returned untouched, so every developer payload stays
124
+ * byte-identical (D-01, D-03). For `business` and `engineer` the payload gains `audience`
125
+ * and `audience_ref`, and `response_language` becomes the resolved language name, so the
126
+ * person's language wins over a project's (D-02). Person-file warnings go to stderr only,
127
+ * never into the JSON (D-04).
128
+ */
129
+ function withAudience(result, projectLanguage) {
130
+ // Assignment keeps an existing response_language key in place, as before.
131
+ return Object.assign(result, audienceFields(projectLanguage));
132
+ }
133
+ /**
134
+ * The fields `withAudience` adds, on their own: `{}` for a developer (set or defaulted),
135
+ * otherwise `audience`, `audience_ref` and the resolved `response_language`. Phase 40 CR-01:
136
+ * `pp-tools audience fields` prints this object, so a workflow that asks the person something
137
+ * before its first init call learns the audience first, resolved by this same code.
138
+ */
139
+ function audienceFields(projectLanguage) {
140
+ const person = (0, audience_cjs_1.readPerson)(process.env);
141
+ const aud = (0, audience_cjs_1.resolveAudience)(process.env, person);
142
+ for (const w of aud.warnings)
143
+ process.stderr.write(`pp-tools: warning: ${w}\n`);
144
+ if (aud.audience === 'developer')
145
+ return {};
146
+ const lang = (0, audience_cjs_1.resolveLanguage)({ env: process.env, person, projectResponseLanguage: projectLanguage });
147
+ const fields = { audience: aud.audience };
148
+ // The compiled file is pp-core/bin/lib/init.cjs, so ../../references is pp-core/references.
149
+ // bin/install.js copies pp-core/ wholesale for every runtime, so the path resolves in every
150
+ // install. Absolute with forward slashes; never derived from env, the person file or config.
151
+ fields['audience_ref'] = toPosixPath(node_path_1.default.join(__dirname, '..', '..', 'references', 'audience.md'));
152
+ if (typeof lang.name === 'string' && lang.name !== '') {
153
+ fields['response_language'] = lang.name;
154
+ }
155
+ return fields;
117
156
  }
118
157
  function getInitGitState(cwd) {
119
158
  const info = gitWorktreeInfoInternal(cwd);
@@ -1487,7 +1526,7 @@ function cmdInitListWorkspaces(cwd, raw) {
1487
1526
  workspaces,
1488
1527
  workspace_count: workspaces.length,
1489
1528
  };
1490
- output(result, raw);
1529
+ output(withAudience(result, null), raw);
1491
1530
  }
1492
1531
  function cmdInitRemoveWorkspace(cwd, name, raw) {
1493
1532
  const homedir = process.env['HOME'] || node_os_1.default.homedir();
@@ -1550,7 +1589,7 @@ function cmdInitRemoveWorkspace(cwd, name, raw) {
1550
1589
  dirty_repos: dirtyRepos,
1551
1590
  has_dirty_repos: dirtyRepos.length > 0,
1552
1591
  };
1553
- output(result, raw);
1592
+ output(withAudience(result, null), raw);
1554
1593
  }
1555
1594
  function buildAgentSkillsBlock(config, agentType, projectRoot) {
1556
1595
  const runtime = (config && config['runtime']) || 'claude';
@@ -1916,6 +1955,8 @@ module.exports = {
1916
1955
  cmdInitNewWorkspace,
1917
1956
  cmdInitListWorkspaces,
1918
1957
  cmdInitRemoveWorkspace,
1958
+ // Phase 40 CR-01: the audience fields alone, printed by `pp-tools audience fields`.
1959
+ audienceFields,
1919
1960
  detectChildRepos,
1920
1961
  buildAgentSkillsBlock,
1921
1962
  cmdAgentSkills,
@@ -15,6 +15,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
15
15
  const node_os_1 = __importDefault(require("node:os"));
16
16
  const node_path_1 = __importDefault(require("node:path"));
17
17
  const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
18
+ const messages_cjs_1 = require("./messages.cjs");
18
19
  // ─── Temp-file helpers (needed by output()) ──────────────────────────────────
19
20
  /**
20
21
  * Dedicated GSD temp directory: path.join(os.tmpdir(), 'gsd').
@@ -232,6 +233,10 @@ const ERROR_REASON = Object.freeze({
232
233
  DISCOVERY_FLOW_PAGE_FAILED: 'discovery_flow_page_failed',
233
234
  // timeline (Phase 35: end with no matching open run)
234
235
  TIMELINE_NO_OPEN_RUN: 'timeline_no_open_run',
236
+ // audience (Phase 39: person file could not be saved)
237
+ AUDIENCE_WRITE_FAILED: 'audience_write_failed',
238
+ // explain (Phase 41: pp-tools explain <code>)
239
+ EXPLAIN_UNKNOWN_CODE: 'explain_unknown_code',
235
240
  // generic
236
241
  USAGE: 'usage',
237
242
  UNKNOWN: 'unknown',
@@ -265,6 +270,33 @@ function error(message, reason = ERROR_REASON.UNKNOWN) {
265
270
  }
266
271
  process.exit(1);
267
272
  }
273
+ /**
274
+ * Emit a catalog message as an error and exit (Phase 41 D-05, D-08).
275
+ *
276
+ * The `--json-errors` `message` is the DEVELOPER text for every audience,
277
+ * because workflows and tests parse it. In plain mode a developer gets
278
+ * `Error: ` plus the developer text, exactly as error() writes today; a
279
+ * business user or engineer gets the resolved wording with its `Ref: <code>`
280
+ * line and no `Error: ` prefix, since the first sentence already says what
281
+ * happened. `params` may be a function of the audience so a caller can supply
282
+ * different human-facing values (a session number and a section title) than the
283
+ * developer text uses.
284
+ */
285
+ function errorCode(code, params, reason = ERROR_REASON.UNKNOWN) {
286
+ const p = (a) => (typeof params === 'function' ? params(a) : params);
287
+ if (_jsonErrorMode) {
288
+ const message = (0, messages_cjs_1.msgDeveloper)(code, p('developer'));
289
+ writeAllSync(2, JSON.stringify({ ok: false, reason, message }) + '\n');
290
+ }
291
+ else {
292
+ const audience = (0, messages_cjs_1.audienceOf)();
293
+ if (audience === 'developer')
294
+ writeAllSync(2, 'Error: ' + (0, messages_cjs_1.msgDeveloper)(code, p('developer')) + '\n');
295
+ else
296
+ writeAllSync(2, (0, messages_cjs_1.msg)(code, p(audience), { audience }) + '\n');
297
+ }
298
+ process.exit(1);
299
+ }
268
300
  // ─── Pareto visual brand: banner engine (Phase 17 / UIBR-01, UIBR-02) ─────────
269
301
  //
270
302
  // A hand-rolled ANSI banner emitter (no color library — CLAUDE.md constraint).
@@ -387,6 +419,7 @@ module.exports = {
387
419
  setJsonErrorMode,
388
420
  getJsonErrorMode,
389
421
  error,
422
+ errorCode,
390
423
  // Pareto visual brand banner engine (Phase 17 / UIBR-01, UIBR-02)
391
424
  PARETO_BLUE,
392
425
  CREW,
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ /**
3
+ * Display-time labels for values the code reads back (Phase 41, D-13).
4
+ *
5
+ * ADR-457 build-at-publish: compiles to the gitignored
6
+ * `pp-core/bin/lib/message-labels.cjs`.
7
+ *
8
+ * Display time only: writers write and readers read the canonical word
9
+ * ("Executed", "Pre-Filled"); only the text a person reads is relabelled, at
10
+ * the last step before output. A developer, and any unknown value or domain,
11
+ * gets the canonical word back. D-14 allowlists the modules that may import
12
+ * this file (plan 41-11 enforces it), so a label can never flow into a parser.
13
+ *
14
+ * `state.status` and `state.next_action` relabel the STATE.md lifecycle words
15
+ * and recommended next actions the statusline shows (Phase 43, MSG-06). They are
16
+ * their own domains because those words (executing, verifying, ...) are not
17
+ * `phase.status` keys, which are ROADMAP words pinned to the contract snapshot.
18
+ *
19
+ * Wording follows pp-core/references/audience.md: the business column never
20
+ * shows an internal word; the engineer column keeps real terms.
21
+ */
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.LABELS = void 0;
24
+ exports.label = label;
25
+ exports.LABELS = Object.freeze({
26
+ 'phase.status': Object.freeze({
27
+ Pending: { engineer: 'Pending', business: 'Waiting' },
28
+ 'Not Started': { engineer: 'Not started', business: 'Not started yet' },
29
+ Planned: { engineer: 'Planned', business: 'Ready to build' },
30
+ 'In Progress': { engineer: 'In progress', business: 'Being worked on' },
31
+ Executed: { engineer: 'Executed (not yet verified)', business: 'Built, not yet checked' },
32
+ 'Needs Review': { engineer: 'Needs review', business: 'Waiting for your review' },
33
+ Complete: { engineer: 'Complete', business: 'Done' },
34
+ }),
35
+ 'discovery.status': Object.freeze({
36
+ 'Not Started': { engineer: 'Not started', business: 'Not started yet' },
37
+ 'Pre-Filled': { engineer: 'Pre-filled from documents', business: 'Prepared from your documents' },
38
+ 'In Progress': { engineer: 'In progress', business: 'Being worked on' },
39
+ Completed: { engineer: 'Completed', business: 'Done' },
40
+ }),
41
+ // STATE.md frontmatter `status`: normalizeStateStatus output plus the pass-through values
42
+ // (`Defining requirements`, `Active`) and the statusline body fallback `complete`.
43
+ 'state.status': Object.freeze({
44
+ discussing: { engineer: 'discussing', business: 'being discussed' },
45
+ planning: { engineer: 'planning', business: 'being planned' },
46
+ executing: { engineer: 'executing', business: 'being built' },
47
+ verifying: { engineer: 'verifying', business: 'being checked' },
48
+ completed: { engineer: 'completed', business: 'done' },
49
+ complete: { engineer: 'complete', business: 'done' },
50
+ paused: { engineer: 'paused', business: 'on hold' },
51
+ unknown: { engineer: 'unknown', business: 'not recorded yet' },
52
+ 'Defining requirements': { engineer: 'defining requirements', business: 'agreeing what to build' },
53
+ Active: { engineer: 'active', business: 'under way' },
54
+ }),
55
+ // STATE.md frontmatter `next_action`: the recommended next command, when idle.
56
+ 'state.next_action': Object.freeze({
57
+ 'discuss-phase': { engineer: 'discuss phase', business: 'talk through stage' },
58
+ 'plan-phase': { engineer: 'plan phase', business: 'plan stage' },
59
+ 'execute-phase': { engineer: 'execute phase', business: 'build stage' },
60
+ 'verify-phase': { engineer: 'verify phase', business: 'check stage' },
61
+ 'verify-work': { engineer: 'verify work (UAT)', business: 'test stage' },
62
+ }),
63
+ });
64
+ /** The word to show for a canonical value. Developer and anything unknown get the canonical word back. */
65
+ function label(domain, canonical, opts) {
66
+ if (opts.audience === 'developer')
67
+ return canonical;
68
+ if (!Object.hasOwn(exports.LABELS, domain))
69
+ return canonical;
70
+ const table = exports.LABELS[domain];
71
+ if (!Object.hasOwn(table, canonical))
72
+ return canonical;
73
+ const pair = table[canonical];
74
+ return opts.audience === 'business' ? pair.business : pair.engineer;
75
+ }
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ /**
3
+ * Catalog slice: core messages (Phase 41, plan 41-05). Data only.
4
+ *
5
+ * ADR-457 build-at-publish: compiles to the gitignored
6
+ * `pp-core/bin/lib/messages/core.cjs`. Imports nothing but the entry type.
7
+ * Wording follows pp-core/references/audience.md. Every code is a stable key;
8
+ * `Ref:` lines are added by the engine at render time, never written here.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.core = void 0;
12
+ exports.core = Object.freeze({
13
+ explain_unknown_code: {
14
+ reach: 'business',
15
+ developer: 'No explanation for {code}.',
16
+ engineer: 'No explanation for {code}. No message has that reference code. Check that you copied the whole code from the line after "Ref:".',
17
+ business: 'No explanation for {code}. Copy the whole code from the line after "Ref:" and try again, or send it to Pareto as it is.',
18
+ explain: 'Every message from pp-core ends with a reference code on its own line. This command explains that code. An unknown code usually means it was cut short when it was copied.',
19
+ },
20
+ });