@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
@@ -23,6 +23,10 @@ const phaseLocatorMod = require("./phase-locator.cjs");
23
23
  const { findPhaseInternal } = phaseLocatorMod;
24
24
  const decisions_cjs_1 = require("./decisions.cjs");
25
25
  const ui_safety_gate_cjs_1 = require("./ui-safety-gate.cjs");
26
+ const semver_compare_cjs_1 = require("./semver-compare.cjs");
27
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
28
+ const projectRootMod = require("./project-root.cjs");
29
+ const { findProjectRoot } = projectRootMod;
26
30
  // eslint-disable-next-line @typescript-eslint/no-require-imports
27
31
  const verifyModule = require("./verify.cjs");
28
32
  const { cmdVerifySchemaDrift, cmdVerifyCodebaseDrift } = verifyModule;
@@ -345,7 +349,7 @@ function cmdDecisionCoverageVerify(projectDir, args, raw) {
345
349
  * Returns JSON: { frontend: boolean, hasUiSpec: boolean, block: boolean }
346
350
  * block = frontend && !hasUiSpec (gate fires when UI work is detected but no spec exists)
347
351
  *
348
- * Invocable as: gsd_run check ui-plan-gate <phase>
352
+ * Invocable as: pp_run check ui-plan-gate <phase>
349
353
  *
350
354
  * Uses checkUiPresence from ui-safety-gate.cjs — does NOT reimplement frontend detection.
351
355
  * Uses getRoadmapPhaseWithFallback + findPhaseInternal from leaf modules for phase data.
@@ -455,8 +459,8 @@ function cmdUiPlanGate(projectDir, args, raw) {
455
459
  * block = frontend && hasUiFiles && !hasUiSpec
456
460
  *
457
461
  * Args: check ui-safety-gate <phase>
458
- * Invocable as: gsd_run check ui-safety-gate <phase>
459
- * or gsd_run check ui.safety-gate <phase> (dots normalized to hyphens)
462
+ * Invocable as: pp_run check ui-safety-gate <phase>
463
+ * or pp_run check ui.safety-gate <phase> (dots normalized to hyphens)
460
464
  *
461
465
  * Uses checkUiPresence from ui-safety-gate.cjs — does NOT reimplement frontend detection.
462
466
  * Checks whether any files changed in recent git history match frontend file patterns.
@@ -701,7 +705,7 @@ function cmdTddReviewCheckpoint(projectDir, args, raw) {
701
705
  * This gate is always advisory (passed: true) — it never blocks phase advancement.
702
706
  *
703
707
  * Args: check gap-analysis.plan-post <phase-dir> [phase-req-ids]
704
- * Invocable as: gsd_run check gap-analysis.plan-post <phase-dir> [phase-req-ids]
708
+ * Invocable as: pp_run check gap-analysis.plan-post <phase-dir> [phase-req-ids]
705
709
  */
706
710
  function cmdGapAnalysisPlanPost(projectDir, args, raw) {
707
711
  // args[0] = 'check', args[1] = 'gap-analysis-plan-post' (normalized), args[2] = phaseDir, args[3] = phaseReqIds
@@ -727,12 +731,182 @@ function cmdGapAnalysisPlanPost(projectDir, args, raw) {
727
731
  message: result.table || result.summary || '',
728
732
  }, raw, undefined);
729
733
  }
734
+ const DRIFT_SEVERITY_ORDINAL = {
735
+ none: 0,
736
+ minor: 1,
737
+ major: 2,
738
+ breaking: 3,
739
+ };
740
+ /**
741
+ * Classify drift severity from pinned vs latest semver tuples.
742
+ *
743
+ * Pre-condition: compareSemverCore(pinned, latest) < 0 (pinned is strictly behind).
744
+ * Severity mapping (A3):
745
+ * differing major → 'major' (breaking aliased to major; semver can't distinguish)
746
+ * equal major, differing minor → 'minor'
747
+ * equal major+minor, differing patch → 'none' (below minor; always warn, not block)
748
+ */
749
+ function classifyDriftSeverity(pinned, latest) {
750
+ const [pMaj, pMin] = (0, semver_compare_cjs_1.toNumericTuple)(pinned);
751
+ const [lMaj, lMin] = (0, semver_compare_cjs_1.toNumericTuple)(latest);
752
+ if (lMaj > pMaj)
753
+ return 'major';
754
+ if (lMin > pMin)
755
+ return 'minor';
756
+ return 'none'; // patch-level difference
757
+ }
758
+ /**
759
+ * Read pareto.drift_block_on from .planning/config.json.
760
+ * Returns 'major' (default) when config is absent, malformed, or the key is unset.
761
+ */
762
+ function readParetoConfig(projectDir) {
763
+ const configPath = node_path_1.default.join(projectDir, '.planning', 'config.json');
764
+ try {
765
+ const parsed = JSON.parse(node_fs_1.default.readFileSync(configPath, 'utf-8'));
766
+ const pareto = parsed['pareto'] ?? {};
767
+ const raw = pareto['drift_block_on'];
768
+ if (typeof raw === 'string' && raw in DRIFT_SEVERITY_ORDINAL) {
769
+ return { drift_block_on: raw };
770
+ }
771
+ }
772
+ catch { /* config absent or malformed → use default */ }
773
+ return { drift_block_on: 'major' };
774
+ }
775
+ /**
776
+ * Resolve the latest stable tag for a platform repo via git ls-remote.
777
+ *
778
+ * T-10-04: every candidate tag is filtered through isStableTripletSemver — never shell-eval.
779
+ * Returns '' when offline or the remote is unreachable (callers treat this as "unknown latest").
780
+ */
781
+ function resolveLatestTagViaGitRemote(repoShortPath) {
782
+ // Construct GitLab URL from "group/project" format (e.g. "pareto4/pareto-platform-gcp").
783
+ const remoteUrl = `https://gitlab.com/${repoShortPath}`;
784
+ try {
785
+ const lsOutput = (0, node_child_process_1.execFileSync)('git', ['ls-remote', '--tags', '--refs', '--sort=-v:refname', remoteUrl], { encoding: 'utf-8', timeout: 10_000, maxBuffer: 512 * 1024, windowsHide: true });
786
+ // Sort is descending by version (--sort=-v:refname) so the first isStableTripletSemver match
787
+ // is the latest stable tag. T-10-04: never use an unfiltered tag string.
788
+ const tag = lsOutput
789
+ .split('\n')
790
+ .map((line) => {
791
+ const m = line.match(/\brefs\/tags\/([^\s^]+)$/);
792
+ return m ? m[1].trim() : '';
793
+ })
794
+ .find((t) => t && (0, semver_compare_cjs_1.isStableTripletSemver)(t));
795
+ return tag ?? '';
796
+ }
797
+ catch {
798
+ return ''; // offline or unreachable → signals "unknown latest" (Pitfall 4)
799
+ }
800
+ }
801
+ /**
802
+ * ship.platform-drift query handler (HOOK-02).
803
+ *
804
+ * Derives a graded severity verdict from the pinned-vs-latest platform tag diff,
805
+ * maps it to the pareto.drift_block_on threshold, and degrades safely:
806
+ * - Inert when .platform-bindings.json is absent (HOOK-04 runtime file layer).
807
+ * - Non-blocking warn when latest is unresolved or offline (T-10-06, Pitfall 4).
808
+ *
809
+ * Verdict shape: { status: 'inert'|'pass'|'warn'|'block', block, severity, pinned, latest, present }
810
+ * block (boolean) mirrors status === 'block' — the uniform gate contract field the host
811
+ * loop reads regardless of which capability owns the gate.
812
+ *
813
+ * Invocable as: pp_run check ship.platform-drift [--latest-tag <tag>] [--raw]
814
+ * The dot → hyphen normalization in routeCheckCommand maps "ship.platform-drift"
815
+ * to the "ship-platform-drift" branch.
816
+ *
817
+ * Reuse:
818
+ * - Phase 7 reader contract: reads .platform-bindings.json directly at repo root
819
+ * (same find/parse/TOCTOU pattern as cmdValidateBindings in platform-bindings.cts).
820
+ * - semver-compare.cjs: toNumericTuple, compareSemverCore, isStableTripletSemver
821
+ * (no reimplemented semver; no shell-eval of tag strings).
822
+ */
823
+ function cmdShipPlatformDrift(cwd, args, raw) {
824
+ // Step 1: Resolve project root (same approach as cmdValidateBindings in platform-bindings.cts).
825
+ let root;
826
+ try {
827
+ root = findProjectRoot(cwd) || cwd;
828
+ }
829
+ catch {
830
+ root = cwd;
831
+ }
832
+ // Step 1b: HOOK-04 runtime file layer — absent → inert (never error(), opt-in default).
833
+ const bindingsPath = node_path_1.default.join(root, '.platform-bindings.json');
834
+ if (!node_fs_1.default.existsSync(bindingsPath)) {
835
+ output({ status: 'inert', block: false, present: false }, raw, undefined);
836
+ return;
837
+ }
838
+ // Step 2: Parse .platform-bindings.json (malformed structure → graceful warn, not block).
839
+ let bindingsObj;
840
+ try {
841
+ const parsed = JSON.parse(node_fs_1.default.readFileSync(bindingsPath, 'utf-8'));
842
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
843
+ output({
844
+ status: 'warn', block: false, severity: 'none',
845
+ pinned: null, latest: null, present: true,
846
+ message: 'Malformed .platform-bindings.json structure — drift check skipped',
847
+ }, raw, undefined);
848
+ return;
849
+ }
850
+ bindingsObj = parsed;
851
+ }
852
+ catch {
853
+ // TOCTOU guard — file vanished between existsSync and readFileSync → inert path.
854
+ output({ status: 'inert', block: false, present: false }, raw, undefined);
855
+ return;
856
+ }
857
+ // Extract platform.tag (PINNED) and platform.repo for remote tag resolution.
858
+ const platformField = (typeof bindingsObj['platform'] === 'object' &&
859
+ bindingsObj['platform'] !== null &&
860
+ !Array.isArray(bindingsObj['platform'])) ? bindingsObj['platform'] : null;
861
+ const pinned = typeof platformField?.['tag'] === 'string' ? platformField['tag'].trim() : '';
862
+ const platformRepo = typeof platformField?.['repo'] === 'string' ? platformField['repo'].trim() : '';
863
+ // Guard: missing or invalid pinned tag → advisory warn, do not block.
864
+ if (!pinned || !(0, semver_compare_cjs_1.isStableTripletSemver)(pinned)) {
865
+ output({
866
+ status: 'warn', block: false, severity: 'none',
867
+ pinned: pinned || null, latest: null, present: true,
868
+ message: 'platform.tag is missing or not a valid semver in .platform-bindings.json — drift check advisory only',
869
+ }, raw, undefined);
870
+ return;
871
+ }
872
+ // Step 3: Resolve LATEST via --latest-tag arg (deterministic) or git ls-remote (live).
873
+ const latestTagArgIdx = args.indexOf('--latest-tag');
874
+ let latest = latestTagArgIdx >= 0 ? (args[latestTagArgIdx + 1] ?? '') : '';
875
+ if (!latest && platformRepo) {
876
+ // T-10-04: resolveLatestTagViaGitRemote filters all tags through isStableTripletSemver.
877
+ latest = resolveLatestTagViaGitRemote(platformRepo);
878
+ }
879
+ // Step 4: Offline / unresolved latest → non-blocking warn (T-10-06, Pitfall 4).
880
+ // A disconnected dev or CI run must never be blocked by an unresolvable remote.
881
+ if (!latest || !(0, semver_compare_cjs_1.isStableTripletSemver)(latest)) {
882
+ output({
883
+ status: 'warn', block: false, severity: 'none',
884
+ pinned, latest: latest || null, present: true,
885
+ message: 'Latest platform tag could not be resolved (offline or unreachable remote) — drift check advisory only',
886
+ }, raw, undefined);
887
+ return;
888
+ }
889
+ // Step 4b: Pinned is current or ahead of latest → pass.
890
+ if ((0, semver_compare_cjs_1.compareSemverCore)(pinned, latest) >= 0) {
891
+ output({ status: 'pass', block: false, severity: 'none', pinned, latest, present: true }, raw, undefined);
892
+ return;
893
+ }
894
+ // Step 5: Classify drift severity from tuple diff (A3; breaking aliased to major for now).
895
+ const severity = classifyDriftSeverity(pinned, latest);
896
+ // Step 6: Read pareto.drift_block_on threshold (default 'major').
897
+ const { drift_block_on: threshold } = readParetoConfig(cwd);
898
+ const thresholdOrd = DRIFT_SEVERITY_ORDINAL[threshold] ?? DRIFT_SEVERITY_ORDINAL['major'];
899
+ const severityOrd = DRIFT_SEVERITY_ORDINAL[severity];
900
+ // Step 7: Emit verdict — block when severity ≥ threshold, warn otherwise.
901
+ const status = severityOrd >= thresholdOrd ? 'block' : 'warn';
902
+ output({ status, block: status === 'block', severity, pinned, latest, present: true }, raw, undefined);
903
+ }
730
904
  function routeCheckCommand({ args, cwd, raw }) {
731
905
  // Normalize dots to hyphens in the subcommand so both forms are accepted.
732
906
  // This makes `check.query = "ui.plan-gate"` (dotted form in capability.json gates)
733
- // directly runnable as `gsd_run check ui.plan-gate` — the dot is normalized to
907
+ // directly runnable as `pp_run check ui.plan-gate` — the dot is normalized to
734
908
  // `ui-plan-gate` before routing. The generic gate-dispatch in §5.6 reads
735
- // `check.query` from the active gate hook and runs `gsd_run check ${hook.check.query}`,
909
+ // `check.query` from the active gate hook and runs `pp_run check ${hook.check.query}`,
736
910
  // so the declared query must be dispatchable exactly as declared.
737
911
  const rawSubcommand = args[1];
738
912
  const subcommand = typeof rawSubcommand === 'string' ? rawSubcommand.replace(/\./g, '-') : rawSubcommand;
@@ -764,6 +938,13 @@ function routeCheckCommand({ args, cwd, raw }) {
764
938
  cmdUiSafetyGate(cwd, args, raw);
765
939
  return;
766
940
  }
941
+ if (subcommand === 'ship-platform-drift') {
942
+ // ship.platform-drift — graded platform-tag drift check (HOOK-02).
943
+ // Dot-to-hyphen normalization means query "ship.platform-drift" routes here.
944
+ // Reuses Phase 7 reader contract + semver-compare; inert when bindings absent (HOOK-04).
945
+ cmdShipPlatformDrift(cwd, args, raw);
946
+ return;
947
+ }
767
948
  if (subcommand === 'verify-schema-drift') {
768
949
  // Delegates to verify.schema-drift — drift capability gate at execute:wave:post (blocking).
769
950
  // Dot-to-hyphen normalization means query "verify.schema-drift" routes here.
@@ -783,11 +964,11 @@ function routeCheckCommand({ args, cwd, raw }) {
783
964
  // The deterministic test-tier prohibition PRODUCER/gate (#1259, ADR-550 D5d). Locates the
784
965
  // wired mechanical check (node-test or lint-rule), confirms fail-first, runs it, builds
785
966
  // enforcementEvidence, and emits the dispositionForProhibition verdict. Invocable as
786
- // `gsd_run check prohibition-enforcement <request.json>`.
967
+ // `pp_run check prohibition-enforcement <request.json>`.
787
968
  (0, prohibition_enforcement_cjs_1.routeProhibitionEnforcement)(args, raw);
788
969
  return;
789
970
  }
790
- error('Unknown check subcommand. Available: auto-mode, decision-coverage-plan, decision-coverage-verify, gap-analysis-plan-post, prohibition-enforcement, tdd-review-checkpoint, ui-plan-gate, ui-safety-gate, verify-schema-drift, verify-codebase-drift', ERROR_REASON.SDK_UNKNOWN_COMMAND);
971
+ error('Unknown check subcommand. Available: auto-mode, decision-coverage-plan, decision-coverage-verify, gap-analysis-plan-post, prohibition-enforcement, ship-platform-drift, tdd-review-checkpoint, ui-plan-gate, ui-safety-gate, verify-schema-drift, verify-codebase-drift', ERROR_REASON.SDK_UNKNOWN_COMMAND);
791
972
  }
792
973
  module.exports = {
793
974
  routeCheckCommand,
@@ -797,4 +978,5 @@ module.exports = {
797
978
  computeUiSafetyGate,
798
979
  cmdGapAnalysisPlanPost,
799
980
  cmdTddReviewCheckpoint,
981
+ cmdShipPlatformDrift,
800
982
  };
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * CJS Command Router Adapter Module
4
4
  *
5
- * Compatibility routing for gsd-tools.cjs command families. Uses generated
5
+ * Compatibility routing for pp-tools.cjs command families. Uses generated
6
6
  * command metadata for availability and small family-local argument shapers for
7
7
  * CJS handler calls.
8
8
  *
@@ -20,6 +20,7 @@ exports.CLUSTERS = void 0;
20
20
  exports.allClusteredSkills = allClusteredSkills;
21
21
  exports.CLUSTERS = Object.freeze({
22
22
  core_loop: Object.freeze([
23
+ 'init',
23
24
  'new-project',
24
25
  'discovery',
25
26
  'discuss-phase',
@@ -16,7 +16,7 @@ exports.resolveCodeReviewWorkflow = resolveCodeReviewWorkflow;
16
16
  /**
17
17
  * Parse code-review flags from an argv array. The first positional argument
18
18
  * (phase number) is ignored — phase validation is handled by
19
- * `gsd-tools query init.phase-op`. Unknown flags are silently ignored.
19
+ * `pp-tools query init.phase-op`. Unknown flags are silently ignored.
20
20
  */
21
21
  function parseCodeReviewFlags(argv) {
22
22
  const flags = {
@@ -583,6 +583,13 @@ exports.NON_FAMILY_COMMAND_ALIASES = [
583
583
  ],
584
584
  "mutation": false
585
585
  },
586
+ {
587
+ "canonical": "check.ship-platform-drift",
588
+ "aliases": [
589
+ "check ship-platform-drift"
590
+ ],
591
+ "mutation": false
592
+ },
586
593
  {
587
594
  "canonical": "commit",
588
595
  "aliases": [],
@@ -502,7 +502,7 @@ function loadConfig(cwd, options = {}) {
502
502
  const warnKey = unknownKeys.join(',');
503
503
  if (!_warnedUnknownConfigKeys.has(warnKey)) {
504
504
  _warnedUnknownConfigKeys.add(warnKey);
505
- process.stderr.write(`gsd-tools: warning: unknown config key(s) in .planning/config.json: ${unknownKeys.join(', ')} — these will be ignored\n`);
505
+ process.stderr.write(`pp-tools: warning: unknown config key(s) in .planning/config.json: ${unknownKeys.join(', ')} — these will be ignored\n`);
506
506
  }
507
507
  }
508
508
  // #2517 — Validate runtime/tier values
@@ -749,7 +749,7 @@ function cmdConfigPath(cwd, _raw, workstreamContext = null) {
749
749
  *
750
750
  * Wraps the Configuration Module's migrateOnDisk() for the CLI surface. This
751
751
  * is the Phase 2 acceptance-criteria deliverable for opt-in migration (#3536):
752
- * users can run `gsd-tools migrate-config` to apply all four legacy-key
752
+ * users can run `pp-tools migrate-config` to apply all four legacy-key
753
753
  * migrations to their .planning/config.json without having to load any config
754
754
  * implicitly via another command.
755
755
  *
@@ -8,7 +8,7 @@
8
8
  * Pure function. Callers pass tokensUsed + contextWindow; the
9
9
  * classifier returns the percent and one of three states. Recommendation
10
10
  * strings are NOT in this module — formatting is the renderer's job
11
- * (see `validate context` in gsd-tools.cjs). That separation lets the
11
+ * (see `validate context` in pp-tools.cjs). That separation lets the
12
12
  * copy change without touching this module's tests.
13
13
  *
14
14
  * Thresholds:
@@ -241,7 +241,7 @@ function detectMonorepoWorkspaces(cwd) {
241
241
  * resolution. Follows the cmdInitMapCodebase pattern.
242
242
  *
243
243
  * @example
244
- * node gsd-tools.cjs docs-init --raw
244
+ * node pp-tools.cjs docs-init --raw
245
245
  */
246
246
  function cmdDocsInit(cwd, raw) {
247
247
  const config = loadConfig(cwd);
@@ -367,7 +367,7 @@ function parseMustHavesBlock(content, blockName) {
367
367
  if (items.length === 0 && blockLines.length > 0) {
368
368
  const nonEmptyLines = blockLines.filter(l => l.trim() !== '').length;
369
369
  if (nonEmptyLines > 0) {
370
- process.stderr.write(`[gsd-tools] WARNING: must_haves.${blockName} block has ${nonEmptyLines} content lines but parsed 0 items. ` +
370
+ process.stderr.write(`[pp-tools] WARNING: must_haves.${blockName} block has ${nonEmptyLines} content lines but parsed 0 items. ` +
371
371
  `Possible YAML formatting issue — verification will fall back to LLM-derived truths.\n`);
372
372
  }
373
373
  }
@@ -208,9 +208,9 @@ function gitWorktreeInfoInternal(cwd) {
208
208
  }
209
209
  // ─── CLI entry point ──────────────────────────────────────────────────────────
210
210
  /**
211
- * CLI command: `gsd-tools git base-branch`
211
+ * CLI command: `pp-tools git base-branch`
212
212
  * Resolves the default branch and writes it to stdout (raw string, newline-terminated).
213
- * Called by workflows via `gsd_run query git.base-branch`.
213
+ * Called by workflows via `pp_run query git.base-branch`.
214
214
  */
215
215
  function cmdGitBaseBranch(cwd, _args, deps) {
216
216
  const branch = resolveBaseBranch(cwd, deps);
@@ -1,9 +1,9 @@
1
1
  'use strict';
2
2
  /**
3
- * Graphify command router — CLI subcommand dispatcher for `gsd-tools graphify`.
3
+ * Graphify command router — CLI subcommand dispatcher for `pp-tools graphify`.
4
4
  *
5
5
  * ADR-959 (phase 4d-impl-2) pilot: first real capability command cutover.
6
- * Extracted from the hardcoded `case 'graphify':` arm in gsd-tools.cjs.
6
+ * Extracted from the hardcoded `case 'graphify':` arm in pp-tools.cjs.
7
7
  * Behaviour is preserved byte-for-behaviour from the prior inline case;
8
8
  * the dispatch path now flows: default → dispatchCapabilityCommand →
9
9
  * require(graphify-command-router.cjs) → routeGraphifyCommand.
@@ -34,7 +34,7 @@ function routeGraphifyCommand({ args, cwd, raw, error, _graphify }) {
34
34
  if (subcommand === 'query') {
35
35
  const term = args[2];
36
36
  if (!term) {
37
- error('Usage: gsd-tools graphify query <term>', ERROR_REASON.USAGE);
37
+ error('Usage: pp-tools graphify query <term>', ERROR_REASON.USAGE);
38
38
  return;
39
39
  }
40
40
  const budgetIdx = args.indexOf('--budget');
@@ -42,7 +42,7 @@ function routeGraphifyCommand({ args, cwd, raw, error, _graphify }) {
42
42
  if (budgetIdx !== -1) {
43
43
  const rawBudget = args[budgetIdx + 1];
44
44
  if (rawBudget === undefined || Number.isNaN(parseInt(rawBudget, 10))) {
45
- error('Usage: gsd-tools graphify query <term> [--budget <N>]', ERROR_REASON.USAGE);
45
+ error('Usage: pp-tools graphify query <term> [--budget <N>]', ERROR_REASON.USAGE);
46
46
  return;
47
47
  }
48
48
  budget = parseInt(rawBudget, 10);
@@ -20,7 +20,7 @@ const { isCapabilityActive } = capabilityStateMod;
20
20
  * Return the standard disabled response object.
21
21
  */
22
22
  function disabledResponse() {
23
- return { disabled: true, message: 'graphify is not enabled. Enable with: gsd-tools config-set graphify.enabled true' };
23
+ return { disabled: true, message: 'graphify is not enabled. Enable with: pp-tools config-set graphify.enabled true' };
24
24
  }
25
25
  // ─── Subprocess Helper ───────────────────────────────────────────────────────
26
26
  /**
@@ -392,7 +392,7 @@ function writePlanningDir(artifacts, planningRoot) {
392
392
  }
393
393
  // ─── Command Handler ──────────────────────────────────────────────────────────
394
394
  /**
395
- * Entry point called from gsd-tools.cjs.
395
+ * Entry point called from pp-tools.cjs.
396
396
  * Supports: --force, --dry-run, --path <dir>
397
397
  */
398
398
  function cmdFromGsd2(args, cwd, raw) {
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  /**
3
3
  * Manifest-backed init 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
  * Phase 6: all init.* subcommands have SDK equivalents and are dispatched
7
7
  * via executeForCjs (the sync bridge). CJS fallback retained when:
@@ -1218,7 +1218,7 @@ function cmdInitManager(cwd, raw) {
1218
1218
  const safe = tokens.every((t) => /^--[a-zA-Z0-9][-a-zA-Z0-9]*$/.test(t) ||
1219
1219
  /^[a-zA-Z0-9][-a-zA-Z0-9_.]*$/.test(t));
1220
1220
  if (!safe) {
1221
- process.stderr.write(`gsd-tools: warning: manager.flags contains invalid tokens, ignoring: ${val}\n`);
1221
+ process.stderr.write(`pp-tools: warning: manager.flags contains invalid tokens, ignoring: ${val}\n`);
1222
1222
  return '';
1223
1223
  }
1224
1224
  return val;
@@ -1,10 +1,10 @@
1
1
  'use strict';
2
2
  /**
3
- * Intel command router — CLI subcommand dispatcher for `gsd-tools intel`.
3
+ * Intel command router — CLI subcommand dispatcher for `pp-tools intel`.
4
4
  *
5
5
  * ADR-959 (phase 4d-impl-4): intel command family cutover — last first-party
6
6
  * command cutover in the initial capability rollout.
7
- * Extracted from the hardcoded `case 'intel':` arm in gsd-tools.cjs.
7
+ * Extracted from the hardcoded `case 'intel':` arm in pp-tools.cjs.
8
8
  * Behaviour is preserved byte-for-behaviour from the prior inline case;
9
9
  * the dispatch path now flows: default → dispatchCapabilityCommand →
10
10
  * require(intel-command-router.cjs) → routeIntelCommand.
@@ -56,7 +56,7 @@ function routeIntelCommand({ args, cwd, raw, error, _intel, _core }) {
56
56
  if (subcommand === 'query') {
57
57
  const term = args[2];
58
58
  if (!term) {
59
- error('Usage: gsd-tools intel query <term>', ERROR_REASON.USAGE);
59
+ error('Usage: pp-tools intel query <term>', ERROR_REASON.USAGE);
60
60
  return;
61
61
  }
62
62
  const planningDir = path.join(cwd, '.planning');
@@ -85,7 +85,7 @@ function routeIntelCommand({ args, cwd, raw, error, _intel, _core }) {
85
85
  else if (subcommand === 'patch-meta') {
86
86
  const filePath = args[2];
87
87
  if (!filePath) {
88
- error('Usage: gsd-tools intel patch-meta <file-path>', ERROR_REASON.USAGE);
88
+ error('Usage: pp-tools intel patch-meta <file-path>', ERROR_REASON.USAGE);
89
89
  return;
90
90
  }
91
91
  c.output(intel.intelPatchMeta(path.resolve(cwd, filePath)), raw);
@@ -97,7 +97,7 @@ function routeIntelCommand({ args, cwd, raw, error, _intel, _core }) {
97
97
  else if (subcommand === 'extract-exports') {
98
98
  const filePath = args[2];
99
99
  if (!filePath) {
100
- error('Usage: gsd-tools intel extract-exports <file-path>', ERROR_REASON.USAGE);
100
+ error('Usage: pp-tools intel extract-exports <file-path>', ERROR_REASON.USAGE);
101
101
  return;
102
102
  }
103
103
  c.output(intel.intelExtractExports(path.resolve(cwd, filePath)), raw);
@@ -240,7 +240,7 @@ function intelUpdate(planningDir) {
240
240
  return disabledResponse();
241
241
  return {
242
242
  action: 'spawn_agent',
243
- message: 'Run gsd-tools intel update or spawn gsd-intel-updater agent for full refresh',
243
+ message: 'Run pp-tools intel update or spawn gsd-intel-updater agent for full refresh',
244
244
  };
245
245
  }
246
246
  /**
@@ -161,7 +161,7 @@ const ERROR_REASON = Object.freeze({
161
161
  CONFIG_NO_FILE: 'config_no_file',
162
162
  CONFIG_PARSE_FAILED: 'config_parse_failed',
163
163
  CONFIG_INVALID_KEY: 'config_invalid_key',
164
- // SDK / gsd-tools dispatch
164
+ // SDK / pp-tools dispatch
165
165
  SDK_FAIL_FAST: 'sdk_fail_fast',
166
166
  SDK_UNKNOWN_COMMAND: 'sdk_unknown_command',
167
167
  SDK_MISSING_ARG: 'sdk_missing_arg',
@@ -175,13 +175,15 @@ const ERROR_REASON = Object.freeze({
175
175
  HOOKS_OPT_OUT: 'hooks_opt_out',
176
176
  // security-scan
177
177
  SECURITY_SCAN_FAILED: 'security_scan_failed',
178
+ // platform-bindings
179
+ PLATFORM_BINDINGS_PARSE_FAILED: 'platform_bindings_parse_failed',
178
180
  // generic
179
181
  USAGE: 'usage',
180
182
  UNKNOWN: 'unknown',
181
183
  });
182
184
  /**
183
185
  * Process-level flag: when true, error() emits structured JSON to stderr
184
- * instead of plain "Error: <message>" text. Set by gsd-tools.cjs when the
186
+ * instead of plain "Error: <message>" text. Set by pp-tools.cjs when the
185
187
  * CLI is invoked with `--json-errors`. Tests opt in to typed-IR error
186
188
  * assertions by passing that flag and parsing the JSON.
187
189
  *
@@ -10,7 +10,7 @@
10
10
  * at plan:pre (ui-phase) and autonomous.md at verify:post (ui-review). Further per-feature
11
11
  * cutovers are ongoing.
12
12
  *
13
- * Command surface: gsd-tools loop render-hooks <point>
13
+ * Command surface: pp-tools loop render-hooks <point>
14
14
  *
15
15
  * Exports (three things):
16
16
  * resolveLoopHooks({ point, registry, config }) → { point, activeHooks }
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  /**
3
3
  * Manifest-backed phase 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
  * Unsupported in this router:
7
7
  * - scaffold: routed through top-level scaffold command.
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  /**
3
3
  * Manifest-backed phases subcommand router.
4
- * Keeps gsd-tools.cjs thin while preserving current CJS semantics.
4
+ * Keeps pp-tools.cjs thin while preserving current CJS semantics.
5
5
  *
6
6
  * Unsupported in this router (treated as unknown):
7
7
  * - archive: `phases archive` is excluded from the subcommands list so it
@@ -8,7 +8,7 @@
8
8
  * Design constraints:
9
9
  * - Pure module: no I/O, no require() calls, no side effects.
10
10
  * - All inputs are validated; unknown values throw a TypeError.
11
- * - Consumed by the `gsd-tools drift-guard` CLI seam and by tests.
11
+ * - Consumed by the `pp-tools drift-guard` CLI seam and by tests.
12
12
  *
13
13
  * Authority ladder (rung values determine MISSING severity):
14
14
  * grep=0 intel=1 treesitter=2 lsp=3 scip=4