@pareto4/pp-core 1.5.4 → 1.6.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.
Files changed (62) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/agents/pp-code-fixer.md +3 -3
  3. package/bin/install.js +0 -6
  4. package/commands/pp-init.md +36 -0
  5. package/commands/pp-ns-project.md +2 -1
  6. package/gemini-extension.json +1 -1
  7. package/package.json +1 -1
  8. package/pp-core/bin/lib/agent-install-check.cjs +1 -1
  9. package/pp-core/bin/lib/audit-command-router.cjs +3 -3
  10. package/pp-core/bin/lib/audit.cjs +1 -1
  11. package/pp-core/bin/lib/capability-activation.cjs +1 -1
  12. package/pp-core/bin/lib/capability-registry.cjs +138 -5
  13. package/pp-core/bin/lib/capability-state.cjs +1 -1
  14. package/pp-core/bin/lib/capability-writer.cjs +1 -1
  15. package/pp-core/bin/lib/check-command-router.cjs +190 -8
  16. package/pp-core/bin/lib/cjs-command-router-adapter.cjs +1 -1
  17. package/pp-core/bin/lib/clusters.cjs +1 -0
  18. package/pp-core/bin/lib/code-review-flags.cjs +1 -1
  19. package/pp-core/bin/lib/command-aliases.cjs +7 -0
  20. package/pp-core/bin/lib/config-loader.cjs +1 -1
  21. package/pp-core/bin/lib/config.cjs +1 -1
  22. package/pp-core/bin/lib/context-utilization.cjs +1 -1
  23. package/pp-core/bin/lib/docs.cjs +1 -1
  24. package/pp-core/bin/lib/frontmatter.cjs +1 -1
  25. package/pp-core/bin/lib/git-base-branch.cjs +2 -2
  26. package/pp-core/bin/lib/graphify-command-router.cjs +4 -4
  27. package/pp-core/bin/lib/graphify.cjs +1 -1
  28. package/pp-core/bin/lib/gsd2-import.cjs +1 -1
  29. package/pp-core/bin/lib/init-command-router.cjs +1 -1
  30. package/pp-core/bin/lib/init.cjs +1 -1
  31. package/pp-core/bin/lib/intel-command-router.cjs +5 -5
  32. package/pp-core/bin/lib/intel.cjs +1 -1
  33. package/pp-core/bin/lib/io.cjs +4 -2
  34. package/pp-core/bin/lib/loop-resolver.cjs +1 -1
  35. package/pp-core/bin/lib/phase-command-router.cjs +1 -1
  36. package/pp-core/bin/lib/phases-command-router.cjs +1 -1
  37. package/pp-core/bin/lib/plan-drift-guard.cjs +1 -1
  38. package/pp-core/bin/lib/platform-bindings.cjs +336 -0
  39. package/pp-core/bin/lib/profile-output.cjs +216 -1
  40. package/pp-core/bin/lib/prohibition-enforcement.cjs +2 -2
  41. package/pp-core/bin/lib/roadmap-command-router.cjs +2 -2
  42. package/pp-core/bin/lib/roadmap-parser.cjs +1 -1
  43. package/pp-core/bin/lib/state-command-router.cjs +1 -1
  44. package/pp-core/bin/lib/state.cjs +2 -2
  45. package/pp-core/bin/lib/validate-command-router.cjs +1 -1
  46. package/pp-core/bin/lib/verify-command-router.cjs +1 -1
  47. package/pp-core/bin/lib/verify.cjs +1 -1
  48. package/pp-core/bin/lib/worktree-base-ref.cjs +2 -2
  49. package/pp-core/bin/lib/worktree-safety.cjs +1 -1
  50. package/pp-core/bin/pp-tools.cjs +61 -24
  51. package/pp-core/workflows/discovery-sprint.md +27 -10
  52. package/pp-core/workflows/execute-phase/steps/client-context-gate.md +37 -0
  53. package/pp-core/workflows/execute-phase.md +4 -0
  54. package/pp-core/workflows/init.md +530 -0
  55. package/pp-core/workflows/new-project.md +54 -1
  56. package/pp-core/workflows/ship.md +24 -1
  57. package/pp-core/workflows/verify-work.md +1 -0
  58. package/scripts/lint-allow-test-rule-refs.allowlist.json +1 -0
  59. package/assets/gsd-logo-2000-transparent.png +0 -0
  60. package/assets/gsd-logo-2000-transparent.svg +0 -17
  61. package/assets/gsd-logo-2000.png +0 -0
  62. package/assets/gsd-logo-2000.svg +0 -21
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  /**
3
3
  * Manifest-backed verify subcommand router.
4
- * Keeps gsd-tools.cjs thin while preserving existing command semantics.
4
+ * Keeps pp-tools.cjs thin while preserving existing command semantics.
5
5
  *
6
6
  * ADR-457 build-at-publish: the hand-written bin/lib/verify-command-router.cjs
7
7
  * collapsed to a TypeScript source of truth. Behaviour is preserved byte-for-behaviour
@@ -1432,7 +1432,7 @@ function cmdValidateHealth(cwd, options, raw) {
1432
1432
  getMilestoneFromPhaseId: getMilestoneFromPhaseId,
1433
1433
  });
1434
1434
  for (const mm of mismatches) {
1435
- addIssue('warning', 'W021', `Phase ${mm.phaseId}: integer prefix implies ${mm.expectedMilestone} but listed under ${mm.foundInMilestone}`, 'Run `gsd-tools roadmap upgrade --convention milestone-prefixed` to migrate (dry-run by default)');
1435
+ addIssue('warning', 'W021', `Phase ${mm.phaseId}: integer prefix implies ${mm.expectedMilestone} but listed under ${mm.foundInMilestone}`, 'Run `pp-tools roadmap upgrade --convention milestone-prefixed` to migrate (dry-run by default)');
1436
1436
  }
1437
1437
  }
1438
1438
  }
@@ -94,9 +94,9 @@ function parseJsonc(text) {
94
94
  }
95
95
  // ─── Message constants (verbatim — downstream docs/tests depend on these) ─────
96
96
  function buildMsgDiverged(headSha, forkRef, forkSha) {
97
- return `⚠ Worktree base mismatch: HEAD (${shortSha(headSha)}) differs from ${forkRef} (${shortSha(forkSha)}). Running this phase sequentially on the main working tree. To keep parallel worktrees, set worktree.baseRef:"head" in .claude/settings.local.json (or run: gsd-tools worktree set-baseref). See #683.`;
97
+ return `⚠ Worktree base mismatch: HEAD (${shortSha(headSha)}) differs from ${forkRef} (${shortSha(forkSha)}). Running this phase sequentially on the main working tree. To keep parallel worktrees, set worktree.baseRef:"head" in .claude/settings.local.json (or run: pp-tools worktree set-baseref). See #683.`;
98
98
  }
99
- const MSG_UNKNOWN = `⚠ Cannot determine the worktree fork base (origin/HEAD unresolved). Running this phase sequentially on the main working tree to avoid a base mismatch. To keep parallel worktrees, set worktree.baseRef:"head" in .claude/settings.local.json (or run: gsd-tools worktree set-baseref). See #683.`;
99
+ const MSG_UNKNOWN = `⚠ Cannot determine the worktree fork base (origin/HEAD unresolved). Running this phase sequentially on the main working tree to avoid a base mismatch. To keep parallel worktrees, set worktree.baseRef:"head" in .claude/settings.local.json (or run: pp-tools worktree set-baseref). See #683.`;
100
100
  // ─── Exports ──────────────────────────────────────────────────────────────────
101
101
  /**
102
102
  * Returns the first 8 characters of a SHA, or '' if null/empty.
@@ -958,7 +958,7 @@ function pruneOrphanedWorktrees(repoRoot) {
958
958
  const plan = planWorktreePrune(repoRoot, { allowDestructive: false }, { parseWorktreePorcelain });
959
959
  const pruneResult = executeWorktreePrunePlan(plan);
960
960
  if (pruneResult && pruneResult.timedOut) {
961
- process.stderr.write('[gsd-tools] WARNING: worktree health check degraded' +
961
+ process.stderr.write('[pp-tools] WARNING: worktree health check degraded' +
962
962
  ' — git worktree prune timed out after 10s.' +
963
963
  ' Orphaned worktree metadata may remain until the next successful run.\n');
964
964
  }
@@ -6,7 +6,7 @@
6
6
  * Replaces repetitive inline bash patterns across ~50 GSD command/workflow/agent files.
7
7
  * Centralizes: config parsing, model resolution, phase lookup, git commits, summary verification.
8
8
  *
9
- * Usage: node gsd-tools.cjs <command> [args] [--raw] [--pick <field>]
9
+ * Usage: node pp-tools.cjs <command> [args] [--raw] [--pick <field>]
10
10
  *
11
11
  * Atomic Commands:
12
12
  * state load Load project config + state
@@ -209,6 +209,7 @@ const phase = require('./lib/phase.cjs');
209
209
  const roadmap = require('./lib/roadmap.cjs');
210
210
  const verify = require('./lib/verify.cjs');
211
211
  const config = require('./lib/config.cjs');
212
+ const platformBindings = require('./lib/platform-bindings.cjs');
212
213
  const template = require('./lib/template.cjs');
213
214
  const milestone = require('./lib/milestone.cjs');
214
215
  const commands = require('./lib/commands.cjs');
@@ -236,6 +237,7 @@ const { routeTaskCommand } = require('./lib/task-command-router.cjs');
236
237
  const { parseNamedArgs, parseMultiwordArg } = require('./lib/command-arg-projection.cjs');
237
238
  const { cmdGitBaseBranch } = require('./lib/git-base-branch.cjs');
238
239
  const { getEffectiveAuthority, classifyDriftSeverity } = require('./lib/plan-drift-guard.cjs');
240
+ const profileOutput = require('./lib/profile-output.cjs');
239
241
 
240
242
  // ─── Bridge collapsed (Phase 4) ────────────────────────────────────────────────
241
243
  // Non-family commands now run through their CJS handlers directly. Keep the
@@ -251,7 +253,7 @@ const { getEffectiveAuthority, classifyDriftSeverity } = require('./lib/plan-dri
251
253
  * @param {object} opts
252
254
  * @param {string} opts.registryCommand - legacy bridge placeholder
253
255
  * @param {string[]} opts.registryArgs - legacy bridge placeholder
254
- * @param {string} opts.legacyCommand - original gsd-tools command name
256
+ * @param {string} opts.legacyCommand - original pp-tools command name
255
257
  * @param {string[]} opts.legacyArgs - original args
256
258
  * @param {string} opts.cwd - project dir
257
259
  * @param {boolean} opts.raw - raw output mode
@@ -291,7 +293,7 @@ function _dispatchNonFamily({ registryCommand, registryArgs, legacyCommand, lega
291
293
  * before requiring — defense-in-depth against corrupted/hand-edited registry entries.
292
294
  *
293
295
  * @param {object} opts
294
- * @param {string} opts.command The command name (top-level gsd-tools command)
296
+ * @param {string} opts.command The command name (top-level pp-tools command)
295
297
  * @param {string[]} opts.args Remaining args passed to the router
296
298
  * @param {string} opts.cwd Project working directory
297
299
  * @param {boolean} opts.raw Raw output mode flag
@@ -485,7 +487,7 @@ async function main() {
485
487
  let command = args[0];
486
488
 
487
489
  // Accept `query` as a meta-prefix for canonical dotted/spaced commands.
488
- // Workflows may call `node gsd-tools.cjs query <command>` directly.
490
+ // Workflows may call `node pp-tools.cjs query <command>` directly.
489
491
  if (command === 'query') {
490
492
  args.shift();
491
493
  command = args[0];
@@ -510,21 +512,21 @@ async function main() {
510
512
  }
511
513
  }
512
514
 
513
- // Top-level usage string — emitted by `gsd-tools` (no args) and by
514
- // `gsd-tools --help` / any `--help` request below.
515
+ // Top-level usage string — emitted by `pp-tools` (no args) and by
516
+ // `pp-tools --help` / any `--help` request below.
515
517
  // CR feedback: the command list must enumerate every top-level command
516
518
  // supported by the dispatcher so `--help` is actually useful for
517
519
  // discovery; previously it was a partial subset that didn't include
518
520
  // phase / roadmap / milestone / progress / etc.
519
- const TOP_LEVEL_USAGE = 'Usage: gsd-tools <command> [args] [--raw] [--pick <field>] [--cwd <path>] [--ws <name>] [--json-errors]\n' +
521
+ const TOP_LEVEL_USAGE = 'Usage: pp-tools <command> [args] [--raw] [--pick <field>] [--cwd <path>] [--ws <name>] [--json-errors]\n' +
520
522
  'Commands: agent, agent-skills, audit-open, audit-uat, check, check-commit, commit, commit-to-subrepo, ' +
521
523
  'config-ensure-section, config-get, config-new-project, config-path, config-set, migrate-config, ' +
522
524
  'current-timestamp, detect-custom-files, docs-init, drift-guard, effort, extract-messages, find-phase, ' +
523
525
  'from-gsd2, frontmatter, gap-analysis, generate-claude-md, generate-claude-profile, ' +
524
- 'generate-dev-preferences, generate-slug, graphify, history-digest, init, intel, ' +
525
- 'capability, classify-confidence, git, learnings, list-todos, loop, milestone, package-legitimacy, phase, phase-plan-index, phases, profile-questionnaire, ' +
526
+ 'generate-client-context, generate-dev-preferences, generate-slug, graphify, history-digest, init, intel, ' +
527
+ 'capability, classify-confidence, git, learnings, list-todos, loop, milestone, package-legitimacy, phase, phase-plan-index, phases, platform-bindings, profile-questionnaire, ' +
526
528
  'profile-sample, progress, prompt-budget, requirements, research-plan, research-store, resolve-granularity, resolve-model, roadmap, scaffold, state, ' +
527
- 'task, template, user-story, validate, verify, verify-path-exists, verify-summary, workstream, worktree\n\n' +
529
+ 'task, template, user-story, validate, validate-bindings, verify, verify-path-exists, verify-summary, workstream, worktree\n\n' +
528
530
  'Global flags:\n' +
529
531
  ' --raw Emit raw output without post-processing\n' +
530
532
  ' --pick <field> Extract a single field from JSON output (dot/bracket notation)\n' +
@@ -532,7 +534,7 @@ async function main() {
532
534
  ' --ws <name> Override active workstream (or set GSD_WORKSTREAM)\n' +
533
535
  ' --json-errors Emit structured JSON error objects on stderr (or set GSD_JSON_ERRORS=1)\n\n' +
534
536
  'For command-specific argument requirements, invoke the command without args ' +
535
- '(e.g. `gsd-tools phase add`) — the resulting error lists what is required.';
537
+ '(e.g. `pp-tools phase add`) — the resulting error lists what is required.';
536
538
 
537
539
  if (!command) {
538
540
  error(TOP_LEVEL_USAGE);
@@ -556,7 +558,7 @@ async function main() {
556
558
  const NEVER_VALID_FLAGS = new Set(['--version', '-v']);
557
559
  for (const arg of args) {
558
560
  if (NEVER_VALID_FLAGS.has(arg)) {
559
- error(`Unknown flag: ${arg}\ngsd-tools does not accept version flags. Run "gsd-tools" with no arguments for usage.`, ERROR_REASON.USAGE);
561
+ error(`Unknown flag: ${arg}\npp-tools does not accept version flags. Run "pp-tools" with no arguments for usage.`, ERROR_REASON.USAGE);
560
562
  }
561
563
  }
562
564
 
@@ -697,6 +699,41 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
697
699
  break;
698
700
  }
699
701
 
702
+ case 'platform-bindings': {
703
+ platformBindings.cmdPlatformBindings(cwd, args.slice(1), raw);
704
+ break;
705
+ }
706
+
707
+ case 'validate-bindings': {
708
+ platformBindings.cmdValidateBindings(cwd, args.slice(1), raw);
709
+ break;
710
+ }
711
+
712
+ case 'generate-client-context': {
713
+ // generate-client-context --from-json <path> [--output <path>] [--force]
714
+ // generate-client-context --read [--output <path>]
715
+ const gcArgs = args.slice(1);
716
+ const gcOptions = {};
717
+ for (let i = 0; i < gcArgs.length; i++) {
718
+ const a = gcArgs[i];
719
+ if (a === '--from-json') {
720
+ const v = gcArgs[i + 1];
721
+ if (v !== undefined && !v.startsWith('--')) { gcOptions.fromJson = v; i++; }
722
+ else error('--from-json requires a <path> argument');
723
+ } else if (a === '--output') {
724
+ const v = gcArgs[i + 1];
725
+ if (v !== undefined && !v.startsWith('--')) { gcOptions.output = v; i++; }
726
+ else error('--output requires a <path> argument');
727
+ } else if (a === '--read') {
728
+ gcOptions.read = true;
729
+ } else if (a === '--force') {
730
+ gcOptions.force = true;
731
+ }
732
+ }
733
+ profileOutput.cmdGenerateClientContext(cwd, gcOptions, raw);
734
+ break;
735
+ }
736
+
700
737
  case 'resolve-granularity': {
701
738
  // Parse optional --granularity <val> flag (space form only); positional is phase-type.
702
739
  // The =form (--granularity=<val>) is intentionally not supported: parseNamedArgs and
@@ -1500,18 +1537,18 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1500
1537
  } else if (subcommand === 'query') {
1501
1538
  const tagIdx = args.indexOf('--tag');
1502
1539
  const tag = tagIdx !== -1 ? args[tagIdx + 1] : null;
1503
- if (!tag) error('Usage: gsd-tools learnings query --tag <tag>', ERROR_REASON.USAGE);
1540
+ if (!tag) error('Usage: pp-tools learnings query --tag <tag>', ERROR_REASON.USAGE);
1504
1541
  learnings.cmdLearningsQuery(tag, raw);
1505
1542
  } else if (subcommand === 'copy') {
1506
1543
  learnings.cmdLearningsCopy(cwd, raw);
1507
1544
  } else if (subcommand === 'prune') {
1508
1545
  const olderIdx = args.indexOf('--older-than');
1509
1546
  const olderThan = olderIdx !== -1 ? args[olderIdx + 1] : null;
1510
- if (!olderThan) error('Usage: gsd-tools learnings prune --older-than <duration>', ERROR_REASON.USAGE);
1547
+ if (!olderThan) error('Usage: pp-tools learnings prune --older-than <duration>', ERROR_REASON.USAGE);
1511
1548
  learnings.cmdLearningsPrune(olderThan, raw);
1512
1549
  } else if (subcommand === 'delete') {
1513
1550
  const id = args[2];
1514
- if (!id) error('Usage: gsd-tools learnings delete <id>', ERROR_REASON.USAGE);
1551
+ if (!id) error('Usage: pp-tools learnings delete <id>', ERROR_REASON.USAGE);
1515
1552
  learnings.cmdLearningsDelete(id, raw);
1516
1553
  } else {
1517
1554
  error('Unknown learnings subcommand. Available: list, query, copy, prune, delete', ERROR_REASON.SDK_UNKNOWN_COMMAND);
@@ -1550,7 +1587,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1550
1587
  const configDirIdx = args.indexOf('--config-dir');
1551
1588
  const configDir = configDirIdx !== -1 ? args[configDirIdx + 1] : null;
1552
1589
  if (!configDir) {
1553
- error('Usage: gsd-tools detect-custom-files --config-dir <path>', ERROR_REASON.USAGE);
1590
+ error('Usage: pp-tools detect-custom-files --config-dir <path>', ERROR_REASON.USAGE);
1554
1591
  }
1555
1592
  const resolvedConfigDir = path.resolve(configDir);
1556
1593
  if (!fs.existsSync(resolvedConfigDir)) {
@@ -1859,7 +1896,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1859
1896
  if (subcommand === 'get') {
1860
1897
  const key = args[2];
1861
1898
  if (!key || key.startsWith('--')) {
1862
- error('Usage: gsd-tools research-store get <key> [--kind <k>]', ERROR_REASON.USAGE);
1899
+ error('Usage: pp-tools research-store get <key> [--kind <k>]', ERROR_REASON.USAGE);
1863
1900
  }
1864
1901
  if (!researchStore.isValidResearchKey(key)) {
1865
1902
  error('research-store: <key> must be a 64-char sha256 hex (use research-plan to obtain keys)', ERROR_REASON.USAGE);
@@ -1870,7 +1907,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1870
1907
  } else if (subcommand === 'put') {
1871
1908
  const key = args[2];
1872
1909
  if (!key || key.startsWith('--')) {
1873
- error('Usage: gsd-tools research-store put <key> --content <str> --source <s> --provider <p> --confidence <c> --kind <k>', ERROR_REASON.USAGE);
1910
+ error('Usage: pp-tools research-store put <key> --content <str> --source <s> --provider <p> --confidence <c> --kind <k>', ERROR_REASON.USAGE);
1874
1911
  }
1875
1912
  if (!researchStore.isValidResearchKey(key)) {
1876
1913
  error('research-store: <key> must be a 64-char sha256 hex (use research-plan to obtain keys)', ERROR_REASON.USAGE);
@@ -1895,7 +1932,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1895
1932
  const confidence = getFlagValue(confidenceIdx, '--confidence');
1896
1933
  const kind = getFlagValue(kindIdx, '--kind');
1897
1934
  if (!content || !source || !provider || !confidence || !kind) {
1898
- error('Usage: gsd-tools research-store put <key> --content <str> --source <s> --provider <p> --confidence <c> --kind <k>', ERROR_REASON.USAGE);
1935
+ error('Usage: pp-tools research-store put <key> --content <str> --source <s> --provider <p> --confidence <c> --kind <k>', ERROR_REASON.USAGE);
1899
1936
  }
1900
1937
  const entry = researchStore.putResearch(cwd, key, { content, source, provider, confidence, kind }, { homeDir });
1901
1938
  output(entry, raw);
@@ -1916,7 +1953,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1916
1953
  const inputIdx = args.indexOf('--input');
1917
1954
  const inputPath = inputIdx !== -1 ? args[inputIdx + 1] : null;
1918
1955
  if (!inputPath || inputPath.startsWith('--')) {
1919
- error('Usage: gsd-tools research-plan --input <path>', ERROR_REASON.USAGE);
1956
+ error('Usage: pp-tools research-plan --input <path>', ERROR_REASON.USAGE);
1920
1957
  }
1921
1958
  let planInput;
1922
1959
  try {
@@ -1950,7 +1987,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1950
1987
  const providerIdx = args.indexOf('--provider');
1951
1988
  const provider = providerIdx !== -1 ? args[providerIdx + 1] : null;
1952
1989
  if (!provider || provider.startsWith('--')) {
1953
- error('Usage: gsd-tools query classify-confidence --provider <id> [--package <name> --ecosystem <npm|pypi|crates>] [--verified]', ERROR_REASON.USAGE);
1990
+ error('Usage: pp-tools query classify-confidence --provider <id> [--package <name> --ecosystem <npm|pypi|crates>] [--verified]', ERROR_REASON.USAGE);
1954
1991
  }
1955
1992
  const verified = args.includes('--verified');
1956
1993
  const pkgIdx = args.indexOf('--package');
@@ -1961,7 +1998,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1961
1998
  if (pkg && (!pkg.startsWith('--'))) {
1962
1999
  const VALID_ECOSYSTEMS = new Set(['npm', 'pypi', 'crates']);
1963
2000
  if (!ecosystem || ecosystem.startsWith('--') || !VALID_ECOSYSTEMS.has(ecosystem)) {
1964
- error('Usage: gsd-tools query classify-confidence --provider <id> [--package <name> --ecosystem <npm|pypi|crates>] [--verified]', ERROR_REASON.USAGE);
2001
+ error('Usage: pp-tools query classify-confidence --provider <id> [--package <name> --ecosystem <npm|pypi|crates>] [--verified]', ERROR_REASON.USAGE);
1965
2002
  }
1966
2003
  const pkgLegitimacy = require('./lib/package-legitimacy.cjs');
1967
2004
  const results = await pkgLegitimacy.checkPackages({ ecosystem, packages: [pkg] }, {});
@@ -1989,7 +2026,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1989
2026
  const ecosystem = ecoIdx !== -1 ? args[ecoIdx + 1] : null;
1990
2027
  const VALID_ECOSYSTEMS = new Set(['npm', 'pypi', 'crates']);
1991
2028
  if (!ecosystem || !VALID_ECOSYSTEMS.has(ecosystem)) {
1992
- error('Usage: gsd-tools package-legitimacy check --ecosystem <npm|pypi|crates> <pkg1> ...', ERROR_REASON.USAGE);
2029
+ error('Usage: pp-tools package-legitimacy check --ecosystem <npm|pypi|crates> <pkg1> ...', ERROR_REASON.USAGE);
1993
2030
  }
1994
2031
  // Collect positional package names.
1995
2032
  // Only --ecosystem takes a value. Every non-flag arg is a package name.
@@ -2004,7 +2041,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
2004
2041
  packages.push(a);
2005
2042
  }
2006
2043
  if (packages.length === 0) {
2007
- error('Usage: gsd-tools package-legitimacy check --ecosystem <eco> <pkg1> <pkg2> ...', ERROR_REASON.USAGE);
2044
+ error('Usage: pp-tools package-legitimacy check --ecosystem <eco> <pkg1> <pkg2> ...', ERROR_REASON.USAGE);
2008
2045
  }
2009
2046
  let pkgResults;
2010
2047
  try {
@@ -16,7 +16,19 @@ First parse `SESSION_ARG` (the optional session number 1–13) from `$ARGUMENTS`
16
16
 
17
17
  ```bash
18
18
  _PP_SHIM_NAME="pp-tools.cjs"; _PP_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; PP_TOOLS="${_PP_RUNTIME_ROOT}/pp-core/bin/${_PP_SHIM_NAME}"; if [ -f "$PP_TOOLS" ]; then pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${_PP_RUNTIME_ROOT}/.claude/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${_PP_RUNTIME_ROOT}/.claude/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${_PP_RUNTIME_ROOT}/.codex/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${_PP_RUNTIME_ROOT}/.codex/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif command -v pp-tools >/dev/null 2>&1; then PP_TOOLS="$(command -v pp-tools)"; pp_run() { "$PP_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="$HOME/.claude/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${HERMES_HOME:-$HOME/.hermes}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CODEX_HOME:-$HOME/.codex}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; else echo "ERROR: pp-tools.cjs not found at $PP_TOOLS and pp-tools is not on PATH. Run: npx -y @pareto4/pp-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${PP_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${PP_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
19
- SESSION_ARG=$(printf '%s\n' "${ARGUMENTS:-}" | grep -oE '(1[0-3]|[1-9])' | head -1)
19
+ # WR-03: validate the FULL numeric token. The old pattern (1[0-3]|[1-9]) let grep
20
+ # truncate out-of-range input to a spuriously valid session (14→1, 25→2, 99→9).
21
+ # Extract the whole digit run, then range-check 1–13; flag anything else as invalid.
22
+ SESSION_RAW=$(printf '%s' "${ARGUMENTS:-}" | grep -oE '[0-9]+' | head -1)
23
+ SESSION_ARG=""
24
+ SESSION_INVALID=""
25
+ if [ -n "$SESSION_RAW" ]; then
26
+ if [ "$SESSION_RAW" -ge 1 ] 2>/dev/null && [ "$SESSION_RAW" -le 13 ] 2>/dev/null; then
27
+ SESSION_ARG="$SESSION_RAW"
28
+ else
29
+ SESSION_INVALID="$SESSION_RAW"
30
+ fi
31
+ fi
20
32
  INIT=$(pp_run query init.phase-op "${SESSION_ARG:-}")
21
33
  if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
22
34
  ```
@@ -26,9 +38,10 @@ if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
26
38
  **Text mode:** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
27
39
 
28
40
  **Dispatch:**
29
- - If `SESSION_ARG` is empty → execute **Flow 1: Show Progress**
30
- - If `SESSION_ARG` is a number 1–13 → execute **Flow 2: Facilitate Session N**
31
- - Otherwise → show usage error
41
+ - If `SESSION_INVALID` is non-empty → show a usage error: "Session `$SESSION_INVALID` is out
42
+ of range — valid sessions are 1–13." Do not run any session or the progress view.
43
+ - Else if `SESSION_ARG` is empty (no numeric argument) → execute **Flow 1: Show Progress**
44
+ - Else (`SESSION_ARG` is a number 1–13) → execute **Flow 2: Facilitate Session N**
32
45
 
33
46
  ---
34
47
 
@@ -694,8 +707,8 @@ This session uses the **Session 3: Process-First Protocol** (Steps 5A–5G descr
694
707
  2. **Technology stack selection**
695
708
  - What is the preferred frontend technology (React, Angular, Vue, Mendix, etc.)?
696
709
  - What is the preferred backend technology (Node.js, .NET, Java, Python)?
697
- - What database technology is appropriate (relational, document, graph, time-series)?
698
- - What messaging or eventing technology will be used (Kafka, RabbitMQ, Azure Service Bus)?
710
+ - What database technology is appropriate (relational e.g. Cloud SQL via pareto-platform-gcp, document, graph, time-series)?
711
+ - What messaging or eventing technology will be used (Google Cloud Pub/Sub via pareto-platform-gcp, Kafka, RabbitMQ)?
699
712
  - What caching strategy applies (Redis, in-memory, CDN)?
700
713
 
701
714
  3. **Key architectural components**
@@ -717,6 +730,8 @@ This session uses the **Session 3: Process-First Protocol** (Steps 5A–5G descr
717
730
 
718
731
  **Notes Template:**
719
732
 
733
+ > **Platform standard (D-08):** The Pareto platform standard is GCP / `pareto-platform-gcp`. Fill this table against that baseline unless the client has an overriding constraint.
734
+
720
735
  **Technology Stack Decisions**
721
736
 
722
737
  | Layer | Technology | Version | Rationale | Alternatives Considered | Risk |
@@ -1129,7 +1144,7 @@ This session uses the **Session 3: Process-First Protocol** (Steps 5A–5G descr
1129
1144
  **What We Cover:**
1130
1145
 
1131
1146
  1. **Cloud / hosting strategy**
1132
- - What cloud provider will be used (AWS, Azure, GCP, on-premises, hybrid)?
1147
+ - What cloud provider will be used (GCP / `pareto-platform-gcp` is the Pareto platform standard; or AWS, Azure, on-premises, hybrid)?
1133
1148
  - What is the account and subscription structure?
1134
1149
  - What regions and availability zones are required?
1135
1150
  - Are there data sovereignty or residency requirements that constrain hosting?
@@ -1138,12 +1153,12 @@ This session uses the **Session 3: Process-First Protocol** (Steps 5A–5G descr
1138
1153
  2. **Deployment strategy**
1139
1154
  - What deployment model will be used (blue-green, canary, rolling, immutable)?
1140
1155
  - How will the CI/CD pipeline be structured?
1141
- - What tools will be used for CI/CD (GitHub Actions, Azure DevOps, GitLab CI, Jenkins)?
1156
+ - What tools will be used for CI/CD (GitLab CI is the Pareto platform pipeline; or GitHub Actions, Azure DevOps, Jenkins)?
1142
1157
  - How will infrastructure be managed (IaC with Terraform, Pulumi, CloudFormation)?
1143
- - What is the approach to containerization (Docker, Kubernetes, ECS, App Service)?
1158
+ - What is the approach to containerization (GKE or Cloud Run via pareto-platform-gcp; Docker/Kubernetes on-prem; or other cloud options)?
1144
1159
 
1145
1160
  3. **Monitoring, logging, and alerting**
1146
- - What monitoring platform will be used (Datadog, New Relic, Azure Monitor, CloudWatch)?
1161
+ - What monitoring platform will be used (Google Cloud Monitoring / Cloud Logging via pareto-platform-gcp; or Datadog, New Relic, CloudWatch)?
1147
1162
  - What metrics will be tracked (latency, error rate, throughput, saturation)?
1148
1163
  - What is the alerting strategy (thresholds, escalation chains, on-call rotation)?
1149
1164
  - How will application logs be aggregated and queried?
@@ -1162,6 +1177,8 @@ This session uses the **Session 3: Process-First Protocol** (Steps 5A–5G descr
1162
1177
 
1163
1178
  **Notes Template:**
1164
1179
 
1180
+ > **Platform standard (D-08):** The Pareto deployment platform is GCP / `pareto-platform-gcp` with GitLab CI as the CI/CD pipeline and Google Cloud Monitoring / Cloud Logging for observability. Fill the tables below against this baseline unless the client has an overriding constraint.
1181
+
1165
1182
  **Infrastructure Architecture**
1166
1183
 
1167
1184
  | Component | Service / Product | Region | Tier / SKU | Redundancy | Est. Monthly Cost | Notes |
@@ -0,0 +1,37 @@
1
+ # Client-context gate (Build) — execute-phase sub-step
2
+
3
+ Confirms the Build agent has client context in hand before any executor is dispatched.
4
+ Gated entirely on `.platform-bindings.json` (D-13 / D-04, mirroring Phase 9): when no
5
+ bindings file is present this gate is a **no-op** — no prompt fires and Build proceeds
6
+ unchanged. All checks are **offline** — this gate never calls Obsidian, MCP, or any network
7
+ resource. The captured `## Client Context & Patterns` block in CLAUDE.md (the Phase 9
8
+ artifact, already on the Build/Validate read path) is the source of truth; the live vault is
9
+ out of scope here.
10
+
11
+ ```bash
12
+ _PP_SHIM_NAME="pp-tools.cjs"; _PP_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; PP_TOOLS="${_PP_RUNTIME_ROOT}/pp-core/bin/${_PP_SHIM_NAME}"; if [ -f "$PP_TOOLS" ]; then pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${_PP_RUNTIME_ROOT}/.claude/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${_PP_RUNTIME_ROOT}/.claude/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${_PP_RUNTIME_ROOT}/.codex/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${_PP_RUNTIME_ROOT}/.codex/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif command -v pp-tools >/dev/null 2>&1; then PP_TOOLS="$(command -v pp-tools)"; pp_run() { "$PP_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="$HOME/.claude/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${HERMES_HOME:-$HOME/.hermes}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CODEX_HOME:-$HOME/.codex}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; else echo "ERROR: pp-tools.cjs not found at $PP_TOOLS and pp-tools is not on PATH. Run: npx -y @pareto4/pp-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${PP_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${PP_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
13
+ CTX_GATE_PRESENT=$(pp_run query platform-bindings --pick present 2>/dev/null || echo false)
14
+ CTX_BLOCK=$(pp_run query generate-client-context --read 2>/dev/null || echo '{"present":false}')
15
+ CTX_BLOCK_PRESENT=$(printf '%s' "$CTX_BLOCK" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{process.stdout.write(String((JSON.parse(d).present)||false));}catch{process.stdout.write('false');}")
16
+ ```
17
+
18
+ **If `CTX_GATE_PRESENT` != `true`:** skip entirely (non-Pareto project — fully inert).
19
+
20
+ **If `CTX_GATE_PRESENT` == `true` and `CTX_BLOCK_PRESENT` == `true`:** the client-context
21
+ block exists and is on the executor's read path. Surface a one-line confirmation
22
+ ("Client context loaded from CLAUDE.md — Build will apply captured client patterns.") and
23
+ require an explicit `confirmed` acknowledgment (`AskUserQuestion`, or a typed `confirmed` in
24
+ TEXT_MODE) before dispatching any executor. Advisory checkpoint, not a hard block.
25
+
26
+ **If `CTX_GATE_PRESENT` == `true` and `CTX_BLOCK_PRESENT` == `false`:** bindings indicate a
27
+ Pareto/client engagement but no client-context block was captured. Warn: "No
28
+ `## Client Context & Patterns` block found in CLAUDE.md — Build may proceed without client
29
+ patterns." Offer (`AskUserQuestion` / TEXT_MODE numbered list):
30
+
31
+ - **Capture now (recommended)** — pause and run `/pp-init` (its gated client-context step) to
32
+ write the block, then re-run `/pp-execute-phase`.
33
+ - **Proceed without** — continue knowingly; the executor builds without captured client
34
+ context. Requires an explicit `confirmed`.
35
+
36
+ `TEXT_MODE` follows the same rule as the host workflow (`--text` in `$ARGUMENTS`): replace
37
+ `AskUserQuestion` with a plain-text numbered prompt.
@@ -336,6 +336,10 @@ pp_run query state.begin-phase --phase "${PHASE_NUMBER}" --name "${PHASE_NAME}"
336
336
  This updates Status, Last Activity, Current focus, Current Position, and plan counts in STATE.md so frontmatter and body text reflect the active phase immediately.
337
337
  </step>
338
338
 
339
+ <step name="client_context_gate">
340
+ Read and execute `pp-core/workflows/execute-phase/steps/client-context-gate.md` — Pareto client-context gate (inert without `.platform-bindings.json`; confirms the Phase 9 CLAUDE.md block first).
341
+ </step>
342
+
339
343
  <step name="discover_and_group_plans">
340
344
  Load plan inventory with wave grouping in one call:
341
345