@pareto4/pp-core 1.17.1 → 1.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +5 -3
- package/agents/pp-code-fixer.md +9 -16
- package/agents/pp-code-reviewer.md +2 -78
- package/agents/pp-codebase-mapper.md +3 -0
- package/agents/pp-debugger.md +3 -2
- package/agents/pp-executor.md +25 -30
- package/agents/pp-integration-checker.md +1 -0
- package/agents/pp-phase-researcher.md +3 -1
- package/agents/pp-plan-checker.md +3 -0
- package/agents/pp-planner.md +16 -12
- package/agents/pp-research-synthesizer.md +1 -1
- package/agents/pp-ui-auditor.md +1 -0
- package/agents/pp-ui-researcher.md +3 -1
- package/agents/pp-vault-reviewer.md +43 -189
- package/agents/pp-verifier.md +10 -8
- package/commands/pp-add-tests.md +1 -1
- package/commands/pp-debug.md +1 -0
- package/commands/pp-init.md +4 -3
- package/commands/pp-ns-manage.md +1 -1
- package/commands/pp-ns-review.md +1 -1
- package/commands/pp-quick.md +3 -0
- package/commands/pp-review-backlog.md +2 -1
- package/commands/pp-vault-promote.md +7 -8
- package/commands/pp-vault-review.md +6 -7
- package/gemini-extension.json +1 -1
- package/package.json +3 -6
- package/pp-core/bin/lib/capability-registry.cjs +15 -357
- package/pp-core/bin/lib/capability-state.cjs +15 -79
- package/pp-core/bin/lib/capability-writer.cjs +2 -16
- package/pp-core/bin/lib/check-command-router.cjs +342 -2298
- package/pp-core/bin/lib/clusters.cjs +2 -2
- package/pp-core/bin/lib/commands.cjs +79 -4
- package/pp-core/bin/lib/compose-bindings.cjs +99 -16
- package/pp-core/bin/lib/config.cjs +3 -6
- package/pp-core/bin/lib/discovery-rollup.cjs +9 -6
- package/pp-core/bin/lib/discovery-screens.cjs +20 -2
- package/pp-core/bin/lib/discovery-sweep.cjs +12 -3
- package/pp-core/bin/lib/discovery-vault-check.cjs +37 -3
- package/pp-core/bin/lib/git-base-branch.cjs +10 -13
- package/pp-core/bin/lib/git-provider.cjs +5 -6
- package/pp-core/bin/lib/init-contract.cjs +55 -42
- package/pp-core/bin/lib/init-interactive.cjs +6 -20
- package/pp-core/bin/lib/io.cjs +2 -8
- package/pp-core/bin/lib/loop-resolver.cjs +8 -32
- package/pp-core/bin/lib/org-profile.cjs +16 -92
- package/pp-core/bin/lib/vault-command-router.cjs +174 -1206
- package/pp-core/bin/lib/vault-coordinates.cjs +4 -4
- package/pp-core/bin/lib/vault-index.cjs +44 -74
- package/pp-core/bin/lib/vault-seal.cjs +37 -122
- package/pp-core/bin/lib/vault-standards-promote.cjs +577 -0
- package/pp-core/bin/lib/vault-standards-review.cjs +293 -0
- package/pp-core/bin/lib/vault-standards.cjs +15 -16
- package/pp-core/bin/lib/vault-trace.cjs +40 -3016
- package/pp-core/bin/lib/vault-update.cjs +5 -26
- package/pp-core/bin/lib/vault.cjs +122 -316
- package/pp-core/bin/pp-tools.cjs +15 -36
- package/pp-core/org-profile.json +1 -1
- package/pp-core/references/autonomous-best-effort.md +4 -3
- package/pp-core/references/autonomous-lifecycle.md +1 -0
- package/pp-core/references/autonomous-smart-discuss.md +5 -1
- package/pp-core/references/discovery-id-conventions.md +35 -2
- package/pp-core/references/execute-mvp-tdd.md +3 -3
- package/pp-core/references/git-integration.md +57 -33
- package/pp-core/references/git-planning-commit.md +31 -9
- package/pp-core/references/phase-argument-parsing.md +1 -0
- package/pp-core/references/planner-gap-closure.md +1 -0
- package/pp-core/references/planner-revision.md +3 -1
- package/pp-core/references/planner-tdd.md +4 -4
- package/pp-core/references/planner-vault-standards.md +16 -25
- package/pp-core/references/planning-config.md +3 -3
- package/pp-core/references/shell-portability.md +28 -0
- package/pp-core/references/tdd.md +20 -16
- package/pp-core/references/worktree-path-safety.md +1 -0
- package/pp-core/templates/README.md +1 -1
- package/pp-core/templates/VAULT-REVIEW.md +24 -170
- package/pp-core/templates/milestone.md +5 -5
- package/pp-core/workflows/add-backlog.md +1 -1
- package/pp-core/workflows/add-tests.md +8 -5
- package/pp-core/workflows/add-todo.md +3 -1
- package/pp-core/workflows/ai-integration-phase.md +2 -1
- package/pp-core/workflows/audit-fix.md +7 -6
- package/pp-core/workflows/audit-milestone.md +1 -0
- package/pp-core/workflows/autonomous.md +6 -0
- package/pp-core/workflows/check-todos.md +2 -1
- package/pp-core/workflows/cleanup.md +3 -1
- package/pp-core/workflows/code-review-fix.md +15 -8
- package/pp-core/workflows/code-review.md +16 -58
- package/pp-core/workflows/complete-milestone.md +13 -3
- package/pp-core/workflows/debug.md +4 -2
- package/pp-core/workflows/diagnose-issues.md +1 -1
- package/pp-core/workflows/discovery-sprint/prototype.md +2 -2
- package/pp-core/workflows/discovery-sprint/session-13-gates.md +1 -1
- package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +51 -7
- package/pp-core/workflows/discovery-sprint/sessions/02-as-is.md +5 -3
- package/pp-core/workflows/discovery-sprint/sessions/03-to-be.md +10 -8
- package/pp-core/workflows/discovery-sprint/sessions/04-ux-design.md +11 -2
- package/pp-core/workflows/discovery-sprint/sessions/05-architecture.md +5 -5
- package/pp-core/workflows/discovery-sprint/sessions/06-security.md +3 -3
- package/pp-core/workflows/discovery-sprint/sessions/07-identity.md +2 -2
- package/pp-core/workflows/discovery-sprint/sessions/08-integration.md +14 -2
- package/pp-core/workflows/discovery-sprint/sessions/09-qa.md +2 -2
- package/pp-core/workflows/discovery-sprint/sessions/10-infrastructure.md +10 -10
- package/pp-core/workflows/discovery-sprint/sessions/11-roadmap.md +11 -8
- package/pp-core/workflows/discovery-sprint/sessions/12-delivery-model.md +21 -18
- package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +5 -3
- package/pp-core/workflows/discovery-sprint/templates/context-template.md +9 -3
- package/pp-core/workflows/discovery-sprint/vault-context.md +17 -12
- package/pp-core/workflows/discovery-sprint.md +11 -8
- package/pp-core/workflows/discovery-to-build/steps/architecture-deliverables.md +1 -0
- package/pp-core/workflows/discuss-phase-assumptions.md +5 -3
- package/pp-core/workflows/discuss-phase.md +8 -3
- package/pp-core/workflows/docs-update.md +1 -0
- package/pp-core/workflows/eval-review.md +2 -1
- package/pp-core/workflows/execute-phase/steps/check-query-gate.md +13 -14
- package/pp-core/workflows/execute-phase/steps/cross-ai-delegation.md +1 -0
- package/pp-core/workflows/execute-phase/steps/execute-pre-dispatch.md +5 -25
- package/pp-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -0
- package/pp-core/workflows/execute-phase/steps/vault-fetch.md +18 -6
- package/pp-core/workflows/execute-phase/steps/vault-load-gate.md +6 -109
- package/pp-core/workflows/execute-phase/steps/verify-post-gate.md +1 -17
- package/pp-core/workflows/execute-phase.md +21 -12
- package/pp-core/workflows/execute-plan.md +24 -15
- package/pp-core/workflows/explore.md +1 -1
- package/pp-core/workflows/forensics.md +4 -2
- package/pp-core/workflows/health.md +1 -0
- package/pp-core/workflows/help/modes/full.md +1 -1
- package/pp-core/workflows/import.md +2 -1
- package/pp-core/workflows/ingest-docs.md +1 -0
- package/pp-core/workflows/init/steps/client-context-capture.md +1 -0
- package/pp-core/workflows/init/steps/github-provisioning.md +145 -0
- package/pp-core/workflows/init/steps/gitlab-provisioning.md +18 -4
- package/pp-core/workflows/init/steps/reachability-outcome.md +3 -1
- package/pp-core/workflows/init.md +56 -66
- package/pp-core/workflows/map-codebase.md +5 -1
- package/pp-core/workflows/milestone-summary.md +2 -1
- package/pp-core/workflows/mvp-phase.md +1 -0
- package/pp-core/workflows/new-milestone.md +9 -5
- package/pp-core/workflows/new-project.md +9 -8
- package/pp-core/workflows/new-workspace.md +1 -0
- package/pp-core/workflows/next.md +2 -1
- package/pp-core/workflows/pause-work.md +6 -4
- package/pp-core/workflows/plan-milestone-gaps.md +3 -1
- package/pp-core/workflows/plan-phase/steps/chunked-planning.md +1 -1
- package/pp-core/workflows/plan-phase.md +21 -6
- package/pp-core/workflows/plan-review-convergence.md +2 -0
- package/pp-core/workflows/plant-seed.md +4 -2
- package/pp-core/workflows/pr-branch.md +2 -0
- package/pp-core/workflows/progress.md +5 -0
- package/pp-core/workflows/quick.md +19 -12
- package/pp-core/workflows/reapply-patches.md +3 -0
- package/pp-core/workflows/remove-phase.md +1 -1
- package/pp-core/workflows/resume-project.md +3 -0
- package/pp-core/workflows/review.md +7 -1
- package/pp-core/workflows/secure-phase.md +2 -1
- package/pp-core/workflows/session-report.md +1 -0
- package/pp-core/workflows/ship.md +8 -1
- package/pp-core/workflows/sketch-wrap-up.md +1 -1
- package/pp-core/workflows/sketch.md +3 -2
- package/pp-core/workflows/spec-phase.md +2 -1
- package/pp-core/workflows/spike-wrap-up.md +1 -1
- package/pp-core/workflows/spike.md +4 -3
- package/pp-core/workflows/sync-skills.md +5 -2
- package/pp-core/workflows/thread.md +3 -2
- package/pp-core/workflows/transition.md +6 -0
- package/pp-core/workflows/ui-phase.md +3 -1
- package/pp-core/workflows/ui-review.md +2 -1
- package/pp-core/workflows/ultraplan-phase.md +1 -0
- package/pp-core/workflows/undo.md +14 -10
- package/pp-core/workflows/update.md +5 -0
- package/pp-core/workflows/validate-phase.md +9 -6
- package/pp-core/workflows/vault-review.md +65 -186
- package/pp-core/workflows/vault-write-back.md +78 -110
- package/pp-core/workflows/verify-phase.md +5 -0
- package/pp-core/workflows/verify-work.md +9 -2
- package/scripts/gen-capability-registry.cjs +12 -93
- package/scripts/lint-test-file-count.allowlist.json +1 -19
- package/scripts/lint-test-file-count.cjs +5 -6
- package/scripts/prompt-injection-scan.sh +1 -4
- package/scripts/sync-runtime-launcher.cjs +115 -5
- package/pp-core/bin/lib/practices-review-check.cjs +0 -944
- package/pp-core/bin/lib/vault-audit.cjs +0 -2594
- package/pp-core/bin/lib/vault-mode.cjs +0 -235
- package/pp-core/bin/lib/vault-practices.cjs +0 -2057
- package/pp-core/bin/lib/vault-review.cjs +0 -2102
- package/pp-core/bin/lib/vault-write-back.cjs +0 -905
- package/pp-core/references/planner-vault-practices.md +0 -95
- package/pp-core/references/practice-arithmetic-contract.md +0 -121
- package/pp-core/references/verifier-practice-verdicts.md +0 -263
- package/pp-core/templates/practices/README.md +0 -248
- package/pp-core/templates/practices/error-handling.md +0 -18
- package/pp-core/templates/practices/minimal-comments.md +0 -14
- package/pp-core/templates/practices/naming-convention.md +0 -18
- package/pp-core/templates/practices/practice-template.md +0 -23
- package/pp-core/workflows/execute-phase/steps/verifier-practice-payload.md +0 -165
- package/scripts/gen-practice-arithmetic-contract.cjs +0 -412
- package/scripts/practice-arithmetic-contract.allow.json +0 -35
- package/scripts/record-vault-mode-golden.cjs +0 -89
- package/scripts/vault-calibrate.cjs +0 -996
|
@@ -45,8 +45,8 @@ exports.CLUSTERS = Object.freeze({
|
|
|
45
45
|
'add-tests',
|
|
46
46
|
'secure-phase',
|
|
47
47
|
// Retroactive, non-gating review of a completed phase against the client
|
|
48
|
-
// vault's
|
|
49
|
-
// secure-phase / ui-review / validate-phase.
|
|
48
|
+
// vault's standards documents (`check vault-review`, a report) — the same
|
|
49
|
+
// family as secure-phase / ui-review / validate-phase.
|
|
50
50
|
'vault-review',
|
|
51
51
|
]),
|
|
52
52
|
milestone: Object.freeze([
|
|
@@ -14,7 +14,7 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
14
14
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
15
15
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
16
16
|
const ioMod = require("./io.cjs");
|
|
17
|
-
const { output, error } = ioMod;
|
|
17
|
+
const { output, error, ERROR_REASON } = ioMod;
|
|
18
18
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
19
19
|
const configLoaderMod = require("./config-loader.cjs");
|
|
20
20
|
const { loadConfig, isGitIgnored } = configLoaderMod;
|
|
@@ -423,10 +423,76 @@ function cmdEffortSync(cwd, raw, opts) {
|
|
|
423
423
|
}
|
|
424
424
|
output({ synced, skipped, changes, dry_run: dryRun, agents_dir: agentsDir }, raw, synced > 0 ? 'changed' : 'ok');
|
|
425
425
|
}
|
|
426
|
-
|
|
426
|
+
// ─── Commit trailers (Phase: / Quick:) ────────────────────────────────────────
|
|
427
|
+
//
|
|
428
|
+
// The Conventional Commit scope names a part of the codebase (`docs(planning)`,
|
|
429
|
+
// `feat(auth)`), never a phase or plan number. The phase, plan or quick-task
|
|
430
|
+
// identity a commit belongs to travels as ONE git trailer instead —
|
|
431
|
+
// `Phase: 02-03`, `Phase: 02` or `Quick: <id>` — so readers find it with
|
|
432
|
+
// `%(trailers:key=Phase)` rather than by parsing the subject.
|
|
433
|
+
/** Loose id shape: digits, letters, dots, dashes, underscores; no whitespace. */
|
|
434
|
+
const COMMIT_TRAILER_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
435
|
+
/**
|
|
436
|
+
* Pull `--phase <id>` and `--quick <id>` (and their `--flag=<id>` forms) out of
|
|
437
|
+
* a `commit` / `commit-to-subrepo` argv, so neither the value nor the flag is
|
|
438
|
+
* taken for part of the message or for a `--files` path. A flag with no value
|
|
439
|
+
* yields the empty string, which `buildCommitTrailer` rejects.
|
|
440
|
+
*/
|
|
441
|
+
function extractCommitTrailerArgs(args) {
|
|
442
|
+
const rest = [];
|
|
443
|
+
let phase;
|
|
444
|
+
let quick;
|
|
445
|
+
for (let i = 0; i < args.length; i++) {
|
|
446
|
+
const a = args[i];
|
|
447
|
+
const eq = /^--(phase|quick)=(.*)$/s.exec(a);
|
|
448
|
+
if (eq) {
|
|
449
|
+
if (eq[1] === 'phase')
|
|
450
|
+
phase = eq[2];
|
|
451
|
+
else
|
|
452
|
+
quick = eq[2];
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
if (a === '--phase' || a === '--quick') {
|
|
456
|
+
const next = args[i + 1];
|
|
457
|
+
const hasValue = next !== undefined && !next.startsWith('--');
|
|
458
|
+
const value = hasValue ? next : '';
|
|
459
|
+
if (hasValue)
|
|
460
|
+
i++;
|
|
461
|
+
if (a === '--phase')
|
|
462
|
+
phase = value;
|
|
463
|
+
else
|
|
464
|
+
quick = value;
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
rest.push(a);
|
|
468
|
+
}
|
|
469
|
+
return { args: rest, phase, quick };
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* The trailer paragraph for a commit, or null when neither id is given.
|
|
473
|
+
* Both ids may be given; they share one paragraph so git reads both as
|
|
474
|
+
* trailers. Errors (exit 1, reason `usage`) on an id that is empty or holds
|
|
475
|
+
* anything but letters, digits, dots, dashes or underscores — a newline in
|
|
476
|
+
* an id would otherwise forge further trailers.
|
|
477
|
+
*/
|
|
478
|
+
function buildCommitTrailer(opts) {
|
|
479
|
+
const lines = [];
|
|
480
|
+
for (const [key, flag, value] of [['Phase', '--phase', opts.phase], ['Quick', '--quick', opts.quick]]) {
|
|
481
|
+
if (value === undefined)
|
|
482
|
+
continue;
|
|
483
|
+
if (!COMMIT_TRAILER_ID_RE.test(value)) {
|
|
484
|
+
error(`${flag} must be a phase/plan or quick-task id such as 02 or 02-03 (letters, digits, dots, dashes; got ${JSON.stringify(value)})`, ERROR_REASON.USAGE);
|
|
485
|
+
}
|
|
486
|
+
lines.push(`${key}: ${value}`);
|
|
487
|
+
}
|
|
488
|
+
return lines.length > 0 ? lines.join('\n') : null;
|
|
489
|
+
}
|
|
490
|
+
function cmdCommit(cwd, message, files, raw, amend, noVerify, trailerIds = {}) {
|
|
427
491
|
if (!message && !amend) {
|
|
428
492
|
error('commit message required');
|
|
429
493
|
}
|
|
494
|
+
// --amend keeps the previous message (--no-edit), so a trailer is ignored there.
|
|
495
|
+
const trailer = amend ? null : buildCommitTrailer(trailerIds);
|
|
430
496
|
// Sanitize commit message: strip invisible chars and injection markers
|
|
431
497
|
// that could hijack agent context when commit messages are read back
|
|
432
498
|
let sanitizedMessage = message;
|
|
@@ -513,6 +579,9 @@ function cmdCommit(cwd, message, files, raw, amend, noVerify) {
|
|
|
513
579
|
}
|
|
514
580
|
// Commit (--no-verify skips pre-commit hooks, used by parallel executor agents)
|
|
515
581
|
const commitArgs = amend ? ['commit', '--amend', '--no-edit'] : ['commit', '-m', sanitizedMessage];
|
|
582
|
+
// A second -m is its own paragraph, which git reads as the trailer block.
|
|
583
|
+
if (trailer)
|
|
584
|
+
commitArgs.push('-m', trailer);
|
|
516
585
|
if (noVerify)
|
|
517
586
|
commitArgs.push('--no-verify');
|
|
518
587
|
const commitResult = (0, shell_command_projection_cjs_1.execGit)(commitArgs, { cwd });
|
|
@@ -591,10 +660,11 @@ function groupFilesBySubrepo(files, subRepos) {
|
|
|
591
660
|
}
|
|
592
661
|
return { grouped, unmatched };
|
|
593
662
|
}
|
|
594
|
-
function cmdCommitToSubrepo(cwd, message, files, raw) {
|
|
663
|
+
function cmdCommitToSubrepo(cwd, message, files, raw, trailerIds = {}) {
|
|
595
664
|
if (!message) {
|
|
596
665
|
error('commit message required');
|
|
597
666
|
}
|
|
667
|
+
const trailer = buildCommitTrailer(trailerIds);
|
|
598
668
|
const config = loadConfig(cwd);
|
|
599
669
|
const subRepos = config['sub_repos'];
|
|
600
670
|
if (!subRepos || subRepos.length === 0) {
|
|
@@ -617,7 +687,10 @@ function cmdCommitToSubrepo(cwd, message, files, raw) {
|
|
|
617
687
|
(0, shell_command_projection_cjs_1.execGit)(['add', relativePath], { cwd: repoCwd });
|
|
618
688
|
}
|
|
619
689
|
// Commit
|
|
620
|
-
const
|
|
690
|
+
const commitArgs = ['commit', '-m', message];
|
|
691
|
+
if (trailer)
|
|
692
|
+
commitArgs.push('-m', trailer);
|
|
693
|
+
const commitResult = (0, shell_command_projection_cjs_1.execGit)(commitArgs, { cwd: repoCwd });
|
|
621
694
|
if (commitResult.exitCode !== 0) {
|
|
622
695
|
if (commitResult.stdout.includes('nothing to commit') || commitResult.stderr.includes('nothing to commit')) {
|
|
623
696
|
repos[repo] = { committed: false, hash: null, files: repoFiles, reason: 'nothing_to_commit' };
|
|
@@ -1252,6 +1325,8 @@ module.exports = {
|
|
|
1252
1325
|
cmdEffortSync,
|
|
1253
1326
|
cmdCommit,
|
|
1254
1327
|
cmdCommitToSubrepo,
|
|
1328
|
+
extractCommitTrailerArgs,
|
|
1329
|
+
buildCommitTrailer,
|
|
1255
1330
|
cmdSummaryExtract,
|
|
1256
1331
|
cmdWebsearch,
|
|
1257
1332
|
cmdProgressRender,
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
* `JSON.stringify`/object literals — never by `.md` shell string
|
|
32
32
|
* concatenation.
|
|
33
33
|
*
|
|
34
|
-
* Its importers today are `src/vault-update.cts`, `src/init-contract.cts
|
|
35
|
-
*
|
|
34
|
+
* Its importers today are `src/vault-update.cts`, `src/init-contract.cts`
|
|
35
|
+
* and (quick 261003-lqi) `src/check-command-router.cts`
|
|
36
36
|
* — and this module imports none of them, so its top-level imports of `./org-profile.cjs`, `./platform-bindings.cjs`,
|
|
37
37
|
* `./git-provider.cjs`, `./vault-coordinates.cjs`, `./io.cjs`,
|
|
38
38
|
* `./project-root.cjs`, `./package-identity.cjs` and
|
|
@@ -54,6 +54,8 @@ exports.resolveLatestVaultTag = resolveLatestVaultTag;
|
|
|
54
54
|
exports.resolvePlatformRepoUrl = resolvePlatformRepoUrl;
|
|
55
55
|
exports.resolveLatestPlatformTag = resolveLatestPlatformTag;
|
|
56
56
|
exports.resolveCreatedWithFrom = resolveCreatedWithFrom;
|
|
57
|
+
exports.clientVaultChoice = clientVaultChoice;
|
|
58
|
+
exports.validateClientVaultAnswer = validateClientVaultAnswer;
|
|
57
59
|
exports.composeBindings = composeBindings;
|
|
58
60
|
exports.redactComposeResultForEmission = redactComposeResultForEmission;
|
|
59
61
|
exports.cmdComposeBindings = cmdComposeBindings;
|
|
@@ -579,7 +581,7 @@ function resolveCreatedWith() {
|
|
|
579
581
|
* string. Every emitted field is one the schema's `additionalProperties:
|
|
580
582
|
* false` property sets already allow.
|
|
581
583
|
*/
|
|
582
|
-
function composeCandidate(cwd, seed, provider, opts) {
|
|
584
|
+
function composeCandidate(cwd, seed, provider, opts, existingClient) {
|
|
583
585
|
const candidate = {};
|
|
584
586
|
const provenance = {};
|
|
585
587
|
const warnings = [];
|
|
@@ -614,23 +616,37 @@ function composeCandidate(cwd, seed, provider, opts) {
|
|
|
614
616
|
// client.name / client.slug (project-answer)
|
|
615
617
|
setField('client.name', cfgGet(cwd, 'project.client_name', undefined), 'project-answer');
|
|
616
618
|
setField('client.slug', cfgGet(cwd, 'project.client_slug', undefined), 'project-answer');
|
|
617
|
-
// client.vaultRepo
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
if (
|
|
619
|
+
// client.vaultRepo (project-answer or profile-seed). The seeded ref and
|
|
620
|
+
// host describe the seeded repo, so they are proposed only while that repo
|
|
621
|
+
// is the one the binding will name.
|
|
622
|
+
const answerRepo = isUsableString(opts.clientVaultRepo) ? opts.clientVaultRepo.trim() : undefined;
|
|
623
|
+
const boundRepo = existingClient.vaultRepo ?? answerRepo ?? seed.clientVaultRepo;
|
|
624
|
+
const seedIsBound = answerRepo === undefined && boundRepo === seed.clientVaultRepo;
|
|
625
|
+
if (answerRepo !== undefined) {
|
|
626
|
+
setField('client.vaultRepo', answerRepo, 'project-answer');
|
|
627
|
+
}
|
|
628
|
+
else {
|
|
629
|
+
setField('client.vaultRepo', seed.clientVaultRepo, 'profile-seed');
|
|
630
|
+
}
|
|
631
|
+
if (seedIsBound) {
|
|
632
|
+
setField('client.vaultRef', seed.clientVaultRef, 'profile-seed');
|
|
633
|
+
setField('client.vaultHost', seed.clientVaultHost, 'profile-seed');
|
|
634
|
+
}
|
|
635
|
+
// client.vaultRef (resolved-tag) — FORK-ONLY (PBI 74316). When the bound
|
|
636
|
+
// vault repo has no ref yet, propose that repo's latest stable tag.
|
|
637
|
+
const boundRef = existingClient.vaultRef ?? (seedIsBound ? seed.clientVaultRef : undefined);
|
|
638
|
+
if (isUsableString(boundRepo) && !isUsableString(boundRef)) {
|
|
624
639
|
if (opts.offline === true) {
|
|
625
640
|
pushDeduped(warnings, [
|
|
626
641
|
bound('client.vaultRef was not proposed (offline) — the vault resolves "main" until the project is pinned with "pp-tools vault update <tag>".'),
|
|
627
642
|
]);
|
|
628
643
|
}
|
|
629
644
|
else {
|
|
630
|
-
const vaultHost =
|
|
631
|
-
? seed.clientVaultHost
|
|
632
|
-
|
|
633
|
-
|
|
645
|
+
const vaultHost = existingClient.vaultHost ??
|
|
646
|
+
(seedIsBound && isUsableString(seed.clientVaultHost) ? seed.clientVaultHost : undefined) ??
|
|
647
|
+
hostRes.host ??
|
|
648
|
+
(0, vault_coordinates_cjs_1.defaultHostForProvider)(provider);
|
|
649
|
+
const vaultTag = resolveLatestVaultTag(boundRepo, vaultHost, provider, opts);
|
|
634
650
|
pushDeduped(warnings, vaultTag.warnings);
|
|
635
651
|
if (vaultTag.tag) {
|
|
636
652
|
setField('client.vaultRef', vaultTag.tag, 'resolved-tag');
|
|
@@ -746,6 +762,73 @@ function readExistingBindings(cwd) {
|
|
|
746
762
|
};
|
|
747
763
|
}
|
|
748
764
|
}
|
|
765
|
+
/** Read the usable `client.vault*` strings of an existing binding; the merge
|
|
766
|
+
* keeps them, so the ref proposal must follow the repo they name. */
|
|
767
|
+
function existingVaultCoordinates(data) {
|
|
768
|
+
const client = data.client;
|
|
769
|
+
if (!isObject(client))
|
|
770
|
+
return {};
|
|
771
|
+
const out = {};
|
|
772
|
+
if (isUsableString(client.vaultRepo))
|
|
773
|
+
out.vaultRepo = client.vaultRepo;
|
|
774
|
+
if (isUsableString(client.vaultRef))
|
|
775
|
+
out.vaultRef = client.vaultRef;
|
|
776
|
+
if (isUsableString(client.vaultHost))
|
|
777
|
+
out.vaultHost = client.vaultHost;
|
|
778
|
+
return out;
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* Whether `/pp-init` asks for a client vault: whenever the project has none
|
|
782
|
+
* bound yet and a binding can be composed at all. Without a `platform.repo`
|
|
783
|
+
* from the org profile or an existing binding nothing is written (INIT-02),
|
|
784
|
+
* so an answer would be dropped silently. `defaultRepo` is the org
|
|
785
|
+
* profile's seeded vault a blank answer keeps, display-safe
|
|
786
|
+
* (`redactCoordinateForEmission`), or `null` when nothing is seeded and a
|
|
787
|
+
* blank answer binds no client vault. Never throws.
|
|
788
|
+
*/
|
|
789
|
+
function clientVaultChoice(cwd, opts = {}) {
|
|
790
|
+
try {
|
|
791
|
+
const seed = readSeed(cwd, opts).values;
|
|
792
|
+
const seedRepo = seed.clientVaultRepo;
|
|
793
|
+
const existing = readExistingBindings(cwd);
|
|
794
|
+
const existingPlatform = existing.data.platform;
|
|
795
|
+
const composable = isUsableString(seed.platformRepo) || (isObject(existingPlatform) && isUsableString(existingPlatform.repo));
|
|
796
|
+
const offered = composable && !existing.malformed && existingVaultCoordinates(existing.data).vaultRepo === undefined;
|
|
797
|
+
return { offered, defaultRepo: offered && isUsableString(seedRepo) ? String((0, platform_bindings_cjs_1.redactCoordinateForEmission)(seedRepo)) : null };
|
|
798
|
+
}
|
|
799
|
+
catch {
|
|
800
|
+
return { offered: false, defaultRepo: null };
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
// ─── validateClientVaultAnswer ────────────────────────────────────────────────
|
|
804
|
+
/**
|
|
805
|
+
* Validate the `/pp-init` client-vault answer. Blank or `none` means "keep
|
|
806
|
+
* the org profile's seeded vault, if any" (`value: null`). Anything else must pass the
|
|
807
|
+
* seed scalar guard, `validateBindings`' own `client.vaultRepo` rule without
|
|
808
|
+
* its machine-specific warning, and carry nothing the emission redactor would
|
|
809
|
+
* hide, so this adds no second pattern for the field.
|
|
810
|
+
*/
|
|
811
|
+
function validateClientVaultAnswer(answer) {
|
|
812
|
+
if (typeof answer !== 'string' || answer.trim() === '' || answer.trim().toLowerCase() === 'none') {
|
|
813
|
+
return { ok: true, value: null };
|
|
814
|
+
}
|
|
815
|
+
const scalar = dispositionSeedValue(`${org_profile_cjs_1.ORG_PROFILE_SEED_SECTION}.client.vaultRef`, answer);
|
|
816
|
+
if (!scalar.ok)
|
|
817
|
+
return { ok: false, reason: scalar.reason };
|
|
818
|
+
const probe = (0, platform_bindings_cjs_1.validateBindings)({
|
|
819
|
+
schemaVersion: platform_bindings_cjs_1.CONTRACT_SCHEMA_VERSION,
|
|
820
|
+
platform: { repo: 'placeholder/placeholder' },
|
|
821
|
+
client: { vaultRepo: scalar.value },
|
|
822
|
+
});
|
|
823
|
+
// The binding is committed: a machine-specific path resolves nowhere else, and a credential would be published.
|
|
824
|
+
const vaultIssues = [...probe.errors, ...probe.warnings].filter((e) => leadingToken(e).startsWith('client.vaultRepo'));
|
|
825
|
+
if (vaultIssues.length > 0)
|
|
826
|
+
return { ok: false, reason: vaultIssues.join('; ') };
|
|
827
|
+
if ((0, platform_bindings_cjs_1.redactCoordinateForEmission)(scalar.value) !== scalar.value) {
|
|
828
|
+
return { ok: false, reason: 'a vault repo must not carry credentials — the binding is committed; use a credential helper' };
|
|
829
|
+
}
|
|
830
|
+
return { ok: true, value: scalar.value };
|
|
831
|
+
}
|
|
749
832
|
/**
|
|
750
833
|
* D-08's absent-only merge. `merged` starts as a deep clone of `existing` —
|
|
751
834
|
* every field the existing file already carries (including the provisional
|
|
@@ -816,9 +899,9 @@ function composeBindings(cwd, opts = {}) {
|
|
|
816
899
|
pushDeduped(warnings, providerWarnings);
|
|
817
900
|
const seedResult = readSeed(cwd, { _globalRoot: opts._globalRoot });
|
|
818
901
|
pushDeduped(warnings, seedResult.warnings);
|
|
819
|
-
const candidateResult = composeCandidate(cwd, seedResult.values, provider, opts);
|
|
820
|
-
pushDeduped(warnings, candidateResult.warnings);
|
|
821
902
|
const existing = readExistingBindings(cwd);
|
|
903
|
+
const candidateResult = composeCandidate(cwd, seedResult.values, provider, opts, existingVaultCoordinates(existing.data));
|
|
904
|
+
pushDeduped(warnings, candidateResult.warnings);
|
|
822
905
|
pushDeduped(warnings, existing.warnings);
|
|
823
906
|
if (existing.malformed) {
|
|
824
907
|
return {
|
|
@@ -527,7 +527,6 @@ function isStringMember(list, value) {
|
|
|
527
527
|
*/
|
|
528
528
|
const BOOLEAN_ONLY_CONFIG_KEYS = Object.freeze([
|
|
529
529
|
'vault.enabled',
|
|
530
|
-
'vault.practices_enabled',
|
|
531
530
|
'vault.required',
|
|
532
531
|
'workflow.schema_drift_gate',
|
|
533
532
|
'workflow.nyquist_validation',
|
|
@@ -641,8 +640,8 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
|
|
|
641
640
|
error(`Invalid plan_review.source_grounding_authority '${val}'. Valid values: ${VALID_SOURCE_GROUNDING_AUTHORITIES.join(', ')}`);
|
|
642
641
|
}
|
|
643
642
|
// Capability enum keys, in ONE place (Vault Assurance 13-05, D-07). Measured
|
|
644
|
-
// 2026-10-09: `config-set workflow.code_review_depth bogus`
|
|
645
|
-
//
|
|
643
|
+
// 2026-10-09: `config-set workflow.code_review_depth bogus` wrote at exit 0,
|
|
644
|
+
// because no guard
|
|
646
645
|
// consulted the registry's declared `values`. This runs AFTER every
|
|
647
646
|
// hand-coded guard above, so a key that already has its own guard
|
|
648
647
|
// (`workflow.drift_action`) keeps its message byte-identical; the
|
|
@@ -653,9 +652,7 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
|
|
|
653
652
|
// The TYPE is checked before membership (13-REVIEW CR-01): a value starting
|
|
654
653
|
// with `[` is JSON-parsed above, and `String(['standards']) === 'standards'`,
|
|
655
654
|
// so a membership-only test let a one-element array through and wrote it —
|
|
656
|
-
// which `
|
|
657
|
-
// `federated-config` silently reads back as the default for the other enum
|
|
658
|
-
// keys. Every declared capability enum value is a string, so a non-string
|
|
655
|
+
// which `federated-config` silently reads back as the default. Every declared capability enum value is a string, so a non-string
|
|
659
656
|
// parse (array, object, number, boolean) can never be a member.
|
|
660
657
|
const enumValues = capabilityEnumValues(kp);
|
|
661
658
|
if (enumValues && !isStringMember(enumValues, parsedValue)) {
|
|
@@ -12,11 +12,12 @@
|
|
|
12
12
|
* relation suffix (`refines` / `absorbed into`) whose target requirement ID
|
|
13
13
|
* must exist in the same table. Grammar single-source: same reference file.
|
|
14
14
|
*
|
|
15
|
-
* Phase 3 (D-44), extended by Phase 4 (D-50): a Prefix Registry
|
|
16
|
-
* claim one of the
|
|
17
|
-
* — they all match the requirement-ID regex, so
|
|
18
|
-
* the decision / action / sparring /
|
|
19
|
-
*
|
|
15
|
+
* Phase 3 (D-44), extended by Phase 4 (D-50) and issue #12: a Prefix Registry
|
|
16
|
+
* row may not claim one of the seven reserved prefixes (`D-` `A-` `SP-` `DG-`
|
|
17
|
+
* `RS-` `SCR-` `PRC-`) — they all match the requirement-ID regex, so
|
|
18
|
+
* registering one collides with the decision / action / sparring /
|
|
19
|
+
* data-gathering / research / screen / process ID families. Reserved set
|
|
20
|
+
* single-source: same reference file.
|
|
20
21
|
*
|
|
21
22
|
* Fail-loud contract (D-08): any unparseable row, malformed/duplicate ID,
|
|
22
23
|
* unknown prefix, out-of-enum MoSCoW/Disposition, or strike/disposition
|
|
@@ -46,7 +47,9 @@ const MOSCOW = Object.freeze({ MUST: 'Must', SHOULD: 'Should', COULD: 'Could', W
|
|
|
46
47
|
// `SCR-<nn>` sequence, so an engagement registering `SCR-` as a domain prefix
|
|
47
48
|
// would make `SCR-01` ambiguous between the screen inventory and the
|
|
48
49
|
// requirements table.
|
|
49
|
-
|
|
50
|
+
// Issue #12 added `PRC-`: Session 2 numbers its Process Inventory `PRC-<nn>`,
|
|
51
|
+
// which a later session cites beside requirement IDs.
|
|
52
|
+
const RESERVED_PREFIXES = new Set(['D-', 'A-', 'SP-', 'DG-', 'RS-', 'SCR-', 'PRC-']);
|
|
50
53
|
const ROW_ID_RE = /^([A-Z][A-Z0-9]*-)(\d+)$/;
|
|
51
54
|
const DROPPED_RE = /^Dropped \((D-\d{2}-\d+)\)$/;
|
|
52
55
|
const SEPARATOR_ROW_RE = /^\s*\|[\s:|-]+\|\s*$/;
|
|
@@ -1287,6 +1287,23 @@ const SCAN_SKIP_DIRS = Object.freeze(new Set([
|
|
|
1287
1287
|
'.next', '.nuxt', '.turbo', '.cache', 'coverage',
|
|
1288
1288
|
'.venv', 'venv', '__pycache__', 'vendor', 'target',
|
|
1289
1289
|
]));
|
|
1290
|
+
/**
|
|
1291
|
+
* True when `dir` holds a pp-core install (`<dir>/pp-core/VERSION`, the marker
|
|
1292
|
+
* `update-context` reads): a project-local runtime directory such as
|
|
1293
|
+
* `.claude` or `.codex`. The walk never enters one. Its engine files, agents
|
|
1294
|
+
* and commands spell the instrumentation literals out, and none of it ships
|
|
1295
|
+
* in the engagement's build, so walking it reports pp-core itself as a leak
|
|
1296
|
+
* (issue #12, item 7). Keyed on the marker rather than on directory names so
|
|
1297
|
+
* every runtime's install directory is covered without a third list of them.
|
|
1298
|
+
*/
|
|
1299
|
+
function isPpCoreInstallDir(dir) {
|
|
1300
|
+
try {
|
|
1301
|
+
return node_fs_1.default.statSync(node_path_1.default.join(dir, 'pp-core', 'VERSION')).isFile();
|
|
1302
|
+
}
|
|
1303
|
+
catch {
|
|
1304
|
+
return false;
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1290
1307
|
/**
|
|
1291
1308
|
* The file extensions the scan actually READS. An allowlist rather than a
|
|
1292
1309
|
* denylist, because the bound has to hold for file types nobody has thought of
|
|
@@ -1475,7 +1492,8 @@ function loadScanAllowlist(loaded) {
|
|
|
1475
1492
|
* - every entry is resolved and confirmed strictly beneath the resolved
|
|
1476
1493
|
* project root before any stat or read, the same posture the roll-up
|
|
1477
1494
|
* traversal guard takes (T-04-16);
|
|
1478
|
-
* - `SCAN_SKIP_DIRS` keeps the walk out of the dependency tree
|
|
1495
|
+
* - `SCAN_SKIP_DIRS` keeps the walk out of the dependency tree, and
|
|
1496
|
+
* `isPpCoreInstallDir` out of a project-local pp-core install;
|
|
1479
1497
|
* - `SCAN_EXTENSIONS` bounds what is read;
|
|
1480
1498
|
* - the file and byte caps stop the walk.
|
|
1481
1499
|
*
|
|
@@ -1536,7 +1554,7 @@ function scanBuildTree(cwd, options = {}) {
|
|
|
1536
1554
|
if (stat.isSymbolicLink())
|
|
1537
1555
|
continue;
|
|
1538
1556
|
if (stat.isDirectory()) {
|
|
1539
|
-
if (!SCAN_SKIP_DIRS.has(name))
|
|
1557
|
+
if (!SCAN_SKIP_DIRS.has(name) && !isPpCoreInstallDir(full))
|
|
1540
1558
|
stack.push(full);
|
|
1541
1559
|
continue;
|
|
1542
1560
|
}
|
|
@@ -63,12 +63,21 @@ function parseAttrs(src) {
|
|
|
63
63
|
}
|
|
64
64
|
return attrs;
|
|
65
65
|
}
|
|
66
|
-
|
|
66
|
+
/**
|
|
67
|
+
* A multi-word PascalCase name (`UiButton`, `UiSelect`): the component spelling
|
|
68
|
+
* of a kebab-case custom element (`ui-button`), which a string template keeps
|
|
69
|
+
* and lowercasing would hide (issue #12, item 8). A single word (`Div`) or an
|
|
70
|
+
* all-caps tag (`BUTTON`) is plain HTML.
|
|
71
|
+
*/
|
|
72
|
+
function isPascalComponent(rawTag) {
|
|
73
|
+
return /^[A-Z][a-z0-9]+[A-Z]/.test(rawTag);
|
|
74
|
+
}
|
|
75
|
+
function isFunctional(tag, attrs, rawTag = tag) {
|
|
67
76
|
if (attrs.has('data-req'))
|
|
68
77
|
return true;
|
|
69
78
|
if (tag === 'input' && (attrs.get('type') || '').toLowerCase() === 'hidden')
|
|
70
79
|
return false;
|
|
71
|
-
if (FUNCTIONAL_TAGS.has(tag) || tag.includes('-'))
|
|
80
|
+
if (FUNCTIONAL_TAGS.has(tag) || tag.includes('-') || isPascalComponent(rawTag))
|
|
72
81
|
return true;
|
|
73
82
|
const role = (attrs.get('role') || '').toLowerCase();
|
|
74
83
|
return FUNCTIONAL_ROLES.has(role);
|
|
@@ -107,7 +116,7 @@ function scanPage(page, html, violations) {
|
|
|
107
116
|
const parentScreen = stack.length ? stack[stack.length - 1].screen : null;
|
|
108
117
|
const screen = screenFrom(tag, attrs) ?? parentScreen;
|
|
109
118
|
const line = lineAt(text, m.index);
|
|
110
|
-
if (isFunctional(tag, attrs)) {
|
|
119
|
+
if (isFunctional(tag, attrs, m[2])) {
|
|
111
120
|
const id = attrs.get('id') ?? null;
|
|
112
121
|
let reqs = [];
|
|
113
122
|
if (attrs.has('data-req')) {
|
|
@@ -12,7 +12,11 @@
|
|
|
12
12
|
* - every entry is one well-formed line (`- `<path>` — <why>`, or
|
|
13
13
|
* `- (none) — <why>` when nothing was selected);
|
|
14
14
|
* - every selected document was USED: cited `[vault: <path>]` somewhere in
|
|
15
|
-
* the session notes, or marked `— not used: <reason>` on its own line
|
|
15
|
+
* the session notes, or marked `— not used: <reason>` on its own line;
|
|
16
|
+
* - when the selection used `INDEX.md`, every entry the vault marks
|
|
17
|
+
* `[cross-cutting]` has a record line (issue #12, items 16-17): those rules
|
|
18
|
+
* bind every session, so leaving one out is how an approval route went
|
|
19
|
+
* unseen until Session 12.
|
|
16
20
|
*
|
|
17
21
|
* The selection itself is the facilitator's judgement and is not re-derived
|
|
18
22
|
* here; what is checked is that the record exists, is complete, and that what
|
|
@@ -109,7 +113,7 @@ function parseEntry(n, text) {
|
|
|
109
113
|
* A citation counts only OUTSIDE the section: the record naming a path is not
|
|
110
114
|
* evidence that the path was used.
|
|
111
115
|
*/
|
|
112
|
-
function checkVaultContext(content) {
|
|
116
|
+
function checkVaultContext(content, crossCutting = []) {
|
|
113
117
|
const violations = [];
|
|
114
118
|
const section = extractSection(content);
|
|
115
119
|
if (section === null) {
|
|
@@ -163,8 +167,38 @@ function checkVaultContext(content) {
|
|
|
163
167
|
violations.push({ reason: 'document_unused', line: doc.line, path: doc.path });
|
|
164
168
|
}
|
|
165
169
|
}
|
|
170
|
+
// The cross-cutting entries are owed a line only when the selection read the
|
|
171
|
+
// vault's own index: SUMMARY.md carries no mark, and `none` read nothing.
|
|
172
|
+
if (index === 'INDEX.md') {
|
|
173
|
+
const recorded = new Set(docs.map((d) => d.path));
|
|
174
|
+
for (const p of crossCutting) {
|
|
175
|
+
if (!recorded.has(p))
|
|
176
|
+
violations.push({ reason: 'cross_cutting_missing', path: p });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
166
179
|
return { ok: violations.length === 0, index, documents: docs.length, cited, not_used: notUsed, violations };
|
|
167
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* The paths the cached `INDEX.md` marks `[cross-cutting]`, read through the
|
|
183
|
+
* vault's one index-state function so the bounded read, the withheld filter
|
|
184
|
+
* and the path rule are the engine's own. Withheld entries never reach it;
|
|
185
|
+
* unsafe ones are dropped, since no record line could name them. A vault that
|
|
186
|
+
* is off, uncached or unreadable owes nothing here — `vault status` reports
|
|
187
|
+
* that state.
|
|
188
|
+
*/
|
|
189
|
+
function crossCuttingPaths(cwd) {
|
|
190
|
+
try {
|
|
191
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
192
|
+
const vault = require('./vault.cjs');
|
|
193
|
+
const state = vault.computeVaultIndexState(cwd);
|
|
194
|
+
if (state === null)
|
|
195
|
+
return [];
|
|
196
|
+
return state.entries.filter((e) => e.crossCutting === true && e.resolution !== 'unsafe').map((e) => e.path);
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
return [];
|
|
200
|
+
}
|
|
201
|
+
}
|
|
168
202
|
/** Parse the session argument: a bare integer 1–13, leading zeros allowed (`03` is session 3). */
|
|
169
203
|
function parseSessionArg(raw) {
|
|
170
204
|
if (typeof raw !== 'string' || !/^\d{1,3}$/.test(raw))
|
|
@@ -189,7 +223,7 @@ function checkSessionVaultContext(cwd, n) {
|
|
|
189
223
|
violations: [{ reason: 'session_file_missing' }],
|
|
190
224
|
};
|
|
191
225
|
}
|
|
192
|
-
return { session: n, file: rel, ...checkVaultContext(content) };
|
|
226
|
+
return { session: n, file: rel, ...checkVaultContext(content, crossCuttingPaths(cwd)) };
|
|
193
227
|
}
|
|
194
228
|
function formatViolation(v) {
|
|
195
229
|
const where = v.line !== undefined ? `line ${v.line}: ` : '';
|
|
@@ -118,12 +118,11 @@ function isPlausibleBranchRef(value) {
|
|
|
118
118
|
}
|
|
119
119
|
/**
|
|
120
120
|
* WHAT A CALLER SAYS WHEN THE OVERRIDE IS REFUSED. One sentence, exported, for
|
|
121
|
-
* every channel —
|
|
121
|
+
* every channel — a caller states it and never spells it.
|
|
122
122
|
*
|
|
123
123
|
* A second spelling of this sentence would be a second definition of what the
|
|
124
|
-
* mechanism did,
|
|
125
|
-
*
|
|
126
|
-
* `check ship.practices` message all carry these exact bytes.
|
|
124
|
+
* mechanism did, so every channel that states the refusal carries these exact
|
|
125
|
+
* bytes.
|
|
127
126
|
*
|
|
128
127
|
* The reason carries NO copy of the refused value: the value is client-supplied
|
|
129
128
|
* and one of its shapes is an absolute filesystem path, so echoing it back onto
|
|
@@ -296,15 +295,13 @@ function resolveBaseBranchResult(cwd, deps) {
|
|
|
296
295
|
* Consults the full precedence ladder and always returns a non-empty string.
|
|
297
296
|
* Never throws.
|
|
298
297
|
*
|
|
299
|
-
* The bare-string form.
|
|
300
|
-
* `resolveBaseBranchResult` instead and states the refusal (W16):
|
|
301
|
-
*
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
*
|
|
305
|
-
*
|
|
306
|
-
* sentence. A new caller reaching for this form should ask whether that is true
|
|
307
|
-
* of it as well; if it has a channel, it owes the operator the reason.
|
|
298
|
+
* The bare-string form. A consumer that computes a range takes
|
|
299
|
+
* `resolveBaseBranchResult` instead and states the refusal (W16): a gate that
|
|
300
|
+
* refused in silence would run over a range the project never configured and
|
|
301
|
+
* report success on it. What remains here is `cmdGitBaseBranch`, whose entire
|
|
302
|
+
* contract is one branch name on stdout and which therefore genuinely has
|
|
303
|
+
* nowhere to put a sentence. A new caller reaching for this form should ask
|
|
304
|
+
* whether that is true of it as well; if it has a channel, it owes the operator the reason.
|
|
308
305
|
*/
|
|
309
306
|
function resolveBaseBranch(cwd, deps) {
|
|
310
307
|
return resolveBaseBranchResult(cwd, deps).branch;
|
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
* provider, what environment a clone needs, and whether a host CLI (`glab`)
|
|
15
15
|
* may be spawned at all. It imports node builtins plus `./org-profile.cjs` —
|
|
16
16
|
* still never `./vault.cjs`, `./vault-coordinates.cjs`, `./io.cjs` or
|
|
17
|
-
* `./shell-command-projection.cjs` — so that
|
|
18
|
-
*
|
|
19
|
-
*
|
|
17
|
+
* `./shell-command-projection.cjs` — so that `src/vault.cts`, the vault write
|
|
18
|
+
* path and the other vault modules can import it without forming a require
|
|
19
|
+
* cycle in the CommonJS graph `require()` loads.
|
|
20
20
|
* `./org-profile.cjs` is the one exception: it is itself a zero-import leaf
|
|
21
21
|
* (node builtins only, see its own header), so importing it here creates no
|
|
22
22
|
* load-time cycle even though it lazily requires this module back at
|
|
@@ -176,9 +176,8 @@ const compiledLiteralCloneTargetPatterns = exports.LITERAL_CLONE_TARGET_PATTERNS
|
|
|
176
176
|
* absolute path, a scheme-qualified URL, or scp-style `user@host:path` —
|
|
177
177
|
* rather than a `group/project` slug that must be composed onto a host.
|
|
178
178
|
*
|
|
179
|
-
* This is now the SINGLE definition. `src/vault.cts`
|
|
180
|
-
*
|
|
181
|
-
* each keeping a copy — a read/write divergence in this predicate was defect
|
|
179
|
+
* This is now the SINGLE definition. `src/vault.cts` imports it from here
|
|
180
|
+
* (30-02) rather than keeping a copy — a read/write divergence in this predicate was defect
|
|
182
181
|
* P-1 in Phase 29 (the write path used the value verbatim while the read path
|
|
183
182
|
* always composed a URL, so a slug broke writing and a path broke reading).
|
|
184
183
|
*
|