@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
|
@@ -44,6 +44,10 @@ exports.CLUSTERS = Object.freeze({
|
|
|
44
44
|
'eval-review',
|
|
45
45
|
'add-tests',
|
|
46
46
|
'secure-phase',
|
|
47
|
+
// Retroactive, non-gating review of a completed phase against the client
|
|
48
|
+
// vault's practices (Vault Assurance #71924, 06-07) — the same family as
|
|
49
|
+
// secure-phase / ui-review / validate-phase.
|
|
50
|
+
'vault-review',
|
|
47
51
|
]),
|
|
48
52
|
milestone: Object.freeze([
|
|
49
53
|
'new-milestone',
|
|
@@ -698,20 +698,6 @@ exports.NON_FAMILY_COMMAND_ALIASES = [
|
|
|
698
698
|
],
|
|
699
699
|
"mutation": true
|
|
700
700
|
},
|
|
701
|
-
{
|
|
702
|
-
"canonical": "phase.dispatch-plan",
|
|
703
|
-
"aliases": [
|
|
704
|
-
"phase dispatch-plan"
|
|
705
|
-
],
|
|
706
|
-
"mutation": false
|
|
707
|
-
},
|
|
708
|
-
{
|
|
709
|
-
"canonical": "phase.install-run",
|
|
710
|
-
"aliases": [
|
|
711
|
-
"phase install-run"
|
|
712
|
-
],
|
|
713
|
-
"mutation": true
|
|
714
|
-
},
|
|
715
701
|
{
|
|
716
702
|
"canonical": "phase.mvp-mode",
|
|
717
703
|
"aliases": [
|
|
@@ -719,20 +705,6 @@ exports.NON_FAMILY_COMMAND_ALIASES = [
|
|
|
719
705
|
],
|
|
720
706
|
"mutation": false
|
|
721
707
|
},
|
|
722
|
-
{
|
|
723
|
-
"canonical": "phase.tier",
|
|
724
|
-
"aliases": [
|
|
725
|
-
"phase tier"
|
|
726
|
-
],
|
|
727
|
-
"mutation": false
|
|
728
|
-
},
|
|
729
|
-
{
|
|
730
|
-
"canonical": "phase.tier-propose",
|
|
731
|
-
"aliases": [
|
|
732
|
-
"phase tier-propose"
|
|
733
|
-
],
|
|
734
|
-
"mutation": true
|
|
735
|
-
},
|
|
736
708
|
{
|
|
737
709
|
"canonical": "progress.bar",
|
|
738
710
|
"aliases": [
|
|
@@ -47,6 +47,10 @@ const { extractFrontmatter } = frontmatter;
|
|
|
47
47
|
const modelProfiles = require("./model-profiles.cjs");
|
|
48
48
|
const { MODEL_PROFILES, VALID_PHASE_TYPES } = modelProfiles;
|
|
49
49
|
const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
|
|
50
|
+
// 16-03: the one `.planning/` path test. The seal leaf's top level imports only
|
|
51
|
+
// Node built-ins and it never requires this module, so the edge adds no cycle.
|
|
52
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
53
|
+
const vaultSeal = require("./vault-seal.cjs");
|
|
50
54
|
// ─── Phase Status ─────────────────────────────────────────────────────────────
|
|
51
55
|
/**
|
|
52
56
|
* Determine phase status by checking plan/summary counts AND verification state.
|
|
@@ -458,7 +462,7 @@ function cmdCommit(cwd, message, files, raw, amend, noVerify) {
|
|
|
458
462
|
const phaseMatch = (files || []).join(' ').match(/(\d+(?:\.\d+)*)-/);
|
|
459
463
|
if (phaseMatch) {
|
|
460
464
|
const phaseNum = phaseMatch[1];
|
|
461
|
-
// D-03: a prior milestone's archived phase whose number the current
|
|
465
|
+
// PBI 75323 D-03: a prior milestone's archived phase whose number the current
|
|
462
466
|
// ROADMAP reuses names no branch (the phase-scope owner drops it).
|
|
463
467
|
const phaseInfo = resolvePhaseForRead(cwd, phaseNum);
|
|
464
468
|
if (phaseInfo) {
|
|
@@ -907,7 +911,7 @@ function cmdTodoMatchPhase(cwd, phase, raw) {
|
|
|
907
911
|
const phaseKeywords = new Set(phaseText.split(/[\s\-_/.,;:()\[\]{}|]+/)
|
|
908
912
|
.map(w => w.replace(/[^a-z0-9]/g, ''))
|
|
909
913
|
.filter(w => w.length > 2 && !stopWords.has(w)));
|
|
910
|
-
// Find phase directory to get expected file paths (D-03: through the
|
|
914
|
+
// Find phase directory to get expected file paths (PBI 75323 D-03: through the
|
|
911
915
|
// phase-scope owner, so a prior milestone's archive never lends this phase its plans)
|
|
912
916
|
const phaseInfoDisk = resolvePhaseForRead(cwd, phase);
|
|
913
917
|
const phasePlans = [];
|
|
@@ -993,7 +997,7 @@ function cmdScaffold(cwd, type, options, raw) {
|
|
|
993
997
|
const { phase, name } = options;
|
|
994
998
|
const padded = phase ? normalizePhaseName(phase) : '00';
|
|
995
999
|
const today = new Date().toISOString().split('T')[0];
|
|
996
|
-
// Find phase directory. D-02 (todo "template-fill-writes-
|
|
1000
|
+
// Find phase directory. PBI 75323 / quick 261009-gvf D-02 (todo "template-fill-writes-
|
|
997
1001
|
// into-an-archived-phase-directory"): context / uat / verification never write into a
|
|
998
1002
|
// closed milestone's archive. The phase-scope owner hands back a live match only; an
|
|
999
1003
|
// archive this phase still names is refused naming it, and a prior milestone's archive
|
|
@@ -1223,7 +1227,8 @@ function cmdCheckCommit(cwd, raw) {
|
|
|
1223
1227
|
// commit_docs is false — check if any .planning/ files are staged
|
|
1224
1228
|
const stagedResult = (0, shell_command_projection_cjs_1.execGit)(['diff', '--cached', '--name-only'], { cwd });
|
|
1225
1229
|
if (stagedResult.exitCode === 0) {
|
|
1226
|
-
|
|
1230
|
+
// 16-03: the seal leaf owns the `.planning/` path test (both separators).
|
|
1231
|
+
const planningFiles = stagedResult.stdout.split('\n').filter((f) => vaultSeal.isPlanningPath(f));
|
|
1227
1232
|
if (planningFiles.length > 0) {
|
|
1228
1233
|
error(`commit_docs is false but ${planningFiles.length} .planning/ file(s) are staged:\n` +
|
|
1229
1234
|
planningFiles.map(f => ` ${f}`).join('\n') +
|
|
@@ -31,8 +31,9 @@
|
|
|
31
31
|
* `JSON.stringify`/object literals — never by `.md` shell string
|
|
32
32
|
* concatenation.
|
|
33
33
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
34
|
+
* Its importers today are `src/vault-update.cts`, `src/init-contract.cts`,
|
|
35
|
+
* `src/vault-practices.cts` and (quick 261003-lqi) `src/check-command-router.cts`
|
|
36
|
+
* — and this module imports none of them, so its top-level imports of `./org-profile.cjs`, `./platform-bindings.cjs`,
|
|
36
37
|
* `./git-provider.cjs`, `./vault-coordinates.cjs`, `./io.cjs`,
|
|
37
38
|
* `./project-root.cjs`, `./package-identity.cjs` and
|
|
38
39
|
* `./shell-command-projection.cjs` create no require cycle: none of those
|
|
@@ -47,13 +48,18 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
47
48
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
49
|
exports.COMPOSE_BINDINGS_FLAGS = exports.SEED_VALUE_MAX_LENGTH = void 0;
|
|
49
50
|
exports._resetPlatformTagMemoForTests = _resetPlatformTagMemoForTests;
|
|
51
|
+
exports.listStableTagsAt = listStableTagsAt;
|
|
52
|
+
exports.pickLatestStableTag = pickLatestStableTag;
|
|
53
|
+
exports.resolveLatestVaultTag = resolveLatestVaultTag;
|
|
54
|
+
exports.resolvePlatformRepoUrl = resolvePlatformRepoUrl;
|
|
55
|
+
exports.resolveLatestPlatformTag = resolveLatestPlatformTag;
|
|
50
56
|
exports.resolveCreatedWithFrom = resolveCreatedWithFrom;
|
|
51
57
|
exports.composeBindings = composeBindings;
|
|
52
58
|
exports.redactComposeResultForEmission = redactComposeResultForEmission;
|
|
53
59
|
exports.cmdComposeBindings = cmdComposeBindings;
|
|
60
|
+
exports.cmdPlatformLatestTag = cmdPlatformLatestTag;
|
|
54
61
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
55
62
|
const node_path_1 = __importDefault(require("node:path"));
|
|
56
|
-
const node_child_process_1 = __importDefault(require("node:child_process"));
|
|
57
63
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
58
64
|
const io = require("./io.cjs");
|
|
59
65
|
const { output } = io;
|
|
@@ -354,17 +360,112 @@ const platformTagMemo = new Map();
|
|
|
354
360
|
* `src/active-workstream-store.cts`'s controlling-TTY probe cache. */
|
|
355
361
|
function _resetPlatformTagMemoForTests() {
|
|
356
362
|
platformTagMemo.clear();
|
|
363
|
+
vaultTagMemo.clear();
|
|
364
|
+
}
|
|
365
|
+
/** Per-process memo for `resolveLatestVaultTag`, keyed by clone target. */
|
|
366
|
+
const vaultTagMemo = new Map();
|
|
367
|
+
/**
|
|
368
|
+
* List the stable `vX.Y.Z` tags at `url`, never shell-eval'd (T-34-25):
|
|
369
|
+
* `git ls-remote` is spawned with an argv array through `execGitLocal`, the
|
|
370
|
+
* owner of a vault git invocation's environment (D-116, D-117): the URL names
|
|
371
|
+
* the remote, but git still reads the config of the repository an inherited
|
|
372
|
+
* location variable selects, so the location is unset and transport is kept.
|
|
373
|
+
* Bounded by a 10 s `timeout`, `windowsHide` and spawnSync's default buffer
|
|
374
|
+
* (T-34-26), with the child's stderr piped and discarded — the owner prints
|
|
375
|
+
* nothing (`init-tag-lookup-raw-git-error`). `--` separates the target from
|
|
376
|
+
* the options so a flag-shaped value can never be read as one (T-29-26).
|
|
377
|
+
* Filtered with `isStableTripletSemver`, the single blessed predicate. THROWS
|
|
378
|
+
* on any git fault (a non-zero exit, which the owner also reports for a
|
|
379
|
+
* timeout or a missing git) — callers own the degrade-to-warning policy.
|
|
380
|
+
*/
|
|
381
|
+
function listStableTagsAt(url, opts = {}) {
|
|
382
|
+
const lsOutput = (() => {
|
|
383
|
+
if (typeof opts._lsRemote === 'function')
|
|
384
|
+
return opts._lsRemote(url);
|
|
385
|
+
const result = (0, shell_command_projection_cjs_1.execGitLocal)(['ls-remote', '--tags', '--refs', '--sort=-v:refname', '--', url, 'v*'], { timeout: 10_000 });
|
|
386
|
+
if (result.exitCode !== 0 || result.error)
|
|
387
|
+
throw new Error(`git ls-remote exited ${result.exitCode}`);
|
|
388
|
+
return result.stdout;
|
|
389
|
+
})();
|
|
390
|
+
return lsOutput
|
|
391
|
+
.split('\n')
|
|
392
|
+
.map((line) => {
|
|
393
|
+
const m = line.match(/\brefs\/tags\/([^\s^]+)$/);
|
|
394
|
+
return m ? m[1].trim() : '';
|
|
395
|
+
})
|
|
396
|
+
.filter((t) => t && (0, semver_compare_cjs_1.isStableTripletSemver)(t));
|
|
397
|
+
}
|
|
398
|
+
/** The highest tag by `compareSemverCore`, or `null` for an empty list. */
|
|
399
|
+
function pickLatestStableTag(tags) {
|
|
400
|
+
if (tags.length === 0)
|
|
401
|
+
return null;
|
|
402
|
+
let latest = tags[0];
|
|
403
|
+
for (const t of tags) {
|
|
404
|
+
if ((0, semver_compare_cjs_1.compareSemverCore)(t, latest) > 0)
|
|
405
|
+
latest = t;
|
|
406
|
+
}
|
|
407
|
+
return latest;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* FORK-ONLY (PBI 74316). Propose the vault version a NEW project builds on:
|
|
411
|
+
* the latest stable tag of the client vault repo. The embedded org profile
|
|
412
|
+
* seeds no vault ref on purpose — the version is a per-project choice,
|
|
413
|
+
* committed in `.platform-bindings.json`, so no pp-core release can move it.
|
|
414
|
+
* The proposal reaches the operator through the provenance table
|
|
415
|
+
* (`resolved-tag`) they confirm before anything is written.
|
|
416
|
+
*
|
|
417
|
+
* The clone target is composed by `composeCloneTarget`, so the scp-style SSH
|
|
418
|
+
* form passes through verbatim. Tagless, offline and unreachable all degrade
|
|
419
|
+
* to `{ tag: null }` plus one bounded warning naming the consequence: with no
|
|
420
|
+
* `client.vaultRef` the vault resolves `main`, i.e. the project is unpinned.
|
|
421
|
+
*/
|
|
422
|
+
function resolveLatestVaultTag(repo, host, provider, opts = {}) {
|
|
423
|
+
const unpinned = (why) => ({
|
|
424
|
+
tag: null,
|
|
425
|
+
warnings: [
|
|
426
|
+
bound(`client.vaultRef could not be proposed — ${why}; the vault resolves "main" until the project is pinned with "pp-tools vault update <tag>".`),
|
|
427
|
+
],
|
|
428
|
+
});
|
|
429
|
+
const target = (0, git_provider_cjs_1.composeCloneTarget)(provider, repo, host);
|
|
430
|
+
const cached = vaultTagMemo.get(target.url);
|
|
431
|
+
if (cached)
|
|
432
|
+
return cached;
|
|
433
|
+
let result;
|
|
434
|
+
try {
|
|
435
|
+
const tag = pickLatestStableTag(listStableTagsAt(target.url, opts));
|
|
436
|
+
result = tag ? { tag, warnings: [] } : unpinned('the vault repo has no stable v* tag');
|
|
437
|
+
}
|
|
438
|
+
catch {
|
|
439
|
+
result = unpinned('the tag lookup failed or was unreachable');
|
|
440
|
+
}
|
|
441
|
+
vaultTagMemo.set(target.url, result);
|
|
442
|
+
return result;
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* The platform repo URL rule — the ONE place the platform URL string is
|
|
446
|
+
* composed (quick 261003-my0). The host is `resolvePlatformHost(cwd).host`
|
|
447
|
+
* when it resolves, else `defaultHostForProvider(provider)`; it is never
|
|
448
|
+
* derived from the repo string (Phase 31 D-09). `resolveLatestPlatformTag`
|
|
449
|
+
* and, through it, `pp-tools init`, `check ship.platform-drift` and
|
|
450
|
+
* `query platform-latest-tag` all read this rule, so a shell copy cannot
|
|
451
|
+
* drift from it. May throw when `resolvePlatformHost` throws; the owner's
|
|
452
|
+
* try/catch is the degrade policy.
|
|
453
|
+
*/
|
|
454
|
+
function resolvePlatformRepoUrl(cwd, repo, provider) {
|
|
455
|
+
const host = (0, vault_coordinates_cjs_1.resolvePlatformHost)(cwd).host ?? (0, vault_coordinates_cjs_1.defaultHostForProvider)(provider);
|
|
456
|
+
return `https://${host}/${repo}`;
|
|
357
457
|
}
|
|
358
458
|
/**
|
|
359
459
|
* Resolve the latest STABLE tag for `repo` over the resolved host, never
|
|
360
|
-
* shell-eval'd (T-34-25): `git ls-remote` is spawned with an argv array
|
|
361
|
-
* `
|
|
362
|
-
* (T-34-26). The
|
|
363
|
-
*
|
|
364
|
-
*
|
|
460
|
+
* shell-eval'd (T-34-25): `git ls-remote` is spawned with an argv array
|
|
461
|
+
* through `execGitLocal` (D-116, D-117), bounded by `timeout`/`windowsHide`
|
|
462
|
+
* (T-34-26). The URL comes from `resolvePlatformRepoUrl`: the host is
|
|
463
|
+
* `resolvePlatformHost(cwd).host` when it resolves, else
|
|
464
|
+
* `defaultHostForProvider(provider)` — the host is authoritative and never
|
|
465
|
+
* derived from the repo string (Phase 31 D-09).
|
|
365
466
|
*
|
|
366
467
|
* The child's stderr is piped into the parent's buffer rather than inherited
|
|
367
|
-
* —
|
|
468
|
+
* — the owner spawns with `stdio: 'pipe'` — and deliberately
|
|
368
469
|
* discarded rather than logged: the bounded warning this function returns on
|
|
369
470
|
* failure is the single user-visible surface for a lookup failure. Inheriting
|
|
370
471
|
* the child's stream let a raw git `fatal:` line reach the terminal during an
|
|
@@ -372,22 +473,29 @@ function _resetPlatformTagMemoForTests() {
|
|
|
372
473
|
*
|
|
373
474
|
* Every candidate ref is filtered with `isStableTripletSemver` and ordered
|
|
374
475
|
* with `compareSemverCore` — the single blessed comparison source, never
|
|
375
|
-
* reimplemented. An empty result degrades to `{ tag: null,
|
|
376
|
-
* (tagless is valid, D-01/D-02); any thrown fault
|
|
377
|
-
* DNS failure, non-zero git exit
|
|
476
|
+
* reimplemented. An empty result degrades to `{ tag: null, outcome:
|
|
477
|
+
* 'tagless', warnings: [] }` (tagless is valid, D-01/D-02); any thrown fault
|
|
478
|
+
* (offline, unreachable, DNS failure, non-zero git exit, an unresolvable
|
|
479
|
+
* host) degrades to `{ tag: null, outcome: 'unreachable', warnings: [one
|
|
378
480
|
* bounded warning] }` — offline, unreachable and tagless all resolve to the
|
|
379
|
-
* platform being unpinned, never a compose failure
|
|
481
|
+
* platform being unpinned, never a compose failure, and `outcome` says which
|
|
482
|
+
* one was measured (quick 261003-my0).
|
|
483
|
+
*
|
|
484
|
+
* Quick 261003-lqi: the ship-time drift check (`cmdShipPlatformDrift`) calls
|
|
485
|
+
* this same owner, so init and ship resolve one URL from one host rule.
|
|
486
|
+
* Quick 261003-my0: the init.md / new-project.md preflights reach it through
|
|
487
|
+
* `query platform-latest-tag` (`cmdPlatformLatestTag`).
|
|
380
488
|
*/
|
|
381
489
|
function resolveLatestPlatformTag(cwd, repo, provider, opts) {
|
|
382
490
|
let url;
|
|
383
491
|
try {
|
|
384
|
-
|
|
385
|
-
const host = hostRes.host ?? (0, vault_coordinates_cjs_1.defaultHostForProvider)(provider);
|
|
386
|
-
url = `https://${host}/${repo}`;
|
|
492
|
+
url = resolvePlatformRepoUrl(cwd, repo, provider);
|
|
387
493
|
}
|
|
388
494
|
catch {
|
|
389
495
|
return {
|
|
390
496
|
tag: null,
|
|
497
|
+
url: null,
|
|
498
|
+
outcome: 'unreachable',
|
|
391
499
|
warnings: [
|
|
392
500
|
bound('platform.tag could not be resolved — the tag lookup failed or was unreachable; the platform is unpinned.'),
|
|
393
501
|
],
|
|
@@ -398,37 +506,14 @@ function resolveLatestPlatformTag(cwd, repo, provider, opts) {
|
|
|
398
506
|
return cached;
|
|
399
507
|
let result;
|
|
400
508
|
try {
|
|
401
|
-
const
|
|
402
|
-
|
|
403
|
-
: node_child_process_1.default.execFileSync('git', ['ls-remote', '--tags', '--refs', '--sort=-v:refname', url, 'v*'], {
|
|
404
|
-
encoding: 'utf-8',
|
|
405
|
-
timeout: 10_000,
|
|
406
|
-
maxBuffer: 512 * 1024,
|
|
407
|
-
windowsHide: true,
|
|
408
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
409
|
-
});
|
|
410
|
-
const tags = lsOutput
|
|
411
|
-
.split('\n')
|
|
412
|
-
.map((line) => {
|
|
413
|
-
const m = line.match(/\brefs\/tags\/([^\s^]+)$/);
|
|
414
|
-
return m ? m[1].trim() : '';
|
|
415
|
-
})
|
|
416
|
-
.filter((t) => t && (0, semver_compare_cjs_1.isStableTripletSemver)(t));
|
|
417
|
-
if (tags.length === 0) {
|
|
418
|
-
result = { tag: null, warnings: [] };
|
|
419
|
-
}
|
|
420
|
-
else {
|
|
421
|
-
let latest = tags[0];
|
|
422
|
-
for (const t of tags) {
|
|
423
|
-
if ((0, semver_compare_cjs_1.compareSemverCore)(t, latest) > 0)
|
|
424
|
-
latest = t;
|
|
425
|
-
}
|
|
426
|
-
result = { tag: latest, warnings: [] };
|
|
427
|
-
}
|
|
509
|
+
const tag = pickLatestStableTag(listStableTagsAt(url, opts));
|
|
510
|
+
result = tag ? { tag, url, outcome: 'resolved', warnings: [] } : { tag: null, url, outcome: 'tagless', warnings: [] };
|
|
428
511
|
}
|
|
429
512
|
catch {
|
|
430
513
|
result = {
|
|
431
514
|
tag: null,
|
|
515
|
+
url,
|
|
516
|
+
outcome: 'unreachable',
|
|
432
517
|
warnings: [
|
|
433
518
|
bound('platform.tag could not be resolved — the tag lookup failed or was unreachable; the platform is unpinned.'),
|
|
434
519
|
],
|
|
@@ -533,6 +618,25 @@ function composeCandidate(cwd, seed, provider, opts) {
|
|
|
533
618
|
setField('client.vaultRepo', seed.clientVaultRepo, 'profile-seed');
|
|
534
619
|
setField('client.vaultRef', seed.clientVaultRef, 'profile-seed');
|
|
535
620
|
setField('client.vaultHost', seed.clientVaultHost, 'profile-seed');
|
|
621
|
+
// client.vaultRef (resolved-tag) — FORK-ONLY (PBI 74316). When the seed
|
|
622
|
+
// names a vault repo but no ref, propose the repo's latest stable tag.
|
|
623
|
+
if (isUsableString(seed.clientVaultRepo) && !isUsableString(seed.clientVaultRef)) {
|
|
624
|
+
if (opts.offline === true) {
|
|
625
|
+
pushDeduped(warnings, [
|
|
626
|
+
bound('client.vaultRef was not proposed (offline) — the vault resolves "main" until the project is pinned with "pp-tools vault update <tag>".'),
|
|
627
|
+
]);
|
|
628
|
+
}
|
|
629
|
+
else {
|
|
630
|
+
const vaultHost = isUsableString(seed.clientVaultHost)
|
|
631
|
+
? seed.clientVaultHost
|
|
632
|
+
: (hostRes.host ?? (0, vault_coordinates_cjs_1.defaultHostForProvider)(provider));
|
|
633
|
+
const vaultTag = resolveLatestVaultTag(seed.clientVaultRepo, vaultHost, provider, opts);
|
|
634
|
+
pushDeduped(warnings, vaultTag.warnings);
|
|
635
|
+
if (vaultTag.tag) {
|
|
636
|
+
setField('client.vaultRef', vaultTag.tag, 'resolved-tag');
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
536
640
|
// project.name / project.slug (project-answer)
|
|
537
641
|
setField('project.name', cfgGet(cwd, 'project.name', undefined), 'project-answer');
|
|
538
642
|
setField('project.slug', cfgGet(cwd, 'project.slug', undefined), 'project-answer');
|
|
@@ -796,6 +900,41 @@ function composeBindings(cwd, opts = {}) {
|
|
|
796
900
|
};
|
|
797
901
|
}
|
|
798
902
|
}
|
|
903
|
+
// ─── Emission projection (VAULT-EGRESS-1) ─────────────────────────────────────
|
|
904
|
+
/** The seed short names whose bindings field is a coordinate — DERIVED from
|
|
905
|
+
* `SEED_KEY_SHORT_NAMES` and the coordinate field list, never retyped. */
|
|
906
|
+
const SEED_COORDINATE_SHORT_NAMES = Object.freeze(Object.entries(SEED_KEY_SHORT_NAMES)
|
|
907
|
+
.filter(([seedKey]) => platform_bindings_cjs_1.BINDINGS_COORDINATE_FIELDS.includes(seedKey.slice(org_profile_cjs_1.ORG_PROFILE_SEED_SECTION.length + 1)))
|
|
908
|
+
.map(([, short]) => short));
|
|
909
|
+
/**
|
|
910
|
+
* VAULT-EGRESS-1 (quick 261005-8py gap closure): the display-safe copy of a
|
|
911
|
+
* compose result, for EMISSION only. Every place a coordinate field can carry
|
|
912
|
+
* a value — `composed`, `seed`, each `provenance[].value` and each
|
|
913
|
+
* `drift[].profileValue/effectiveValue` — goes through the one owner. The
|
|
914
|
+
* comparisons that decided `drift` and `action` already ran on the RAW values
|
|
915
|
+
* inside `composeBindings`, and `composeBindings(..., { write: true })` writes
|
|
916
|
+
* the RAW composed object; neither path ever sees this projection.
|
|
917
|
+
*/
|
|
918
|
+
function redactComposeResultForEmission(result) {
|
|
919
|
+
const seed = result.seed === undefined ? undefined : { ...result.seed };
|
|
920
|
+
if (seed !== undefined) {
|
|
921
|
+
for (const short of SEED_COORDINATE_SHORT_NAMES) {
|
|
922
|
+
if (typeof seed[short] === 'string')
|
|
923
|
+
seed[short] = (0, platform_bindings_cjs_1.redactCoordinateForEmission)(seed[short]);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
return {
|
|
927
|
+
...result,
|
|
928
|
+
...(seed === undefined ? {} : { seed }),
|
|
929
|
+
composed: result.composed === null ? null : (0, platform_bindings_cjs_1.redactBindingsForEmission)(result.composed),
|
|
930
|
+
provenance: result.provenance.map((e) => ({ ...e, value: (0, platform_bindings_cjs_1.redactBindingsField)(e.field, e.value) })),
|
|
931
|
+
drift: result.drift.map((d) => ({
|
|
932
|
+
...d,
|
|
933
|
+
profileValue: (0, platform_bindings_cjs_1.redactBindingsField)(d.key, d.profileValue),
|
|
934
|
+
effectiveValue: (0, platform_bindings_cjs_1.redactBindingsField)(d.key, d.effectiveValue),
|
|
935
|
+
})),
|
|
936
|
+
};
|
|
937
|
+
}
|
|
799
938
|
// ─── cmdComposeBindings ───────────────────────────────────────────────────────
|
|
800
939
|
/**
|
|
801
940
|
* CLI adapter for `pp-tools query compose-bindings`. Resolves the project
|
|
@@ -803,39 +942,6 @@ function composeBindings(cwd, opts = {}) {
|
|
|
803
942
|
* does, then calls `composeBindings` and emits the result as data — a
|
|
804
943
|
* verdict is never a process error.
|
|
805
944
|
*/
|
|
806
|
-
/**
|
|
807
|
-
* Pack 6 D-07: the display-safe copy of a compose result, for EMISSION only.
|
|
808
|
-
* Every remote coordinate field (`platform.repo`, `client.vaultRepo`) in
|
|
809
|
-
* `composed`, `seed`, `provenance` and `drift` goes through the one owner
|
|
810
|
-
* (`redactCloneTarget`, src/vault.cts); warnings go through the prose
|
|
811
|
-
* redactor. `composeBindings` itself, its drift/action comparison and every
|
|
812
|
-
* write keep the RAW values — never write or compare the result of this.
|
|
813
|
-
*/
|
|
814
|
-
function redactComposeResultForEmission(result) {
|
|
815
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
816
|
-
const v = require('./vault.cjs');
|
|
817
|
-
const COORD = new Set(['platform.repo', 'client.vaultRepo']);
|
|
818
|
-
const safe = (x) => (typeof x === 'string' ? v.redactCloneTarget(x) : x);
|
|
819
|
-
const out = JSON.parse(JSON.stringify(result));
|
|
820
|
-
if (out.composed && typeof out.composed === 'object') {
|
|
821
|
-
for (const field of COORD) {
|
|
822
|
-
const [ns, key] = field.split('.');
|
|
823
|
-
const parent = out.composed[ns];
|
|
824
|
-
if (parent && typeof parent === 'object' && typeof parent[key] === 'string')
|
|
825
|
-
parent[key] = safe(parent[key]);
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
if (out.seed) {
|
|
829
|
-
if (typeof out.seed.platformRepo === 'string')
|
|
830
|
-
out.seed.platformRepo = safe(out.seed.platformRepo);
|
|
831
|
-
if (typeof out.seed.clientVaultRepo === 'string')
|
|
832
|
-
out.seed.clientVaultRepo = safe(out.seed.clientVaultRepo);
|
|
833
|
-
}
|
|
834
|
-
out.provenance = (out.provenance ?? []).map((p) => (COORD.has(p.field) ? { ...p, value: safe(p.value) } : p));
|
|
835
|
-
out.drift = (out.drift ?? []).map((d) => COORD.has(d.key) ? { ...d, profileValue: safe(d.profileValue), effectiveValue: safe(d.effectiveValue) } : d);
|
|
836
|
-
out.warnings = (out.warnings ?? []).map((w) => (typeof w === 'string' ? v.redactRemoteText(w) : w));
|
|
837
|
-
return out;
|
|
838
|
-
}
|
|
839
945
|
function cmdComposeBindings(cwd, options, raw) {
|
|
840
946
|
let root;
|
|
841
947
|
try {
|
|
@@ -847,3 +953,74 @@ function cmdComposeBindings(cwd, options, raw) {
|
|
|
847
953
|
const result = composeBindings(root, { write: options.write === true, offline: options.offline === true });
|
|
848
954
|
output(redactComposeResultForEmission(result), raw);
|
|
849
955
|
}
|
|
956
|
+
// ─── cmdPlatformLatestTag ─────────────────────────────────────────────────────
|
|
957
|
+
/**
|
|
958
|
+
* CLI adapter for `pp-tools query platform-latest-tag` (quick 261003-my0).
|
|
959
|
+
* Its whole job is to run the owner — `resolveLatestPlatformTag`, the same
|
|
960
|
+
* lookup `pp-tools init` and `check ship.platform-drift` use — and print
|
|
961
|
+
* `{ present, pinned, repo, provider, url, latest, outcome }`, so a shell
|
|
962
|
+
* preflight (init.md / new-project.md Step 0) reads `--pick latest` instead
|
|
963
|
+
* of composing a URL of its own.
|
|
964
|
+
*
|
|
965
|
+
* Absent bindings → `{ present: false }` with NO network call (the parity
|
|
966
|
+
* `--help` smoke runs from a repo root that has no bindings file). Malformed
|
|
967
|
+
* bindings → `outcome: 'malformed'` and no lookup — inert data, never
|
|
968
|
+
* `error()`, the same posture as the drift check's malformed arm. No
|
|
969
|
+
* `platform.repo` → `outcome: 'no-repo'`. `args` is accepted for the
|
|
970
|
+
* dispatcher's uniform shape; no flags are read (`--pick`/`--raw`/`--cwd`
|
|
971
|
+
* are stripped before dispatch, and `--pick` prints `''` for a null field —
|
|
972
|
+
* the shell contract). The owner's warnings are init-time prose and are not
|
|
973
|
+
* forwarded.
|
|
974
|
+
*/
|
|
975
|
+
function cmdPlatformLatestTag(cwd, args, raw) {
|
|
976
|
+
void args;
|
|
977
|
+
let root;
|
|
978
|
+
try {
|
|
979
|
+
root = (0, project_root_cjs_1.findProjectRoot)(cwd) || cwd;
|
|
980
|
+
}
|
|
981
|
+
catch {
|
|
982
|
+
root = cwd;
|
|
983
|
+
}
|
|
984
|
+
const bindingsPath = node_path_1.default.join(root, '.platform-bindings.json');
|
|
985
|
+
if (!node_fs_1.default.existsSync(bindingsPath)) {
|
|
986
|
+
output({ present: false }, raw);
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
let provider;
|
|
990
|
+
try {
|
|
991
|
+
provider = (0, git_provider_cjs_1.resolveProvider)(root).provider;
|
|
992
|
+
}
|
|
993
|
+
catch {
|
|
994
|
+
provider = git_provider_cjs_1.DEFAULT_PROVIDER;
|
|
995
|
+
}
|
|
996
|
+
let parsed;
|
|
997
|
+
try {
|
|
998
|
+
parsed = JSON.parse(node_fs_1.default.readFileSync(bindingsPath, 'utf-8'));
|
|
999
|
+
}
|
|
1000
|
+
catch {
|
|
1001
|
+
parsed = null;
|
|
1002
|
+
}
|
|
1003
|
+
if (!isObject(parsed)) {
|
|
1004
|
+
output({ present: true, pinned: null, repo: null, provider, url: null, latest: null, outcome: 'malformed' }, raw);
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
const platform = isObject(parsed.platform) ? parsed.platform : null;
|
|
1008
|
+
const trimmedOrNull = (v) => typeof v === 'string' && v.trim() !== '' ? v.trim() : null;
|
|
1009
|
+
const pinned = trimmedOrNull(platform?.tag);
|
|
1010
|
+
const repo = trimmedOrNull(platform?.repo);
|
|
1011
|
+
if (repo === null) {
|
|
1012
|
+
output({ present: true, pinned, repo: null, provider, url: null, latest: null, outcome: 'no-repo' }, raw);
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
const result = resolveLatestPlatformTag(root, repo, provider, {});
|
|
1016
|
+
// VAULT-EGRESS-1: `repo` and `url` are display-safe; the lookup above used the raw repo.
|
|
1017
|
+
output({
|
|
1018
|
+
present: true,
|
|
1019
|
+
pinned,
|
|
1020
|
+
repo: (0, platform_bindings_cjs_1.redactCoordinateForEmission)(repo),
|
|
1021
|
+
provider,
|
|
1022
|
+
url: (0, platform_bindings_cjs_1.redactCoordinateForEmission)(result.url),
|
|
1023
|
+
latest: result.tag,
|
|
1024
|
+
outcome: result.outcome,
|
|
1025
|
+
}, raw);
|
|
1026
|
+
}
|
|
@@ -43,9 +43,6 @@ const model_catalog_cjs_1 = require("./model-catalog.cjs");
|
|
|
43
43
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
44
44
|
const federatedConfigModule = require("./federated-config.cjs");
|
|
45
45
|
const { mergeFederatedConfig } = federatedConfigModule;
|
|
46
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
47
|
-
const capabilityActivationModule = require("./capability-activation.cjs");
|
|
48
|
-
const { activationConfigFilesFor } = capabilityActivationModule;
|
|
49
46
|
// The capability-registry.cjs is generated and lives in the same gsd-core/bin/lib/ output dir.
|
|
50
47
|
// Both config-loader.cjs and capability-registry.cjs land in gsd-core/bin/lib/ at build time.
|
|
51
48
|
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment
|
|
@@ -373,16 +370,16 @@ function _readsGlobalDefaults(cwd, ws) {
|
|
|
373
370
|
}
|
|
374
371
|
/**
|
|
375
372
|
* Every config file `loadConfig(cwd, options)` reads, as absolute paths in read
|
|
376
|
-
* order. ONE owner of "which config files does the
|
|
377
|
-
* takes its paths from the same `_configPathsFor`, so a reader that
|
|
378
|
-
* an unreadable config (`
|
|
379
|
-
* files without restating the workstream ladder.
|
|
373
|
+
* order (16-04, D-28). ONE owner of "which config files does the gate read":
|
|
374
|
+
* `loadConfig` takes its paths from the same `_configPathsFor`, so a reader that
|
|
375
|
+
* must refuse an unreadable config (`vaultAdmissionVerdict` in src/vault.cts)
|
|
376
|
+
* can parse exactly these files without restating the workstream ladder.
|
|
380
377
|
*
|
|
381
378
|
* Why a caller needs it: `loadConfig` swallows a parse failure in every one of
|
|
382
379
|
* these files (root → "workstream config stands alone", active → the defaults),
|
|
383
|
-
* so its RESULT cannot say whether
|
|
384
|
-
* nothing. Files that do not exist are still listed; absence is
|
|
385
|
-
* for `loadConfig`, and the caller decides what it means.
|
|
380
|
+
* so its RESULT cannot say whether the gate read the project's config or read
|
|
381
|
+
* nothing (research C-4). Files that do not exist are still listed; absence is
|
|
382
|
+
* not a failure for `loadConfig`, and the caller decides what it means.
|
|
386
383
|
*/
|
|
387
384
|
function configFilesFor(cwd, options = {}) {
|
|
388
385
|
const ws = _activeWorkstreamFor(options);
|
|
@@ -395,97 +392,6 @@ function configFilesFor(cwd, options = {}) {
|
|
|
395
392
|
files.push(_globalDefaultsPath());
|
|
396
393
|
return files;
|
|
397
394
|
}
|
|
398
|
-
/** The error text is bounded; composed text never reaches the cap, but it holds anyway. */
|
|
399
|
-
const UNREADABLE_ERROR_CAP = 200;
|
|
400
|
-
/**
|
|
401
|
-
* Describe why a config file could not be used WITHOUT echoing it. V8's
|
|
402
|
-
* `JSON.parse` messages sometimes quote the input (`Unexpected token '}',
|
|
403
|
-
* "{"a":tru}" is not valid JSON`), so only the position is carried over.
|
|
404
|
-
*/
|
|
405
|
-
function _describeConfigReadFailure(err) {
|
|
406
|
-
const e = err;
|
|
407
|
-
if (e && typeof e.code === 'string' && /^[A-Z][A-Z0-9_]*$/.test(e.code))
|
|
408
|
-
return `cannot be read (${e.code})`;
|
|
409
|
-
const message = e && typeof e.message === 'string' ? e.message : '';
|
|
410
|
-
const lineCol = /\(line (\d+) column (\d+)\)/.exec(message);
|
|
411
|
-
if (lineCol)
|
|
412
|
-
return `not valid JSON (line ${lineCol[1]}, column ${lineCol[2]})`;
|
|
413
|
-
const position = /at position (\d+)/.exec(message);
|
|
414
|
-
if (position)
|
|
415
|
-
return `not valid JSON (at position ${position[1]})`;
|
|
416
|
-
if (/Unexpected end of JSON input/.test(message))
|
|
417
|
-
return 'not valid JSON (the text ends before the JSON does)';
|
|
418
|
-
return 'not valid JSON';
|
|
419
|
-
}
|
|
420
|
-
/** How a refusal names a config file: never an absolute user path. */
|
|
421
|
-
function _configDisplayPath(cwd, abs) {
|
|
422
|
-
const under = (base) => {
|
|
423
|
-
const rel = node_path_1.default.relative(base, abs);
|
|
424
|
-
return rel !== '' && !rel.startsWith('..') && !node_path_1.default.isAbsolute(rel) ? rel.split(node_path_1.default.sep).join('/') : null;
|
|
425
|
-
};
|
|
426
|
-
const inProject = under(cwd);
|
|
427
|
-
if (inProject !== null)
|
|
428
|
-
return inProject;
|
|
429
|
-
const homeOverride = process.env['GSD_HOME'];
|
|
430
|
-
const inHomeOverride = homeOverride ? under(homeOverride) : null;
|
|
431
|
-
if (inHomeOverride !== null)
|
|
432
|
-
return `$GSD_HOME/${inHomeOverride}`;
|
|
433
|
-
const inHome = under(node_os_1.default.homedir());
|
|
434
|
-
if (inHome !== null)
|
|
435
|
-
return `~/${inHome}`;
|
|
436
|
-
return node_path_1.default.basename(abs);
|
|
437
|
-
}
|
|
438
|
-
/**
|
|
439
|
-
* Every config file the hook resolver would read that EXISTS but cannot be
|
|
440
|
-
* used: unreadable, not JSON, or JSON that is not an object (`loadConfig`
|
|
441
|
-
* reads none of an array's or a null's keys and answers the defaults, the same
|
|
442
|
-
* silent fallback as a parse failure). Empty when every file is usable or
|
|
443
|
-
* absent: a MISSING config is a fresh project and reads as the defaults.
|
|
444
|
-
*
|
|
445
|
-
* The files are the union, in first-seen order, of the loader's list
|
|
446
|
-
* (`configFilesFor`) and the activation resolver's raw levels
|
|
447
|
-
* (`activationConfigFilesFor`, which can name a root config the loader never
|
|
448
|
-
* opens under `GSD_PROJECT`). Reads go through `platformReadSync`, the reader
|
|
449
|
-
* `loadConfig` uses, so a file the loader can read is never called unreadable.
|
|
450
|
-
*
|
|
451
|
-
* This is the ONE owner of "can the project's config be read". Both swallowing
|
|
452
|
-
* readers (`loadConfig`, `resolveConfigKey`) stay tolerant for their many
|
|
453
|
-
* callers; a caller whose answer governs a gate (`loop render-hooks`, the vault
|
|
454
|
-
* load verdict) asks this first and refuses. Never throws.
|
|
455
|
-
*/
|
|
456
|
-
function unreadableConfigFiles(cwd, options = {}) {
|
|
457
|
-
const out = [];
|
|
458
|
-
let files;
|
|
459
|
-
try {
|
|
460
|
-
files = [...new Set([...configFilesFor(cwd, options), ...activationConfigFilesFor(cwd)])];
|
|
461
|
-
}
|
|
462
|
-
catch (err) {
|
|
463
|
-
// An invalid GSD_WORKSTREAM / GSD_PROJECT segment: the config cannot be located, so it cannot be read.
|
|
464
|
-
return [{ file: '.planning/config.json', error: `cannot be located (${err instanceof Error ? err.message : String(err)})`.slice(0, UNREADABLE_ERROR_CAP) }];
|
|
465
|
-
}
|
|
466
|
-
for (const file of files) {
|
|
467
|
-
let error = null;
|
|
468
|
-
try {
|
|
469
|
-
const raw = (0, shell_command_projection_cjs_1.platformReadSync)(file);
|
|
470
|
-
if (raw === null)
|
|
471
|
-
continue;
|
|
472
|
-
const parsed = JSON.parse(raw);
|
|
473
|
-
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
474
|
-
error = 'valid JSON but not an object, so none of its settings can be read';
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
catch (err) {
|
|
478
|
-
error = _describeConfigReadFailure(err);
|
|
479
|
-
}
|
|
480
|
-
if (error !== null)
|
|
481
|
-
out.push({ file: _configDisplayPath(cwd, file), error: error.slice(0, UNREADABLE_ERROR_CAP) });
|
|
482
|
-
}
|
|
483
|
-
return out;
|
|
484
|
-
}
|
|
485
|
-
/** One line per unreadable file, `<file>: <error>`, for a refusal message. */
|
|
486
|
-
function describeUnreadableConfig(unreadable) {
|
|
487
|
-
return unreadable.map((u) => `${u.file}: ${u.error}`).join('; ');
|
|
488
|
-
}
|
|
489
395
|
function loadConfig(cwd, options = {}) {
|
|
490
396
|
// When GSD_WORKSTREAM is set, load root config first so workstream config
|
|
491
397
|
// can inherit from it. This prevents users from duplicating model_overrides,
|
|
@@ -839,8 +745,6 @@ function loadConfig(cwd, options = {}) {
|
|
|
839
745
|
module.exports = {
|
|
840
746
|
loadConfig,
|
|
841
747
|
configFilesFor,
|
|
842
|
-
unreadableConfigFiles,
|
|
843
|
-
describeUnreadableConfig,
|
|
844
748
|
isGitIgnored,
|
|
845
749
|
CONFIG_DEFAULTS,
|
|
846
750
|
_getConfigDefault,
|
|
@@ -29,7 +29,7 @@ function isCapabilityConfigKey(keyPath) {
|
|
|
29
29
|
/**
|
|
30
30
|
* The declared `values` of a capability `type: "enum"` config key, or `null`
|
|
31
31
|
* when the key is not a capability key, is not an enum, or carries no value
|
|
32
|
-
* list. 13-05 / D-07: this is the ONE lookup of enum values —
|
|
32
|
+
* list. Vault Assurance 13-05 / D-07: this is the ONE lookup of enum values —
|
|
33
33
|
* `config-set` asks it rather than restating any list, so the registry stays
|
|
34
34
|
* the single source of what an enum key may hold.
|
|
35
35
|
*/
|