@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
|
@@ -82,21 +82,28 @@ 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
|
-
'
|
|
94
|
-
'tests/
|
|
95
|
-
'tests/security
|
|
96
|
-
'tests/
|
|
97
|
-
'
|
|
98
|
-
'
|
|
99
|
-
'
|
|
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-practices-matcher.test.cjs::the CR-01 fence-spoof suite plants a real instruction-override line and a forged end-fence marker inside practice bodies on purpose, each paired with an assertion that the RENDERED practices block keeps the planted text inside the fence and leaves exactly one genuine closer, so the payload must be a literal for the defang proof to mean anything; measured 3 pattern hits at HEAD b4dd6b3 on 2026-10-04'
|
|
99
|
+
'tests/vault-review.security.test.cjs::the CR-02 suite seeds a practice body with directive-shaped text aimed at the practice judge (the DIRECTIVE_TEXT constant) and asserts the rendered review entry carries it only between the fence markers, after the reference-data preamble, so the judge reads it as data; the text must be a literal for that assertion to mean anything; measured 1 pattern hit at HEAD b4dd6b3 on 2026-10-04'
|
|
100
|
+
'tests/practices-review-check-property.test.cjs::a false positive rather than a payload, since the fence-parsing invariance property builds a never-closed fence whose second-opener line is ordinary English about whether the client line becomes a cut point, and one role-pattern regex matches that plain wording; the fixture text stays as written; measured 1 pattern hit at HEAD b4dd6b3 on 2026-10-04'
|
|
101
|
+
'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'
|
|
102
|
+
'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'
|
|
103
|
+
'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'
|
|
104
|
+
'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'
|
|
105
|
+
'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'
|
|
106
|
+
'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'
|
|
100
107
|
)
|
|
101
108
|
|
|
102
109
|
is_allowlisted() {
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* scripts/record-vault-mode-golden.cjs — the vault-mode golden recorder
|
|
6
|
+
* (Vault Assurance v1.2, phase 13; MODE-01, PARK-04; D-01).
|
|
7
|
+
*
|
|
8
|
+
* --write record every enablement cell (key absent — the canonical
|
|
9
|
+
* recording), normalise, and write tests/fixtures/vault-mode-golden/<cell>.json
|
|
10
|
+
* --check record the same and compare against the committed files; print
|
|
11
|
+
* every differing path and exit 1 on any difference
|
|
12
|
+
*
|
|
13
|
+
* Neither or both flags: usage, exit 2. No path argument: the golden directory
|
|
14
|
+
* is the harness's `GOLDEN_DIR`, so the recorder and the test cannot disagree
|
|
15
|
+
* about where the goldens live.
|
|
16
|
+
*
|
|
17
|
+
* A THIN CLI. Every fixture, capture, normalisation and comparison rule lives in
|
|
18
|
+
* the shared harness module required below, which the golden test requires too.
|
|
19
|
+
* This script is never run by the test suite: a re-record is a deliberate,
|
|
20
|
+
* reviewable re-baseline (D-01), and it must run on a tree whose practices-mode
|
|
21
|
+
* behaviour is the one being pinned.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const fs = require('node:fs');
|
|
25
|
+
const path = require('node:path');
|
|
26
|
+
const harness = require('../tests/helpers/vault-mode-golden-harness.cjs');
|
|
27
|
+
|
|
28
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
29
|
+
const USAGE = 'usage: record-vault-mode-golden.cjs (--write | --check)';
|
|
30
|
+
|
|
31
|
+
function describeDiff(d) {
|
|
32
|
+
const at = d.firstDiffAt === undefined ? '' : ` (first differing byte at ${d.firstDiffAt})`;
|
|
33
|
+
const show = (v) => (v === undefined ? '<absent>' : JSON.stringify(v).slice(0, 200));
|
|
34
|
+
return ` ${d.path}${at}\n expected: ${show(d.expected)}\n actual: ${show(d.actual)}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function main() {
|
|
38
|
+
const write = process.argv.includes('--write');
|
|
39
|
+
const check = process.argv.includes('--check');
|
|
40
|
+
if (write === check) {
|
|
41
|
+
console.error(USAGE);
|
|
42
|
+
process.exitCode = 2;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let failed = 0;
|
|
47
|
+
for (const cell of harness.ENABLEMENT_CELLS) {
|
|
48
|
+
const raw = harness.recordCell({ cell: cell.name, modeKey: harness.KEY_CONFIGS.absent });
|
|
49
|
+
const normalised = harness.normaliseRecording(raw);
|
|
50
|
+
const count = Object.keys(normalised.commands).length;
|
|
51
|
+
const dropped = Object.entries(raw.meta.droppedHookCounts)
|
|
52
|
+
.map(([id, n]) => `${id}=${n}`)
|
|
53
|
+
.join(' ');
|
|
54
|
+
const target = harness.goldenPathFor(cell.name);
|
|
55
|
+
const rel = path.relative(ROOT, target);
|
|
56
|
+
console.log(`${cell.name}: ${count} commands recorded (render-hooks projection dropped: ${dropped})`);
|
|
57
|
+
|
|
58
|
+
if (write) {
|
|
59
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
60
|
+
fs.writeFileSync(target, harness.serialise(normalised), 'utf8');
|
|
61
|
+
console.log(` wrote ${rel}`);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let expected;
|
|
66
|
+
try {
|
|
67
|
+
expected = JSON.parse(fs.readFileSync(target, 'utf8'));
|
|
68
|
+
} catch (e) {
|
|
69
|
+
console.error(` FAIL: ${rel} could not be read as JSON (${e.message})`);
|
|
70
|
+
failed++;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const { equal, diffs } = harness.compareRecordings(expected, normalised);
|
|
74
|
+
if (equal) {
|
|
75
|
+
console.log(` matches ${rel}`);
|
|
76
|
+
} else {
|
|
77
|
+
failed++;
|
|
78
|
+
console.error(` FAIL: ${rel} differs at ${diffs.length} path(s):`);
|
|
79
|
+
for (const d of diffs) console.error(describeDiff(d));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (check && failed > 0) {
|
|
84
|
+
console.error(`\nFAIL: ${failed} golden cell(s) differ from a fresh recording.`);
|
|
85
|
+
process.exitCode = 1;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
main();
|
|
File without changes
|
|
@@ -11,10 +11,11 @@
|
|
|
11
11
|
* - VERSIONED_MANIFESTS — JSON runtime-integration manifests whose top-level
|
|
12
12
|
* `version` field MUST track the package version.
|
|
13
13
|
* - VERSIONED_TEXT_FILES — plain-text manifests whose ENTIRE content is the
|
|
14
|
-
* version followed by one newline. Root `VERSION` is the repo layout's
|
|
15
|
-
* version source for `
|
|
16
|
-
* repo build stamps `createdWith`
|
|
17
|
-
*
|
|
14
|
+
* version followed by one newline. Root `VERSION` is the repo layout's only
|
|
15
|
+
* version source for `resolveCreatedWithFrom` (src/compose-bindings.cts), so a
|
|
16
|
+
* repo build stamps `createdWith` and the practices block from it. It drifted
|
|
17
|
+
* from 1.11.1 through three releases because a plain-text file could not join
|
|
18
|
+
* the JSON loop (quick 261003-kmd closed the todo).
|
|
18
19
|
*
|
|
19
20
|
* Usage:
|
|
20
21
|
* node scripts/sync-manifest-versions.cjs # stamp + report
|
package/scripts/sync-rulesets.sh
CHANGED
|
@@ -4,309 +4,65 @@
|
|
|
4
4
|
/**
|
|
5
5
|
* @file update-size-baseline.cjs
|
|
6
6
|
*
|
|
7
|
-
* Regenerates the committed per-file size
|
|
8
|
-
* (`tests/workflow-size-baseline.json
|
|
9
|
-
* the current files.
|
|
7
|
+
* Regenerates the committed per-file workflow size baseline
|
|
8
|
+
* (`tests/workflow-size-baseline.json`) from the current workflow files.
|
|
10
9
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* phase, in the phase's closing commit (`--reason "phase NN close"`, from
|
|
15
|
-
* execute-phase), and otherwise only when one commit must grow past the budget.
|
|
16
|
-
* Because a raise is the review record of growth, RAISING any entry requires a
|
|
17
|
-
* reason:
|
|
10
|
+
* Run via `npm run size:baseline` whenever a workflow file legitimately grows
|
|
11
|
+
* or shrinks. Growth must still be justified in the PR; this script only
|
|
12
|
+
* records the new reality so the CI guard (issue #1074) can diff against it.
|
|
18
13
|
*
|
|
19
|
-
*
|
|
20
|
-
* npm run size:baseline -- --reason "<why>" # also allowed to raise
|
|
21
|
-
*
|
|
22
|
-
* Without `--reason`, a run that would raise any entry is REFUSED: it exits
|
|
23
|
-
* non-zero and writes nothing (neither baseline). With a reason, every raise is
|
|
24
|
-
* recorded in the baseline file's `_rebaselines` array (newest last) as
|
|
25
|
-
* `{ date, reason, deltas: [{ file, from, to, delta }] }` — the committed
|
|
26
|
-
* review record of a deliberate growth.
|
|
27
|
-
*
|
|
28
|
-
* Idempotent: running it twice with no file changes produces no diff (a run
|
|
29
|
-
* with nothing to raise never appends a `_rebaselines` entry).
|
|
14
|
+
* Idempotent: running it twice with no file changes produces no diff.
|
|
30
15
|
*/
|
|
31
16
|
|
|
32
17
|
const fs = require('fs');
|
|
33
18
|
const path = require('path');
|
|
34
|
-
const { measureMdFiles,
|
|
19
|
+
const { measureMdFiles, WORKFLOWS_DIR } = require('./workflow-size.cjs');
|
|
35
20
|
|
|
36
21
|
const BASELINE_PATH = path.join(__dirname, '..', 'tests', 'workflow-size-baseline.json');
|
|
37
22
|
const AGENTS_DIR = path.join(__dirname, '..', 'agents');
|
|
38
23
|
const AGENT_BASELINE_PATH = path.join(__dirname, '..', 'tests', 'agent-size-baseline.json');
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
/** Key of the raise log inside a baseline file. Never a measured filename. */
|
|
42
|
-
const REBASELINES_KEY = '_rebaselines';
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Split a parsed baseline file into its size entries and its raise log.
|
|
46
|
-
* Size entries are the numeric values; metadata keys (`_rebaselines`) are not
|
|
47
|
-
* sizes and must never reach a guard as one.
|
|
48
|
-
*
|
|
49
|
-
* @param {object|null} raw - Parsed baseline JSON (null when absent).
|
|
50
|
-
* @returns {{ sizes: Object<string, number>, rebaselines: Array<object> }}
|
|
51
|
-
*/
|
|
52
|
-
function splitBaseline(raw) {
|
|
53
|
-
const sizes = {};
|
|
54
|
-
let rebaselines = [];
|
|
55
|
-
if (raw && typeof raw === 'object') {
|
|
56
|
-
for (const [key, value] of Object.entries(raw)) {
|
|
57
|
-
if (key === REBASELINES_KEY) rebaselines = value;
|
|
58
|
-
else sizes[key] = value;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return { sizes, rebaselines };
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Read and parse a baseline file; `null` when it does not exist.
|
|
66
|
-
*
|
|
67
|
-
* @param {string} filePath
|
|
68
|
-
* @returns {object|null}
|
|
69
|
-
*/
|
|
70
|
-
function readBaseline(filePath) {
|
|
71
|
-
let text;
|
|
72
|
-
try {
|
|
73
|
-
text = fs.readFileSync(filePath, 'utf-8');
|
|
74
|
-
} catch (err) {
|
|
75
|
-
if (err.code === 'ENOENT') return null;
|
|
76
|
-
throw err;
|
|
77
|
-
}
|
|
78
|
-
return JSON.parse(text);
|
|
79
|
-
}
|
|
24
|
+
const isGsdAgent = (f) => f.startsWith('pp-');
|
|
80
25
|
|
|
81
26
|
/**
|
|
82
|
-
* Serialize to the on-disk baseline format:
|
|
83
|
-
*
|
|
84
|
-
* minimal-diff artifact).
|
|
27
|
+
* Serialize a size map to the on-disk baseline format: keys sorted, 2-space
|
|
28
|
+
* indent, trailing newline (so the file is a stable, minimal-diff artifact).
|
|
85
29
|
*
|
|
86
30
|
* @param {Object<string, number>} sizes
|
|
87
|
-
* @param {Array<object>} [rebaselines]
|
|
88
31
|
* @returns {string}
|
|
89
32
|
*/
|
|
90
|
-
function serializeBaseline(sizes
|
|
33
|
+
function serializeBaseline(sizes) {
|
|
91
34
|
const sorted = {};
|
|
92
35
|
for (const key of Object.keys(sizes).sort()) sorted[key] = sizes[key];
|
|
93
|
-
sorted[REBASELINES_KEY] = rebaselines;
|
|
94
36
|
return JSON.stringify(sorted, null, 2) + '\n';
|
|
95
37
|
}
|
|
96
38
|
|
|
97
39
|
/**
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
* @param {Object<string, number>} oldSizes
|
|
101
|
-
* @param {Object<string, number>} newSizes
|
|
102
|
-
* @returns {{ raised: Array<{file:string,from:number,to:number,delta:number}>,
|
|
103
|
-
* lowered: Array<{file:string,from:number,to:number,delta:number}>,
|
|
104
|
-
* added: string[], removed: string[] }}
|
|
105
|
-
*/
|
|
106
|
-
function diffSizes(oldSizes, newSizes) {
|
|
107
|
-
const raised = [];
|
|
108
|
-
const lowered = [];
|
|
109
|
-
const added = [];
|
|
110
|
-
for (const file of Object.keys(newSizes).sort()) {
|
|
111
|
-
if (!(file in oldSizes)) {
|
|
112
|
-
added.push(file);
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
const from = oldSizes[file];
|
|
116
|
-
const to = newSizes[file];
|
|
117
|
-
if (to > from) raised.push({ file, from, to, delta: to - from });
|
|
118
|
-
else if (to < from) lowered.push({ file, from, to, delta: from - to });
|
|
119
|
-
}
|
|
120
|
-
const removed = Object.keys(oldSizes).filter((f) => !(f in newSizes)).sort();
|
|
121
|
-
return { raised, lowered, added, removed };
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Validate a `_rebaselines` log. Returns a list of problems (empty = valid).
|
|
126
|
-
* Every entry must carry an ISO date, a non-empty reason and a non-empty list
|
|
127
|
-
* of positive deltas whose arithmetic holds; dates must be non-decreasing
|
|
128
|
-
* (newest last).
|
|
129
|
-
*
|
|
130
|
-
* @param {unknown} log
|
|
131
|
-
* @returns {string[]}
|
|
132
|
-
*/
|
|
133
|
-
function validateRebaselineLog(log) {
|
|
134
|
-
if (!Array.isArray(log)) return [`${REBASELINES_KEY} must be an array, got ${JSON.stringify(log)}`];
|
|
135
|
-
const problems = [];
|
|
136
|
-
const isSize = (n) => Number.isInteger(n) && n >= 0;
|
|
137
|
-
let prevDate = '';
|
|
138
|
-
log.forEach((entry, i) => {
|
|
139
|
-
const at = `${REBASELINES_KEY}[${i}]`;
|
|
140
|
-
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
|
|
141
|
-
problems.push(`${at} must be an object`);
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
if (typeof entry.date !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(entry.date)) {
|
|
145
|
-
problems.push(`${at}.date must be YYYY-MM-DD, got ${JSON.stringify(entry.date)}`);
|
|
146
|
-
} else {
|
|
147
|
-
if (entry.date < prevDate) problems.push(`${at}.date ${entry.date} is older than the entry before it (newest must be last)`);
|
|
148
|
-
prevDate = entry.date;
|
|
149
|
-
}
|
|
150
|
-
if (typeof entry.reason !== 'string' || entry.reason.trim() === '') {
|
|
151
|
-
problems.push(`${at}.reason must be a non-empty string`);
|
|
152
|
-
}
|
|
153
|
-
if (!Array.isArray(entry.deltas) || entry.deltas.length === 0) {
|
|
154
|
-
problems.push(`${at}.deltas must be a non-empty array`);
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
entry.deltas.forEach((d, j) => {
|
|
158
|
-
const dat = `${at}.deltas[${j}]`;
|
|
159
|
-
if (!d || typeof d.file !== 'string' || d.file === '') problems.push(`${dat}.file must be a non-empty string`);
|
|
160
|
-
if (!d || !isSize(d.from) || !isSize(d.to)) problems.push(`${dat}.from/.to must be non-negative integers`);
|
|
161
|
-
else if (!Number.isInteger(d.delta) || d.delta <= 0 || d.delta !== d.to - d.from) {
|
|
162
|
-
problems.push(`${dat}.delta must be the positive difference to - from (${d.to - d.from}), got ${JSON.stringify(d.delta)}`);
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
});
|
|
166
|
-
return problems;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Compute (but do not write) a regenerated baseline.
|
|
40
|
+
* Write a baseline file from the measured `.md` sizes in a directory.
|
|
171
41
|
*
|
|
172
42
|
* @param {object} [opts]
|
|
173
43
|
* @param {string} [opts.dir] - Directory to measure (default: workflows).
|
|
174
|
-
* @param {string} [opts.outPath] - Baseline file (default: workflow
|
|
175
|
-
* @param {function(string): boolean} [opts.predicate] -
|
|
176
|
-
* @
|
|
177
|
-
* Measurer (default: recursive `measureMdTree`; agents pass `measureMdFiles`).
|
|
178
|
-
* @param {string} [opts.reason] - Justification; required to raise any entry.
|
|
179
|
-
* @param {string} [opts.today] - Date stamp for a raise (default: today, UTC).
|
|
180
|
-
* @returns {{ outPath: string, refused: boolean, count: number, content: string|null,
|
|
181
|
-
* change: ReturnType<typeof diffSizes> }}
|
|
44
|
+
* @param {string} [opts.outPath] - Baseline file to write (default: workflow).
|
|
45
|
+
* @param {function(string): boolean} [opts.predicate] - Filename filter.
|
|
46
|
+
* @returns {{ outPath: string, count: number, content: string }}
|
|
182
47
|
*/
|
|
183
|
-
function
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
reason,
|
|
189
|
-
today = new Date().toISOString().slice(0, 10),
|
|
190
|
-
} = {}) {
|
|
191
|
-
const sizes = measure(dir, predicate);
|
|
192
|
-
const { sizes: oldSizes, rebaselines: oldLog } = splitBaseline(readBaseline(outPath));
|
|
193
|
-
const change = diffSizes(oldSizes, sizes);
|
|
194
|
-
const count = Object.keys(sizes).length;
|
|
195
|
-
const hasReason = typeof reason === 'string' && reason.trim() !== '';
|
|
196
|
-
if (change.raised.length > 0 && !hasReason) {
|
|
197
|
-
return { outPath, refused: true, count, content: null, change };
|
|
198
|
-
}
|
|
199
|
-
const log = Array.isArray(oldLog) ? [...oldLog] : [];
|
|
200
|
-
if (change.raised.length > 0) {
|
|
201
|
-
log.push({ date: today, reason: reason.trim(), deltas: change.raised });
|
|
202
|
-
}
|
|
203
|
-
return { outPath, refused: false, count, content: serializeBaseline(sizes, log), change };
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Regenerate one baseline file. A refused plan (a raise without a reason)
|
|
208
|
-
* writes nothing.
|
|
209
|
-
*
|
|
210
|
-
* @param {Parameters<typeof planBaseline>[0]} [opts]
|
|
211
|
-
* @returns {ReturnType<typeof planBaseline>}
|
|
212
|
-
*/
|
|
213
|
-
function generateBaseline(opts = {}) {
|
|
214
|
-
const plan = planBaseline(opts);
|
|
215
|
-
if (!plan.refused) fs.writeFileSync(plan.outPath, plan.content);
|
|
216
|
-
return plan;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Parse CLI arguments: only `--reason <text>` / `--reason=<text>`.
|
|
221
|
-
*
|
|
222
|
-
* @param {string[]} argv
|
|
223
|
-
* @returns {{ reason?: string }}
|
|
224
|
-
*/
|
|
225
|
-
function parseArgs(argv) {
|
|
226
|
-
const out = {};
|
|
227
|
-
for (let i = 0; i < argv.length; i++) {
|
|
228
|
-
const arg = argv[i];
|
|
229
|
-
if (arg === '--reason') {
|
|
230
|
-
out.reason = argv[++i];
|
|
231
|
-
if (out.reason === undefined) throw new Error('--reason needs a value');
|
|
232
|
-
} else if (arg.startsWith('--reason=')) {
|
|
233
|
-
out.reason = arg.slice('--reason='.length);
|
|
234
|
-
} else {
|
|
235
|
-
throw new Error(`unknown argument: ${arg} (only --reason "<text>" is accepted)`);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
if (out.reason !== undefined && out.reason.trim() === '') throw new Error('--reason must not be empty');
|
|
239
|
-
return out;
|
|
48
|
+
function generateBaseline({ dir = WORKFLOWS_DIR, outPath = BASELINE_PATH, predicate } = {}) {
|
|
49
|
+
const sizes = measureMdFiles(dir, predicate);
|
|
50
|
+
const content = serializeBaseline(sizes);
|
|
51
|
+
fs.writeFileSync(outPath, content);
|
|
52
|
+
return { outPath, count: Object.keys(sizes).length, content };
|
|
240
53
|
}
|
|
241
54
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
* @param {string[]} argv
|
|
252
|
-
* @param {object} [io]
|
|
253
|
-
* @param {Array<object>} [io.targets]
|
|
254
|
-
* @param {string} [io.today]
|
|
255
|
-
* @param {NodeJS.WritableStream} [io.stdout]
|
|
256
|
-
* @param {NodeJS.WritableStream} [io.stderr]
|
|
257
|
-
* @returns {number} exit code
|
|
258
|
-
*/
|
|
259
|
-
function main(argv, { targets = TARGETS, today, stdout = process.stdout, stderr = process.stderr } = {}) {
|
|
260
|
-
let args;
|
|
261
|
-
try {
|
|
262
|
-
args = parseArgs(argv);
|
|
263
|
-
} catch (err) {
|
|
264
|
-
stderr.write(`size:baseline: ${err.message}\n`);
|
|
265
|
-
return 2;
|
|
266
|
-
}
|
|
267
|
-
const plans = targets.map((t) => ({ t, plan: planBaseline({ ...t, reason: args.reason, today }) }));
|
|
268
|
-
const refused = plans.filter((p) => p.plan.refused);
|
|
269
|
-
if (refused.length > 0) {
|
|
270
|
-
const lines = refused.flatMap(({ t, plan }) =>
|
|
271
|
-
plan.change.raised.map((r) => ` - ${t.label}: ${r.file}: ${r.from} → ${r.to} (+${r.delta})`)
|
|
272
|
-
);
|
|
273
|
-
stderr.write(
|
|
274
|
-
'size:baseline: REFUSED — this run would RAISE baseline entries, which needs a reason:\n' +
|
|
275
|
-
`${lines.join('\n')}\n` +
|
|
276
|
-
'Shrink the file(s), or re-run with: npm run size:baseline -- --reason "<why this growth is needed>"\n' +
|
|
277
|
-
'Nothing was written.\n'
|
|
278
|
-
);
|
|
279
|
-
return 1;
|
|
280
|
-
}
|
|
281
|
-
for (const { t, plan } of plans) {
|
|
282
|
-
fs.writeFileSync(plan.outPath, plan.content);
|
|
283
|
-
const c = plan.change;
|
|
284
|
-
stdout.write(
|
|
285
|
-
`Wrote ${plan.count} ${t.label} sizes to ${path.relative(process.cwd(), plan.outPath)} ` +
|
|
286
|
-
`(raised ${c.raised.length}, lowered ${c.lowered.length}, added ${c.added.length}, removed ${c.removed.length})\n`
|
|
55
|
+
if (require.main === module) {
|
|
56
|
+
const targets = [
|
|
57
|
+
{ label: 'workflow', dir: WORKFLOWS_DIR, outPath: BASELINE_PATH },
|
|
58
|
+
{ label: 'agent', dir: AGENTS_DIR, outPath: AGENT_BASELINE_PATH, predicate: isGsdAgent },
|
|
59
|
+
];
|
|
60
|
+
for (const t of targets) {
|
|
61
|
+
const { outPath, count } = generateBaseline(t);
|
|
62
|
+
process.stdout.write(
|
|
63
|
+
`Wrote ${count} ${t.label} sizes to ${path.relative(process.cwd(), outPath)}\n`
|
|
287
64
|
);
|
|
288
65
|
}
|
|
289
|
-
if (args.reason !== undefined && plans.every((p) => p.plan.change.raised.length === 0)) {
|
|
290
|
-
stdout.write('Note: nothing was raised, so --reason was not recorded.\n');
|
|
291
|
-
}
|
|
292
|
-
return 0;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
if (require.main === module) {
|
|
296
|
-
process.exitCode = main(process.argv.slice(2));
|
|
297
66
|
}
|
|
298
67
|
|
|
299
|
-
module.exports = {
|
|
300
|
-
generateBaseline,
|
|
301
|
-
planBaseline,
|
|
302
|
-
serializeBaseline,
|
|
303
|
-
splitBaseline,
|
|
304
|
-
readBaseline,
|
|
305
|
-
diffSizes,
|
|
306
|
-
validateRebaselineLog,
|
|
307
|
-
parseArgs,
|
|
308
|
-
main,
|
|
309
|
-
REBASELINES_KEY,
|
|
310
|
-
BASELINE_PATH,
|
|
311
|
-
AGENT_BASELINE_PATH,
|
|
312
|
-
};
|
|
68
|
+
module.exports = { generateBaseline, serializeBaseline, BASELINE_PATH, AGENT_BASELINE_PATH };
|