@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.
- package/.claude-plugin/plugin.json +3 -3
- package/README.md +26 -9
- package/agents/pp-code-reviewer.md +3 -2
- package/agents/pp-debug-session-manager.md +9 -6
- package/agents/pp-executor.md +7 -21
- package/agents/pp-plan-checker.md +4 -4
- package/agents/pp-planner.md +23 -9
- package/agents/pp-vault-reviewer.md +274 -0
- package/agents/pp-verifier.md +7 -3
- package/bin/install.js +10 -3
- package/commands/pp-execute-phase.md +1 -3
- package/commands/pp-ns-review.md +2 -1
- package/commands/pp-plan-phase.md +1 -2
- package/commands/pp-vault-review.md +35 -0
- 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 +14 -19
- 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 +526 -22
- package/pp-core/bin/lib/capability-state.cjs +160 -4
- package/pp-core/bin/lib/capability-writer.cjs +19 -5
- package/pp-core/bin/lib/check-command-router.cjs +3867 -274
- package/pp-core/bin/lib/clusters.cjs +4 -0
- package/pp-core/bin/lib/command-aliases.cjs +0 -28
- package/pp-core/bin/lib/commands.cjs +9 -4
- package/pp-core/bin/lib/compose-bindings.cjs +253 -76
- 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 +154 -62
- package/pp-core/bin/lib/decisions.cjs +104 -27
- package/pp-core/bin/lib/discovery-vault-check.cjs +246 -0
- package/pp-core/bin/lib/frontmatter.cjs +1 -1
- package/pp-core/bin/lib/git-base-branch.cjs +138 -10
- package/pp-core/bin/lib/git-provider.cjs +4 -4
- package/pp-core/bin/lib/init-command-router.cjs +5 -8
- package/pp-core/bin/lib/init-contract.cjs +653 -629
- package/pp-core/bin/lib/init-interactive.cjs +77 -21
- 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 +8 -0
- package/pp-core/bin/lib/loop-resolver.cjs +169 -47
- package/pp-core/bin/lib/org-profile.cjs +544 -53
- package/pp-core/bin/lib/package-identity.cjs +3 -3
- package/pp-core/bin/lib/phase-command-router.cjs +1 -23
- package/pp-core/bin/lib/phase-scope.cjs +1 -1
- package/pp-core/bin/lib/phase.cjs +11 -203
- package/pp-core/bin/lib/platform-bindings.cjs +40 -19
- package/pp-core/bin/lib/practices-review-check.cjs +15 -9
- 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-audit.cjs +2594 -0
- package/pp-core/bin/lib/vault-command-router.cjs +2756 -60
- package/pp-core/bin/lib/vault-coordinates.cjs +13 -4
- package/pp-core/bin/lib/vault-index.cjs +699 -0
- package/pp-core/bin/lib/vault-mode.cjs +235 -0
- package/pp-core/bin/lib/vault-practices.cjs +1194 -89
- package/pp-core/bin/lib/vault-review.cjs +2102 -0
- package/pp-core/bin/lib/vault-seal.cjs +1936 -0
- package/pp-core/bin/lib/vault-standards.cjs +1505 -0
- package/pp-core/bin/lib/vault-trace.cjs +3184 -0
- package/pp-core/bin/lib/vault-update.cjs +242 -0
- package/pp-core/bin/lib/vault-write-back.cjs +78 -31
- package/pp-core/bin/lib/vault.cjs +2713 -634
- 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 +184 -37
- 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 +1 -0
- 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 -14
- 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-practices.md +31 -3
- package/pp-core/references/planner-vault-standards.md +320 -0
- package/pp-core/references/planning-config.md +0 -8
- package/pp-core/references/practice-arithmetic-contract.md +121 -0
- package/pp-core/references/tdd.md +0 -1
- package/pp-core/references/verifier-practice-verdicts.md +263 -0
- package/pp-core/templates/README.md +1 -0
- package/pp-core/templates/VAULT-REVIEW.md +202 -0
- package/pp-core/templates/phase-prompt.md +2 -6
- package/pp-core/templates/practices/README.md +137 -11
- package/pp-core/templates/project-charter.md +226 -0
- package/pp-core/workflows/_runtime-launcher.snippet.sh +0 -0
- package/pp-core/workflows/add-tests.md +55 -0
- package/pp-core/workflows/audit-fix.md +63 -0
- 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 -1
- package/pp-core/workflows/code-review.md +10 -1
- package/pp-core/workflows/complete-milestone.md +1 -1
- package/pp-core/workflows/debug.md +56 -2
- package/pp-core/workflows/discovery-sprint/prefill.md +21 -0
- package/pp-core/workflows/discovery-sprint/prototype.md +46 -48
- 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 +213 -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 +9 -1
- package/pp-core/workflows/discuss-phase.md +3 -10
- package/pp-core/workflows/execute-phase/steps/check-query-gate.md +329 -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 +98 -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 +322 -18
- package/pp-core/workflows/execute-phase/steps/verifier-practice-payload.md +165 -0
- package/pp-core/workflows/execute-phase/steps/verify-post-gate.md +163 -0
- package/pp-core/workflows/execute-phase.md +154 -110
- package/pp-core/workflows/execute-plan.md +58 -4
- package/pp-core/workflows/fast.md +53 -0
- package/pp-core/workflows/help/modes/full.md +1 -0
- 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 +99 -43
- package/pp-core/workflows/quick.md +120 -2
- package/pp-core/workflows/secure-phase.md +1 -1
- package/pp-core/workflows/ship.md +57 -24
- package/pp-core/workflows/update.md +3 -3
- package/pp-core/workflows/validate-phase.md +60 -1
- package/pp-core/workflows/vault-review.md +325 -0
- package/pp-core/workflows/verify-work.md +13 -12
- 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 +199 -0
- package/scripts/gen-practice-arithmetic-contract.cjs +412 -0
- 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 +13 -2
- package/scripts/practice-arithmetic-contract.allow.json +35 -0
- package/scripts/prompt-injection-scan.sh +22 -15
- package/scripts/record-vault-mode-golden.cjs +89 -0
- 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/vault-calibrate.cjs +996 -0
- package/scripts/verify-opengsd-sweep.cjs +3 -5
- package/scripts/workflow-size.cjs +9 -61
- package/pp-core/bin/lib/dispatch-plan.cjs +0 -472
- package/pp-core/bin/lib/install-run.cjs +0 -536
- package/pp-core/bin/lib/phase-tier.cjs +0 -467
- package/pp-core/bin/lib/plan-graph.cjs +0 -368
- package/pp-core/references/planner-plan-graph.md +0 -66
- 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 -72
- package/pp-core/workflows/plan-phase/steps/phase-tier.md +0 -34
- 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
|
@@ -90,11 +90,12 @@ function parseArgs(argv) {
|
|
|
90
90
|
return { ok: true, opts };
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
//
|
|
93
|
+
// Release tag gate (#71924 gap plan 01-06). CI's
|
|
94
|
+
// `tag_version_check` job runs `verify` on a bare checkout: no `npm ci`, no
|
|
95
|
+
// `build:lib`, and the compiled semver module under pp-core/bin/lib is
|
|
96
|
+
// gitignored build output. The first shipped version of that step required the
|
|
97
|
+
// compiled module at the top of this file, so it died at require time on every
|
|
98
|
+
// hosted checkout (never observed live: the step was skipped on every probe).
|
|
98
99
|
// `verify` and `render` therefore require node builtins and tracked files only;
|
|
99
100
|
// the compiled dependency is loaded lazily by `extract`, which the update
|
|
100
101
|
// workflow runs on built trees.
|
|
File without changes
|
|
@@ -4,56 +4,42 @@
|
|
|
4
4
|
/**
|
|
5
5
|
* Scaffolds a new changeset fragment (#2975).
|
|
6
6
|
*
|
|
7
|
-
* npm run changeset -- --type Fixed --pr 1234 --body "fix the thing"
|
|
7
|
+
* npm run changeset -- --type Fixed --pr 1234 --body "fix the thing" [--name <slug>]
|
|
8
8
|
*
|
|
9
|
-
* Writes `.changeset/<
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* Writes `.changeset/<pr>-<slug>.md` with frontmatter + body. The slug is
|
|
10
|
+
* --name, else derived from the bold lead sentence; a taken name gets -2, -3, ...
|
|
11
|
+
* FORK-ONLY (PBI 74336): upstream draws a random three-word name.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
const fs = require('node:fs');
|
|
15
15
|
const path = require('node:path');
|
|
16
16
|
const { ExitError, runMain } = require('../lib/cli-exit.cjs');
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// the random draw repeats.
|
|
22
|
-
const ADJECTIVES = [
|
|
23
|
-
'silly', 'brave', 'calm', 'eager', 'gentle', 'happy', 'jolly', 'kind',
|
|
24
|
-
'lively', 'merry', 'nimble', 'plucky', 'quick', 'sturdy', 'witty', 'zesty',
|
|
25
|
-
'bold', 'clever', 'daring', 'fierce', 'graceful', 'humble', 'lucky', 'noble',
|
|
26
|
-
'proud', 'rapid', 'sharp', 'tidy', 'vivid', 'wise', 'agile', 'curious',
|
|
27
|
-
'eager', 'gallant', 'mellow', 'patient', 'serene', 'steady', 'sturdy', 'sunny',
|
|
28
|
-
];
|
|
29
|
-
const NOUNS_A = [
|
|
30
|
-
'bears', 'birds', 'cats', 'dogs', 'elks', 'foxes', 'goats', 'hawks',
|
|
31
|
-
'ibex', 'jays', 'koalas', 'lynx', 'moles', 'newts', 'otters', 'pumas',
|
|
32
|
-
'quails', 'rams', 'seals', 'tigers', 'voles', 'wolves', 'yaks', 'zebras',
|
|
33
|
-
'badgers', 'cranes', 'deer', 'eagles', 'finches', 'geese', 'herons', 'jaguars',
|
|
34
|
-
'lemurs', 'mice', 'orcas', 'pandas', 'ravens', 'sloths', 'tunas', 'wasps',
|
|
35
|
-
];
|
|
36
|
-
const NOUNS_B = [
|
|
37
|
-
'dance', 'sing', 'leap', 'run', 'jump', 'climb', 'fly', 'swim',
|
|
38
|
-
'rest', 'wake', 'roam', 'greet', 'wander', 'gather', 'forage', 'travel',
|
|
39
|
-
'glide', 'sprint', 'tumble', 'wave', 'cheer', 'rally', 'parade', 'march',
|
|
40
|
-
'hop', 'frolic', 'caper', 'romp', 'zip', 'dart', 'snooze', 'munch',
|
|
41
|
-
'chatter', 'squeak', 'howl', 'bark', 'purr', 'roar', 'hum', 'click',
|
|
42
|
-
];
|
|
18
|
+
const SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
19
|
+
const SLUG_STOPWORDS = new Set(['a', 'an', 'the', 'and', 'or', 'of', 'to', 'for', 'in', 'on', 'is', 'are', 'now', 'its']);
|
|
20
|
+
const SLUG_MAX_WORDS = 7;
|
|
43
21
|
|
|
44
|
-
|
|
45
|
-
|
|
22
|
+
// Kebab-case slug from the bold lead sentence (or the first sentence), minus filler words.
|
|
23
|
+
function deriveFragmentSlug(body) {
|
|
24
|
+
const bold = /\*\*([^*]+)\*\*/.exec(body || '');
|
|
25
|
+
const lead = bold ? bold[1] : String(body || '').trim().split(/[.!?\n]/)[0];
|
|
26
|
+
const words = lead
|
|
27
|
+
.toLowerCase()
|
|
28
|
+
.replace(/'s\b/g, '')
|
|
29
|
+
.split(/[^a-z0-9]+/)
|
|
30
|
+
.filter((w) => w && !SLUG_STOPWORDS.has(w));
|
|
31
|
+
return words.slice(0, SLUG_MAX_WORDS).join('-') || 'change';
|
|
46
32
|
}
|
|
47
33
|
|
|
48
|
-
function
|
|
49
|
-
return `${
|
|
34
|
+
function fragmentBaseName({ pr, body, name }) {
|
|
35
|
+
return `${pr}-${name || deriveFragmentSlug(body)}`;
|
|
50
36
|
}
|
|
51
37
|
|
|
52
38
|
// Allowed Keep-a-Changelog section types. Used by both scaffoldFragment
|
|
53
39
|
// (sanitization at write time) and parse.cjs (validation at consume time).
|
|
54
40
|
const ALLOWED_TYPES = new Set(['Added', 'Changed', 'Deprecated', 'Removed', 'Fixed', 'Security']);
|
|
55
41
|
|
|
56
|
-
function scaffoldFragment({ repo, type, pr, body }) {
|
|
42
|
+
function scaffoldFragment({ repo, type, pr, body, name = null }) {
|
|
57
43
|
// Sanitize: reject any type value not on the allowlist BEFORE embedding it
|
|
58
44
|
// in frontmatter. A newline in `type` would corrupt the fragment; an
|
|
59
45
|
// unrecognized value would be rejected later by parse.cjs but with a
|
|
@@ -63,28 +49,24 @@ function scaffoldFragment({ repo, type, pr, body }) {
|
|
|
63
49
|
`scaffoldFragment: type=${JSON.stringify(type)} is not one of [${[...ALLOWED_TYPES].join(', ')}]`,
|
|
64
50
|
);
|
|
65
51
|
}
|
|
52
|
+
if (name !== null && !SLUG_RE.test(name)) {
|
|
53
|
+
throw new Error(`scaffoldFragment: name=${JSON.stringify(name)} is not kebab-case (a-z, 0-9, hyphens)`);
|
|
54
|
+
}
|
|
66
55
|
const dir = path.join(repo, '.changeset');
|
|
67
56
|
fs.mkdirSync(dir, { recursive: true });
|
|
68
57
|
const content = `---\ntype: ${type}\npr: ${pr}\n---\n${body}\n`;
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
for (let i = 0; i < 16; i++) {
|
|
74
|
-
const name = generateFragmentName();
|
|
75
|
-
const target = path.join(dir, `${name}.md`);
|
|
58
|
+
// flag 'wx' fails with EEXIST instead of overwriting, so concurrent runs cannot clobber a fragment.
|
|
59
|
+
const base = fragmentBaseName({ pr, body, name });
|
|
60
|
+
for (let i = 1; i <= 50; i++) {
|
|
61
|
+
const target = path.join(dir, `${i === 1 ? base : `${base}-${i}`}.md`);
|
|
76
62
|
try {
|
|
77
63
|
fs.writeFileSync(target, content, { flag: 'wx' });
|
|
78
64
|
return target;
|
|
79
65
|
} catch (e) {
|
|
80
66
|
if (e.code !== 'EEXIST') throw e;
|
|
81
|
-
// collision — try another random draw
|
|
82
67
|
}
|
|
83
68
|
}
|
|
84
|
-
throw new Error(
|
|
85
|
-
'scaffoldFragment: 16 random filename draws all collided; ' +
|
|
86
|
-
'expand the word lists or investigate corrupted .changeset/ state',
|
|
87
|
-
);
|
|
69
|
+
throw new Error(`scaffoldFragment: ${base}.md and 49 numbered variants already exist; pass --name`);
|
|
88
70
|
}
|
|
89
71
|
|
|
90
72
|
function parseArgs(argv) {
|
|
@@ -102,7 +84,7 @@ function parseArgs(argv) {
|
|
|
102
84
|
|
|
103
85
|
for (let i = 0; i < argv.length; i++) {
|
|
104
86
|
const a = argv[i];
|
|
105
|
-
if (a === '--type' || a === '--pr' || a === '--body' || a === '--repo') {
|
|
87
|
+
if (a === '--type' || a === '--pr' || a === '--body' || a === '--name' || a === '--repo') {
|
|
106
88
|
const r = requireValue(a, i);
|
|
107
89
|
if (!r.ok) return { ok: false, error: r.error };
|
|
108
90
|
if (a === '--type') opts.type = r.value;
|
|
@@ -114,6 +96,7 @@ function parseArgs(argv) {
|
|
|
114
96
|
const trimmed = r.value.trim();
|
|
115
97
|
opts.pr = /^\d+$/.test(trimmed) ? Number(trimmed) : NaN;
|
|
116
98
|
} else if (a === '--body') opts.body = r.value;
|
|
99
|
+
else if (a === '--name') opts.name = r.value;
|
|
117
100
|
else if (a === '--repo') opts.repo = r.value;
|
|
118
101
|
i++;
|
|
119
102
|
continue;
|
|
@@ -127,7 +110,7 @@ function main() {
|
|
|
127
110
|
const parsed = parseArgs(process.argv.slice(2));
|
|
128
111
|
if (!parsed.ok) {
|
|
129
112
|
process.stderr.write(`${parsed.error}\n`);
|
|
130
|
-
process.stderr.write('usage: changeset/new.cjs --type <Fixed|Added|...> --pr NNNN --body "..."\n');
|
|
113
|
+
process.stderr.write('usage: changeset/new.cjs --type <Fixed|Added|...> --pr NNNN --body "..." [--name <slug>]\n');
|
|
131
114
|
throw new ExitError(2);
|
|
132
115
|
}
|
|
133
116
|
const { opts } = parsed;
|
|
@@ -140,7 +123,7 @@ function main() {
|
|
|
140
123
|
const prMissing = opts.pr === null;
|
|
141
124
|
const prNaN = typeof opts.pr === 'number' && Number.isNaN(opts.pr);
|
|
142
125
|
if (!opts.type || prMissing || prNaN || !opts.body) {
|
|
143
|
-
throw new ExitError(2, 'usage: changeset/new.cjs --type <Fixed|Added|...> --pr NNNN --body "..."');
|
|
126
|
+
throw new ExitError(2, 'usage: changeset/new.cjs --type <Fixed|Added|...> --pr NNNN --body "..." [--name <slug>]');
|
|
144
127
|
}
|
|
145
128
|
const file = scaffoldFragment(opts);
|
|
146
129
|
process.stdout.write(`${path.relative(process.cwd(), file)}\n`);
|
|
@@ -148,4 +131,4 @@ function main() {
|
|
|
148
131
|
|
|
149
132
|
if (require.main === module) runMain(main);
|
|
150
133
|
|
|
151
|
-
module.exports = {
|
|
134
|
+
module.exports = { deriveFragmentSlug, fragmentBaseName, scaffoldFragment, parseArgs, ALLOWED_TYPES };
|
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
|
|
|
@@ -1319,6 +1331,177 @@ function validateAgainstContract(cap, capId, knownAgentIds) {
|
|
|
1319
1331
|
return errors;
|
|
1320
1332
|
}
|
|
1321
1333
|
|
|
1334
|
+
/**
|
|
1335
|
+
* Phase 15 D-02: validate the per-hook `vaultMode` filter field.
|
|
1336
|
+
*
|
|
1337
|
+
* The pure loop resolver evaluates `vaultMode` against an injected mode value
|
|
1338
|
+
* and never emits it, so an unknown or misplaced value must be refused HERE,
|
|
1339
|
+
* at generation, never interpreted at runtime. Three rules, each reported on
|
|
1340
|
+
* its own:
|
|
1341
|
+
* (a) the field is allowed on contributions and gates only (a step is
|
|
1342
|
+
* refused);
|
|
1343
|
+
* (b) the value must be a string in the vault capability's declared
|
|
1344
|
+
* `config["vault.mode"].values`. That enum declaration is the ONE owner of
|
|
1345
|
+
* the closed set; it is read here, never restated. With no vault
|
|
1346
|
+
* capability, or no such values array, there is no closed set and every
|
|
1347
|
+
* `vaultMode` is refused (fail closed);
|
|
1348
|
+
* (c) the declaring capability must be `vault` or list `vault` in `requires`,
|
|
1349
|
+
* so a filtered hook can only exist where the vault gate's closure holds.
|
|
1350
|
+
*
|
|
1351
|
+
* Presence is own-property presence, so a JSON `null` is refused under (b).
|
|
1352
|
+
*
|
|
1353
|
+
* @param {Map<string, object>} capMap
|
|
1354
|
+
* @returns {string[]} error strings; empty = all pass
|
|
1355
|
+
*/
|
|
1356
|
+
function validateVaultModeField(capMap) {
|
|
1357
|
+
const errors = [];
|
|
1358
|
+
const vaultCap = capMap.get('vault');
|
|
1359
|
+
const modeDecl = vaultCap && vaultCap.config && typeof vaultCap.config === 'object'
|
|
1360
|
+
? vaultCap.config['vault.mode']
|
|
1361
|
+
: undefined;
|
|
1362
|
+
const allowed = modeDecl && Array.isArray(modeDecl.values)
|
|
1363
|
+
? modeDecl.values.filter((v) => typeof v === 'string')
|
|
1364
|
+
: null;
|
|
1365
|
+
const KINDS = [['steps', 'step'], ['contributions', 'contribution'], ['gates', 'gate']];
|
|
1366
|
+
for (const [capId, cap] of capMap) {
|
|
1367
|
+
if (!cap || typeof cap !== 'object') continue;
|
|
1368
|
+
const prefix = 'capability "' + capId + '"';
|
|
1369
|
+
const requiresVault = Array.isArray(cap.requires) && cap.requires.includes('vault');
|
|
1370
|
+
for (const [arrKey, kind] of KINDS) {
|
|
1371
|
+
const arr = Array.isArray(cap[arrKey]) ? cap[arrKey] : [];
|
|
1372
|
+
for (const hook of arr) {
|
|
1373
|
+
if (!hook || typeof hook !== 'object') continue;
|
|
1374
|
+
if (!Object.prototype.hasOwnProperty.call(hook, 'vaultMode')) continue;
|
|
1375
|
+
const where = prefix + ' ' + kind + ' at point "' + String(hook.point) + '"';
|
|
1376
|
+
const value = hook.vaultMode;
|
|
1377
|
+
if (kind === 'step') {
|
|
1378
|
+
errors.push(where + ': vaultMode is allowed on contributions and gates only');
|
|
1379
|
+
}
|
|
1380
|
+
if (allowed === null) {
|
|
1381
|
+
errors.push(
|
|
1382
|
+
where + ': vaultMode ' + JSON.stringify(value) + ' cannot be checked: the vault capability ' +
|
|
1383
|
+
'declares no "vault.mode" values, which own the closed set',
|
|
1384
|
+
);
|
|
1385
|
+
} else if (typeof value !== 'string' || !allowed.includes(value)) {
|
|
1386
|
+
errors.push(
|
|
1387
|
+
where + ': vaultMode ' + JSON.stringify(value) + ' is not one of [' + allowed.join(', ') +
|
|
1388
|
+
'] (the closed set owned by the vault capability\'s "vault.mode" values)',
|
|
1389
|
+
);
|
|
1390
|
+
}
|
|
1391
|
+
if (capId !== 'vault' && !requiresVault) {
|
|
1392
|
+
errors.push(
|
|
1393
|
+
where + ': vaultMode is allowed only on the "vault" capability or a capability that ' +
|
|
1394
|
+
'lists "vault" in requires',
|
|
1395
|
+
);
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
return errors;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
/**
|
|
1404
|
+
* Quick 261015-ect (QD-13): the vault-family rule for `requiresPlatformBindings`,
|
|
1405
|
+
* the mirror of the vaultMode rule (c) above. A capability that is `vault` or
|
|
1406
|
+
* lists `vault` in `requires` MUST declare the field, so a vault-family
|
|
1407
|
+
* capability added later cannot render hooks on a project the vault does not
|
|
1408
|
+
* govern by forgetting it. A capability outside the family may declare it too.
|
|
1409
|
+
*
|
|
1410
|
+
* @param {Map<string, object>} capMap
|
|
1411
|
+
* @returns {string[]} error strings; empty = all pass
|
|
1412
|
+
*/
|
|
1413
|
+
function validatePlatformBindingsField(capMap) {
|
|
1414
|
+
const errors = [];
|
|
1415
|
+
for (const [capId, cap] of capMap) {
|
|
1416
|
+
if (!cap || typeof cap !== 'object') continue;
|
|
1417
|
+
const requiresVault = Array.isArray(cap.requires) && cap.requires.includes('vault');
|
|
1418
|
+
if (capId !== 'vault' && !requiresVault) continue;
|
|
1419
|
+
if (!Object.prototype.hasOwnProperty.call(cap, 'requiresPlatformBindings')) {
|
|
1420
|
+
errors.push(
|
|
1421
|
+
'capability "' + capId + '" is vault or requires vault and must declare requiresPlatformBindings, ' +
|
|
1422
|
+
'so a vault-family capability cannot render hooks on a project the vault does not govern',
|
|
1423
|
+
);
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
return errors;
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
/**
|
|
1430
|
+
* Phase 16 D-19: the per-hook seal field's name. The resolver reads it as
|
|
1431
|
+
* `SEAL_FIELD` from capability-state; this generator cannot require that module
|
|
1432
|
+
* (its load graph reaches surface.cjs and config-loader.cjs, which read the
|
|
1433
|
+
* very registry this script regenerates), so the literal is restated here and
|
|
1434
|
+
* pinned equal to `SEAL_FIELD` by tests/capability-registry.test.cjs.
|
|
1435
|
+
*/
|
|
1436
|
+
const SEAL_FIELD_NAME = 'onRecordedArtifacts';
|
|
1437
|
+
|
|
1438
|
+
/**
|
|
1439
|
+
* Phase 16 D-19 / D-25 / D-26: validate the per-hook seal field.
|
|
1440
|
+
*
|
|
1441
|
+
* A gate carrying the field renders for an enabled, config-inactive capability
|
|
1442
|
+
* over recorded vault artifacts, bypassing the capability's active check and
|
|
1443
|
+
* its `when` (D-18). That is a widening of render behaviour, so the field is
|
|
1444
|
+
* closed HERE, at generation, to exactly the shape the two shipped seal gates
|
|
1445
|
+
* have. Six rules, each reported on its own:
|
|
1446
|
+
* (a) gates only (a step or contribution carrying it is refused);
|
|
1447
|
+
* (b) the value is literal `true` (`"true"`, `1`, `null`, `false` refused);
|
|
1448
|
+
* (c) the declaring capability lists `vault` in `requires` and is not `vault`
|
|
1449
|
+
* itself (D-26: with the vault off the injected mode is null, so both
|
|
1450
|
+
* modes' filtered gates pass and sealing the vault capability too would
|
|
1451
|
+
* dispatch `check vault.trace` twice);
|
|
1452
|
+
* (d) the gate also carries `vaultMode` (the resolver filters by mode BEFORE
|
|
1453
|
+
* the predicate, research C-1);
|
|
1454
|
+
* (e) the gate is `blocking: true`;
|
|
1455
|
+
* (f) the point is `ship:pre` (D-21: the requirement's block point; a seal at
|
|
1456
|
+
* verify:post would halt execute-phase on a switch set on purpose).
|
|
1457
|
+
*
|
|
1458
|
+
* Presence is own-property presence.
|
|
1459
|
+
*
|
|
1460
|
+
* @param {Map<string, object>} capMap
|
|
1461
|
+
* @returns {string[]} error strings; empty = all pass
|
|
1462
|
+
*/
|
|
1463
|
+
function validateSealField(capMap) {
|
|
1464
|
+
const errors = [];
|
|
1465
|
+
const KINDS = [['steps', 'step'], ['contributions', 'contribution'], ['gates', 'gate']];
|
|
1466
|
+
for (const [capId, cap] of capMap) {
|
|
1467
|
+
if (!cap || typeof cap !== 'object') continue;
|
|
1468
|
+
const prefix = 'capability "' + capId + '"';
|
|
1469
|
+
const requiresVault = Array.isArray(cap.requires) && cap.requires.includes('vault');
|
|
1470
|
+
for (const [arrKey, kind] of KINDS) {
|
|
1471
|
+
const arr = Array.isArray(cap[arrKey]) ? cap[arrKey] : [];
|
|
1472
|
+
for (const hook of arr) {
|
|
1473
|
+
if (!hook || typeof hook !== 'object') continue;
|
|
1474
|
+
if (!Object.prototype.hasOwnProperty.call(hook, SEAL_FIELD_NAME)) continue;
|
|
1475
|
+
const where = prefix + ' ' + kind + ' at point "' + String(hook.point) + '"';
|
|
1476
|
+
const value = hook[SEAL_FIELD_NAME];
|
|
1477
|
+
if (kind !== 'gate') {
|
|
1478
|
+
errors.push(where + ': ' + SEAL_FIELD_NAME + ' is allowed on gates only');
|
|
1479
|
+
continue;
|
|
1480
|
+
}
|
|
1481
|
+
if (value !== true) {
|
|
1482
|
+
errors.push(where + ': ' + SEAL_FIELD_NAME + ' must be literal true (got ' + JSON.stringify(value) + ')');
|
|
1483
|
+
}
|
|
1484
|
+
if (capId === 'vault' || !requiresVault) {
|
|
1485
|
+
errors.push(
|
|
1486
|
+
where + ': ' + SEAL_FIELD_NAME + ' is allowed only on a capability that lists "vault" in requires ' +
|
|
1487
|
+
'and is not "vault" itself',
|
|
1488
|
+
);
|
|
1489
|
+
}
|
|
1490
|
+
if (!Object.prototype.hasOwnProperty.call(hook, 'vaultMode')) {
|
|
1491
|
+
errors.push(where + ': a gate carrying ' + SEAL_FIELD_NAME + ' must also carry vaultMode');
|
|
1492
|
+
}
|
|
1493
|
+
if (hook.blocking !== true) {
|
|
1494
|
+
errors.push(where + ': a gate carrying ' + SEAL_FIELD_NAME + ' must be blocking: true');
|
|
1495
|
+
}
|
|
1496
|
+
if (hook.point !== 'ship:pre') {
|
|
1497
|
+
errors.push(where + ': ' + SEAL_FIELD_NAME + ' is allowed only at point "ship:pre"');
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
return errors;
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1322
1505
|
/**
|
|
1323
1506
|
* C1+C2: Global consumes-satisfiability validation.
|
|
1324
1507
|
*
|
|
@@ -1479,6 +1662,18 @@ const TIER_RANK = { core: 0, standard: 1, full: 2 };
|
|
|
1479
1662
|
function validateCrossCapability(capMap, centralKeys) {
|
|
1480
1663
|
const errors = [];
|
|
1481
1664
|
|
|
1665
|
+
// Phase 15 D-02: the per-hook vaultMode filter field (needs capMap to read the
|
|
1666
|
+
// vault capability's closed value set and every capability's `requires`).
|
|
1667
|
+
errors.push(...validateVaultModeField(capMap));
|
|
1668
|
+
|
|
1669
|
+
// Phase 16 D-19: the per-hook seal field (needs capMap for every capability's
|
|
1670
|
+
// `requires`).
|
|
1671
|
+
errors.push(...validateSealField(capMap));
|
|
1672
|
+
|
|
1673
|
+
// Quick 261015-ect (QD-13): every vault-family capability declares
|
|
1674
|
+
// requiresPlatformBindings (needs capMap for every capability's `requires`).
|
|
1675
|
+
errors.push(...validatePlatformBindingsField(capMap));
|
|
1676
|
+
|
|
1482
1677
|
// Ownership: one owner per skill stem + agent name
|
|
1483
1678
|
const skillOwner = new Map(); // skill → capId
|
|
1484
1679
|
const agentOwner = new Map(); // agent → capId
|
|
@@ -2646,6 +2841,10 @@ module.exports = {
|
|
|
2646
2841
|
validateAgainstContract,
|
|
2647
2842
|
validateConsumesGlobal,
|
|
2648
2843
|
validateCrossCapability,
|
|
2844
|
+
validateVaultModeField,
|
|
2845
|
+
validateSealField,
|
|
2846
|
+
validatePlatformBindingsField,
|
|
2847
|
+
SEAL_FIELD_NAME,
|
|
2649
2848
|
classifyCrossErrors,
|
|
2650
2849
|
loadCentralConfigKeys,
|
|
2651
2850
|
loadAndValidate,
|