@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
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Message catalog engine (Phase 41: MSG-02, MSG-03, MSG-04; D-01..D-06, D-20).
|
|
4
|
+
*
|
|
5
|
+
* ADR-457 build-at-publish: this TypeScript file is the SOURCE OF TRUTH. It
|
|
6
|
+
* compiles to the gitignored `pp-core/bin/lib/messages.cjs`; do NOT hand-edit
|
|
7
|
+
* the compiled output.
|
|
8
|
+
*
|
|
9
|
+
* One catalog keyed by a stable code. Each entry carries `developer` (today's
|
|
10
|
+
* text, frozen byte for byte), optional `engineer`, `business` and `explain`.
|
|
11
|
+
* A business lookup resolves business, then engineer, then developer; an
|
|
12
|
+
* engineer lookup engineer, then developer; a developer lookup developer
|
|
13
|
+
* (D-01). Business and engineer messages end with a `Ref: <code>` line (D-05,
|
|
14
|
+
* D-06); developer text never does.
|
|
15
|
+
*
|
|
16
|
+
* Params are data. Substitution is a single pass with a function replacer, so
|
|
17
|
+
* a value that holds `{x}` or `$&` is never re-expanded (T-41-12). For business
|
|
18
|
+
* and engineer every param except the slots an entry marks `rawParams` is
|
|
19
|
+
* stripped of control characters, whitespace-collapsed and capped (T-41-11): at
|
|
20
|
+
* ECHO_CAP for an echo, at DIAG_CAP for the slots an entry marks `longParams`
|
|
21
|
+
* (diagnostics and approval values the person must read in full); the developer path
|
|
22
|
+
* passes params through untouched (D-08). An unknown code renders the code
|
|
23
|
+
* itself, never blank and never a throw (D-04). Lookups use a Map only, so
|
|
24
|
+
* `__proto__` and `constructor` are unknown codes (T-41-13).
|
|
25
|
+
*
|
|
26
|
+
* Machine channels never route through the audience path: `io.errorCode` writes
|
|
27
|
+
* `msgDeveloper` to the `--json-errors` message for every audience. This module
|
|
28
|
+
* must never import `io.cjs` (io imports this module). It imports Node built-ins
|
|
29
|
+
* (through the audience leaf), `audience.cjs` and the slices only, so hooks can
|
|
30
|
+
* load it later (D-02).
|
|
31
|
+
*
|
|
32
|
+
* The audience is resolved through the Phase 39 leaf (`PP_AUDIENCE`, then the
|
|
33
|
+
* person file, then developer), silently (D-20): warnings are printed by the
|
|
34
|
+
* commands that own them, never by a lookup.
|
|
35
|
+
*/
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.SLICES = exports.DIAG_CAP = exports.ECHO_CAP = void 0;
|
|
38
|
+
exports.duplicateCodesOf = duplicateCodesOf;
|
|
39
|
+
exports.registry = registry;
|
|
40
|
+
exports.has = has;
|
|
41
|
+
exports.audienceOf = audienceOf;
|
|
42
|
+
exports.sanitizeParam = sanitizeParam;
|
|
43
|
+
exports.fill = fill;
|
|
44
|
+
exports.pick = pick;
|
|
45
|
+
exports.refLine = refLine;
|
|
46
|
+
exports.msgDeveloper = msgDeveloper;
|
|
47
|
+
exports.text = text;
|
|
48
|
+
exports.msg = msg;
|
|
49
|
+
exports.explainText = explainText;
|
|
50
|
+
const audience_cjs_1 = require("./audience.cjs");
|
|
51
|
+
const core_cjs_1 = require("./messages/core.cjs");
|
|
52
|
+
const discovery_cjs_1 = require("./messages/discovery.cjs");
|
|
53
|
+
const init_cjs_1 = require("./messages/init.cjs");
|
|
54
|
+
const progress_cjs_1 = require("./messages/progress.cjs");
|
|
55
|
+
const gate_cjs_1 = require("./messages/gate.cjs");
|
|
56
|
+
const hook_cjs_1 = require("./messages/hook.cjs");
|
|
57
|
+
/**
|
|
58
|
+
* Every control character `\s` does not match (C0 minus `\t\n\v\f\r`, DEL and
|
|
59
|
+
* C1), plus every Unicode format character (category Cf: bidi overrides and
|
|
60
|
+
* isolates, zero-width characters, the BOM, tag characters). A right-to-left
|
|
61
|
+
* override in a file name would make the shown name differ from the real one
|
|
62
|
+
* (Trojan Source). Stripped BEFORE the whitespace collapse.
|
|
63
|
+
*/
|
|
64
|
+
const CONTROL_CHARS_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x9f\p{Cf}]/gu;
|
|
65
|
+
const SLOT_RE = /\{([a-z][a-z0-9_]*)\}/g;
|
|
66
|
+
/** The cap for a slot that echoes a typed value (T-41-11). */
|
|
67
|
+
exports.ECHO_CAP = 120;
|
|
68
|
+
/**
|
|
69
|
+
* The cap for a `longParams` slot. `composeBindings` bounds each warning at 200
|
|
70
|
+
* characters, so this holds five full warnings joined with `; `.
|
|
71
|
+
*/
|
|
72
|
+
exports.DIAG_CAP = 1000;
|
|
73
|
+
exports.SLICES = Object.freeze([
|
|
74
|
+
['core', core_cjs_1.core],
|
|
75
|
+
['discovery', discovery_cjs_1.discovery],
|
|
76
|
+
['init', init_cjs_1.init],
|
|
77
|
+
['progress', progress_cjs_1.progress],
|
|
78
|
+
['gate', gate_cjs_1.gate],
|
|
79
|
+
['hook', hook_cjs_1.hook],
|
|
80
|
+
]);
|
|
81
|
+
/** Codes defined in more than one slice, sorted. */
|
|
82
|
+
function duplicateCodesOf(slices) {
|
|
83
|
+
const seen = new Set();
|
|
84
|
+
const dup = new Set();
|
|
85
|
+
for (const [, slice] of slices) {
|
|
86
|
+
for (const code of Object.keys(slice)) {
|
|
87
|
+
if (seen.has(code))
|
|
88
|
+
dup.add(code);
|
|
89
|
+
seen.add(code);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return [...dup].sort();
|
|
93
|
+
}
|
|
94
|
+
let _registry = null;
|
|
95
|
+
/** The catalog as one Map, built once. On a duplicate code the first slice wins; the gate test reports duplicates. */
|
|
96
|
+
function registry() {
|
|
97
|
+
if (_registry === null) {
|
|
98
|
+
const map = new Map();
|
|
99
|
+
for (const [, slice] of exports.SLICES) {
|
|
100
|
+
for (const code of Object.keys(slice)) {
|
|
101
|
+
if (!map.has(code))
|
|
102
|
+
map.set(code, slice[code]);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
_registry = map;
|
|
106
|
+
}
|
|
107
|
+
return _registry;
|
|
108
|
+
}
|
|
109
|
+
function has(code) {
|
|
110
|
+
return registry().has(code);
|
|
111
|
+
}
|
|
112
|
+
/** The audience for this process (D-20). Never prints, never throws; developer on any failure. */
|
|
113
|
+
function audienceOf(env = process.env) {
|
|
114
|
+
try {
|
|
115
|
+
return (0, audience_cjs_1.resolveAudience)(env, (0, audience_cjs_1.readPerson)(env)).audience;
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return 'developer';
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/** Strip control and format characters, collapse whitespace, trim, cap at `cap` (T-41-11). Order matters. */
|
|
122
|
+
function sanitizeParam(value, cap = exports.ECHO_CAP) {
|
|
123
|
+
let raw;
|
|
124
|
+
if (typeof value === 'number')
|
|
125
|
+
raw = String(value);
|
|
126
|
+
else if (typeof value === 'string')
|
|
127
|
+
raw = value;
|
|
128
|
+
else
|
|
129
|
+
return '';
|
|
130
|
+
const collapsed = raw.replace(CONTROL_CHARS_RE, '').replace(/\s+/g, ' ').trim();
|
|
131
|
+
if (collapsed.length <= cap)
|
|
132
|
+
return collapsed;
|
|
133
|
+
return `${collapsed.slice(0, cap)}…`;
|
|
134
|
+
}
|
|
135
|
+
function rawParam(value) {
|
|
136
|
+
return typeof value === 'string' || typeof value === 'number' ? String(value) : '';
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* One pass of `{name}` substitution with a function replacer. A missing param
|
|
140
|
+
* renders empty in the developer text (unchanged, D-08) and as its own `{name}`
|
|
141
|
+
* slot for business and engineer, so a renderer that fails to supply a value
|
|
142
|
+
* shows visibly instead of leaving a silent blank (D-04). When sanitising, only
|
|
143
|
+
* the slots in `opts.rawParams` (the entry's own marking) pass through as written,
|
|
144
|
+
* and the slots in `opts.longParams` are capped at DIAG_CAP instead of ECHO_CAP.
|
|
145
|
+
*/
|
|
146
|
+
function fill(template, params, opts) {
|
|
147
|
+
return template.replace(SLOT_RE, (match, name) => {
|
|
148
|
+
if (!Object.hasOwn(params, name))
|
|
149
|
+
return opts.sanitize ? match : '';
|
|
150
|
+
const value = params[name];
|
|
151
|
+
if (!opts.sanitize || (opts.rawParams?.includes(name) ?? false))
|
|
152
|
+
return rawParam(value);
|
|
153
|
+
return sanitizeParam(value, (opts.longParams?.includes(name) ?? false) ? exports.DIAG_CAP : exports.ECHO_CAP);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
/** The wording for an audience: business, engineer, then developer fallbacks (D-01). */
|
|
157
|
+
function pick(entry, audience) {
|
|
158
|
+
if (audience === 'business')
|
|
159
|
+
return entry.business ?? entry.engineer ?? entry.developer;
|
|
160
|
+
if (audience === 'engineer')
|
|
161
|
+
return entry.engineer ?? entry.developer;
|
|
162
|
+
return entry.developer;
|
|
163
|
+
}
|
|
164
|
+
function refLine(code) {
|
|
165
|
+
return `Ref: ${code}`;
|
|
166
|
+
}
|
|
167
|
+
/** The developer text: today's wording, params untouched (D-05, D-08). Unknown code renders the code. */
|
|
168
|
+
function msgDeveloper(code, params = {}, _registry) {
|
|
169
|
+
const entry = (_registry ?? registry()).get(code);
|
|
170
|
+
if (entry === undefined)
|
|
171
|
+
return code;
|
|
172
|
+
return fill(entry.developer, params, { sanitize: false });
|
|
173
|
+
}
|
|
174
|
+
/** The text for an audience with no Ref line. Unknown code renders the code. */
|
|
175
|
+
function text(code, params = {}, opts) {
|
|
176
|
+
const entry = (opts._registry ?? registry()).get(code);
|
|
177
|
+
if (entry === undefined)
|
|
178
|
+
return code;
|
|
179
|
+
if (opts.audience === 'developer')
|
|
180
|
+
return msgDeveloper(code, params, opts._registry);
|
|
181
|
+
return fill(pick(entry, opts.audience), params, {
|
|
182
|
+
sanitize: true,
|
|
183
|
+
rawParams: entry.rawParams,
|
|
184
|
+
longParams: entry.longParams,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
/** The message for an audience: business and engineer messages end with the Ref line (D-05, D-06). */
|
|
188
|
+
function msg(code, params = {}, opts) {
|
|
189
|
+
const entry = (opts._registry ?? registry()).get(code);
|
|
190
|
+
if (entry === undefined)
|
|
191
|
+
return code;
|
|
192
|
+
if (opts.audience === 'developer')
|
|
193
|
+
return msgDeveloper(code, params, opts._registry);
|
|
194
|
+
const body = text(code, params, opts);
|
|
195
|
+
if (entry.part === true)
|
|
196
|
+
return body;
|
|
197
|
+
return `${body}\n${refLine(code)}`;
|
|
198
|
+
}
|
|
199
|
+
/** What `pp-tools explain <code>` shows, or null for an unknown code. */
|
|
200
|
+
function explainText(code, opts) {
|
|
201
|
+
const entry = (opts._registry ?? registry()).get(code);
|
|
202
|
+
if (entry === undefined)
|
|
203
|
+
return null;
|
|
204
|
+
const body = typeof entry.explain === 'string' && entry.explain !== ''
|
|
205
|
+
? entry.explain
|
|
206
|
+
: pick(entry, opts.audience).replace(SLOT_RE, (_m, name) => `<${name}>`);
|
|
207
|
+
return `${body}\n${refLine(code)}`;
|
|
208
|
+
}
|
|
@@ -39,6 +39,7 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
39
39
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
40
40
|
const childProcess = require("node:child_process");
|
|
41
41
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
42
|
+
const messages_cjs_1 = require("./messages.cjs");
|
|
42
43
|
// CR-04 (03-11): this router's path arms resolve their containment boundary the
|
|
43
44
|
// same way `cmdVaultTrace` in src/check-command-router.cts does. project-root.cjs is a
|
|
44
45
|
// leaf (node builtins only), so this adds no cycle.
|
|
@@ -484,6 +485,12 @@ function routeVaultCommand({ args, cwd, raw, error, _vault, _core, _exec, _orgPr
|
|
|
484
485
|
* `config-unreadable`, one more line per unreadable file names the file and the
|
|
485
486
|
* repair; `--raw` prints the verdict object instead.
|
|
486
487
|
*
|
|
488
|
+
* Phase 43 (D-12): for a business user or engineer the `--explain` lines are the
|
|
489
|
+
* card for the stopped check (`gate.vault.admission.blocked`), its `Ref:` line,
|
|
490
|
+
* `Details:` and then the same repair lines. The card is display text: the first
|
|
491
|
+
* line is the token for every audience, and without `--explain`, with `--raw`
|
|
492
|
+
* and for every other state the output is unchanged.
|
|
493
|
+
*
|
|
487
494
|
* The answer comes from `vaultAdmissionVerdict` in the REAL src/vault.cts, never
|
|
488
495
|
* from the injectable `_vault` seam: a governance decision must not be
|
|
489
496
|
* switchable by a test mock.
|
|
@@ -518,9 +525,15 @@ function admissionVerb({ cwd, rest, raw, error, c }) {
|
|
|
518
525
|
}
|
|
519
526
|
const lines = [verdict.state];
|
|
520
527
|
if (explain && verdict.state === 'config-unreadable') {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
528
|
+
const code = 'gate.vault.admission.blocked';
|
|
529
|
+
const repair = verdict.unreadable.map((u) => (0, messages_cjs_1.msgDeveloper)(code, { file: u.file, error: u.error }));
|
|
530
|
+
const audience = (0, messages_cjs_1.audienceOf)(process.env);
|
|
531
|
+
if (audience === 'developer') {
|
|
532
|
+
lines.push(...repair);
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
// D-12: the card for a business user or engineer, then today's repair line(s) under Details.
|
|
536
|
+
lines.push((0, messages_cjs_1.text)(code, {}, { audience }), (0, messages_cjs_1.refLine)(code), 'Details:', ...repair);
|
|
524
537
|
}
|
|
525
538
|
}
|
|
526
539
|
c.output(verdict, true, `${lines.join('\n')}\n`);
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shared violation renderer (Phase 41: MSG-01, MSG-02, MSG-03; D-07, D-11, D-13).
|
|
4
|
+
*
|
|
5
|
+
* ADR-457 build-at-publish: this TypeScript file is the SOURCE OF TRUTH. It
|
|
6
|
+
* compiles to the gitignored `pp-core/bin/lib/violation-render.cjs`; do NOT
|
|
7
|
+
* hand-edit the compiled output.
|
|
8
|
+
*
|
|
9
|
+
* D-11: the only place a discovery violation becomes text. Built for every
|
|
10
|
+
* discovery domain; only `discovery.session` and `discovery.input` are cut over
|
|
11
|
+
* in this phase (the other 17 formatViolation copies stay behind the
|
|
12
|
+
* shrink-only ratchet in tests/fixtures/format-violation-allowlist.json).
|
|
13
|
+
*
|
|
14
|
+
* A violation is `{ reason, ...fields }`; its catalog code is
|
|
15
|
+
* `<domain>.<reason>`. A developer gets the exact line the old code printed
|
|
16
|
+
* (`msgDeveloper`, params untouched). A business user or engineer gets a block:
|
|
17
|
+
* the message with its own `Ref:` line, the first line prefixed `- ` and every
|
|
18
|
+
* following line indented, so each problem carries its code (D-05, D-07).
|
|
19
|
+
*
|
|
20
|
+
* Canonical values stay canonical (D-13): the engines pass the literals the
|
|
21
|
+
* person must write (`None identified in Discovery.`, `[TO CONFIRM]`,
|
|
22
|
+
* `Completed`) through `extra`; this module only adds a display `status` through
|
|
23
|
+
* `label()`. It never imports a discovery engine (the engines import it).
|
|
24
|
+
*/
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.violationParams = violationParams;
|
|
27
|
+
exports.renderViolation = renderViolation;
|
|
28
|
+
const messages_cjs_1 = require("./messages.cjs");
|
|
29
|
+
const message_labels_cjs_1 = require("./message-labels.cjs");
|
|
30
|
+
const SESSION_FILE_RE = /session-(\d{2})\.md$/;
|
|
31
|
+
const SESSION_ID_RE = /^session (\d+)$/;
|
|
32
|
+
function sessionNumberOf(v) {
|
|
33
|
+
for (const field of ['artifact', 'session']) {
|
|
34
|
+
const value = v[field];
|
|
35
|
+
if (typeof value !== 'string')
|
|
36
|
+
continue;
|
|
37
|
+
const m = SESSION_FILE_RE.exec(value);
|
|
38
|
+
if (m !== null)
|
|
39
|
+
return String(Number(m[1]));
|
|
40
|
+
}
|
|
41
|
+
if (typeof v.id === 'string') {
|
|
42
|
+
const m = SESSION_ID_RE.exec(v.id);
|
|
43
|
+
if (m !== null)
|
|
44
|
+
return String(Number(m[1]));
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
/** The catalog params for a violation: every string or number field except `reason`, plus the session number, `extra`, and a display `status`. */
|
|
49
|
+
function violationParams(v, opts = {}) {
|
|
50
|
+
const fields = { ...v };
|
|
51
|
+
const params = {};
|
|
52
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
53
|
+
if (key === 'reason')
|
|
54
|
+
continue;
|
|
55
|
+
if (typeof value === 'string' || typeof value === 'number')
|
|
56
|
+
params[key] = String(value);
|
|
57
|
+
}
|
|
58
|
+
const n = sessionNumberOf(fields);
|
|
59
|
+
if (n !== null)
|
|
60
|
+
params.session_number = n;
|
|
61
|
+
const extra = opts.extra ?? {};
|
|
62
|
+
for (const [key, value] of Object.entries(extra))
|
|
63
|
+
params[key] = String(value);
|
|
64
|
+
const statusWord = extra.status_word;
|
|
65
|
+
if (typeof statusWord === 'string') {
|
|
66
|
+
params.status = (0, message_labels_cjs_1.label)('discovery.status', statusWord, { audience: opts.audience ?? 'developer' });
|
|
67
|
+
}
|
|
68
|
+
return params;
|
|
69
|
+
}
|
|
70
|
+
/** One violation as text for an audience. An unknown reason renders its code, never throws. */
|
|
71
|
+
function renderViolation(domain, v, opts) {
|
|
72
|
+
const code = `${domain}.${v.reason}`;
|
|
73
|
+
const params = violationParams(v, opts);
|
|
74
|
+
if (opts.audience === 'developer')
|
|
75
|
+
return (0, messages_cjs_1.msgDeveloper)(code, params);
|
|
76
|
+
const lines = (0, messages_cjs_1.msg)(code, params, { audience: opts.audience }).split('\n');
|
|
77
|
+
return lines.map((line, i) => (i === 0 ? `- ${line}` : ` ${line}`)).join('\n');
|
|
78
|
+
}
|
package/pp-core/bin/pp-tools.cjs
CHANGED
|
@@ -71,6 +71,23 @@
|
|
|
71
71
|
* [--plan P] [--id ID]
|
|
72
72
|
* timeline summary <phase> [--banner] Fold a phase's runs: wall-clock, agent time, longest
|
|
73
73
|
*
|
|
74
|
+
* Audience (Phase 39):
|
|
75
|
+
* audience status [--raw] Show the person's audience, language and where each came from
|
|
76
|
+
* audience set <business|engineer|developer> [--language en|nl | --clear-language]
|
|
77
|
+
* Save the setting in ~/.gsd/person.json (GSD_HOME-rooted); no
|
|
78
|
+
* --language keeps a stored language, --clear-language removes it
|
|
79
|
+
* audience set --clear-language Remove only the stored language; the stored audience is untouched
|
|
80
|
+
* audience answer <reply> Save a first-run reply (1, 2, 3 or a name); refused when the
|
|
81
|
+
* reply is blank or an audience is already saved
|
|
82
|
+
* PP_AUDIENCE=<value> Override the audience for one command or shell; never stored
|
|
83
|
+
*
|
|
84
|
+
* Explain (Phase 41):
|
|
85
|
+
* explain <code> [--raw] Longer explanation of a message's Ref code, in the person's wording
|
|
86
|
+
*
|
|
87
|
+
* Gate card (Phase 43):
|
|
88
|
+
* gate-card <query> [--state blocked|advisory|error] [--details] [--raw]
|
|
89
|
+
* The stopped-check card in the person's wording; prints nothing for a developer
|
|
90
|
+
*
|
|
74
91
|
* Ceremony (Phase 37):
|
|
75
92
|
* ceremony classify <phase> [--full] [--research] [--skip-research] [--skip-verify] [--write]
|
|
76
93
|
* Classify a phase (trust boundary, codebase to match, single-concern), print the
|
|
@@ -210,7 +227,7 @@ const path = require('path');
|
|
|
210
227
|
const readline = require('readline');
|
|
211
228
|
const { ExitError, runMain } = require('./lib/cli-exit.cjs');
|
|
212
229
|
const io = require('./lib/io.cjs');
|
|
213
|
-
const { error, ERROR_REASON, setJsonErrorMode, output } = io;
|
|
230
|
+
const { error, errorCode, ERROR_REASON, setJsonErrorMode, output } = io;
|
|
214
231
|
const projectRoot = require('./lib/project-root.cjs');
|
|
215
232
|
// Resolve findProjectRoot lazily at call time rather than binding it at module
|
|
216
233
|
// load. It is sourced from project-root.cjs; a call-time lookup is robust
|
|
@@ -226,6 +243,9 @@ const verify = require('./lib/verify.cjs');
|
|
|
226
243
|
const config = require('./lib/config.cjs');
|
|
227
244
|
const platformBindings = require('./lib/platform-bindings.cjs');
|
|
228
245
|
const timeline = require('./lib/timeline.cjs');
|
|
246
|
+
const audience = require('./lib/audience-command.cjs');
|
|
247
|
+
const explain = require('./lib/explain-command.cjs');
|
|
248
|
+
const gateCard = require('./lib/gate-card.cjs');
|
|
229
249
|
const composeBindings = require('./lib/compose-bindings.cjs');
|
|
230
250
|
const initContract = require('./lib/init-contract.cjs');
|
|
231
251
|
const initInteractive = require('./lib/init-interactive.cjs');
|
|
@@ -538,11 +558,11 @@ async function main() {
|
|
|
538
558
|
// discovery; previously it was a partial subset that didn't include
|
|
539
559
|
// phase / roadmap / milestone / progress / etc.
|
|
540
560
|
const TOP_LEVEL_USAGE = 'Usage: pp-tools <command> [args] [--raw] [--pick <field>] [--cwd <path>] [--ws <name>] [--json-errors]\n' +
|
|
541
|
-
'Commands: agent, agent-skills, audit-open, audit-uat, check, check-commit, commit, commit-to-subrepo, ' +
|
|
561
|
+
'Commands: agent, agent-skills, audience, audit-open, audit-uat, check, check-commit, commit, commit-to-subrepo, ' +
|
|
542
562
|
'compose-bindings, config-ensure-section, config-get, config-new-project, config-path, config-set, migrate-config, ' +
|
|
543
|
-
'current-timestamp, detect-custom-files, discovery, docs-init, drift-guard, effort, extract-messages, find-phase, ' +
|
|
563
|
+
'current-timestamp, detect-custom-files, discovery, docs-init, drift-guard, effort, explain, extract-messages, find-phase, ' +
|
|
544
564
|
'from-gsd2, frontmatter, gap-analysis, generate-claude-md, generate-claude-profile, ' +
|
|
545
|
-
'generate-client-context, generate-dev-preferences, generate-slug, graphify, history-digest, init, init-contract, intel, ' +
|
|
565
|
+
'gate-card, generate-client-context, generate-dev-preferences, generate-slug, graphify, history-digest, init, init-contract, intel, ' +
|
|
546
566
|
'capability, ceremony, classify-confidence, git, learnings, list-todos, loop, milestone, package-legitimacy, phase, phase-plan-index, phases, platform-bindings, platform-latest-tag, profile-questionnaire, ' +
|
|
547
567
|
'profile-sample, progress, prompt-budget, requirements, research-plan, research-store, resolve-granularity, resolve-model, roadmap, scaffold, state, ' +
|
|
548
568
|
'task, template, timeline, user-story, validate, validate-bindings, verify, verify-path-exists, verify-summary, workstream, worktree\n\n' +
|
|
@@ -570,7 +590,8 @@ async function main() {
|
|
|
570
590
|
// through this dispatcher. Rendering top-level usage on --help is strictly
|
|
571
591
|
// better UX than the old short-circuit that printed unrelated usage text.
|
|
572
592
|
const HELP_FLAGS = new Set(['-h', '--help', '-?', '--h', '--usage']);
|
|
573
|
-
|
|
593
|
+
// Phase 41: `explain` answers its own help (its usage line names the Ref code), so it is not intercepted here.
|
|
594
|
+
if (command !== 'explain' && args.some((a) => HELP_FLAGS.has(a))) {
|
|
574
595
|
process.stdout.write(TOP_LEVEL_USAGE + '\n');
|
|
575
596
|
return;
|
|
576
597
|
}
|
|
@@ -732,14 +753,19 @@ function createReadlinePrompter({ input = process.stdin, output = process.stdout
|
|
|
732
753
|
const rl = readline.createInterface({ input, output });
|
|
733
754
|
return {
|
|
734
755
|
ask(question) {
|
|
756
|
+
// Phase 41: the chrome is catalog text. A developer (no `audience` on the question) renders
|
|
757
|
+
// exactly what this prompter always printed; business and engineer get their wording and a Ref line.
|
|
758
|
+
const messages = require('./lib/messages.cjs');
|
|
759
|
+
const aud = question.audience === 'business' || question.audience === 'engineer' ? question.audience : 'developer';
|
|
760
|
+
const chrome = (code, params) => messages.text(code, params, { audience: aud });
|
|
735
761
|
let rendered = question.prompt;
|
|
736
762
|
if (question.options && question.options.length > 0) {
|
|
737
763
|
const optionsRendered = question.options
|
|
738
764
|
.map((opt) =>
|
|
739
|
-
opt === question.preselected ? `${opt} (${question.preselectedLabel || 'preselected'})` : opt,
|
|
765
|
+
opt === question.preselected ? `${opt} (${question.preselectedLabel || chrome('init.chrome.preselected', {})})` : opt,
|
|
740
766
|
)
|
|
741
767
|
.join(' / ');
|
|
742
|
-
rendered += `\n
|
|
768
|
+
rendered += `\n ${chrome('init.chrome.options', { options: optionsRendered })}`;
|
|
743
769
|
}
|
|
744
770
|
if (question.optionalAnswer && !/press Enter to skip/i.test(question.prompt)) {
|
|
745
771
|
// UAT-13-F2: a detected org profile is a HINT inside the prompt text
|
|
@@ -747,7 +773,10 @@ function createReadlinePrompter({ input = process.stdin, output = process.stdout
|
|
|
747
773
|
// text may already carry the skip hint (the org profile prompts do),
|
|
748
774
|
// so it is added only for an optional question whose text lacks it
|
|
749
775
|
// (I-1).
|
|
750
|
-
rendered +=
|
|
776
|
+
rendered += `\n ${chrome('init.chrome.skip_hint', {})}`;
|
|
777
|
+
}
|
|
778
|
+
if (aud !== 'developer' && typeof question.code === 'string') {
|
|
779
|
+
rendered += `\n${messages.refLine(question.code)}`;
|
|
751
780
|
}
|
|
752
781
|
return new Promise((resolve) => {
|
|
753
782
|
rl.question(`${rendered}\n> `, (answer) => resolve(answer));
|
|
@@ -866,6 +895,21 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
866
895
|
break;
|
|
867
896
|
}
|
|
868
897
|
|
|
898
|
+
case 'audience': {
|
|
899
|
+
audience.cmdAudience(cwd, args.slice(1), raw);
|
|
900
|
+
break;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
case 'explain': {
|
|
904
|
+
explain.cmdExplain(cwd, args.slice(1), raw);
|
|
905
|
+
break;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
case 'gate-card': {
|
|
909
|
+
gateCard.cmdGateCard(cwd, args.slice(1), raw);
|
|
910
|
+
break;
|
|
911
|
+
}
|
|
912
|
+
|
|
869
913
|
case 'ceremony': {
|
|
870
914
|
// WR-04: loaded here, not at the top, so a missing ceremony-gates.json fails
|
|
871
915
|
// only the ceremony command, never every pp-tools command.
|
|
@@ -1659,35 +1703,28 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
1659
1703
|
const INIT_INTERACTIVE_FLAGS = ['--offline', '--reset'];
|
|
1660
1704
|
const unrecognizedInitFlag = args.slice(1).find((a) => !INIT_INTERACTIVE_FLAGS.includes(a));
|
|
1661
1705
|
if (unrecognizedInitFlag !== undefined) {
|
|
1662
|
-
|
|
1663
|
-
`Unrecognized flag "${unrecognizedInitFlag}" for init. Supported: --offline, --reset. ` +
|
|
1664
|
-
'For init workflow subcommands (execute-phase, plan-phase, new-project, ...), use "pp-tools query init.<subcommand>".',
|
|
1665
|
-
ERROR_REASON.SDK_FAIL_FAST,
|
|
1666
|
-
);
|
|
1706
|
+
errorCode('init.unknown_flag', { flag: unrecognizedInitFlag }, ERROR_REASON.SDK_FAIL_FAST);
|
|
1667
1707
|
break;
|
|
1668
1708
|
}
|
|
1669
1709
|
// Refuse to prompt when stdin is not a TTY — never block on stdin
|
|
1670
1710
|
// in CI; a hang here would be worse than a failure (T-34-71).
|
|
1671
1711
|
if (!process.stdin.isTTY) {
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
'This command cannot run inside an AI-agent shell or a piped session — open a regular ' +
|
|
1675
|
-
'terminal window (e.g. Terminal, iTerm, or Windows Terminal) and run it there. ' +
|
|
1676
|
-
'For a non-interactive/CI-driven session, use "pp-tools query init-contract --session-next" / ' +
|
|
1677
|
-
'"--session-answer <stepId>=<value>" directly instead.',
|
|
1678
|
-
ERROR_REASON.INIT_NON_INTERACTIVE,
|
|
1679
|
-
);
|
|
1712
|
+
// The catalog code is the ERROR_REASON value (D-03); --json-errors keeps the developer text for every audience.
|
|
1713
|
+
errorCode(ERROR_REASON.INIT_NON_INTERACTIVE, {}, ERROR_REASON.INIT_NON_INTERACTIVE);
|
|
1680
1714
|
break;
|
|
1681
1715
|
}
|
|
1682
1716
|
if (args.includes('--reset')) {
|
|
1683
1717
|
initContract.initSessionReset(cwd);
|
|
1684
1718
|
}
|
|
1685
1719
|
const prompter = createReadlinePrompter();
|
|
1720
|
+
const initAudience = require('./lib/messages.cjs').audienceOf(process.env);
|
|
1686
1721
|
try {
|
|
1687
1722
|
const result = await initInteractive.runInitInteractive(cwd, prompter, {
|
|
1688
1723
|
offline: args.includes('--offline'),
|
|
1724
|
+
audience: initAudience,
|
|
1689
1725
|
});
|
|
1690
|
-
|
|
1726
|
+
// Business users and engineers read the human result above; the JSON stays for developers and --raw.
|
|
1727
|
+
if (initInteractive.printsResultJson(initAudience, raw)) output(result, raw);
|
|
1691
1728
|
if (!result.ok) {
|
|
1692
1729
|
process.exitCode = 1;
|
|
1693
1730
|
}
|
|
@@ -2773,13 +2810,45 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
2773
2810
|
} catch (deviationsCheckErr) {
|
|
2774
2811
|
error(`discovery deviations-check: ${deviationsCheckErr && deviationsCheckErr.message ? deviationsCheckErr.message : String(deviationsCheckErr)}`, ERROR_REASON.UNKNOWN);
|
|
2775
2812
|
}
|
|
2813
|
+
} else if (subcommand === 'tests-check') {
|
|
2814
|
+
// discovery tests-check [--require-complete] — _tests.md structure:
|
|
2815
|
+
// one risk row per main process with the matrix rating, end-to-end
|
|
2816
|
+
// for every high risk unless a D-09 decision says why, focus-area
|
|
2817
|
+
// subjects resolving, one acceptance row per story.
|
|
2818
|
+
// --require-complete also fails on an open story or an uncited focus
|
|
2819
|
+
// area (discovery_tests_incomplete).
|
|
2820
|
+
const testsRegister = require('./lib/discovery-tests.cjs');
|
|
2821
|
+
try {
|
|
2822
|
+
testsRegister.cmdDiscoveryTestsCheck(cwd, args, raw);
|
|
2823
|
+
} catch (testsCheckErr) {
|
|
2824
|
+
error(`discovery tests-check: ${testsCheckErr && testsCheckErr.message ? testsCheckErr.message : String(testsCheckErr)}`, ERROR_REASON.UNKNOWN);
|
|
2825
|
+
}
|
|
2826
|
+
} else if (subcommand === 'status') {
|
|
2827
|
+
// discovery status [--raw] — Phase 42 DOOR-01: discovery-only vs build, sprint state and next step; read-only. Exits 1 only on a usage error, or before it runs when the dispatcher refuses an invalid GSD_WORKSTREAM
|
|
2828
|
+
const discoveryStatus = require('./lib/discovery-status.cjs');
|
|
2829
|
+
try {
|
|
2830
|
+
discoveryStatus.cmdDiscoveryStatus(cwd, args, raw);
|
|
2831
|
+
} catch (statusErr) {
|
|
2832
|
+
error(`discovery status: ${statusErr && statusErr.message ? statusErr.message : String(statusErr)}`, ERROR_REASON.UNKNOWN);
|
|
2833
|
+
}
|
|
2834
|
+
} else if (subcommand === 'flow-page') {
|
|
2835
|
+
// discovery flow-page <MP-nn> [--out <file.html>] — render one main
|
|
2836
|
+
// process (swimlane, sub-processes, step matrix) from _processes.md
|
|
2837
|
+
// as a page the agent publishes and republishes while the room
|
|
2838
|
+
// iterates the diagram (Session 3 Step 5C).
|
|
2839
|
+
const flowPage = require('./lib/discovery-flow-page.cjs');
|
|
2840
|
+
try {
|
|
2841
|
+
flowPage.cmdDiscoveryFlowPage(cwd, args, raw);
|
|
2842
|
+
} catch (flowPageErr) {
|
|
2843
|
+
error(`discovery flow-page: ${flowPageErr && flowPageErr.message ? flowPageErr.message : String(flowPageErr)}`, ERROR_REASON.UNKNOWN);
|
|
2844
|
+
}
|
|
2776
2845
|
} else {
|
|
2777
2846
|
// WIRING, NOT HELP TEXT: tests/discovery-artifacts.test.cjs derives each
|
|
2778
2847
|
// subcommand spelling by regexing `<namespace>_<subcommand>_failed` out of
|
|
2779
2848
|
// the engine's exported exit identity, splits this string on commas and
|
|
2780
2849
|
// asserts membership. A spelling missing here is a command the engine
|
|
2781
2850
|
// claims to be and the router does not dispatch.
|
|
2782
|
-
error('Unknown discovery subcommand. Available: rollup, input-check, research-check, screen-check, element-sweep, rebaseline-check, vault-check, charter-check, entity-check, process-check, rules-check, stories-check, phases-check, for-phase, roles-check, integrations-check, migration-check, deviations-check, session-check', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
2851
|
+
error('Unknown discovery subcommand. Available: rollup, input-check, research-check, screen-check, element-sweep, rebaseline-check, vault-check, charter-check, entity-check, process-check, rules-check, stories-check, phases-check, for-phase, roles-check, integrations-check, migration-check, deviations-check, session-check, tests-check, flow-page, status', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
2783
2852
|
}
|
|
2784
2853
|
break;
|
|
2785
2854
|
}
|