@pareto4/pp-core 1.12.1 → 1.17.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 (196) hide show
  1. package/.claude-plugin/plugin.json +3 -3
  2. package/README.md +26 -9
  3. package/agents/pp-code-reviewer.md +3 -2
  4. package/agents/pp-debug-session-manager.md +9 -6
  5. package/agents/pp-executor.md +7 -21
  6. package/agents/pp-plan-checker.md +4 -4
  7. package/agents/pp-planner.md +23 -9
  8. package/agents/pp-vault-reviewer.md +274 -0
  9. package/agents/pp-verifier.md +7 -3
  10. package/bin/install.js +10 -3
  11. package/commands/pp-execute-phase.md +1 -3
  12. package/commands/pp-ns-review.md +2 -1
  13. package/commands/pp-plan-phase.md +1 -2
  14. package/commands/pp-vault-review.md +35 -0
  15. package/commands/pp-verify-work.md +1 -2
  16. package/gemini-extension.json +1 -1
  17. package/hooks/dist/pp-check-update.js +0 -0
  18. package/hooks/dist/pp-cursor-session-start.js +1 -1
  19. package/hooks/dist/pp-statusline.js +1 -1
  20. package/hooks/dist/pp-update-banner.js +0 -0
  21. package/hooks/pp-check-update.js +0 -0
  22. package/hooks/pp-cursor-session-start.js +1 -1
  23. package/hooks/pp-statusline.js +1 -1
  24. package/hooks/pp-update-banner.js +0 -0
  25. package/package.json +14 -19
  26. package/pareto-skills/_shared/create-project-board.sh +0 -0
  27. package/pareto-skills/pp-project-setup/SKILL.md +1 -1
  28. package/pp-core/bin/check-latest-version.cjs +0 -0
  29. package/pp-core/bin/detect-build-commands.sh +2 -2
  30. package/pp-core/bin/lib/capability-activation.cjs +83 -9
  31. package/pp-core/bin/lib/capability-registry.cjs +526 -22
  32. package/pp-core/bin/lib/capability-state.cjs +160 -4
  33. package/pp-core/bin/lib/capability-writer.cjs +19 -5
  34. package/pp-core/bin/lib/check-command-router.cjs +3867 -274
  35. package/pp-core/bin/lib/clusters.cjs +4 -0
  36. package/pp-core/bin/lib/command-aliases.cjs +0 -28
  37. package/pp-core/bin/lib/commands.cjs +9 -4
  38. package/pp-core/bin/lib/compose-bindings.cjs +253 -76
  39. package/pp-core/bin/lib/config-loader.cjs +7 -103
  40. package/pp-core/bin/lib/config-schema.cjs +1 -1
  41. package/pp-core/bin/lib/config.cjs +154 -62
  42. package/pp-core/bin/lib/decisions.cjs +104 -27
  43. package/pp-core/bin/lib/discovery-vault-check.cjs +246 -0
  44. package/pp-core/bin/lib/frontmatter.cjs +1 -1
  45. package/pp-core/bin/lib/git-base-branch.cjs +138 -10
  46. package/pp-core/bin/lib/git-provider.cjs +4 -4
  47. package/pp-core/bin/lib/init-command-router.cjs +5 -8
  48. package/pp-core/bin/lib/init-contract.cjs +653 -629
  49. package/pp-core/bin/lib/init-interactive.cjs +77 -21
  50. package/pp-core/bin/lib/init.cjs +24 -51
  51. package/pp-core/bin/lib/install-profiles.cjs +1 -1
  52. package/pp-core/bin/lib/io.cjs +8 -0
  53. package/pp-core/bin/lib/loop-resolver.cjs +169 -47
  54. package/pp-core/bin/lib/org-profile.cjs +544 -53
  55. package/pp-core/bin/lib/package-identity.cjs +3 -3
  56. package/pp-core/bin/lib/phase-command-router.cjs +1 -23
  57. package/pp-core/bin/lib/phase-scope.cjs +1 -1
  58. package/pp-core/bin/lib/phase.cjs +11 -203
  59. package/pp-core/bin/lib/platform-bindings.cjs +40 -19
  60. package/pp-core/bin/lib/practices-review-check.cjs +15 -9
  61. package/pp-core/bin/lib/profile-output.cjs +1 -1
  62. package/pp-core/bin/lib/shell-command-projection.cjs +104 -1
  63. package/pp-core/bin/lib/state.cjs +11 -85
  64. package/pp-core/bin/lib/template.cjs +1 -1
  65. package/pp-core/bin/lib/vault-audit.cjs +2594 -0
  66. package/pp-core/bin/lib/vault-command-router.cjs +2756 -60
  67. package/pp-core/bin/lib/vault-coordinates.cjs +13 -4
  68. package/pp-core/bin/lib/vault-index.cjs +699 -0
  69. package/pp-core/bin/lib/vault-mode.cjs +235 -0
  70. package/pp-core/bin/lib/vault-practices.cjs +1194 -89
  71. package/pp-core/bin/lib/vault-review.cjs +2102 -0
  72. package/pp-core/bin/lib/vault-seal.cjs +1936 -0
  73. package/pp-core/bin/lib/vault-standards.cjs +1505 -0
  74. package/pp-core/bin/lib/vault-trace.cjs +3184 -0
  75. package/pp-core/bin/lib/vault-update.cjs +242 -0
  76. package/pp-core/bin/lib/vault-write-back.cjs +78 -31
  77. package/pp-core/bin/lib/vault.cjs +2713 -634
  78. package/pp-core/bin/lib/verify.cjs +0 -11
  79. package/pp-core/bin/portable-timeout.sh +1 -1
  80. package/pp-core/bin/pp-tools.cjs +184 -37
  81. package/pp-core/bin/shared/config-defaults.manifest.json +0 -7
  82. package/pp-core/bin/shared/config-schema.manifest.json +0 -3
  83. package/pp-core/bin/shared/model-catalog.json +1 -0
  84. package/pp-core/bin/verify-reapply-patches.cjs +0 -0
  85. package/pp-core/org-profile.json +26 -0
  86. package/pp-core/references/autonomous-best-effort.md +8 -7
  87. package/pp-core/references/loop-hook-dispatch.md +0 -14
  88. package/pp-core/references/planner-gap-closure.md +0 -1
  89. package/pp-core/references/planner-tdd.md +0 -1
  90. package/pp-core/references/planner-vault-practices.md +31 -3
  91. package/pp-core/references/planner-vault-standards.md +320 -0
  92. package/pp-core/references/planning-config.md +0 -8
  93. package/pp-core/references/practice-arithmetic-contract.md +121 -0
  94. package/pp-core/references/tdd.md +0 -1
  95. package/pp-core/references/verifier-practice-verdicts.md +263 -0
  96. package/pp-core/templates/README.md +1 -0
  97. package/pp-core/templates/VAULT-REVIEW.md +202 -0
  98. package/pp-core/templates/phase-prompt.md +2 -6
  99. package/pp-core/templates/practices/README.md +137 -11
  100. package/pp-core/templates/project-charter.md +226 -0
  101. package/pp-core/workflows/_runtime-launcher.snippet.sh +0 -0
  102. package/pp-core/workflows/add-tests.md +55 -0
  103. package/pp-core/workflows/audit-fix.md +63 -0
  104. package/pp-core/workflows/audit-milestone.md +1 -1
  105. package/pp-core/workflows/autonomous.md +85 -11
  106. package/pp-core/workflows/check-todos.md +13 -2
  107. package/pp-core/workflows/code-review-fix.md +59 -1
  108. package/pp-core/workflows/code-review.md +10 -1
  109. package/pp-core/workflows/complete-milestone.md +1 -1
  110. package/pp-core/workflows/debug.md +56 -2
  111. package/pp-core/workflows/discovery-sprint/prefill.md +21 -0
  112. package/pp-core/workflows/discovery-sprint/prototype.md +46 -48
  113. package/pp-core/workflows/discovery-sprint/session-13-gates.md +2 -0
  114. package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +1 -3
  115. package/pp-core/workflows/discovery-sprint/sessions/02-as-is.md +1 -3
  116. package/pp-core/workflows/discovery-sprint/sessions/03-to-be.md +1 -3
  117. package/pp-core/workflows/discovery-sprint/sessions/04-ux-design.md +20 -24
  118. package/pp-core/workflows/discovery-sprint/sessions/05-architecture.md +1 -3
  119. package/pp-core/workflows/discovery-sprint/sessions/06-security.md +1 -3
  120. package/pp-core/workflows/discovery-sprint/sessions/07-identity.md +1 -3
  121. package/pp-core/workflows/discovery-sprint/sessions/08-integration.md +1 -3
  122. package/pp-core/workflows/discovery-sprint/sessions/09-qa.md +1 -3
  123. package/pp-core/workflows/discovery-sprint/sessions/10-infrastructure.md +1 -6
  124. package/pp-core/workflows/discovery-sprint/sessions/11-roadmap.md +1 -3
  125. package/pp-core/workflows/discovery-sprint/sessions/12-delivery-model.md +1 -3
  126. package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +1 -3
  127. package/pp-core/workflows/discovery-sprint/templates/context-template.md +1 -1
  128. package/pp-core/workflows/discovery-sprint/vault-context.md +213 -0
  129. package/pp-core/workflows/discovery-sprint.md +7 -3
  130. package/pp-core/workflows/discovery-to-build/steps/architecture-deliverables.md +4 -4
  131. package/pp-core/workflows/discovery-to-build/templates/pp-handoff.md +43 -1
  132. package/pp-core/workflows/discovery-to-build.md +9 -1
  133. package/pp-core/workflows/discuss-phase.md +3 -10
  134. package/pp-core/workflows/execute-phase/steps/check-query-gate.md +329 -0
  135. package/pp-core/workflows/execute-phase/steps/client-context-gate.md +3 -2
  136. package/pp-core/workflows/execute-phase/steps/cross-ai-delegation.md +1 -1
  137. package/pp-core/workflows/execute-phase/steps/execute-pre-dispatch.md +98 -0
  138. package/pp-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +94 -0
  139. package/pp-core/workflows/execute-phase/steps/post-merge-gate.md +6 -8
  140. package/pp-core/workflows/execute-phase/steps/vault-fetch.md +115 -14
  141. package/pp-core/workflows/execute-phase/steps/vault-load-gate.md +322 -18
  142. package/pp-core/workflows/execute-phase/steps/verifier-practice-payload.md +165 -0
  143. package/pp-core/workflows/execute-phase/steps/verify-post-gate.md +163 -0
  144. package/pp-core/workflows/execute-phase.md +154 -110
  145. package/pp-core/workflows/execute-plan.md +58 -4
  146. package/pp-core/workflows/fast.md +53 -0
  147. package/pp-core/workflows/help/modes/full.md +1 -0
  148. package/pp-core/workflows/init/steps/client-context-capture.md +101 -0
  149. package/pp-core/workflows/init/steps/gitlab-provisioning.md +130 -0
  150. package/pp-core/workflows/init/steps/project-md-deferral-narration.md +66 -0
  151. package/pp-core/workflows/init/steps/reachability-outcome.md +70 -0
  152. package/pp-core/workflows/init.md +232 -272
  153. package/pp-core/workflows/new-project.md +31 -23
  154. package/pp-core/workflows/plan-phase.md +99 -43
  155. package/pp-core/workflows/quick.md +120 -2
  156. package/pp-core/workflows/secure-phase.md +1 -1
  157. package/pp-core/workflows/ship.md +57 -24
  158. package/pp-core/workflows/update.md +3 -3
  159. package/pp-core/workflows/validate-phase.md +60 -1
  160. package/pp-core/workflows/vault-review.md +325 -0
  161. package/pp-core/workflows/verify-work.md +13 -12
  162. package/scripts/changeset/cli.cjs +6 -5
  163. package/scripts/changeset/lint.cjs +0 -0
  164. package/scripts/changeset/new.cjs +33 -50
  165. package/scripts/dev-env.cjs +25 -85
  166. package/scripts/gen-capability-registry.cjs +199 -0
  167. package/scripts/gen-practice-arithmetic-contract.cjs +412 -0
  168. package/scripts/lib/allowlist-ratchet.cjs +1 -103
  169. package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -2
  170. package/scripts/lint-docs-required.cjs +0 -0
  171. package/scripts/lint-pr-check-project-dir.cjs +1 -3
  172. package/scripts/lint-test-file-count.allowlist.json +13 -2
  173. package/scripts/practice-arithmetic-contract.allow.json +35 -0
  174. package/scripts/prompt-injection-scan.sh +22 -15
  175. package/scripts/record-vault-mode-golden.cjs +89 -0
  176. package/scripts/release-channel.cjs +0 -0
  177. package/scripts/sync-manifest-versions.cjs +5 -4
  178. package/scripts/sync-rulesets.sh +1 -1
  179. package/scripts/update-size-baseline.cjs +30 -274
  180. package/scripts/vault-calibrate.cjs +996 -0
  181. package/scripts/verify-opengsd-sweep.cjs +3 -5
  182. package/scripts/workflow-size.cjs +9 -61
  183. package/pp-core/bin/lib/dispatch-plan.cjs +0 -472
  184. package/pp-core/bin/lib/install-run.cjs +0 -536
  185. package/pp-core/bin/lib/phase-tier.cjs +0 -467
  186. package/pp-core/bin/lib/plan-graph.cjs +0 -368
  187. package/pp-core/references/planner-plan-graph.md +0 -66
  188. package/pp-core/workflows/execute-phase/steps/checkpoint-handling.md +0 -45
  189. package/pp-core/workflows/execute-phase/steps/install-run.md +0 -29
  190. package/pp-core/workflows/execute-phase/steps/phase-close-rebaseline.md +0 -23
  191. package/pp-core/workflows/execute-phase/steps/serial-group-dispatch.md +0 -72
  192. package/pp-core/workflows/plan-phase/steps/phase-tier.md +0 -34
  193. package/scripts/lib/size-budget.cjs +0 -311
  194. package/scripts/lint-neutrality-names.cjs +0 -694
  195. package/scripts/lint-org-coordinates.allowlist.json +0 -35
  196. package/scripts/lint-org-coordinates.cjs +0 -426
@@ -0,0 +1,242 @@
1
+ "use strict";
2
+ /**
3
+ * src/vault-update.cts — `pp-tools vault update [<tag>] [--yes]` (PBI 74316).
4
+ *
5
+ * FORK-ONLY. Moves a project to another version of its client vault. The
6
+ * vault version is a PROJECT property: `client.vaultRef` in the committed
7
+ * `.platform-bindings.json` is the one place it lives, so no pp-core release
8
+ * can move it — changing it is a deliberate act, and this is its command.
9
+ *
10
+ * Distinct from its neighbours:
11
+ * - `pp-tools vault fetch` re-reads the vault at the version the project is
12
+ * already pinned to; it never changes `client.vaultRef`.
13
+ * - `/pp-update` updates pp-core itself, not a project's vault.
14
+ *
15
+ * Two steps, so the confirmation is explicit and works the same for a person
16
+ * and for an agent shell: without `--yes` it only reports the plan
17
+ * (current → target) and writes nothing; with `--yes` it writes
18
+ * `client.vaultRef`, fetches the vault at the new version, and reports any
19
+ * practice whose `schemaVersion` is newer than this pp-core supports (the
20
+ * vault version a project picks implies a minimum pp-core, never the reverse).
21
+ * It never commits: the command it prints is the operator's to run.
22
+ *
23
+ * ADR-457 build-at-publish: this TypeScript file is the SOURCE OF TRUTH. It
24
+ * compiles to the git-ignored `pp-core/bin/lib/vault-update.cjs`.
25
+ */
26
+ var __importDefault = (this && this.__importDefault) || function (mod) {
27
+ return (mod && mod.__esModule) ? mod : { "default": mod };
28
+ };
29
+ Object.defineProperty(exports, "__esModule", { value: true });
30
+ exports.AVAILABLE_TAGS_CAP = void 0;
31
+ exports.vaultUpdate = vaultUpdate;
32
+ exports.renderVaultUpdate = renderVaultUpdate;
33
+ const node_fs_1 = __importDefault(require("node:fs"));
34
+ const node_path_1 = __importDefault(require("node:path"));
35
+ const project_root_cjs_1 = require("./project-root.cjs");
36
+ const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
37
+ const semver_compare_cjs_1 = require("./semver-compare.cjs");
38
+ const compose_bindings_cjs_1 = require("./compose-bindings.cjs");
39
+ const git_provider_cjs_1 = require("./git-provider.cjs");
40
+ const vault_coordinates_cjs_1 = require("./vault-coordinates.cjs");
41
+ const platform_bindings_cjs_1 = require("./platform-bindings.cjs");
42
+ // VAULT-EGRESS-1 (quick 261005-8py): the one coordinate redactor. Cycle-free — src/vault.cts imports
43
+ // nothing from this module; the lazy `require` in runFetch exists for the `_fetch` seam default.
44
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
45
+ const vaultModule = require("./vault.cjs");
46
+ const { redactCloneTarget } = vaultModule;
47
+ // ─── Constants ───────────────────────────────────────────────────────────────
48
+ /** How many tags a report lists — enough to pick from, bounded for a long history. */
49
+ exports.AVAILABLE_TAGS_CAP = 10;
50
+ const BINDINGS_FILE = '.platform-bindings.json';
51
+ /** Matches the degrade warning `parsePracticeFile` emits for a newer schemaVersion. */
52
+ const NEWER_SCHEMA_WARNING_RE = /schemaVersion \d+ is newer than this pp-core supports/;
53
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
54
+ function isObject(v) {
55
+ return v !== null && typeof v === 'object' && !Array.isArray(v);
56
+ }
57
+ function usableString(v) {
58
+ return typeof v === 'string' && v.trim() !== '' ? v.trim() : null;
59
+ }
60
+ function resolveRoot(cwd) {
61
+ try {
62
+ return (0, project_root_cjs_1.findProjectRoot)(cwd) || cwd;
63
+ }
64
+ catch {
65
+ return cwd;
66
+ }
67
+ }
68
+ /** Resolve a requested tag against the available list; accepts a missing `v`. */
69
+ function matchRequestedTag(requested, tags) {
70
+ const trimmed = requested.trim();
71
+ if (tags.includes(trimmed))
72
+ return trimmed;
73
+ if (!trimmed.startsWith('v') && tags.includes(`v${trimmed}`))
74
+ return `v${trimmed}`;
75
+ return null;
76
+ }
77
+ function runFetch(root, opts) {
78
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
79
+ const fetchFn = opts._fetch ?? require('./vault.cjs').vaultFetch;
80
+ const manifest = fetchFn(root);
81
+ const warnings = [];
82
+ if (typeof manifest.warning === 'string')
83
+ warnings.push(manifest.warning);
84
+ if (Array.isArray(manifest.warnings)) {
85
+ for (const w of manifest.warnings)
86
+ if (typeof w === 'string' && !warnings.includes(w))
87
+ warnings.push(w);
88
+ }
89
+ return {
90
+ source: typeof manifest.source === 'string' ? manifest.source : 'unknown',
91
+ ref: typeof manifest.ref === 'string' ? manifest.ref : null,
92
+ warnings,
93
+ };
94
+ }
95
+ function findNewerSchemaPractices(root, opts) {
96
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
97
+ const discover = opts._discover ?? require('./vault-practices.cjs').discoverPracticeFiles;
98
+ const discovery = discover(root);
99
+ const ids = [];
100
+ for (const entry of discovery.degraded ?? []) {
101
+ if (entry.warnings.some((w) => NEWER_SCHEMA_WARNING_RE.test(w)) && !ids.includes(entry.id))
102
+ ids.push(entry.id);
103
+ }
104
+ return ids;
105
+ }
106
+ // ─── vaultUpdate ─────────────────────────────────────────────────────────────
107
+ /**
108
+ * Plan — and with `apply` perform — a move of the project's client vault to
109
+ * another stable tag. Never throws: every fault is a result with `ok: false`
110
+ * and a `reason`. Writes exactly one field (`client.vaultRef`) of an existing
111
+ * `.platform-bindings.json`, and only after the whole document still passes
112
+ * `validateBindings`.
113
+ */
114
+ function vaultUpdate(cwd, opts = {}) {
115
+ const root = resolveRoot(cwd);
116
+ const bindingsPath = node_path_1.default.join(root, BINDINGS_FILE);
117
+ const result = {
118
+ ok: false,
119
+ applied: false,
120
+ reason: null,
121
+ message: '',
122
+ bindingsPath,
123
+ repo: null,
124
+ from: null,
125
+ to: null,
126
+ latest: null,
127
+ available: [],
128
+ fetch: null,
129
+ newerSchemaPractices: [],
130
+ nextStep: null,
131
+ };
132
+ const fail = (reason, message) => ({
133
+ ...result,
134
+ ok: false,
135
+ reason,
136
+ message,
137
+ });
138
+ if (!node_fs_1.default.existsSync(bindingsPath)) {
139
+ return fail('no_bindings', `No ${BINDINGS_FILE} in ${root} — run "pp-tools init" first; the vault version lives there.`);
140
+ }
141
+ let bindings;
142
+ try {
143
+ const parsed = JSON.parse(node_fs_1.default.readFileSync(bindingsPath, 'utf-8'));
144
+ if (!isObject(parsed))
145
+ throw new Error('not an object');
146
+ bindings = parsed;
147
+ }
148
+ catch {
149
+ return fail('bindings_unreadable', `${BINDINGS_FILE} could not be read as a JSON object — fix it before updating the vault.`);
150
+ }
151
+ const client = isObject(bindings.client) ? bindings.client : null;
152
+ const repo = client ? usableString(client.vaultRepo) : null;
153
+ if (!client || !repo) {
154
+ return fail('no_client_vault', `${BINDINGS_FILE} names no client vault (client.vaultRepo) — there is no vault version to update.`);
155
+ }
156
+ result.repo = redactCloneTarget(repo);
157
+ result.from = usableString(client.vaultRef);
158
+ let tags;
159
+ try {
160
+ const provider = (0, git_provider_cjs_1.resolveProvider)(root).provider;
161
+ const host = usableString(client.vaultHost) ?? (0, vault_coordinates_cjs_1.resolvePlatformHost)(root).host ?? (0, vault_coordinates_cjs_1.defaultHostForProvider)(provider);
162
+ const target = (0, git_provider_cjs_1.composeCloneTarget)(provider, repo, host);
163
+ tags = (0, compose_bindings_cjs_1.listStableTagsAt)(target.url, { _lsRemote: opts._lsRemote });
164
+ }
165
+ catch {
166
+ return fail('tag_lookup_failed', `The tags of the vault repo "${redactCloneTarget(repo)}" could not be listed — check your network and git access to it.`);
167
+ }
168
+ tags = [...new Set(tags)].sort((a, b) => (0, semver_compare_cjs_1.compareSemverCore)(b, a));
169
+ result.available = tags.slice(0, exports.AVAILABLE_TAGS_CAP);
170
+ result.latest = tags[0] ?? null;
171
+ if (tags.length === 0) {
172
+ return fail('no_tags', `The vault repo "${redactCloneTarget(repo)}" has no stable vX.Y.Z tag to pin to.`);
173
+ }
174
+ const requested = usableString(opts.target);
175
+ const to = requested ? matchRequestedTag(requested, tags) : tags[0];
176
+ if (!to) {
177
+ return fail('unknown_tag', `The vault repo has no tag "${requested}". Available (newest first): ${result.available.join(', ')}.`);
178
+ }
179
+ result.to = to;
180
+ const fromLabel = result.from ?? 'main (unpinned)';
181
+ if (result.from === to) {
182
+ return { ...result, ok: true, message: `The project is already on vault ${to}.` };
183
+ }
184
+ if (opts.apply !== true) {
185
+ return {
186
+ ...result,
187
+ ok: true,
188
+ message: `The project would move from vault ${fromLabel} to ${to}. Nothing was changed.`,
189
+ nextStep: `pp-tools vault update ${to} --yes`,
190
+ };
191
+ }
192
+ const updated = { ...bindings, client: { ...client, vaultRef: to } };
193
+ const validation = (0, platform_bindings_cjs_1.validateBindings)(updated);
194
+ if (!validation.valid) {
195
+ return fail('invalid_bindings', `The updated ${BINDINGS_FILE} would not be valid: ${validation.errors.join('; ')}. Nothing was changed.`);
196
+ }
197
+ try {
198
+ (0, shell_command_projection_cjs_1.platformWriteSync)(bindingsPath, JSON.stringify(updated, null, 2) + '\n');
199
+ }
200
+ catch {
201
+ return fail('write_failed', `${BINDINGS_FILE} could not be written. Nothing was changed.`);
202
+ }
203
+ const applied = {
204
+ ...result,
205
+ ok: true,
206
+ applied: true,
207
+ message: `The project moved from vault ${fromLabel} to ${to}.`,
208
+ nextStep: `git add ${BINDINGS_FILE} && git commit -m "chore(vault): update client vault to ${to}"`,
209
+ };
210
+ try {
211
+ applied.fetch = runFetch(root, opts);
212
+ }
213
+ catch {
214
+ applied.fetch = { source: 'error', ref: null, warnings: ['The vault could not be fetched at the new version — run "pp-tools vault fetch" to retry.'] };
215
+ }
216
+ try {
217
+ applied.newerSchemaPractices = findNewerSchemaPractices(root, opts);
218
+ }
219
+ catch {
220
+ applied.newerSchemaPractices = [];
221
+ }
222
+ return applied;
223
+ }
224
+ /** Plain-text rendering of a `vaultUpdate` result for the non-`--raw` CLI path. */
225
+ function renderVaultUpdate(r) {
226
+ const lines = [r.message];
227
+ if (r.ok && r.latest && r.to !== r.latest) {
228
+ lines.push(`Newest available: ${r.latest}.`);
229
+ }
230
+ if (r.fetch) {
231
+ lines.push(`Fetched the vault: ${r.fetch.source}${r.fetch.ref ? ` at ${r.fetch.ref}` : ''}.`);
232
+ for (const w of r.fetch.warnings)
233
+ lines.push(w);
234
+ }
235
+ if (r.newerSchemaPractices.length > 0) {
236
+ lines.push(`${r.newerSchemaPractices.length} practice(s) in this vault version were written for a newer pp-core and apply only in degraded form: ${r.newerSchemaPractices.join(', ')}. Update pp-core to apply them fully.`);
237
+ }
238
+ if (r.nextStep) {
239
+ lines.push(r.applied ? `Commit the change: ${r.nextStep}` : `To apply: ${r.nextStep}`);
240
+ }
241
+ return lines.join('\n');
242
+ }
@@ -103,6 +103,8 @@ const PROMOTE_GUARD = Object.freeze({
103
103
  WRITE_TARGET: 'write_target',
104
104
  CONCURRENT_EDIT: 'concurrent_edit',
105
105
  TRANSPORT: 'transport',
106
+ // 13-07 (D-14): standards mode — write-back has no practice layer to write to.
107
+ MODE: 'mode',
106
108
  });
107
109
  // ─── Secret-content scanner (WRB-03 / D-13 / D-14) ──────────────────────────────
108
110
  //
@@ -200,7 +202,7 @@ function boundedToken(v, max = 80) {
200
202
  }
201
203
  // ─── Preflight guard chain ───────────────────────────────────────────────────────
202
204
  /**
203
- * Run the promote preflight guard chain: activation, input, format
205
+ * Run the promote preflight guard chain: activation, mode (13-07), input, format
204
206
  * validation (D-03), secret scan (D-13/D-14), target resolution, and the
205
207
  * write-target guard (D-05) — strictly in that order, refusing at the
206
208
  * first failing stage and never continuing past it. Makes no network
@@ -211,16 +213,37 @@ function promotePreflight({ cwd, filePath, _vault, _practices }) {
211
213
  const vault = _vault ?? require('./vault.cjs');
212
214
  // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment
213
215
  const practices = _practices ?? require('./vault-practices.cjs');
214
- // 1. Activation — both vault.enabled and vault.practices_enabled must be
215
- // strictly true. Runs before any filesystem access; rides the two
216
- // existing default-false config keys (no new config key, no capability
217
- // surface widening).
218
- const enabled = vault.cfgGet(cwd, 'vault.enabled', false);
219
- const practicesEnabled = vault.cfgGet(cwd, 'vault.practices_enabled', false);
220
- if (enabled !== true || practicesEnabled !== true) {
216
+ // 1. Activation — the vault-practices capability must be on. Runs before
217
+ // any filesystem access; rides the two existing default-false config keys
218
+ // (no new config key, no capability surface widening). Phase 15 D-24/D-25:
219
+ // asked of the ONE practices-switch reader, `isPracticesGateActive`, which
220
+ // resolves both keys exactly as the vault-practices gates' `when` does. The
221
+ // `cfgGet` pair it replaces read only the root config, so a workstream-scoped
222
+ // switch refused here while the gates rendered (15-RESEARCH C-1). Read from
223
+ // the REAL vault leaf, not the `_vault` seam, like the mode guard below: a
224
+ // mocked config read must not be able to switch promotion on.
225
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
226
+ const switchOwner = require('./vault.cjs');
227
+ if (!switchOwner.isPracticesGateActive(cwd)) {
221
228
  return refusal(PROMOTE_GUARD.ACTIVATION, ERROR_REASON.VAULT_PROMOTE_DISABLED, 'This practice was not promoted: vault write-back is switched off for this project. ' +
222
229
  'Set both vault.enabled and vault.practices_enabled to true in .planning/config.json and run the promotion again.');
223
230
  }
231
+ // 1b. Mode (13-07, D-14, PARK-03) — in standards mode the vault carries
232
+ // standards documents maintained through its INDEX.md, not practice files,
233
+ // so there is nothing a promotion could write. Refused here, after the
234
+ // activation guard and BEFORE the input guard reads `filePath` or any clone
235
+ // runs, so the preview and `--confirmed` share it (both call this chain
236
+ // first). Read from the REAL vault leaf, not the `_vault` seam: a governance
237
+ // decision must not be switchable by a test mock (the 13-06 precedent). The
238
+ // envelope carries NO mode key — a promote verdict already uses that key for
239
+ // create/update — so the mode is named in the message only.
240
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
241
+ const modeOwner = require('./vault.cjs');
242
+ if (modeOwner.isStandardsModeActive(cwd)) {
243
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
244
+ const { notApplicablePromoteMessage } = require('./vault-mode.cjs');
245
+ return refusal(PROMOTE_GUARD.MODE, ERROR_REASON.VAULT_PROMOTE_NOT_APPLICABLE, notApplicablePromoteMessage());
246
+ }
224
247
  // 2. Input — a path to an already-authored practice file (D-02); this
225
248
  // module never composes, rewrites, reformats, or extracts a practice.
226
249
  if (typeof filePath !== 'string' ||
@@ -242,6 +265,44 @@ function promotePreflight({ cwd, filePath, _vault, _practices }) {
242
265
  const cause = parsed.warning.replace(/^.*?\s+skipped:\s*/, '');
243
266
  return refusal(PROMOTE_GUARD.VALIDATION, ERROR_REASON.VAULT_PROMOTE_INVALID_PRACTICE, `This practice was not promoted: ${cause} Fix the practice file and run the promotion again.`);
244
267
  }
268
+ // 3b. Degraded-parse refusal (CHECK-02e, 05-01).
269
+ //
270
+ // WRITE-SIDE STRICTNESS IS DELIBERATE, and the asymmetry with the read side
271
+ // is the point. On the READ side a practice the engine does not fully
272
+ // understand is retained: it came from the vault, it already governs work,
273
+ // and dropping it would silently un-govern that work (ADR-71924 §5). On the
274
+ // WRITE side the same practice is refused, because promoting it pushes the
275
+ // defect INTO the fleet-wide vault that every developer machine and CI runner
276
+ // fetches — one unreviewed key becomes everyone's degraded warning. The read
277
+ // side tolerates what the write side refuses.
278
+ //
279
+ // Both consumers of DOCUMENTED_FRONTMATTER_KEYS therefore moved in the one
280
+ // change that widened it (T-4-49): had only the reader moved, an unknown key
281
+ // would have quietly become promotable.
282
+ if (parsed.degradedWarnings !== undefined && parsed.degradedWarnings.length > 0) {
283
+ const cause = parsed.degradedWarnings[0].replace(/^.*?\s+degraded:\s*/, '');
284
+ return refusal(PROMOTE_GUARD.VALIDATION, ERROR_REASON.VAULT_PROMOTE_INVALID_PRACTICE, `This practice was not promoted: ${cause} Fix the practice file and run the promotion again.`);
285
+ }
286
+ // 3c. Invalid-check-block refusal (CR-05 / G-3, 05-11).
287
+ //
288
+ // An invalid `check:` block is refused on the write side for the same reason
289
+ // 3b refuses a degraded parse, and the consequence here is worse. The reader
290
+ // reports this defect on a THIRD channel — `practice.checkInvalid` — which is
291
+ // neither `!parsed.ok` nor `degradedWarnings`, so both rungs above miss it;
292
+ // and the folding of `checkInvalid` into `degraded[]` happens inside
293
+ // `discoverPracticeFiles`, which this path never calls.
294
+ //
295
+ // Once such a practice is in the fleet vault the defect is not a warning for
296
+ // the next reader: `verdictForPractice`'s FIRST rung maps `checkInvalid` to a
297
+ // fail-closed FAIL(check_invalid), so it becomes `block: true` at ship:pre and
298
+ // verify:post on every machine that fetches the vault. One typo would stop the
299
+ // whole fleet's builds. The refusal covers ANY invalid block regardless of
300
+ // which assertion kind it declared (D-05-05), because `checkInvalid` is the
301
+ // single fail-closed signal for both.
302
+ if (typeof parsed.practice.checkInvalid === 'string' && parsed.practice.checkInvalid.length > 0) {
303
+ return refusal(PROMOTE_GUARD.VALIDATION, ERROR_REASON.VAULT_PROMOTE_INVALID_PRACTICE, `This practice was not promoted: ${parsed.practice.checkInvalid} ` +
304
+ 'Fix the practice file and run the promotion again.');
305
+ }
245
306
  // 4. Secret scan — a hit refuses hard, no override (D-14). The matched
246
307
  // text is never carried, quoted, echoed, logged, or truncated into the
247
308
  // refusal — a truncated secret is still a partial disclosure.
@@ -402,7 +463,9 @@ function buildExpectToken(localRaw, remoteBaseline) {
402
463
  // reading) — duplicating the fix a second time is exactly how that class of
403
464
  // bug recurs.
404
465
  function previewPromotion({ cwd, filePath, _exec, _vault, _practices }) {
405
- const gitExec = _exec ?? shell_command_projection_cjs_1.execGit;
466
+ const gitExec = _exec ?? shell_command_projection_cjs_1.execGitLocal;
467
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment
468
+ const vault = _vault ?? require('./vault.cjs');
406
469
  // 1. Run the full preflight guard chain; refuse unchanged with no clone.
407
470
  const pre = promotePreflight({ cwd, filePath, _vault, _practices });
408
471
  if (pre.status !== 'ok')
@@ -440,7 +503,7 @@ function previewPromotion({ cwd, filePath, _exec, _vault, _practices }) {
440
503
  cloneDir,
441
504
  ], { timeout: 120_000 });
442
505
  if (cl.exitCode !== 0) {
443
- const cause = shownCause(cl.stderr);
506
+ const cause = boundedToken((cl.stderr || '').split(/\r?\n/)[0] || 'unknown error');
444
507
  return refusal(PROMOTE_GUARD.TRANSPORT, ERROR_REASON.VAULT_PROMOTE_TRANSPORT_FAILED, `This practice was not pushed: git could not reach the vault (${cause}). Check your access to the vault and run the promotion again.`);
445
508
  }
446
509
  // 3. Capture the baseline BEFORE writing — an id already present in the
@@ -552,13 +615,13 @@ function previewPromotion({ cwd, filePath, _exec, _vault, _practices }) {
552
615
  // sentence (plan 03); this note additionally explains why, in the
553
616
  // read-only preview an operator reads before confirming.
554
617
  if (pre.sharedSuppressed && pre.sharedSuppressedKind === 'provider-default-unavailable') {
555
- notes.push(`No shared vault is configured for the ${pre.provider} provider — the built-in Pareto shared default applies only to the gitlab provider. Set vault.fallback_repo to enable the shared tier.`);
618
+ notes.push(`No shared vault is configured for the ${pre.provider} provider — the built-in Pareto shared default applies only to the github provider. Set vault.fallback_repo to enable the shared tier.`);
556
619
  }
557
620
  // 7 & 8. Pin both sides in the opaque expect token, then return.
558
621
  const expect = buildExpectToken(pre.raw, baseline);
559
622
  return {
560
623
  status: 'preview',
561
- repo: shownRepo(pre.repo),
624
+ repo: vault.redactCloneTarget(pre.repo),
562
625
  ref: pre.ref,
563
626
  destination: pre.destination,
564
627
  mode,
@@ -577,25 +640,9 @@ function previewPromotion({ cwd, filePath, _exec, _vault, _practices }) {
577
640
  }
578
641
  }
579
642
  // ─── Confirm (write half of the write path) ──────────────────────────────────────
580
- /**
581
- * Pack 6 D-07: the display-safe forms used in every verdict and refusal this
582
- * module EMITS — the one owner in src/vault.cts, lazily required (the
583
- * module's `_vault` seam stays the fetch seam; redaction is never mocked).
584
- * Comparisons, the expect token and every git invocation keep the RAW values.
585
- */
586
- function shownRepo(repo) {
587
- // eslint-disable-next-line @typescript-eslint/no-require-imports
588
- const v = require('./vault.cjs');
589
- return v.redactCloneTarget(repo) ?? repo;
590
- }
591
- function shownCause(stderr) {
592
- // eslint-disable-next-line @typescript-eslint/no-require-imports
593
- const v = require('./vault.cjs');
594
- return boundedToken(v.redactRemoteText((stderr || '').split(/\r?\n/)[0] || 'unknown error'));
595
- }
596
643
  /** Build a TRANSPORT refusal from a failed git invocation's stderr (first line only). */
597
644
  function transportRefusal(res) {
598
- const cause = shownCause(res.stderr);
645
+ const cause = boundedToken((res.stderr || '').split(/\r?\n/)[0] || 'unknown error');
599
646
  return refusal(PROMOTE_GUARD.TRANSPORT, ERROR_REASON.VAULT_PROMOTE_TRANSPORT_FAILED, `This practice was not pushed: git could not reach the vault (${cause}). Check your access to the vault and run the promotion again.`);
600
647
  }
601
648
  /**
@@ -617,7 +664,7 @@ function transportRefusal(res) {
617
664
  * for free, so no separate `git show`/`cat-file` call is needed.
618
665
  */
619
666
  function confirmPromotion({ cwd, filePath, expect, _exec, _vault, _practices }) {
620
- const gitExec = _exec ?? shell_command_projection_cjs_1.execGit;
667
+ const gitExec = _exec ?? shell_command_projection_cjs_1.execGitLocal;
621
668
  // 1. Full preflight re-run — never trust the preview. Re-derives repo,
622
669
  // ref, host, and destination from scratch; never accepts a caller-
623
670
  // supplied id/destination/repo/ref.
@@ -755,7 +802,7 @@ function confirmPromotion({ cwd, filePath, expect, _exec, _vault, _practices })
755
802
  }
756
803
  return {
757
804
  status: 'pushed',
758
- repo: shownRepo(pre.repo),
805
+ repo: vault.redactCloneTarget(pre.repo),
759
806
  ref: pre.ref,
760
807
  destination: pre.destination,
761
808
  mode,