@pareto4/pp-core 1.12.0 → 1.13.0
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/agents/pp-code-reviewer.md +1 -1
- package/agents/pp-executor.md +2 -13
- package/agents/pp-planner.md +1 -1
- package/agents/pp-vault-reviewer.md +51 -0
- package/commands/pp-ns-review.md +2 -1
- package/commands/pp-vault-review.md +33 -0
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/pp-core/bin/lib/capability-activation.cjs +19 -9
- package/pp-core/bin/lib/capability-registry.cjs +200 -10
- package/pp-core/bin/lib/check-command-router.cjs +1242 -37
- package/pp-core/bin/lib/clusters.cjs +1 -0
- package/pp-core/bin/lib/command-aliases.cjs +7 -0
- package/pp-core/bin/lib/compose-bindings.cjs +41 -27
- package/pp-core/bin/lib/config-loader.cjs +151 -8
- package/pp-core/bin/lib/decisions.cjs +104 -27
- package/pp-core/bin/lib/dispatch-plan.cjs +12 -1
- package/pp-core/bin/lib/frontmatter.cjs +37 -0
- package/pp-core/bin/lib/git-base-branch.cjs +136 -10
- package/pp-core/bin/lib/init-contract.cjs +180 -66
- package/pp-core/bin/lib/init-interactive.cjs +4 -20
- package/pp-core/bin/lib/loop-gates.cjs +213 -0
- package/pp-core/bin/lib/loop-resolver.cjs +111 -35
- package/pp-core/bin/lib/org-profile.cjs +218 -44
- package/pp-core/bin/lib/phase-command-router.cjs +7 -1
- package/pp-core/bin/lib/phase-manifest.cjs +527 -0
- package/pp-core/bin/lib/phase-tier.cjs +15 -2
- package/pp-core/bin/lib/phase.cjs +51 -1
- package/pp-core/bin/lib/vault-audit.cjs +2070 -0
- package/pp-core/bin/lib/vault-command-router.cjs +460 -1
- package/pp-core/bin/lib/vault-practices.cjs +1142 -104
- package/pp-core/bin/lib/vault-review.cjs +2359 -0
- package/pp-core/bin/lib/vault-trace.cjs +2831 -0
- package/pp-core/bin/lib/vault-update.cjs +266 -0
- package/pp-core/bin/lib/vault-write-back.cjs +19 -0
- package/pp-core/bin/lib/vault.cjs +313 -11
- package/pp-core/bin/pp-tools.cjs +30 -13
- package/pp-core/bin/shared/model-catalog.json +2 -1
- package/pp-core/references/executor-vault-trace.md +47 -0
- package/pp-core/references/loop-hook-dispatch.md +20 -2
- package/pp-core/references/planner-vault-practices.md +46 -1
- package/pp-core/references/planning-config.md +1 -1
- package/pp-core/templates/practices/README.md +140 -11
- package/pp-core/templates/project-charter.md +226 -0
- package/pp-core/workflows/add-tests.md +4 -0
- package/pp-core/workflows/audit-fix.md +2 -0
- package/pp-core/workflows/audit-milestone.md +1 -1
- package/pp-core/workflows/autonomous.md +3 -3
- package/pp-core/workflows/check-todos.md +1 -1
- package/pp-core/workflows/code-review-fix.md +3 -1
- package/pp-core/workflows/code-review.md +1 -1
- package/pp-core/workflows/debug.md +4 -0
- package/pp-core/workflows/discovery-sprint/prefill.md +9 -0
- package/pp-core/workflows/discovery-sprint/prototype.md +2 -1
- package/pp-core/workflows/discovery-sprint/vault-fetch.md +37 -0
- package/pp-core/workflows/discovery-sprint.md +1 -1
- package/pp-core/workflows/discovery-to-build.md +1 -1
- package/pp-core/workflows/discuss-phase.md +2 -2
- package/pp-core/workflows/execute-phase/steps/serial-group-dispatch.md +1 -0
- package/pp-core/workflows/execute-phase/steps/vault-door-gate.md +19 -0
- package/pp-core/workflows/execute-phase/steps/vault-fetch.md +1 -1
- package/pp-core/workflows/execute-phase/steps/vault-load-gate.md +10 -8
- package/pp-core/workflows/execute-phase.md +29 -12
- package/pp-core/workflows/execute-plan.md +4 -0
- package/pp-core/workflows/fast.md +4 -0
- package/pp-core/workflows/help/modes/full.md +3 -2
- package/pp-core/workflows/init.md +2 -2
- package/pp-core/workflows/new-project.md +1 -1
- package/pp-core/workflows/plan-phase/steps/phase-tier.md +9 -0
- package/pp-core/workflows/plan-phase.md +13 -10
- package/pp-core/workflows/quick.md +5 -1
- package/pp-core/workflows/secure-phase.md +1 -1
- package/pp-core/workflows/ship.md +7 -3
- package/pp-core/workflows/undo.md +1 -1
- package/pp-core/workflows/validate-phase.md +5 -1
- package/pp-core/workflows/vault-review.md +65 -0
- package/pp-core/workflows/verify-work.md +6 -3
- package/scripts/gen-capability-registry.cjs +11 -0
- package/scripts/lint-test-file-count.allowlist.json +10 -1
- package/scripts/prompt-injection-scan.sh +2 -0
|
@@ -48,10 +48,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
48
48
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
49
49
|
};
|
|
50
50
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
51
|
-
exports.HOST_FORBIDDEN_CHARS = exports.PROFILE_REJECTED_CLONE_TARGET_PATTERNS = exports.ORG_PROFILE_SEED_KEYS = exports.ORG_PROFILE_SEED_SECTION = exports.ORG_PROFILE_ALLOWLIST = exports.ORG_PROFILE_DATA_FILE = exports.ORG_PROFILE_CONFIG_KEY = void 0;
|
|
51
|
+
exports.HOST_FORBIDDEN_CHARS = exports.PROFILE_REJECTED_CLONE_TARGET_PATTERNS = exports.ORG_PROFILE_SEED_KEYS = exports.ORG_PROFILE_SEED_SECTION = exports.ORG_PROFILE_ALLOWLIST = exports.TRACE_ACCEPTED_PREDICATE_MAX = exports.TRACE_ACCEPTED_PREDICATES_CAP = exports.VAULT_TRACE_ACCEPTED_PREDICATES_KEY = exports.VAULT_REQUIRED_CONFIG_KEY = exports.ORG_PROFILE_DATA_FILE = exports.ORG_PROFILE_CONFIG_KEY = void 0;
|
|
52
|
+
exports.resolveGlobalPackageRootCandidates = resolveGlobalPackageRootCandidates;
|
|
53
|
+
exports.globalRootCandidatesFor = globalRootCandidatesFor;
|
|
52
54
|
exports.resolveOrgProfileDocument = resolveOrgProfileDocument;
|
|
53
55
|
exports.resolveOrgProfile = resolveOrgProfile;
|
|
54
56
|
exports.readProfileValue = readProfileValue;
|
|
57
|
+
exports.resolveVaultRequired = resolveVaultRequired;
|
|
58
|
+
exports.resolveAcceptedTracePredicates = resolveAcceptedTracePredicates;
|
|
55
59
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
56
60
|
const node_path_1 = __importDefault(require("node:path"));
|
|
57
61
|
// ─── Constants ───────────────────────────────────────────────────────────────
|
|
@@ -62,10 +66,41 @@ exports.ORG_PROFILE_CONFIG_KEY = 'org.profile';
|
|
|
62
66
|
/** The fixed filename read from the resolved package's own directory — never the
|
|
63
67
|
* package's `main`/`exports` entry, and never loaded via `require` (D-08). */
|
|
64
68
|
exports.ORG_PROFILE_DATA_FILE = 'org-profile.json';
|
|
69
|
+
/** The dotted key naming the vault load policy, declared boolean (default
|
|
70
|
+
* false) by `capabilities/vault/capability.json`. Named once here so neither
|
|
71
|
+
* the allowlist, the value guard nor `resolveVaultRequired` restates it. */
|
|
72
|
+
exports.VAULT_REQUIRED_CONFIG_KEY = 'vault.required';
|
|
65
73
|
/**
|
|
66
|
-
*
|
|
74
|
+
* The dotted key naming the practice-trace predicate types a READER accepts
|
|
75
|
+
* besides pp-core's own (`TRACE_PREDICATE_TYPE` in src/vault-trace.cts). It
|
|
76
|
+
* exists so a project that switches engine mid-engagement can keep reading
|
|
77
|
+
* the traces its earlier engine wrote: the org profile of that engagement
|
|
78
|
+
* lists the earlier engine's predicate URI here. Writers never use it — pp-core
|
|
79
|
+
* always writes its own URI — and pp-core itself names no such URI.
|
|
80
|
+
*/
|
|
81
|
+
exports.VAULT_TRACE_ACCEPTED_PREDICATES_KEY = 'vault.trace.accepted_predicates';
|
|
82
|
+
/** At most this many accepted predicate URIs, each at most this long. */
|
|
83
|
+
exports.TRACE_ACCEPTED_PREDICATES_CAP = 8;
|
|
84
|
+
exports.TRACE_ACCEPTED_PREDICATE_MAX = 200;
|
|
85
|
+
/**
|
|
86
|
+
* D-06's fixed allowlist: a profile may contribute exactly these seven dotted
|
|
67
87
|
* config keys. Entry order is not contractual; membership is. Anything else
|
|
68
88
|
* present in a profile document is dropped with a bounded warning.
|
|
89
|
+
*
|
|
90
|
+
* FIVE COORDINATES AND ONE POLICY. The first five answer where to fetch from.
|
|
91
|
+
* `vault.required` answers whether the vault load is mandatory, so one
|
|
92
|
+
* org-profile package can turn the load gate on across a fleet without a
|
|
93
|
+
* per-repository edit. That is this list's sharpest widening: an `npm update`
|
|
94
|
+
* of the profile package can move a build gate without a review in the
|
|
95
|
+
* consuming repository. It is bounded three ways: project config outranks the
|
|
96
|
+
* profile, so any project can opt out explicitly; the profile can only raise
|
|
97
|
+
* the gate from the built-in default `false` (a gate turning on fails
|
|
98
|
+
* closed); and the value guard accepts only the two boolean literals.
|
|
99
|
+
* `vault.enabled` (arming the capability) stays off the list.
|
|
100
|
+
*
|
|
101
|
+
* `vault.trace.accepted_predicates` is READ-ONLY widening: it lets the trace
|
|
102
|
+
* readers accept another engine's predicate URI (a list of https URIs). It
|
|
103
|
+
* changes no writer and no gate polarity.
|
|
69
104
|
*/
|
|
70
105
|
exports.ORG_PROFILE_ALLOWLIST = Object.freeze([
|
|
71
106
|
'vault.provider',
|
|
@@ -73,6 +108,8 @@ exports.ORG_PROFILE_ALLOWLIST = Object.freeze([
|
|
|
73
108
|
'vault.fallback_ref',
|
|
74
109
|
'vault.fallback_host',
|
|
75
110
|
'platform.host',
|
|
111
|
+
exports.VAULT_REQUIRED_CONFIG_KEY,
|
|
112
|
+
exports.VAULT_TRACE_ACCEPTED_PREDICATES_KEY,
|
|
76
113
|
]);
|
|
77
114
|
/**
|
|
78
115
|
* The single top-level key naming the Phase 34 init-only seed section.
|
|
@@ -220,62 +257,108 @@ function describeUnknown(value) {
|
|
|
220
257
|
return Object.prototype.toString.call(value);
|
|
221
258
|
}
|
|
222
259
|
}
|
|
223
|
-
// ───
|
|
260
|
+
// ─── Global package roots ────────────────────────────────────────────────────
|
|
224
261
|
/**
|
|
225
|
-
* D-03's "global" half of the two-path resolution ladder, as a pure
|
|
226
|
-
*
|
|
227
|
-
*
|
|
262
|
+
* D-03's "global" half of the two-path resolution ladder, as a pure heuristic
|
|
263
|
+
* with NO subprocess spawn. Returns an ORDERED list of existing candidate
|
|
264
|
+
* roots (a single root silently missed Homebrew layouts):
|
|
228
265
|
*
|
|
229
266
|
* 1. `NPM_CONFIG_PREFIX` / `npm_config_prefix`, when set to a non-blank
|
|
230
267
|
* string: `<prefix>/node_modules` on `win32`, `<prefix>/lib/node_modules`
|
|
231
268
|
* otherwise.
|
|
232
|
-
* 2.
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
269
|
+
* 2. Every PATH directory containing a `node` entry, mapped through the same
|
|
270
|
+
* `<bin>` → `<global tree>` rule — WITHOUT resolving symlinks. This is what
|
|
271
|
+
* finds Homebrew: `process.execPath` resolves `/opt/homebrew/bin/node` into
|
|
272
|
+
* the Cellar, whose `lib/node_modules` exists but holds only npm, while the
|
|
273
|
+
* real global tree lives at `/opt/homebrew/lib/node_modules` beside the
|
|
274
|
+
* PATH symlink.
|
|
275
|
+
* 3. Derived from `process.execPath` — still correct for nvm/Volta layouts.
|
|
236
276
|
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
277
|
+
* Candidates are deduped and filtered to `fs.existsSync`; resolution tries
|
|
278
|
+
* each in order (first hit wins), so an existing-but-empty earlier root cannot
|
|
279
|
+
* mask a later root that holds the package.
|
|
239
280
|
*
|
|
240
|
-
*
|
|
241
|
-
* `
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
* rather than failing.
|
|
281
|
+
* Detection (the `/pp-init` org-profile hint and its invariant check) passes
|
|
282
|
+
* `explicitPrefixOnly: true`: with `NPM_CONFIG_PREFIX` set, only the prefix
|
|
283
|
+
* root is scanned, because a hint must never come from a tree the operator did
|
|
284
|
+
* not point at. Detection roots are therefore a subset of resolution roots.
|
|
245
285
|
*
|
|
246
|
-
* pp-core's own install directory is deliberately NOT a candidate
|
|
247
|
-
*
|
|
248
|
-
* and no `node_modules` of its own, so resolving relative to it would
|
|
249
|
-
* never find a sibling package.
|
|
286
|
+
* pp-core's own install directory is deliberately NOT a candidate: the
|
|
287
|
+
* runtime install is a bare directory copy with no `node_modules` of its own.
|
|
250
288
|
*/
|
|
251
|
-
|
|
289
|
+
const GLOBAL_ROOT_PATH_SCAN_CAP = 64;
|
|
290
|
+
/** npm's own `<bin dir>` → `<global node_modules>` mapping. */
|
|
291
|
+
function globalRootFromBinDir(binDir) {
|
|
292
|
+
return process.platform === 'win32' ? node_path_1.default.join(binDir, 'node_modules') : node_path_1.default.resolve(binDir, '..', 'lib', 'node_modules');
|
|
293
|
+
}
|
|
294
|
+
function resolveGlobalPackageRootCandidates(opts = {}) {
|
|
295
|
+
const candidates = [];
|
|
296
|
+
const push = (c) => {
|
|
297
|
+
if (!candidates.includes(c))
|
|
298
|
+
candidates.push(c);
|
|
299
|
+
};
|
|
300
|
+
const existing = () => candidates.filter((c) => {
|
|
301
|
+
try {
|
|
302
|
+
return node_fs_1.default.existsSync(c);
|
|
303
|
+
}
|
|
304
|
+
catch {
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
});
|
|
252
308
|
try {
|
|
253
309
|
const prefixEnv = process.env.NPM_CONFIG_PREFIX ?? process.env.npm_config_prefix;
|
|
254
|
-
let candidate;
|
|
255
310
|
if (typeof prefixEnv === 'string' && prefixEnv.trim() !== '') {
|
|
256
311
|
const prefix = prefixEnv.trim();
|
|
257
|
-
|
|
258
|
-
|
|
312
|
+
push(process.platform === 'win32' ? node_path_1.default.join(prefix, 'node_modules') : node_path_1.default.join(prefix, 'lib', 'node_modules'));
|
|
313
|
+
// An explicit prefix is authoritative for detection; one that points
|
|
314
|
+
// nowhere yields [] — never a fall-through to PATH.
|
|
315
|
+
if (opts.explicitPrefixOnly === true)
|
|
316
|
+
return existing();
|
|
259
317
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
318
|
+
const pathDirs = (process.env.PATH ?? '')
|
|
319
|
+
.split(node_path_1.default.delimiter)
|
|
320
|
+
.filter((d) => d.trim() !== '')
|
|
321
|
+
.slice(0, GLOBAL_ROOT_PATH_SCAN_CAP);
|
|
322
|
+
const nodeNames = process.platform === 'win32' ? ['node.exe', 'node.cmd', 'node'] : ['node'];
|
|
323
|
+
for (const dir of pathDirs) {
|
|
324
|
+
for (const name of nodeNames) {
|
|
325
|
+
let found = false;
|
|
326
|
+
try {
|
|
327
|
+
found = node_fs_1.default.existsSync(node_path_1.default.join(dir, name));
|
|
328
|
+
}
|
|
329
|
+
catch {
|
|
330
|
+
// unreadable PATH entry — skip it
|
|
331
|
+
}
|
|
332
|
+
if (found) {
|
|
333
|
+
push(globalRootFromBinDir(dir));
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
266
337
|
}
|
|
267
|
-
|
|
338
|
+
push(globalRootFromBinDir(node_path_1.default.dirname(process.execPath)));
|
|
268
339
|
}
|
|
269
340
|
catch {
|
|
270
|
-
|
|
341
|
+
// degrade to whatever was collected before the fault
|
|
271
342
|
}
|
|
343
|
+
return existing();
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Map the `_globalRoot` test seam (a single root, or `null` for "no global
|
|
347
|
+
* root found") onto the candidate-list shape; when the seam is absent, run the
|
|
348
|
+
* production heuristic. The seam wins over `explicitPrefixOnly`.
|
|
349
|
+
*/
|
|
350
|
+
function globalRootCandidatesFor(opts) {
|
|
351
|
+
if (opts && opts._globalRoot !== undefined) {
|
|
352
|
+
return opts._globalRoot === null ? [] : [opts._globalRoot];
|
|
353
|
+
}
|
|
354
|
+
return resolveGlobalPackageRootCandidates({ explicitPrefixOnly: opts?.explicitPrefixOnly === true });
|
|
272
355
|
}
|
|
273
356
|
// ─── resolveProfilePackage ───────────────────────────────────────────────────
|
|
274
357
|
/**
|
|
275
358
|
* D-03's two-path resolution ladder. Attempt `require.resolve(pkgName +
|
|
276
359
|
* '/package.json', { paths: [cwd] })` first and report `source: 'project'`;
|
|
277
|
-
* on failure attempt the same with `paths: [
|
|
278
|
-
*
|
|
360
|
+
* on failure attempt the same with `paths: [root]` for each global candidate
|
|
361
|
+
* root in order and report `source: 'global'`; otherwise `null`. The
|
|
279
362
|
* project-local hit always wins when both would resolve — the global
|
|
280
363
|
* attempt is only ever reached when the project attempt threw.
|
|
281
364
|
*
|
|
@@ -283,19 +366,19 @@ function resolveGlobalPackageRoot() {
|
|
|
283
366
|
* 220-228): the same try/catch-returns-null shape, one level removed
|
|
284
367
|
* (resolving a data file's sibling `package.json`, not a CLI entry point).
|
|
285
368
|
*/
|
|
286
|
-
function resolveProfilePackage(pkgName, cwd,
|
|
369
|
+
function resolveProfilePackage(pkgName, cwd, globalRoots) {
|
|
287
370
|
try {
|
|
288
371
|
return { pkgJsonPath: require.resolve(`${pkgName}/package.json`, { paths: [cwd] }), source: 'project' };
|
|
289
372
|
}
|
|
290
373
|
catch {
|
|
291
|
-
// fall through to the global
|
|
374
|
+
// fall through to the global attempts below
|
|
292
375
|
}
|
|
293
|
-
|
|
376
|
+
for (const globalRoot of globalRoots) {
|
|
294
377
|
try {
|
|
295
378
|
return { pkgJsonPath: require.resolve(`${pkgName}/package.json`, { paths: [globalRoot] }), source: 'global' };
|
|
296
379
|
}
|
|
297
380
|
catch {
|
|
298
|
-
//
|
|
381
|
+
// try the next candidate root
|
|
299
382
|
}
|
|
300
383
|
}
|
|
301
384
|
return null;
|
|
@@ -388,6 +471,45 @@ function flattenProfileDocument(doc) {
|
|
|
388
471
|
*/
|
|
389
472
|
function profileValueDisposition(key, value) {
|
|
390
473
|
try {
|
|
474
|
+
// The one boolean-typed member, disposed of before the string-only scalar
|
|
475
|
+
// guard below would reject its natural JSON form. Carried as the canonical
|
|
476
|
+
// string "true" / "false".
|
|
477
|
+
if (key === exports.VAULT_REQUIRED_CONFIG_KEY) {
|
|
478
|
+
if (value === true || value === false)
|
|
479
|
+
return { ok: true, value: String(value) };
|
|
480
|
+
if (typeof value === 'string') {
|
|
481
|
+
const t = value.trim();
|
|
482
|
+
if (t === 'true' || t === 'false')
|
|
483
|
+
return { ok: true, value: t };
|
|
484
|
+
}
|
|
485
|
+
return { ok: false, reason: 'value must be the boolean true or false' };
|
|
486
|
+
}
|
|
487
|
+
// The one list-typed member. Carried forward as a canonical JSON array
|
|
488
|
+
// string, so `contributes` stays a string map.
|
|
489
|
+
if (key === exports.VAULT_TRACE_ACCEPTED_PREDICATES_KEY) {
|
|
490
|
+
const list = Array.isArray(value) ? value : null;
|
|
491
|
+
if (list === null || list.length === 0) {
|
|
492
|
+
return { ok: false, reason: 'value must be a non-empty list of https URIs' };
|
|
493
|
+
}
|
|
494
|
+
if (list.length > exports.TRACE_ACCEPTED_PREDICATES_CAP) {
|
|
495
|
+
return { ok: false, reason: `value lists more than ${exports.TRACE_ACCEPTED_PREDICATES_CAP} URIs` };
|
|
496
|
+
}
|
|
497
|
+
const kept = [];
|
|
498
|
+
for (const item of list) {
|
|
499
|
+
if (typeof item !== 'string')
|
|
500
|
+
return { ok: false, reason: 'every entry must be a string' };
|
|
501
|
+
const t = item.trim();
|
|
502
|
+
if (t.length > exports.TRACE_ACCEPTED_PREDICATE_MAX) {
|
|
503
|
+
return { ok: false, reason: `an entry is longer than ${exports.TRACE_ACCEPTED_PREDICATE_MAX} characters` };
|
|
504
|
+
}
|
|
505
|
+
if (!/^https:\/\/[^\s\x00-\x1f\x7f"'<>`\\]+$/.test(t)) {
|
|
506
|
+
return { ok: false, reason: 'every entry must be an https URI with no whitespace or quotes' };
|
|
507
|
+
}
|
|
508
|
+
if (!kept.includes(t))
|
|
509
|
+
kept.push(t);
|
|
510
|
+
}
|
|
511
|
+
return { ok: true, value: JSON.stringify(kept) };
|
|
512
|
+
}
|
|
391
513
|
if (typeof value !== 'string') {
|
|
392
514
|
return { ok: false, reason: 'value must be a string' };
|
|
393
515
|
}
|
|
@@ -458,7 +580,7 @@ function profileValueDisposition(key, value) {
|
|
|
458
580
|
* The `_globalRoot` option is the repo's `_`-prefixed test seam convention
|
|
459
581
|
* (as used by `_vault` / `_exec` / `_writeBack` in
|
|
460
582
|
* `src/vault-command-router.cts`); production callers omit it and the
|
|
461
|
-
* module
|
|
583
|
+
* module walks `resolveGlobalPackageRootCandidates()`. Passing `_globalRoot`
|
|
462
584
|
* explicitly (including `null`, to simulate "no global root found")
|
|
463
585
|
* overrides the production heuristic for that one call.
|
|
464
586
|
*
|
|
@@ -498,16 +620,19 @@ function resolveOrgProfileDocument(cwd, opts) {
|
|
|
498
620
|
if (pkgName === '') {
|
|
499
621
|
return absentDocShape();
|
|
500
622
|
}
|
|
501
|
-
// Step 2: resolve the package (project, then global).
|
|
502
|
-
const
|
|
503
|
-
const hit = resolveProfilePackage(pkgName, cwd,
|
|
623
|
+
// Step 2: resolve the package (project, then every global candidate).
|
|
624
|
+
const globalRoots = globalRootCandidatesFor(opts);
|
|
625
|
+
const hit = resolveProfilePackage(pkgName, cwd, globalRoots);
|
|
504
626
|
if (!hit) {
|
|
505
627
|
const echoed = sanitizeForWarning(pkgName);
|
|
628
|
+
const probed = globalRoots.length > 0
|
|
629
|
+
? `the global npm root(s) ${globalRoots.map((r) => sanitizeForWarning(r)).join(', ')}`
|
|
630
|
+
: 'no locatable global npm root';
|
|
506
631
|
return {
|
|
507
632
|
...absentDocShape(),
|
|
508
633
|
configured: pkgName,
|
|
509
634
|
warnings: [
|
|
510
|
-
bound(`org profile "${echoed}" was not found — resolution was attempted from the project and
|
|
635
|
+
bound(`org profile "${echoed}" was not found — resolution was attempted from the project and ${probed} and found nothing. Falling back to built-in defaults.`),
|
|
511
636
|
],
|
|
512
637
|
};
|
|
513
638
|
}
|
|
@@ -694,3 +819,52 @@ function readProfileValue(cwd, key, opts) {
|
|
|
694
819
|
source: resolution.source,
|
|
695
820
|
};
|
|
696
821
|
}
|
|
822
|
+
/**
|
|
823
|
+
* The ONE ladder for `vault.required`, which `computeVaultVerdict` (the
|
|
824
|
+
* verdict owner) and `vault org-profile` both read:
|
|
825
|
+
*
|
|
826
|
+
* 1. The project's `.planning/config.json`. A present value wins, whatever
|
|
827
|
+
* it is: a JSON boolean is used as is; anything else is treated as false
|
|
828
|
+
* and reported as invalid (the gate card names it), never passed on to the
|
|
829
|
+
* profile — an explicit project value is the project's decision.
|
|
830
|
+
* 2. The org profile, already narrowed to "true" / "false" by the value guard.
|
|
831
|
+
* 3. The registered default, false.
|
|
832
|
+
*
|
|
833
|
+
* Never throws.
|
|
834
|
+
*/
|
|
835
|
+
function resolveVaultRequired(cwd, opts) {
|
|
836
|
+
try {
|
|
837
|
+
const raw = cfgGet(cwd, exports.VAULT_REQUIRED_CONFIG_KEY, undefined);
|
|
838
|
+
if (raw !== undefined) {
|
|
839
|
+
return { required: raw === true, source: 'config', valid: typeof raw === 'boolean', raw, warnings: [] };
|
|
840
|
+
}
|
|
841
|
+
const profile = readProfileValue(cwd, exports.VAULT_REQUIRED_CONFIG_KEY, opts);
|
|
842
|
+
if (profile.value === 'true' || profile.value === 'false') {
|
|
843
|
+
return { required: profile.value === 'true', source: 'profile', valid: true, raw: undefined, warnings: profile.warnings };
|
|
844
|
+
}
|
|
845
|
+
return { required: false, source: 'default', valid: true, raw: undefined, warnings: profile.warnings };
|
|
846
|
+
}
|
|
847
|
+
catch {
|
|
848
|
+
return { required: false, source: 'default', valid: true, raw: undefined, warnings: [] };
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
// ─── resolveAcceptedTracePredicates ──────────────────────────────────────────
|
|
852
|
+
/**
|
|
853
|
+
* The extra practice-trace predicate types the org profile lists under
|
|
854
|
+
* `vault.trace.accepted_predicates` — `[]` when there is no profile, the key is
|
|
855
|
+
* absent, or its value was refused by the value guard. Org profile only: a
|
|
856
|
+
* project config value is not read (the key is not a project setting). Never
|
|
857
|
+
* throws.
|
|
858
|
+
*/
|
|
859
|
+
function resolveAcceptedTracePredicates(cwd, opts) {
|
|
860
|
+
try {
|
|
861
|
+
const profile = readProfileValue(cwd, exports.VAULT_TRACE_ACCEPTED_PREDICATES_KEY, opts);
|
|
862
|
+
if (typeof profile.value !== 'string')
|
|
863
|
+
return [];
|
|
864
|
+
const parsed = JSON.parse(profile.value);
|
|
865
|
+
return Array.isArray(parsed) ? parsed.filter((v) => typeof v === 'string') : [];
|
|
866
|
+
}
|
|
867
|
+
catch {
|
|
868
|
+
return [];
|
|
869
|
+
}
|
|
870
|
+
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Unsupported in this router:
|
|
7
7
|
* - scaffold: routed through top-level scaffold command.
|
|
8
8
|
*
|
|
9
|
-
* CJS-only subcommands: mvp-mode, dispatch-plan, install-run, tier, tier-propose (dispatched directly, before hub).
|
|
9
|
+
* CJS-only subcommands: mvp-mode, dispatch-plan, install-run, tier, tier-propose, manifest (dispatched directly, before hub).
|
|
10
10
|
*
|
|
11
11
|
* #3788: dispatch is mediated by CommandRoutingHub. The public entry point
|
|
12
12
|
* and observable CLI behaviour are unchanged.
|
|
@@ -73,6 +73,12 @@ function routePhaseCommand({ phase, args, cwd, raw, error }) {
|
|
|
73
73
|
phase.cmdPhaseTierPropose(cwd, args.slice(2), raw);
|
|
74
74
|
return;
|
|
75
75
|
}
|
|
76
|
+
// `manifest start|record` (pack 10): write `.phase-manifest.json`, which
|
|
77
|
+
// `/pp-undo --phase` reads (src/phase-manifest.cts).
|
|
78
|
+
if (subcommand === 'manifest') {
|
|
79
|
+
phase.cmdPhaseManifest(cwd, args.slice(2), raw);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
76
82
|
// ── Build the CJS registry ──────────────────────────────────────────────────
|
|
77
83
|
// Each handler receives a ctx object from the hub and must return a HubResult.
|
|
78
84
|
const cjsRegistry = {
|