@pareto4/pp-core 1.13.0 → 1.18.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 +3 -3
- package/README.md +31 -12
- package/agents/pp-code-fixer.md +0 -11
- package/agents/pp-code-reviewer.md +1 -77
- package/agents/pp-debug-session-manager.md +9 -6
- package/agents/pp-executor.md +12 -29
- package/agents/pp-plan-checker.md +4 -4
- package/agents/pp-planner.md +34 -20
- package/agents/pp-vault-reviewer.md +100 -23
- package/agents/pp-verifier.md +1 -3
- package/bin/install.js +10 -3
- package/commands/pp-execute-phase.md +1 -3
- package/commands/pp-ns-manage.md +1 -1
- package/commands/pp-ns-review.md +1 -1
- package/commands/pp-plan-phase.md +1 -2
- package/commands/pp-vault-promote.md +7 -8
- package/commands/pp-vault-review.md +6 -5
- package/commands/pp-verify-work.md +1 -2
- package/gemini-extension.json +1 -1
- package/hooks/dist/pp-check-update.js +0 -0
- package/hooks/dist/pp-cursor-session-start.js +1 -1
- package/hooks/dist/pp-statusline.js +1 -1
- package/hooks/dist/pp-update-banner.js +0 -0
- package/hooks/pp-check-update.js +0 -0
- package/hooks/pp-cursor-session-start.js +1 -1
- package/hooks/pp-statusline.js +1 -1
- package/hooks/pp-update-banner.js +0 -0
- package/package.json +10 -18
- package/pareto-skills/_shared/create-project-board.sh +0 -0
- package/pareto-skills/pp-project-setup/SKILL.md +1 -1
- package/pp-core/bin/check-latest-version.cjs +0 -0
- package/pp-core/bin/detect-build-commands.sh +2 -2
- package/pp-core/bin/lib/capability-activation.cjs +83 -9
- package/pp-core/bin/lib/capability-registry.cjs +201 -229
- package/pp-core/bin/lib/capability-state.cjs +93 -1
- package/pp-core/bin/lib/capability-writer.cjs +3 -3
- package/pp-core/bin/lib/check-command-router.cjs +1906 -1514
- package/pp-core/bin/lib/clusters.cjs +3 -0
- package/pp-core/bin/lib/command-aliases.cjs +0 -35
- package/pp-core/bin/lib/commands.cjs +9 -4
- package/pp-core/bin/lib/compose-bindings.cjs +251 -88
- package/pp-core/bin/lib/config-loader.cjs +7 -103
- package/pp-core/bin/lib/config-schema.cjs +1 -1
- package/pp-core/bin/lib/config.cjs +151 -62
- package/pp-core/bin/lib/decisions.cjs +4 -4
- package/pp-core/bin/lib/discovery-vault-check.cjs +246 -0
- package/pp-core/bin/lib/frontmatter.cjs +1 -38
- package/pp-core/bin/lib/git-base-branch.cjs +11 -12
- package/pp-core/bin/lib/git-provider.cjs +9 -10
- package/pp-core/bin/lib/init-command-router.cjs +5 -8
- package/pp-core/bin/lib/init-contract.cjs +620 -738
- package/pp-core/bin/lib/init-interactive.cjs +62 -4
- package/pp-core/bin/lib/init.cjs +24 -51
- package/pp-core/bin/lib/install-profiles.cjs +1 -1
- package/pp-core/bin/lib/io.cjs +4 -2
- package/pp-core/bin/lib/loop-resolver.cjs +165 -129
- package/pp-core/bin/lib/org-profile.cjs +402 -161
- package/pp-core/bin/lib/package-identity.cjs +3 -3
- package/pp-core/bin/lib/phase-command-router.cjs +1 -29
- package/pp-core/bin/lib/phase-scope.cjs +1 -1
- package/pp-core/bin/lib/phase.cjs +11 -253
- package/pp-core/bin/lib/platform-bindings.cjs +40 -19
- package/pp-core/bin/lib/profile-output.cjs +1 -1
- package/pp-core/bin/lib/shell-command-projection.cjs +104 -1
- package/pp-core/bin/lib/state.cjs +11 -85
- package/pp-core/bin/lib/template.cjs +1 -1
- package/pp-core/bin/lib/vault-command-router.cjs +1682 -477
- package/pp-core/bin/lib/vault-coordinates.cjs +16 -7
- package/pp-core/bin/lib/vault-index.cjs +656 -0
- package/pp-core/bin/lib/vault-seal.cjs +1851 -0
- package/pp-core/bin/lib/vault-standards-promote.cjs +577 -0
- package/pp-core/bin/lib/vault-standards-review.cjs +293 -0
- package/pp-core/bin/lib/vault-standards.cjs +1504 -0
- package/pp-core/bin/lib/vault-trace.cjs +155 -2778
- package/pp-core/bin/lib/vault-update.cjs +10 -59
- package/pp-core/bin/lib/vault.cjs +2570 -950
- package/pp-core/bin/lib/verify.cjs +0 -11
- package/pp-core/bin/portable-timeout.sh +1 -1
- package/pp-core/bin/pp-tools.cjs +157 -54
- package/pp-core/bin/shared/config-defaults.manifest.json +0 -7
- package/pp-core/bin/shared/config-schema.manifest.json +0 -3
- package/pp-core/bin/shared/model-catalog.json +2 -2
- package/pp-core/bin/verify-reapply-patches.cjs +0 -0
- package/pp-core/org-profile.json +26 -0
- package/pp-core/references/autonomous-best-effort.md +8 -7
- package/pp-core/references/loop-hook-dispatch.md +0 -25
- package/pp-core/references/planner-gap-closure.md +0 -1
- package/pp-core/references/planner-tdd.md +0 -1
- package/pp-core/references/planner-vault-standards.md +311 -0
- package/pp-core/references/planning-config.md +0 -8
- package/pp-core/references/tdd.md +0 -1
- package/pp-core/templates/README.md +1 -0
- package/pp-core/templates/VAULT-REVIEW.md +56 -0
- package/pp-core/templates/phase-prompt.md +2 -6
- package/pp-core/workflows/_runtime-launcher.snippet.sh +0 -0
- package/pp-core/workflows/add-tests.md +55 -4
- package/pp-core/workflows/audit-fix.md +63 -2
- package/pp-core/workflows/audit-milestone.md +1 -1
- package/pp-core/workflows/autonomous.md +85 -11
- package/pp-core/workflows/check-todos.md +13 -2
- package/pp-core/workflows/code-review-fix.md +59 -3
- package/pp-core/workflows/code-review.md +10 -57
- package/pp-core/workflows/complete-milestone.md +1 -1
- package/pp-core/workflows/debug.md +54 -4
- package/pp-core/workflows/discovery-sprint/prefill.md +19 -7
- package/pp-core/workflows/discovery-sprint/prototype.md +46 -49
- package/pp-core/workflows/discovery-sprint/session-13-gates.md +2 -0
- package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/02-as-is.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/03-to-be.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/04-ux-design.md +20 -24
- package/pp-core/workflows/discovery-sprint/sessions/05-architecture.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/06-security.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/07-identity.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/08-integration.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/09-qa.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/10-infrastructure.md +1 -6
- package/pp-core/workflows/discovery-sprint/sessions/11-roadmap.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/12-delivery-model.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +1 -3
- package/pp-core/workflows/discovery-sprint/templates/context-template.md +1 -1
- package/pp-core/workflows/discovery-sprint/vault-context.md +207 -0
- package/pp-core/workflows/discovery-sprint.md +7 -3
- package/pp-core/workflows/discovery-to-build/steps/architecture-deliverables.md +4 -4
- package/pp-core/workflows/discovery-to-build/templates/pp-handoff.md +43 -1
- package/pp-core/workflows/discovery-to-build.md +10 -2
- package/pp-core/workflows/discuss-phase.md +3 -10
- package/pp-core/workflows/execute-phase/steps/check-query-gate.md +327 -0
- package/pp-core/workflows/execute-phase/steps/client-context-gate.md +3 -2
- package/pp-core/workflows/execute-phase/steps/cross-ai-delegation.md +1 -1
- package/pp-core/workflows/execute-phase/steps/execute-pre-dispatch.md +78 -0
- package/pp-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +94 -0
- package/pp-core/workflows/execute-phase/steps/post-merge-gate.md +6 -8
- package/pp-core/workflows/execute-phase/steps/vault-fetch.md +115 -14
- package/pp-core/workflows/execute-phase/steps/vault-load-gate.md +219 -20
- package/pp-core/workflows/execute-phase/steps/verify-post-gate.md +147 -0
- package/pp-core/workflows/execute-phase.md +152 -128
- package/pp-core/workflows/execute-plan.md +54 -6
- package/pp-core/workflows/fast.md +53 -4
- package/pp-core/workflows/help/modes/full.md +3 -3
- package/pp-core/workflows/init/steps/client-context-capture.md +101 -0
- package/pp-core/workflows/init/steps/gitlab-provisioning.md +130 -0
- package/pp-core/workflows/init/steps/project-md-deferral-narration.md +66 -0
- package/pp-core/workflows/init/steps/reachability-outcome.md +70 -0
- package/pp-core/workflows/init.md +232 -272
- package/pp-core/workflows/new-project.md +31 -23
- package/pp-core/workflows/plan-phase.md +98 -45
- package/pp-core/workflows/quick.md +117 -3
- package/pp-core/workflows/secure-phase.md +1 -1
- package/pp-core/workflows/ship.md +57 -28
- package/pp-core/workflows/undo.md +1 -1
- package/pp-core/workflows/update.md +3 -3
- package/pp-core/workflows/validate-phase.md +58 -3
- package/pp-core/workflows/vault-review.md +161 -23
- package/pp-core/workflows/vault-write-back.md +73 -110
- package/pp-core/workflows/verify-work.md +12 -14
- package/scripts/changeset/cli.cjs +6 -5
- package/scripts/changeset/lint.cjs +0 -0
- package/scripts/changeset/new.cjs +33 -50
- package/scripts/dev-env.cjs +25 -85
- package/scripts/gen-capability-registry.cjs +120 -13
- package/scripts/lib/allowlist-ratchet.cjs +1 -103
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -2
- package/scripts/lint-docs-required.cjs +0 -0
- package/scripts/lint-pr-check-project-dir.cjs +1 -3
- package/scripts/lint-test-file-count.allowlist.json +3 -19
- package/scripts/lint-test-file-count.cjs +5 -6
- package/scripts/prompt-injection-scan.sh +19 -17
- package/scripts/release-channel.cjs +0 -0
- package/scripts/sync-manifest-versions.cjs +5 -4
- package/scripts/sync-rulesets.sh +1 -1
- package/scripts/update-size-baseline.cjs +30 -274
- package/scripts/verify-opengsd-sweep.cjs +3 -5
- package/scripts/workflow-size.cjs +9 -61
- package/pp-core/bin/lib/dispatch-plan.cjs +0 -483
- package/pp-core/bin/lib/install-run.cjs +0 -536
- package/pp-core/bin/lib/loop-gates.cjs +0 -213
- package/pp-core/bin/lib/phase-manifest.cjs +0 -527
- package/pp-core/bin/lib/phase-tier.cjs +0 -480
- package/pp-core/bin/lib/plan-graph.cjs +0 -368
- package/pp-core/bin/lib/practices-review-check.cjs +0 -938
- package/pp-core/bin/lib/vault-audit.cjs +0 -2070
- package/pp-core/bin/lib/vault-practices.cjs +0 -1990
- package/pp-core/bin/lib/vault-review.cjs +0 -2359
- package/pp-core/bin/lib/vault-write-back.cjs +0 -877
- package/pp-core/references/executor-vault-trace.md +0 -47
- package/pp-core/references/planner-plan-graph.md +0 -66
- package/pp-core/references/planner-vault-practices.md +0 -112
- package/pp-core/templates/practices/README.md +0 -251
- package/pp-core/templates/practices/error-handling.md +0 -18
- package/pp-core/templates/practices/minimal-comments.md +0 -14
- package/pp-core/templates/practices/naming-convention.md +0 -18
- package/pp-core/templates/practices/practice-template.md +0 -23
- package/pp-core/workflows/discovery-sprint/vault-fetch.md +0 -37
- package/pp-core/workflows/execute-phase/steps/checkpoint-handling.md +0 -45
- package/pp-core/workflows/execute-phase/steps/install-run.md +0 -29
- package/pp-core/workflows/execute-phase/steps/phase-close-rebaseline.md +0 -23
- package/pp-core/workflows/execute-phase/steps/serial-group-dispatch.md +0 -73
- package/pp-core/workflows/execute-phase/steps/vault-door-gate.md +0 -19
- package/pp-core/workflows/plan-phase/steps/phase-tier.md +0 -43
- package/scripts/lib/size-budget.cjs +0 -311
- package/scripts/lint-neutrality-names.cjs +0 -694
- package/scripts/lint-org-coordinates.allowlist.json +0 -35
- package/scripts/lint-org-coordinates.cjs +0 -426
package/scripts/dev-env.cjs
CHANGED
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* dev-env.cjs — keep
|
|
5
|
+
* dev-env.cjs — keep the global pp-core install and a development build apart.
|
|
6
|
+
* FORK-ONLY (PBI 74336)
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
+
* setup [--runtime <id>] build this checkout on its current branch, installed --global
|
|
8
9
|
* dev-setup [--config-dir <dir>] [--no-shell] npm ci, install the dev profile, put claude-dev on PATH
|
|
9
10
|
* install [--config-dir <dir>] [--no-build] build, install into a separate Claude profile
|
|
10
11
|
* install --local [<dir>] [--no-build] build, install into a sandbox project
|
|
11
12
|
* claude [--config-dir <dir>] [-- <args>] start the Claude CLI on the dev profile
|
|
12
13
|
*
|
|
13
|
-
* Defaults: the
|
|
14
|
-
* PP_DEV_CONFIG_DIR, else ~/.claude-dev; the sandbox is ../pp-sandbox.
|
|
14
|
+
* Defaults: the profile is PP_DEV_CONFIG_DIR, else ~/.claude-dev; the sandbox is ../pp-sandbox.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
const fs = require('node:fs');
|
|
@@ -25,27 +25,21 @@ const INSTALLER = path.join(ROOT, 'bin', 'install.js');
|
|
|
25
25
|
|
|
26
26
|
const USAGE = [
|
|
27
27
|
'Usage:',
|
|
28
|
-
' npm run setup [--
|
|
28
|
+
' npm run setup [-- --runtime <id>]',
|
|
29
29
|
' npm run dev:setup [-- [--config-dir <dir>] [--no-shell]]',
|
|
30
30
|
' npm run dev:install [-- [--config-dir <dir>] [--no-build]]',
|
|
31
31
|
' npm run dev:install -- --local [<dir>] [--no-build]',
|
|
32
32
|
' npm run dev:claude [-- <claude args>]',
|
|
33
33
|
].join('\n');
|
|
34
34
|
|
|
35
|
-
function
|
|
36
|
-
const opts = { command: '
|
|
35
|
+
function parseSetupArgs(rest) {
|
|
36
|
+
const opts = { command: 'setup', runtime: 'claude' };
|
|
37
37
|
for (let i = 0; i < rest.length; i++) {
|
|
38
38
|
const arg = rest[i];
|
|
39
|
-
if (arg === '--
|
|
40
|
-
const value = rest[++i];
|
|
41
|
-
if (!value) return { error: '--dir requires a path' };
|
|
42
|
-
opts.dir = value;
|
|
43
|
-
} else if (arg === '--runtime') {
|
|
39
|
+
if (arg === '--runtime') {
|
|
44
40
|
const value = rest[++i];
|
|
45
41
|
if (!value) return { error: '--runtime requires a runtime id' };
|
|
46
42
|
opts.runtime = value;
|
|
47
|
-
} else if (!arg.startsWith('-') && opts.tag === null) {
|
|
48
|
-
opts.tag = arg;
|
|
49
43
|
} else {
|
|
50
44
|
return { error: `unknown option: ${arg}` };
|
|
51
45
|
}
|
|
@@ -72,7 +66,7 @@ function parseDevSetupArgs(rest) {
|
|
|
72
66
|
|
|
73
67
|
function parseArgs(argv) {
|
|
74
68
|
const [command, ...rest] = argv;
|
|
75
|
-
if (command === '
|
|
69
|
+
if (command === 'setup') return parseSetupArgs(rest);
|
|
76
70
|
if (command === 'dev-setup') return parseDevSetupArgs(rest);
|
|
77
71
|
if (command !== 'install' && command !== 'claude') {
|
|
78
72
|
return { error: `unknown command: ${command === undefined ? '(none)' : command}` };
|
|
@@ -133,7 +127,7 @@ function resolveLocalDir(opts, root = ROOT) {
|
|
|
133
127
|
return path.resolve(opts.localDir || path.join(root, '..', 'pp-sandbox'));
|
|
134
128
|
}
|
|
135
129
|
|
|
136
|
-
//
|
|
130
|
+
// ~/.claude holds the global install from npm run setup; the dev routes never write there.
|
|
137
131
|
function assertNotStableProfile(dir, home = os.homedir()) {
|
|
138
132
|
if (samePath(dir, path.join(home, '.claude'))) {
|
|
139
133
|
throw new ExitError(1, `Refusing to install a dev build into the stable profile ${dir}.`);
|
|
@@ -167,23 +161,6 @@ function git(args, cwd) {
|
|
|
167
161
|
return execFileSync('git', args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
|
|
168
162
|
}
|
|
169
163
|
|
|
170
|
-
// Highest vX.Y.Z (or X.Y.Z) tag; pre-release tags are skipped.
|
|
171
|
-
function pickLatestTag(tags) {
|
|
172
|
-
const parsed = tags
|
|
173
|
-
.map((tag) => ({ tag, m: /^v?(\d+)\.(\d+)\.(\d+)$/.exec(tag) }))
|
|
174
|
-
.filter((t) => t.m)
|
|
175
|
-
.map((t) => ({ tag: t.tag, v: t.m.slice(1).map(Number) }));
|
|
176
|
-
parsed.sort((a, b) => b.v[0] - a.v[0] || b.v[1] - a.v[1] || b.v[2] - a.v[2]);
|
|
177
|
-
return parsed.length ? parsed[0].tag : null;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Next to the main checkout, so running this from any worktree finds the same directory.
|
|
181
|
-
function resolveStableDir(opts, repo = ROOT) {
|
|
182
|
-
if (opts.dir) return path.resolve(opts.dir);
|
|
183
|
-
const commonDir = path.resolve(repo, git(['rev-parse', '--git-common-dir'], repo));
|
|
184
|
-
return path.join(path.dirname(commonDir), '..', 'pp-core-stable');
|
|
185
|
-
}
|
|
186
|
-
|
|
187
164
|
// The runtimes install.js takes as --<id>, read from the capability manifests so no build is needed.
|
|
188
165
|
function listRuntimes(root = ROOT) {
|
|
189
166
|
const capDir = path.join(root, 'capabilities');
|
|
@@ -206,57 +183,22 @@ function resolveRuntime(id, root = ROOT) {
|
|
|
206
183
|
return found;
|
|
207
184
|
}
|
|
208
185
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
.split(/\r?\n/)
|
|
212
|
-
.filter((line) => line.startsWith('worktree '))
|
|
213
|
-
.map((line) => path.resolve(line.slice('worktree '.length)));
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Adds the worktree at the tag, or moves an existing clean one to it; never touches your own checkout.
|
|
217
|
-
function prepareStableWorktree({ repo, dir, tag }) {
|
|
218
|
-
try {
|
|
219
|
-
git(['rev-parse', '--verify', '--quiet', `refs/tags/${tag}`], repo);
|
|
220
|
-
} catch {
|
|
221
|
-
throw new ExitError(1, `Tag ${tag} does not exist in ${repo}.`);
|
|
222
|
-
}
|
|
223
|
-
let known = listWorktrees(repo).some((w) => samePath(w, dir));
|
|
224
|
-
// A worktree folder deleted by hand stays registered; prune it so it is added again.
|
|
225
|
-
if (known && !fs.existsSync(dir)) {
|
|
226
|
-
git(['worktree', 'prune'], repo);
|
|
227
|
-
known = false;
|
|
228
|
-
}
|
|
229
|
-
if (!known) {
|
|
230
|
-
if (fs.existsSync(dir)) {
|
|
231
|
-
throw new ExitError(1, `${dir} exists but is not a worktree of ${repo}; pass --dir <other dir>.`);
|
|
232
|
-
}
|
|
233
|
-
git(['worktree', 'add', '--detach', dir, tag], repo);
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
if (git(['status', '--porcelain'], dir) !== '') {
|
|
237
|
-
throw new ExitError(1, `The stable worktree ${dir} has local changes; commit or discard them first.`);
|
|
238
|
-
}
|
|
239
|
-
git(['checkout', '--quiet', '--detach', tag], dir);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
function installStable(opts) {
|
|
186
|
+
// Installs the checkout this runs from, so the global install follows its current branch.
|
|
187
|
+
function installSetup(opts) {
|
|
243
188
|
const runtime = resolveRuntime(opts.runtime);
|
|
244
189
|
npmCliPath();
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
} catch {
|
|
249
|
-
process.stderr.write('Could not fetch tags from origin; using the tags already in this clone.\n');
|
|
250
|
-
}
|
|
251
|
-
const tag = opts.tag || pickLatestTag(git(['tag', '--list'], ROOT).split(/\r?\n/));
|
|
252
|
-
if (!tag) throw new ExitError(1, 'No release tag found; pass one: npm run setup -- <tag>.');
|
|
253
|
-
prepareStableWorktree({ repo: ROOT, dir, tag });
|
|
254
|
-
process.stdout.write(`Stable worktree ${dir} is on ${tag}.\n`);
|
|
255
|
-
runNpm(['ci'], dir);
|
|
256
|
-
runNpm(['run', 'build'], dir);
|
|
257
|
-
execFileSync(process.execPath, [path.join(dir, 'bin', 'install.js'), `--${runtime.id}`, '--global'], { cwd: dir, stdio: 'inherit' });
|
|
190
|
+
runNpm(['ci'], ROOT);
|
|
191
|
+
runBuild();
|
|
192
|
+
execFileSync(process.execPath, [INSTALLER, `--${runtime.id}`, '--global'], { cwd: ROOT, stdio: 'inherit' });
|
|
258
193
|
const again = runtime.id === 'claude' ? 'npm run setup' : `npm run setup -- --runtime ${runtime.id}`;
|
|
259
|
-
process.stdout.write(
|
|
194
|
+
process.stdout.write(`
|
|
195
|
+
pp-core from ${describeCheckout(devBuildInfo())} is installed as the global pp-core for ${runtime.title}. Update later with: ${again}
|
|
196
|
+
`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function describeCheckout(info) {
|
|
200
|
+
const ref = info.commit ? `${info.branch || 'detached HEAD'}@${info.commit}` : info.checkout;
|
|
201
|
+
return info.dirty ? `${ref} (with uncommitted changes)` : ref;
|
|
260
202
|
}
|
|
261
203
|
|
|
262
204
|
// The statusline shows a DEV badge with this build's branch and commit when this file is present.
|
|
@@ -429,7 +371,7 @@ function main(argv = process.argv.slice(2)) {
|
|
|
429
371
|
if (opts.error) throw new ExitError(2, `${opts.error}\n${USAGE}`);
|
|
430
372
|
if (opts.command === 'claude') return launchClaude(opts);
|
|
431
373
|
if (opts.command === 'dev-setup') devSetup(opts);
|
|
432
|
-
else if (opts.command === '
|
|
374
|
+
else if (opts.command === 'setup') installSetup(opts);
|
|
433
375
|
else if (opts.local) installSandbox(opts);
|
|
434
376
|
else installProfile(opts);
|
|
435
377
|
return 0;
|
|
@@ -441,9 +383,9 @@ module.exports = {
|
|
|
441
383
|
parseArgs,
|
|
442
384
|
resolveConfigDir,
|
|
443
385
|
resolveLocalDir,
|
|
444
|
-
resolveStableDir,
|
|
445
386
|
resolveLaunchDir,
|
|
446
387
|
devBuildInfo,
|
|
388
|
+
describeCheckout,
|
|
447
389
|
renderRcLauncher,
|
|
448
390
|
upsertLauncher,
|
|
449
391
|
windowsLauncherDir,
|
|
@@ -452,10 +394,8 @@ module.exports = {
|
|
|
452
394
|
writeWindowsLaunchers,
|
|
453
395
|
pathHasEntry,
|
|
454
396
|
appendPathEntry,
|
|
455
|
-
pickLatestTag,
|
|
456
397
|
listRuntimes,
|
|
457
398
|
resolveRuntime,
|
|
458
|
-
prepareStableWorktree,
|
|
459
399
|
assertNotStableProfile,
|
|
460
400
|
assertOutsideRepo,
|
|
461
401
|
samePath,
|
|
@@ -568,6 +568,18 @@ function validateFeatureBody(cap) {
|
|
|
568
568
|
}
|
|
569
569
|
}
|
|
570
570
|
|
|
571
|
+
// Quick 261015-ect (QD-13): requiresPlatformBindings, optional. When present it
|
|
572
|
+
// must be a boolean; the loop resolver drops every hook of a capability that
|
|
573
|
+
// declares `true` on a project with no .platform-bindings.json. Which
|
|
574
|
+
// capabilities MUST declare it is a cross-capability rule
|
|
575
|
+
// (validatePlatformBindingsField).
|
|
576
|
+
if (cap.requiresPlatformBindings !== undefined && typeof cap.requiresPlatformBindings !== 'boolean') {
|
|
577
|
+
errors.push(
|
|
578
|
+
'capability "' + (cap.id || '(unknown)') + '" requiresPlatformBindings must be a boolean (got: ' +
|
|
579
|
+
JSON.stringify(cap.requiresPlatformBindings) + ')',
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
|
|
571
583
|
return errors;
|
|
572
584
|
}
|
|
573
585
|
|
|
@@ -1204,17 +1216,6 @@ function validateGate(gate, prefix) {
|
|
|
1204
1216
|
if (count !== 1) {
|
|
1205
1217
|
errors.push(prefix + '.check must have exactly one of: query, predicate, agentVerdict');
|
|
1206
1218
|
}
|
|
1207
|
-
// check.args: literal tokens `loop run-gates` appends after the phase — query gates only,
|
|
1208
|
-
// at most 8, each a flag or a plain word (the runner refuses anything else at run time too).
|
|
1209
|
-
if (Object.prototype.hasOwnProperty.call(gate.check, 'args')) {
|
|
1210
|
-
const a = gate.check.args;
|
|
1211
|
-
if (!hasQuery) {
|
|
1212
|
-
errors.push(prefix + '.check.args is only valid beside check.query');
|
|
1213
|
-
} else if (!Array.isArray(a) || a.length > 8
|
|
1214
|
-
|| !a.every((t) => typeof t === 'string' && /^(?:--[a-z][a-z0-9-]*|[a-z0-9][a-z0-9._-]*)$/i.test(t))) {
|
|
1215
|
-
errors.push(prefix + '.check.args must be an array of at most 8 flags (--name) or plain words');
|
|
1216
|
-
}
|
|
1217
|
-
}
|
|
1218
1219
|
// agentVerdict forces blocking: false (advisory only)
|
|
1219
1220
|
if (hasAgentVerdict && gate.blocking === true) {
|
|
1220
1221
|
errors.push(
|
|
@@ -1246,8 +1247,7 @@ function validateGate(gate, prefix) {
|
|
|
1246
1247
|
* - contribution.into ∈ step's agentRoles, OR contribution.into names an agent
|
|
1247
1248
|
* already registered by some capability's own `agents` array (a non-pipeline
|
|
1248
1249
|
* consumer of the same loop point, e.g. code-review.md reading execute:post
|
|
1249
|
-
* alongside execute-phase.md
|
|
1250
|
-
* execute:post -> pp-code-reviewer contribution)
|
|
1250
|
+
* alongside execute-phase.md)
|
|
1251
1251
|
* - when references a config key in cap.config
|
|
1252
1252
|
*
|
|
1253
1253
|
* NOTE: step.consumes satisfiability is NOT checked here — it requires the full
|
|
@@ -1330,6 +1330,102 @@ function validateAgainstContract(cap, capId, knownAgentIds) {
|
|
|
1330
1330
|
return errors;
|
|
1331
1331
|
}
|
|
1332
1332
|
|
|
1333
|
+
/**
|
|
1334
|
+
* Quick 261015-ect (QD-13): the vault-family rule for `requiresPlatformBindings`,
|
|
1335
|
+
* A capability that is `vault` or
|
|
1336
|
+
* lists `vault` in `requires` MUST declare the field, so a vault-family
|
|
1337
|
+
* capability added later cannot render hooks on a project the vault does not
|
|
1338
|
+
* govern by forgetting it. A capability outside the family may declare it too.
|
|
1339
|
+
*
|
|
1340
|
+
* @param {Map<string, object>} capMap
|
|
1341
|
+
* @returns {string[]} error strings; empty = all pass
|
|
1342
|
+
*/
|
|
1343
|
+
function validatePlatformBindingsField(capMap) {
|
|
1344
|
+
const errors = [];
|
|
1345
|
+
for (const [capId, cap] of capMap) {
|
|
1346
|
+
if (!cap || typeof cap !== 'object') continue;
|
|
1347
|
+
const requiresVault = Array.isArray(cap.requires) && cap.requires.includes('vault');
|
|
1348
|
+
if (capId !== 'vault' && !requiresVault) continue;
|
|
1349
|
+
if (!Object.prototype.hasOwnProperty.call(cap, 'requiresPlatformBindings')) {
|
|
1350
|
+
errors.push(
|
|
1351
|
+
'capability "' + capId + '" is vault or requires vault and must declare requiresPlatformBindings, ' +
|
|
1352
|
+
'so a vault-family capability cannot render hooks on a project the vault does not govern',
|
|
1353
|
+
);
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
return errors;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
/**
|
|
1360
|
+
* Phase 16 D-19: the per-hook seal field's name. The resolver reads it as
|
|
1361
|
+
* `SEAL_FIELD` from capability-state; this generator cannot require that module
|
|
1362
|
+
* (its load graph reaches surface.cjs and config-loader.cjs, which read the
|
|
1363
|
+
* very registry this script regenerates), so the literal is restated here and
|
|
1364
|
+
* pinned equal to `SEAL_FIELD` by tests/capability-registry.test.cjs.
|
|
1365
|
+
*/
|
|
1366
|
+
const SEAL_FIELD_NAME = 'onRecordedArtifacts';
|
|
1367
|
+
|
|
1368
|
+
/**
|
|
1369
|
+
* Phase 16 D-19 / D-25 / D-26: validate the per-hook seal field.
|
|
1370
|
+
*
|
|
1371
|
+
* A gate carrying the field renders for an enabled, config-inactive capability
|
|
1372
|
+
* over recorded vault artifacts, bypassing the capability's active check and
|
|
1373
|
+
* its `when` (D-18). That is a widening of render behaviour, so the field is
|
|
1374
|
+
* closed HERE, at generation, to exactly the shape the shipped seal gate has.
|
|
1375
|
+
* Five rules, each reported on its own:
|
|
1376
|
+
* (a) gates only (a step or contribution carrying it is refused);
|
|
1377
|
+
* (b) the value is literal `true` (`"true"`, `1`, `null`, `false` refused);
|
|
1378
|
+
* (c) the declaring capability is `vault` or lists `vault` in `requires`.
|
|
1379
|
+
* A seal gate never dispatches twice: the resolver drops it whenever its
|
|
1380
|
+
* capability is active, so it renders only with the vault switched off;
|
|
1381
|
+
* (d) the gate is `blocking: true`;
|
|
1382
|
+
* (e) the point is `ship:pre` (D-21: the requirement's block point; a seal at
|
|
1383
|
+
* verify:post would halt execute-phase on a switch set on purpose).
|
|
1384
|
+
*
|
|
1385
|
+
* Presence is own-property presence.
|
|
1386
|
+
*
|
|
1387
|
+
* @param {Map<string, object>} capMap
|
|
1388
|
+
* @returns {string[]} error strings; empty = all pass
|
|
1389
|
+
*/
|
|
1390
|
+
function validateSealField(capMap) {
|
|
1391
|
+
const errors = [];
|
|
1392
|
+
const KINDS = [['steps', 'step'], ['contributions', 'contribution'], ['gates', 'gate']];
|
|
1393
|
+
for (const [capId, cap] of capMap) {
|
|
1394
|
+
if (!cap || typeof cap !== 'object') continue;
|
|
1395
|
+
const prefix = 'capability "' + capId + '"';
|
|
1396
|
+
const requiresVault = Array.isArray(cap.requires) && cap.requires.includes('vault');
|
|
1397
|
+
for (const [arrKey, kind] of KINDS) {
|
|
1398
|
+
const arr = Array.isArray(cap[arrKey]) ? cap[arrKey] : [];
|
|
1399
|
+
for (const hook of arr) {
|
|
1400
|
+
if (!hook || typeof hook !== 'object') continue;
|
|
1401
|
+
if (!Object.prototype.hasOwnProperty.call(hook, SEAL_FIELD_NAME)) continue;
|
|
1402
|
+
const where = prefix + ' ' + kind + ' at point "' + String(hook.point) + '"';
|
|
1403
|
+
const value = hook[SEAL_FIELD_NAME];
|
|
1404
|
+
if (kind !== 'gate') {
|
|
1405
|
+
errors.push(where + ': ' + SEAL_FIELD_NAME + ' is allowed on gates only');
|
|
1406
|
+
continue;
|
|
1407
|
+
}
|
|
1408
|
+
if (value !== true) {
|
|
1409
|
+
errors.push(where + ': ' + SEAL_FIELD_NAME + ' must be literal true (got ' + JSON.stringify(value) + ')');
|
|
1410
|
+
}
|
|
1411
|
+
if (capId !== 'vault' && !requiresVault) {
|
|
1412
|
+
errors.push(
|
|
1413
|
+
where + ': ' + SEAL_FIELD_NAME + ' is allowed only on the "vault" capability or a capability that ' +
|
|
1414
|
+
'lists "vault" in requires',
|
|
1415
|
+
);
|
|
1416
|
+
}
|
|
1417
|
+
if (hook.blocking !== true) {
|
|
1418
|
+
errors.push(where + ': a gate carrying ' + SEAL_FIELD_NAME + ' must be blocking: true');
|
|
1419
|
+
}
|
|
1420
|
+
if (hook.point !== 'ship:pre') {
|
|
1421
|
+
errors.push(where + ': ' + SEAL_FIELD_NAME + ' is allowed only at point "ship:pre"');
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
return errors;
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1333
1429
|
/**
|
|
1334
1430
|
* C1+C2: Global consumes-satisfiability validation.
|
|
1335
1431
|
*
|
|
@@ -1490,6 +1586,14 @@ const TIER_RANK = { core: 0, standard: 1, full: 2 };
|
|
|
1490
1586
|
function validateCrossCapability(capMap, centralKeys) {
|
|
1491
1587
|
const errors = [];
|
|
1492
1588
|
|
|
1589
|
+
// Phase 16 D-19: the per-hook seal field (needs capMap for every capability's
|
|
1590
|
+
// `requires`).
|
|
1591
|
+
errors.push(...validateSealField(capMap));
|
|
1592
|
+
|
|
1593
|
+
// Quick 261015-ect (QD-13): every vault-family capability declares
|
|
1594
|
+
// requiresPlatformBindings (needs capMap for every capability's `requires`).
|
|
1595
|
+
errors.push(...validatePlatformBindingsField(capMap));
|
|
1596
|
+
|
|
1493
1597
|
// Ownership: one owner per skill stem + agent name
|
|
1494
1598
|
const skillOwner = new Map(); // skill → capId
|
|
1495
1599
|
const agentOwner = new Map(); // agent → capId
|
|
@@ -2657,6 +2761,9 @@ module.exports = {
|
|
|
2657
2761
|
validateAgainstContract,
|
|
2658
2762
|
validateConsumesGlobal,
|
|
2659
2763
|
validateCrossCapability,
|
|
2764
|
+
validateSealField,
|
|
2765
|
+
validatePlatformBindingsField,
|
|
2766
|
+
SEAL_FIELD_NAME,
|
|
2660
2767
|
classifyCrossErrors,
|
|
2661
2768
|
loadCentralConfigKeys,
|
|
2662
2769
|
loadAndValidate,
|
|
@@ -233,106 +233,4 @@ function assertFileBaseline({ label, current, baseline, fail, updateHint }) {
|
|
|
233
233
|
return { grown, shrunk, added, removed };
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
-
|
|
237
|
-
* Assert that each artifact's measured size stays within a per-change DELTA
|
|
238
|
-
* BUDGET of its committed baseline (workflow size budget policy, 2026-10-11).
|
|
239
|
-
*
|
|
240
|
-
* Unlike `assertFileBaseline` (exact-byte pins), small growth is absorbed by a
|
|
241
|
-
* slack so routine edits need no baseline commit, while large or cumulative
|
|
242
|
-
* growth still fails by name:
|
|
243
|
-
*
|
|
244
|
-
* - A file may exceed its baseline by at most `perFileSlack` bytes.
|
|
245
|
-
* - The SUM of all positive overages may not exceed `totalSlack` bytes, so many
|
|
246
|
-
* small growths cannot add up to a large one unseen.
|
|
247
|
-
* - Shrinking always passes and never needs a baseline commit.
|
|
248
|
-
* - A file with no baseline entry (ADDED) or a baseline entry with no file
|
|
249
|
-
* (REMOVED) fails: the baseline must name exactly the files on disk.
|
|
250
|
-
* Recording either needs no reason.
|
|
251
|
-
* - An EMPTY baseline fails: with no entries every file is "added", but an
|
|
252
|
-
* explicit message makes the vacuous-pass case impossible to misread.
|
|
253
|
-
*
|
|
254
|
-
* Overage is measured against the committed baseline, not the previous commit,
|
|
255
|
-
* so the slack is a budget that a re-baseline resets — not a per-commit
|
|
256
|
-
* allowance that can be spent again on every commit.
|
|
257
|
-
*
|
|
258
|
-
* @param {object} opts
|
|
259
|
-
* @param {string} opts.label - Guard name (used in messages).
|
|
260
|
-
* @param {Object<string, number>} opts.current - Measured sizes by name.
|
|
261
|
-
* @param {Object<string, number>} opts.baseline - Committed sizes by name
|
|
262
|
-
* (numbers only; strip metadata keys first).
|
|
263
|
-
* @param {number} opts.perFileSlack - Max bytes one file may exceed its baseline.
|
|
264
|
-
* @param {number} opts.totalSlack - Max sum of all positive overages.
|
|
265
|
-
* @param {function(string): void} opts.fail - Invoked once per violation category.
|
|
266
|
-
* @param {string} [opts.updateHint] - Remediation hint appended to growth failures.
|
|
267
|
-
* @returns {{ overages: Array<{name:string,from:number,to:number,delta:number}>,
|
|
268
|
-
* totalOverage: number,
|
|
269
|
-
* shrunk: Array<{name:string,from:number,to:number,delta:number}>,
|
|
270
|
-
* added: string[], removed: string[] }}
|
|
271
|
-
*/
|
|
272
|
-
function assertDeltaBudget({ label, current, baseline, perFileSlack, totalSlack, fail, updateHint }) {
|
|
273
|
-
const currentNames = new Set(Object.keys(current));
|
|
274
|
-
const baselineNames = new Set(Object.keys(baseline));
|
|
275
|
-
const hint = updateHint ? `\n${updateHint}` : '';
|
|
276
|
-
|
|
277
|
-
if (baselineNames.size === 0) {
|
|
278
|
-
fail(
|
|
279
|
-
`[${label}] the committed baseline has no size entries — an empty baseline would make ` +
|
|
280
|
-
`every growth check pass vacuously. Regenerate it with \`npm run size:baseline\`.`
|
|
281
|
-
);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
const added = [...currentNames].filter((n) => !baselineNames.has(n)).sort();
|
|
285
|
-
const removed = [...baselineNames].filter((n) => !currentNames.has(n)).sort();
|
|
286
|
-
|
|
287
|
-
const overages = [];
|
|
288
|
-
const shrunk = [];
|
|
289
|
-
for (const name of [...currentNames].filter((n) => baselineNames.has(n)).sort()) {
|
|
290
|
-
const from = baseline[name];
|
|
291
|
-
const to = current[name];
|
|
292
|
-
if (!Number.isInteger(from) || from < 0) {
|
|
293
|
-
fail(`[${label}] baseline entry ${name} is not a non-negative integer byte size: ${JSON.stringify(from)}`);
|
|
294
|
-
continue;
|
|
295
|
-
}
|
|
296
|
-
if (to > from) overages.push({ name, from, to, delta: to - from });
|
|
297
|
-
else if (to < from) shrunk.push({ name, from, to, delta: from - to });
|
|
298
|
-
}
|
|
299
|
-
const totalOverage = overages.reduce((sum, o) => sum + o.delta, 0);
|
|
300
|
-
const fmt = (o) => ` - ${o.name}: ${o.from} → ${o.to} (+${o.delta})`;
|
|
301
|
-
|
|
302
|
-
const overFile = overages.filter((o) => o.delta > perFileSlack);
|
|
303
|
-
if (overFile.length > 0) {
|
|
304
|
-
fail(
|
|
305
|
-
`[${label}] ${overFile.length} file(s) grew more than the per-file slack of ${perFileSlack} bytes ` +
|
|
306
|
-
`past the committed baseline. Shrink them (extract content lazily), or re-baseline with a ` +
|
|
307
|
-
`reason: npm run size:baseline -- --reason "<why this growth is needed>"\n` +
|
|
308
|
-
`${overFile.map(fmt).join('\n')}${hint}`
|
|
309
|
-
);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
if (totalOverage > totalSlack) {
|
|
313
|
-
fail(
|
|
314
|
-
`[${label}] total growth past the committed baseline is ${totalOverage} bytes, over the total ` +
|
|
315
|
-
`slack of ${totalSlack} bytes (each file may be under its own slack — the sum is what failed). ` +
|
|
316
|
-
`Shrink, or re-baseline with a reason: npm run size:baseline -- --reason "<why>"\n` +
|
|
317
|
-
`${overages.map(fmt).join('\n')}${hint}`
|
|
318
|
-
);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
if (added.length > 0) {
|
|
322
|
-
fail(
|
|
323
|
-
`[${label}] ${added.length} file(s) are not in the baseline — run \`npm run size:baseline\` ` +
|
|
324
|
-
`to record them (no --reason needed for new files).\n${added.map((n) => ` - ${n}`).join('\n')}`
|
|
325
|
-
);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
if (removed.length > 0) {
|
|
329
|
-
fail(
|
|
330
|
-
`[${label}] ${removed.length} baseline entry(ies) no longer exist — run \`npm run size:baseline\` ` +
|
|
331
|
-
`to drop them (no --reason needed).\n${removed.map((n) => ` - ${n}`).join('\n')}`
|
|
332
|
-
);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
return { overages, totalOverage, shrunk, added, removed };
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
module.exports = { assertWithinAllowlist, assertTightCeiling, assertFileBaseline, assertDeltaBudget };
|
|
236
|
+
module.exports = { assertWithinAllowlist, assertTightCeiling, assertFileBaseline };
|
|
@@ -288,7 +288,6 @@
|
|
|
288
288
|
"tests/read-guard.test.cjs :: source-text-is-the-product",
|
|
289
289
|
"tests/reapply-patches.test.cjs :: source-text-is-the-product",
|
|
290
290
|
"tests/reapply-verify-hunks.test.cjs :: source-text-is-the-product",
|
|
291
|
-
"tests/release-coverage-scope.test.cjs :: source-text-is-the-product",
|
|
292
291
|
"tests/release-tarball-smoke-workflow.test.cjs :: source-text-is-the-product",
|
|
293
292
|
"tests/release-tarball-smoke.install.test.cjs :: integration-test-input",
|
|
294
293
|
"tests/research-agent-profiles.test.cjs :: <runtime-contract-is-the-product> research agent .md content is the governed surface",
|
|
@@ -321,7 +320,6 @@
|
|
|
321
320
|
"tests/windows-test-parity-guard.test.cjs :: structural-regression-guard",
|
|
322
321
|
"tests/workflow-compat.test.cjs :: source-text-is-the-product",
|
|
323
322
|
"tests/workflow-guard-registration.test.cjs :: structural-regression-guard",
|
|
324
|
-
"tests/workflow-maintainer-skip.test.cjs :: source-text-is-the-product",
|
|
325
323
|
"tests/workflow-shell-pinning.test.cjs :: file-scope prefilter, not a test assertion — we need to",
|
|
326
324
|
"tests/workflow-size-budget.test.cjs :: source-text-is-the-product",
|
|
327
325
|
"tests/workspace.test.cjs :: source-text-is-the-product",
|
|
File without changes
|
|
@@ -9,9 +9,7 @@ const { runMain } = require('./lib/cli-exit.cjs');
|
|
|
9
9
|
const ROOT = path.join(__dirname, '..');
|
|
10
10
|
|
|
11
11
|
const DEFAULT_RELATIVE_FILES = [
|
|
12
|
-
'.github/workflows/
|
|
13
|
-
'.github/workflows/pr-template-format.yml',
|
|
14
|
-
'.github/workflows/changeset-required.yml',
|
|
12
|
+
'.github/workflows/ci.yml',
|
|
15
13
|
'scripts/lint-command-contract.cjs',
|
|
16
14
|
'scripts/lint-skill-deps.cjs',
|
|
17
15
|
'scripts/lint-descriptions.cjs',
|
|
@@ -179,30 +179,14 @@
|
|
|
179
179
|
},
|
|
180
180
|
"vault": {
|
|
181
181
|
"files": [
|
|
182
|
-
"vault-
|
|
182
|
+
"feat-71924-vault-required-gate.test.cjs",
|
|
183
|
+
"vault-doors.integration.test.cjs",
|
|
183
184
|
"vault-read-path.test.cjs",
|
|
184
185
|
"vault-security.test.cjs",
|
|
185
186
|
"vault-status-check.test.cjs",
|
|
186
187
|
"vault.test.cjs"
|
|
187
188
|
],
|
|
188
|
-
"issue": "v1.7.0 Client Vault Load — four distinct concerns: vault.test (fetch/status/summary unit), vault-security (VSEC-02/03 write-guard + secret/fence), vault-read-path (VGATE-03 SUMMARY pointer), vault-status-check (VGATE-02 gate-query handler).
|
|
189
|
-
},
|
|
190
|
-
"vault-review": {
|
|
191
|
-
"files": [
|
|
192
|
-
"vault-review.integration.test.cjs",
|
|
193
|
-
"vault-review.security.test.cjs",
|
|
194
|
-
"vault-review.test.cjs"
|
|
195
|
-
],
|
|
196
|
-
"issue": "Agent practice review (vault assurance R5) — three distinct concerns for pp-core/bin/lib/vault-review.cjs: vault-review.test (the pure derivations — vocabularies, both derived-N-A arms, the counts, the range, the clean-render refusal, the scaffold renderer and the line stamper — plus the require-graph barrier), vault-review.integration.test (the seams: the artifact validator and the check vault-review / vault review scaffold|finalize arms driven through the real pp-tools.cjs, where the exit-0 / no-block-key contract is proved), and vault-review.security.test (adversarial practice bodies: a spoofed fence marker, directive-shaped text, a client table inside the fence, an option-shaped base branch). The .security marker is what identifies the adversarial lane to scripts/run-tests.cjs; consolidating would blur three different questions into one file."
|
|
197
|
-
},
|
|
198
|
-
"vault-practices": {
|
|
199
|
-
"files": [
|
|
200
|
-
"vault-practices-examples.test.cjs",
|
|
201
|
-
"vault-practices-matcher.test.cjs",
|
|
202
|
-
"vault-practices.slow.test.cjs",
|
|
203
|
-
"vault-practices.test.cjs"
|
|
204
|
-
],
|
|
205
|
-
"issue": "Phase 24 (24-01/24-02/24-04) — three distinct concerns for pp-core/bin/lib/vault-practices.cjs: vault-practices.test (24-01 parser/validation/discovery unit tests), vault-practices-matcher.test (24-02 glob matcher + two-bucket result + report unit tests), vault-practices-examples.test (24-04 shipped-example drift guard + minimal-comments global-bucket smoke case, PRAC-03 Success Criterion 3). All three test the same production module and are each independently load-bearing; consolidating would blur distinct plan-level verification gates. The practice-file contract extensions added vault-practices.slow.test.cjs — the >5 s worst-case matcher corpus behind the matching cost bound, split out per the .slow suffix convention so the default suite stays fast."
|
|
189
|
+
"issue": "v1.7.0 Client Vault Load — four distinct concerns: vault.test (fetch/status/summary unit), vault-security (VSEC-02/03 write-guard + secret/fence), vault-read-path (VGATE-03 SUMMARY pointer), vault-status-check (VGATE-02 gate-query handler). Vault Assurance phase 02 (#71924) added feat-71924-vault-required-gate.test.cjs as the phase's ONE consolidated source-grep feature file — it owns the source-text assertions for GATE-02, GATE-03, GATE-06, GATE-08, GATE-09 and GATE-10 together, deliberately in a single file so the phase's six requirements do not each spawn a sibling; later plans in the phase extend it rather than adding files. Vault Assurance phase 16 plan 10 added vault-doors.integration.test.cjs as the phase's ONE door fence-proof owner (DOOR-01..05, D-09): every door's shipped fence (execute-plan, quick, fast, debug, the secondary doors) composed by tests/helpers/gate-fence-composer.cjs and run in bash and zsh, one describe per door plan, so 16-12..16-15 extend it rather than each adding a sibling. The hyphen-prefix matcher attributes it here because no vault-doors module exists; it tests workflow fences, not vault.cjs."
|
|
206
190
|
}
|
|
207
191
|
}
|
|
208
192
|
}
|
|
@@ -80,12 +80,11 @@ function prodPrefix(filename) {
|
|
|
80
80
|
// stamps. Five forms are stripped: `.integration`, `.slow`, `.security`,
|
|
81
81
|
// `.install` and the bare `.test`.
|
|
82
82
|
//
|
|
83
|
-
// `.property` is DELIBERATELY NOT stripped
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
// statement. Changing it is a deliberate allowlist migration, not a tidy-up.
|
|
83
|
+
// `.property` is DELIBERATELY NOT stripped: a `<name>.property.test.cjs` keeps
|
|
84
|
+
// the suffix in its prefix, so the hyphen-prefix matcher clusters it under the
|
|
85
|
+
// shortest matching module rather than under `<name>`. Stripping it would
|
|
86
|
+
// re-cluster every property test the allowlist has already counted. Changing
|
|
87
|
+
// it is a deliberate allowlist migration, not a tidy-up.
|
|
89
88
|
function testEffectivePrefix(testName) {
|
|
90
89
|
const bare = testName
|
|
91
90
|
.replace(/\.(integration|slow|security|install)\.test\.(ts|cjs)$/, '')
|
|
@@ -82,23 +82,25 @@ PATTERNS=(
|
|
|
82
82
|
# THIS file and reads the array bash actually expands — adding, removing or
|
|
83
83
|
# un-reasoning an entry here without editing the test turns the suite red.
|
|
84
84
|
ALLOWLIST=(
|
|
85
|
-
'scripts/prompt-injection-scan.sh::the PATTERNS array above is the literal source of every phrase this scanner looks for, so the file matches itself by construction; measured 4 pattern hits at HEAD on 2026-
|
|
86
|
-
'scripts/base64-scan.sh::decodes base64 blobs and re-scans the recovered plaintext against the same injection vocabulary, so it names those phrases inline; measured 2 pattern hits at HEAD on 2026-
|
|
87
|
-
'scripts/secret-scan.sh::third of the three diff-mode scan scripts CI runs together in the
|
|
88
|
-
'tests/security-scan.security.test.cjs::the behavioural suite for this very scanner, so every positive case embeds a real injection phrase as fixture input; measured 28 pattern hits at HEAD on 2026-
|
|
89
|
-
'tests/security.test.cjs::the adversarial suite for the library sanitisers, whose fixtures are real override and role-manipulation strings asserted to be neutralised; measured 54 pattern hits at HEAD on 2026-
|
|
90
|
-
'tests/prompt-injection-scan.security.test.cjs::drives scanForInjection in the compiled security library with crafted payloads, and carries a second allowlist of its own for that separate scanner; measured 11 pattern hits at HEAD on 2026-
|
|
91
|
-
'tests/
|
|
92
|
-
'
|
|
93
|
-
'tests/
|
|
94
|
-
'tests/
|
|
95
|
-
'
|
|
96
|
-
'tests/
|
|
97
|
-
'tests/
|
|
98
|
-
'tests/
|
|
99
|
-
'
|
|
100
|
-
'
|
|
101
|
-
'
|
|
85
|
+
'scripts/prompt-injection-scan.sh::the PATTERNS array above is the literal source of every phrase this scanner looks for, so the file matches itself by construction; measured 4 pattern hits at HEAD on 2026-09-19'
|
|
86
|
+
'scripts/base64-scan.sh::decodes base64 blobs and re-scans the recovered plaintext against the same injection vocabulary, so it names those phrases inline; measured 2 pattern hits at HEAD on 2026-09-19'
|
|
87
|
+
'scripts/secret-scan.sh::third of the three diff-mode scan scripts CI runs together in the security_scan job of .github/workflows/ci.yml; measured 0 pattern hits at HEAD on 2026-09-19, retained because its own exemption list at line 223 already names security-prompt-injection.security.test.cjs and grows with that vocabulary'
|
|
88
|
+
'tests/security-scan.security.test.cjs::the behavioural suite for this very scanner, so every positive case embeds a real injection phrase as fixture input; measured 28 pattern hits at HEAD on 2026-09-19'
|
|
89
|
+
'tests/security.test.cjs::the adversarial suite for the library sanitisers, whose fixtures are real override and role-manipulation strings asserted to be neutralised; measured 54 pattern hits at HEAD on 2026-09-19'
|
|
90
|
+
'tests/prompt-injection-scan.security.test.cjs::drives scanForInjection in the compiled security library with crafted payloads, and carries a second allowlist of its own for that separate scanner; measured 11 pattern hits at HEAD on 2026-09-19'
|
|
91
|
+
'tests/verify.test.cjs::the verification suite plants an instruction-override line in a phase artifact and asserts it is rejected, so the line is present as a negative fixture; measured 1 pattern hit at HEAD on 2026-09-19'
|
|
92
|
+
'pp-core/bin/lib/security.cjs::gitignored build output compiled from src/security.cts per .gitignore line 117, so it is unreachable in diff mode and matters only to a local dir-mode run; it carries the library injection patterns inline; measured 8 pattern hits at HEAD on 2026-09-19'
|
|
93
|
+
'tests/read-injection-scanner.security.test.cjs::exercises the read-time injection scanner hook with the payloads a hostile file would carry, each asserted to be blocked rather than obeyed; measured 14 pattern hits at HEAD on 2026-09-19'
|
|
94
|
+
'tests/security-prompt-injection.security.test.cjs::carries two deliberate NUL bytes at lines 631 and 881 so grep classifies it as binary and reports whole-file matches with no line numbers; 10 binary pattern matches measured at HEAD on 2026-09-19'
|
|
95
|
+
'tests/fixtures/adversarial/security/::a directory of adversarial corpus files whose entire purpose is to BE injection payloads for the suites above; measured 20 pattern hits across its 7 tracked files at HEAD on 2026-09-19'
|
|
96
|
+
'tests/discovery-vault-context.test.cjs::the DISCO-03/SC4 suite plants a real role-manipulation line (the PLANTED constant) into a fixture vault document and asserts the shipped scanner FINDS it, which is the non-vacuity partner for the assertion that the subflow itself scans clean; the payload has to be a literal for that half to mean anything, so the file matches the vocabulary it exists to prove is caught'
|
|
97
|
+
'tests/vault-status-check.test.cjs::sanitisation seeds for the vault status message carry a real instruction and a real control character on purpose, each paired with a non-vacuity assertion that the RENDERED message does not carry it intact; measured 5 pattern hits at HEAD on 2026-09-19'
|
|
98
|
+
'tests/vault-index.integration.test.cjs::the fence-spoof suite seeds a vault document heading and an INDEX description that carry a forged end-fence marker followed by an instruction-override phrase, and asserts the rendered SUMMARY keeps exactly one live end marker with the planted line defanged inside the fence, so the phrase must be a literal for the defang proof to mean anything; measured 4 pattern hits at HEAD 5ddb0b3 on 2026-10-12'
|
|
99
|
+
'tests/vault-seal.integration.test.cjs::the uncovered-commit suite builds a commit whose subject is an instruction-override phrase padded past the cap, modelling a hostile commit message, and asserts the subject leaving the seal leaf is controls-collapsed and bounded to SEAL_SUBJECT_CAP before it can reach a record; measured 1 pattern hit at HEAD 5ddb0b3 on 2026-10-12'
|
|
100
|
+
'tests/vault-standards.integration.test.cjs::the D-14 suites plant instruction-override lines in a standards document and in its INDEX description, each tagged with a random token, and assert that neither the token nor the phrase appears in any output of vault standards block or consulted, so the phrase must be a literal for the absence proof to mean anything; measured 6 pattern hits at HEAD 5ddb0b3 on 2026-10-12'
|
|
101
|
+
'SECURITY.md::the substring rule makes this exempt every path ending in SECURITY.md, including pp-core/templates/SECURITY.md and every per-phase threat register generated from that template; the breadth is accepted because a security policy is exactly where injection phrasing is legitimately quoted; measured 0 pattern hits across all 16 such files on disk at HEAD on 2026-09-19'
|
|
102
|
+
'TEST-EXAMPLES.md::documentation that demonstrates injection examples in order to explain what these scanners catch, so the example text is the point of the file rather than an attack; measured 1 pattern hit at HEAD on 2026-09-19'
|
|
103
|
+
'explanation/security-model.md::matches docs/explanation/security-model.md through the substring rule; it is the prose that explains the injection threat model and quotes the attack shapes pp-core defends against; measured 2 pattern hits at HEAD on 2026-09-19'
|
|
102
104
|
)
|
|
103
105
|
|
|
104
106
|
is_allowlisted() {
|
|
File without changes
|