@pareto4/pp-core 1.13.0 → 1.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. package/.claude-plugin/plugin.json +3 -3
  2. package/README.md +26 -9
  3. package/agents/pp-code-reviewer.md +2 -1
  4. package/agents/pp-debug-session-manager.md +9 -6
  5. package/agents/pp-executor.md +10 -13
  6. package/agents/pp-plan-checker.md +4 -4
  7. package/agents/pp-planner.md +23 -9
  8. package/agents/pp-vault-reviewer.md +244 -21
  9. package/agents/pp-verifier.md +7 -3
  10. package/bin/install.js +10 -3
  11. package/commands/pp-execute-phase.md +1 -3
  12. package/commands/pp-ns-review.md +1 -1
  13. package/commands/pp-plan-phase.md +1 -2
  14. package/commands/pp-vault-review.md +5 -3
  15. package/commands/pp-verify-work.md +1 -2
  16. package/gemini-extension.json +1 -1
  17. package/hooks/dist/pp-check-update.js +0 -0
  18. package/hooks/dist/pp-cursor-session-start.js +1 -1
  19. package/hooks/dist/pp-statusline.js +1 -1
  20. package/hooks/dist/pp-update-banner.js +0 -0
  21. package/hooks/pp-check-update.js +0 -0
  22. package/hooks/pp-cursor-session-start.js +1 -1
  23. package/hooks/pp-statusline.js +1 -1
  24. package/hooks/pp-update-banner.js +0 -0
  25. package/package.json +14 -19
  26. package/pareto-skills/_shared/create-project-board.sh +0 -0
  27. package/pareto-skills/pp-project-setup/SKILL.md +1 -1
  28. package/pp-core/bin/check-latest-version.cjs +0 -0
  29. package/pp-core/bin/detect-build-commands.sh +2 -2
  30. package/pp-core/bin/lib/capability-activation.cjs +83 -9
  31. package/pp-core/bin/lib/capability-registry.cjs +396 -82
  32. package/pp-core/bin/lib/capability-state.cjs +160 -4
  33. package/pp-core/bin/lib/capability-writer.cjs +19 -5
  34. package/pp-core/bin/lib/check-command-router.cjs +3334 -946
  35. package/pp-core/bin/lib/clusters.cjs +3 -0
  36. package/pp-core/bin/lib/command-aliases.cjs +0 -35
  37. package/pp-core/bin/lib/commands.cjs +9 -4
  38. package/pp-core/bin/lib/compose-bindings.cjs +251 -88
  39. package/pp-core/bin/lib/config-loader.cjs +7 -103
  40. package/pp-core/bin/lib/config-schema.cjs +1 -1
  41. package/pp-core/bin/lib/config.cjs +154 -62
  42. package/pp-core/bin/lib/decisions.cjs +4 -4
  43. package/pp-core/bin/lib/discovery-vault-check.cjs +246 -0
  44. package/pp-core/bin/lib/frontmatter.cjs +1 -38
  45. package/pp-core/bin/lib/git-base-branch.cjs +13 -11
  46. package/pp-core/bin/lib/git-provider.cjs +4 -4
  47. package/pp-core/bin/lib/init-command-router.cjs +5 -8
  48. package/pp-core/bin/lib/init-contract.cjs +648 -738
  49. package/pp-core/bin/lib/init-interactive.cjs +76 -4
  50. package/pp-core/bin/lib/init.cjs +24 -51
  51. package/pp-core/bin/lib/install-profiles.cjs +1 -1
  52. package/pp-core/bin/lib/io.cjs +8 -0
  53. package/pp-core/bin/lib/loop-resolver.cjs +189 -129
  54. package/pp-core/bin/lib/org-profile.cjs +469 -152
  55. package/pp-core/bin/lib/package-identity.cjs +3 -3
  56. package/pp-core/bin/lib/phase-command-router.cjs +1 -29
  57. package/pp-core/bin/lib/phase-scope.cjs +1 -1
  58. package/pp-core/bin/lib/phase.cjs +11 -253
  59. package/pp-core/bin/lib/platform-bindings.cjs +40 -19
  60. package/pp-core/bin/lib/practices-review-check.cjs +15 -9
  61. package/pp-core/bin/lib/profile-output.cjs +1 -1
  62. package/pp-core/bin/lib/shell-command-projection.cjs +104 -1
  63. package/pp-core/bin/lib/state.cjs +11 -85
  64. package/pp-core/bin/lib/template.cjs +1 -1
  65. package/pp-core/bin/lib/vault-audit.cjs +650 -126
  66. package/pp-core/bin/lib/vault-command-router.cjs +2604 -367
  67. package/pp-core/bin/lib/vault-coordinates.cjs +13 -4
  68. package/pp-core/bin/lib/vault-index.cjs +699 -0
  69. package/pp-core/bin/lib/vault-mode.cjs +235 -0
  70. package/pp-core/bin/lib/vault-practices.cjs +139 -72
  71. package/pp-core/bin/lib/vault-review.cjs +194 -451
  72. package/pp-core/bin/lib/vault-seal.cjs +1936 -0
  73. package/pp-core/bin/lib/vault-standards.cjs +1505 -0
  74. package/pp-core/bin/lib/vault-trace.cjs +607 -254
  75. package/pp-core/bin/lib/vault-update.cjs +9 -33
  76. package/pp-core/bin/lib/vault-write-back.cjs +70 -42
  77. package/pp-core/bin/lib/vault.cjs +2734 -920
  78. package/pp-core/bin/lib/verify.cjs +0 -11
  79. package/pp-core/bin/portable-timeout.sh +1 -1
  80. package/pp-core/bin/pp-tools.cjs +184 -54
  81. package/pp-core/bin/shared/config-defaults.manifest.json +0 -7
  82. package/pp-core/bin/shared/config-schema.manifest.json +0 -3
  83. package/pp-core/bin/shared/model-catalog.json +2 -2
  84. package/pp-core/bin/verify-reapply-patches.cjs +0 -0
  85. package/pp-core/org-profile.json +26 -0
  86. package/pp-core/references/autonomous-best-effort.md +8 -7
  87. package/pp-core/references/loop-hook-dispatch.md +0 -25
  88. package/pp-core/references/planner-gap-closure.md +0 -1
  89. package/pp-core/references/planner-tdd.md +0 -1
  90. package/pp-core/references/planner-vault-practices.md +17 -34
  91. package/pp-core/references/planner-vault-standards.md +320 -0
  92. package/pp-core/references/planning-config.md +0 -8
  93. package/pp-core/references/practice-arithmetic-contract.md +121 -0
  94. package/pp-core/references/tdd.md +0 -1
  95. package/pp-core/references/verifier-practice-verdicts.md +263 -0
  96. package/pp-core/templates/README.md +1 -0
  97. package/pp-core/templates/VAULT-REVIEW.md +202 -0
  98. package/pp-core/templates/phase-prompt.md +2 -6
  99. package/pp-core/templates/practices/README.md +20 -23
  100. package/pp-core/workflows/_runtime-launcher.snippet.sh +0 -0
  101. package/pp-core/workflows/add-tests.md +55 -4
  102. package/pp-core/workflows/audit-fix.md +63 -2
  103. package/pp-core/workflows/audit-milestone.md +1 -1
  104. package/pp-core/workflows/autonomous.md +85 -11
  105. package/pp-core/workflows/check-todos.md +13 -2
  106. package/pp-core/workflows/code-review-fix.md +59 -3
  107. package/pp-core/workflows/code-review.md +10 -1
  108. package/pp-core/workflows/complete-milestone.md +1 -1
  109. package/pp-core/workflows/debug.md +54 -4
  110. package/pp-core/workflows/discovery-sprint/prefill.md +19 -7
  111. package/pp-core/workflows/discovery-sprint/prototype.md +46 -49
  112. package/pp-core/workflows/discovery-sprint/session-13-gates.md +2 -0
  113. package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +1 -3
  114. package/pp-core/workflows/discovery-sprint/sessions/02-as-is.md +1 -3
  115. package/pp-core/workflows/discovery-sprint/sessions/03-to-be.md +1 -3
  116. package/pp-core/workflows/discovery-sprint/sessions/04-ux-design.md +20 -24
  117. package/pp-core/workflows/discovery-sprint/sessions/05-architecture.md +1 -3
  118. package/pp-core/workflows/discovery-sprint/sessions/06-security.md +1 -3
  119. package/pp-core/workflows/discovery-sprint/sessions/07-identity.md +1 -3
  120. package/pp-core/workflows/discovery-sprint/sessions/08-integration.md +1 -3
  121. package/pp-core/workflows/discovery-sprint/sessions/09-qa.md +1 -3
  122. package/pp-core/workflows/discovery-sprint/sessions/10-infrastructure.md +1 -6
  123. package/pp-core/workflows/discovery-sprint/sessions/11-roadmap.md +1 -3
  124. package/pp-core/workflows/discovery-sprint/sessions/12-delivery-model.md +1 -3
  125. package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +1 -3
  126. package/pp-core/workflows/discovery-sprint/templates/context-template.md +1 -1
  127. package/pp-core/workflows/discovery-sprint/vault-context.md +213 -0
  128. package/pp-core/workflows/discovery-sprint.md +7 -3
  129. package/pp-core/workflows/discovery-to-build/steps/architecture-deliverables.md +4 -4
  130. package/pp-core/workflows/discovery-to-build/templates/pp-handoff.md +43 -1
  131. package/pp-core/workflows/discovery-to-build.md +10 -2
  132. package/pp-core/workflows/discuss-phase.md +3 -10
  133. package/pp-core/workflows/execute-phase/steps/check-query-gate.md +329 -0
  134. package/pp-core/workflows/execute-phase/steps/client-context-gate.md +3 -2
  135. package/pp-core/workflows/execute-phase/steps/cross-ai-delegation.md +1 -1
  136. package/pp-core/workflows/execute-phase/steps/execute-pre-dispatch.md +98 -0
  137. package/pp-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +94 -0
  138. package/pp-core/workflows/execute-phase/steps/post-merge-gate.md +6 -8
  139. package/pp-core/workflows/execute-phase/steps/vault-fetch.md +115 -14
  140. package/pp-core/workflows/execute-phase/steps/vault-load-gate.md +322 -20
  141. package/pp-core/workflows/execute-phase/steps/verifier-practice-payload.md +165 -0
  142. package/pp-core/workflows/execute-phase/steps/verify-post-gate.md +163 -0
  143. package/pp-core/workflows/execute-phase.md +155 -128
  144. package/pp-core/workflows/execute-plan.md +56 -6
  145. package/pp-core/workflows/fast.md +53 -4
  146. package/pp-core/workflows/help/modes/full.md +3 -3
  147. package/pp-core/workflows/init/steps/client-context-capture.md +101 -0
  148. package/pp-core/workflows/init/steps/gitlab-provisioning.md +130 -0
  149. package/pp-core/workflows/init/steps/project-md-deferral-narration.md +66 -0
  150. package/pp-core/workflows/init/steps/reachability-outcome.md +70 -0
  151. package/pp-core/workflows/init.md +232 -272
  152. package/pp-core/workflows/new-project.md +31 -23
  153. package/pp-core/workflows/plan-phase.md +98 -45
  154. package/pp-core/workflows/quick.md +117 -3
  155. package/pp-core/workflows/secure-phase.md +1 -1
  156. package/pp-core/workflows/ship.md +57 -28
  157. package/pp-core/workflows/undo.md +1 -1
  158. package/pp-core/workflows/update.md +3 -3
  159. package/pp-core/workflows/validate-phase.md +58 -3
  160. package/pp-core/workflows/vault-review.md +282 -22
  161. package/pp-core/workflows/verify-work.md +12 -14
  162. package/scripts/changeset/cli.cjs +6 -5
  163. package/scripts/changeset/lint.cjs +0 -0
  164. package/scripts/changeset/new.cjs +33 -50
  165. package/scripts/dev-env.cjs +25 -85
  166. package/scripts/gen-capability-registry.cjs +199 -11
  167. package/scripts/gen-practice-arithmetic-contract.cjs +412 -0
  168. package/scripts/lib/allowlist-ratchet.cjs +1 -103
  169. package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -2
  170. package/scripts/lint-docs-required.cjs +0 -0
  171. package/scripts/lint-pr-check-project-dir.cjs +1 -3
  172. package/scripts/lint-test-file-count.allowlist.json +12 -10
  173. package/scripts/practice-arithmetic-contract.allow.json +35 -0
  174. package/scripts/prompt-injection-scan.sh +22 -17
  175. package/scripts/record-vault-mode-golden.cjs +89 -0
  176. package/scripts/release-channel.cjs +0 -0
  177. package/scripts/sync-manifest-versions.cjs +5 -4
  178. package/scripts/sync-rulesets.sh +1 -1
  179. package/scripts/update-size-baseline.cjs +30 -274
  180. package/scripts/vault-calibrate.cjs +996 -0
  181. package/scripts/verify-opengsd-sweep.cjs +3 -5
  182. package/scripts/workflow-size.cjs +9 -61
  183. package/pp-core/bin/lib/dispatch-plan.cjs +0 -483
  184. package/pp-core/bin/lib/install-run.cjs +0 -536
  185. package/pp-core/bin/lib/loop-gates.cjs +0 -213
  186. package/pp-core/bin/lib/phase-manifest.cjs +0 -527
  187. package/pp-core/bin/lib/phase-tier.cjs +0 -480
  188. package/pp-core/bin/lib/plan-graph.cjs +0 -368
  189. package/pp-core/references/executor-vault-trace.md +0 -47
  190. package/pp-core/references/planner-plan-graph.md +0 -66
  191. package/pp-core/workflows/discovery-sprint/vault-fetch.md +0 -37
  192. package/pp-core/workflows/execute-phase/steps/checkpoint-handling.md +0 -45
  193. package/pp-core/workflows/execute-phase/steps/install-run.md +0 -29
  194. package/pp-core/workflows/execute-phase/steps/phase-close-rebaseline.md +0 -23
  195. package/pp-core/workflows/execute-phase/steps/serial-group-dispatch.md +0 -73
  196. package/pp-core/workflows/execute-phase/steps/vault-door-gate.md +0 -19
  197. package/pp-core/workflows/plan-phase/steps/phase-tier.md +0 -43
  198. package/scripts/lib/size-budget.cjs +0 -311
  199. package/scripts/lint-neutrality-names.cjs +0 -694
  200. package/scripts/lint-org-coordinates.allowlist.json +0 -35
  201. package/scripts/lint-org-coordinates.cjs +0 -426
@@ -41,16 +41,14 @@ _PP_SHIM_NAME="pp-tools.cjs"; _pp_root() { ( for _v in $(env | sed -n 's/^\(GIT_
41
41
  GSD_WS=""
42
42
  echo "$ARGUMENTS" | grep -qE -- '--ws[[:space:]]+[^[:space:]]+' && GSD_WS=$(echo "$ARGUMENTS" | grep -oE -- '--ws[[:space:]]+[^[:space:]]+')
43
43
  PHASE_ARG=$(echo "$ARGUMENTS" | sed -E 's/--ws[[:space:]]+[^[:space:]]+//g' | xargs)
44
- TIER_FLAG=$(printf '%s\n' "$ARGUMENTS" | grep -oE -- '--tier[[:space:]=]+(light|standard|heavy)' | head -1 | sed -E 's/^--tier[[:space:]=]+/--tier=/')
45
- PHASE_ARG=$(echo "$PHASE_ARG" | sed -E 's/--tier[[:space:]]+[a-z]+//g' | xargs)
46
44
 
47
- INIT=$(pp_run query init.verify-work "${PHASE_ARG}" ${GSD_WS} ${TIER_FLAG})
45
+ INIT=$(pp_run query init.verify-work "${PHASE_ARG}" ${GSD_WS})
48
46
  if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
49
47
  AGENT_SKILLS_PLANNER=$(pp_run query agent-skills pp-planner)
50
48
  AGENT_SKILLS_CHECKER=$(pp_run query agent-skills pp-plan-checker)
51
49
  ```
52
50
 
53
- Parse JSON for: `planner_model`, `checker_model`, `commit_docs`, `plan_checker_enabled`, `plan_check_passes`, `phase_tier` (the phase's ceremony tier), `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `has_verification`, `uat_path`.
51
+ Parse JSON for: `planner_model`, `checker_model`, `commit_docs`, `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `has_verification`, `uat_path`.
54
52
 
55
53
  ```bash
56
54
  # MVP mode detection via the centralized phase.mvp-mode resolver.
@@ -113,7 +111,7 @@ Continue to `create_uat_file`.
113
111
  Before UAT, check UI capability activation and whether Playwright/Puppeteer MCP tools are available.
114
112
 
115
113
  ```bash
116
- PLAN_HOOKS_JSON=$(pp_run loop render-hooks plan:pre --raw --phase "${phase_number}" ${TIER_FLAG}) || exit 1
114
+ PLAN_HOOKS_JSON=$(pp_run loop render-hooks plan:pre --raw)
117
115
  UI_SPEC_FILE=$(ls "${PHASE_DIR}"/*-UI-SPEC.md 2>/dev/null | head -1)
118
116
  ```
119
117
 
@@ -460,12 +458,14 @@ Present summary:
460
458
  **If issues == 0:**
461
459
 
462
460
  ```bash
463
- VERIFY_POST_HOOKS_JSON=$(pp_run loop render-hooks verify:post --raw --phase "${phase_number}" ${GSD_WS} ${TIER_FLAG}) || exit 1
464
- VERIFY_POST_GATES_JSON=$(pp_run loop run-gates verify:post --phase "${phase_number}" ${GSD_WS} ${TIER_FLAG}) || exit 1
461
+ VERIFY_POST_HOOKS_JSON=$(pp_run loop render-hooks verify:post --raw)
462
+ printf '%s' "$VERIFY_POST_HOOKS_JSON" | node -e "let h=[];try{h=JSON.parse(require('fs').readFileSync(0,'utf-8')).activeHooks||[]}catch{}console.log('VERIFY_POST_HOOKS_JSON='+JSON.stringify({activeHooks:h}))"
465
463
  SECURITY_FILE=$(ls "${PHASE_DIR}"/*-SECURITY.md 2>/dev/null | head -1)
466
464
  ```
467
465
 
468
- `loop run-gates` runs every active `verify:post` gate and owns the halt: exit 1 (a `⛔` line) means a blocking gate blocked — stop, relay it, do not mark the phase complete. `⚠` lines are advisories: relay and continue.
466
+ **Vault practice gates.** Read `pp-core/workflows/execute-phase/steps/verify-post-gate.md` — it
467
+ dispatches this point's blocking gates to the generic enforcer, which owns the halt. Rendering the
468
+ payload above is not enforcing it, and the branch below marks the phase complete.
469
469
 
470
470
  Resolve active step hooks from `VERIFY_POST_HOOKS_JSON` where `kind == "step"` and `ref.skill == "secure-phase"`.
471
471
 
@@ -624,8 +624,6 @@ On return:
624
624
  <step name="verify_gap_plans">
625
625
  **Verify fix plans with checker:**
626
626
 
627
- **Skip if** `plan_checker_enabled` is false (the tier, or the project's explicit `false`) — go to `present_ready`.
628
-
629
627
  Run this Bash command to emit the branded Validate stage banner (renders on stderr, auto-degrades on non-TTY — do NOT print an inline ASCII banner, that would double-print):
630
628
  ```bash
631
629
  pp_run banner validate "VERIFYING"
@@ -677,11 +675,11 @@ On return:
677
675
  </step>
678
676
 
679
677
  <step name="revision_loop">
680
- **Iterate planner ↔ checker until plans pass (max `plan_check_passes`):**
678
+ **Iterate planner ↔ checker until plans pass (max 3):**
681
679
 
682
- **If iteration_count < plan_check_passes:**
680
+ **If iteration_count < 3:**
683
681
 
684
- Display: `Sending back to planner for revision... (iteration {N}/{plan_check_passes})`
682
+ Display: `Sending back to planner for revision... (iteration {N}/3)`
685
683
 
686
684
  Spawn pp-planner with revision context:
687
685
 
@@ -720,7 +718,7 @@ Do NOT replan from scratch unless issues are fundamental.
720
718
  After planner returns → spawn checker again (verify_gap_plans logic)
721
719
  Increment iteration_count
722
720
 
723
- **If iteration_count >= plan_check_passes:**
721
+ **If iteration_count >= 3:**
724
722
 
725
723
  Display: `Max iterations reached. {N} issues remain.`
726
724
 
@@ -90,11 +90,12 @@ function parseArgs(argv) {
90
90
  return { ok: true, opts };
91
91
  }
92
92
 
93
- // Bare-checkout verify. A release tag job may run `verify` on a bare checkout:
94
- // no `npm ci`, no `build:lib`, and the compiled semver module under
95
- // pp-core/bin/lib is gitignored build output. Requiring that module at the top
96
- // of this file made `verify` (and `render`) die at require time on such a
97
- // checkout.
93
+ // Release tag gate (#71924 gap plan 01-06). CI's
94
+ // `tag_version_check` job runs `verify` on a bare checkout: no `npm ci`, no
95
+ // `build:lib`, and the compiled semver module under pp-core/bin/lib is
96
+ // gitignored build output. The first shipped version of that step required the
97
+ // compiled module at the top of this file, so it died at require time on every
98
+ // hosted checkout (never observed live: the step was skipped on every probe).
98
99
  // `verify` and `render` therefore require node builtins and tracked files only;
99
100
  // the compiled dependency is loaded lazily by `extract`, which the update
100
101
  // workflow runs on built trees.
File without changes
@@ -4,56 +4,42 @@
4
4
  /**
5
5
  * Scaffolds a new changeset fragment (#2975).
6
6
  *
7
- * npm run changeset -- --type Fixed --pr 1234 --body "fix the thing"
7
+ * npm run changeset -- --type Fixed --pr 1234 --body "fix the thing" [--name <slug>]
8
8
  *
9
- * Writes `.changeset/<adjective>-<noun>-<noun>.md` with frontmatter
10
- * + body. The random three-word filename minimizes filename collision
11
- * across concurrent PRs.
9
+ * Writes `.changeset/<pr>-<slug>.md` with frontmatter + body. The slug is
10
+ * --name, else derived from the bold lead sentence; a taken name gets -2, -3, ...
11
+ * FORK-ONLY (PBI 74336): upstream draws a random three-word name.
12
12
  */
13
13
 
14
14
  const fs = require('node:fs');
15
15
  const path = require('node:path');
16
16
  const { ExitError, runMain } = require('../lib/cli-exit.cjs');
17
17
 
18
- // Small word lists — keep the function simple and dependency-free.
19
- // Together this gives ~40 * 40 * 40 = 64,000 distinct names. The lint
20
- // rejects any duplicate filename, so collisions are caught even when
21
- // the random draw repeats.
22
- const ADJECTIVES = [
23
- 'silly', 'brave', 'calm', 'eager', 'gentle', 'happy', 'jolly', 'kind',
24
- 'lively', 'merry', 'nimble', 'plucky', 'quick', 'sturdy', 'witty', 'zesty',
25
- 'bold', 'clever', 'daring', 'fierce', 'graceful', 'humble', 'lucky', 'noble',
26
- 'proud', 'rapid', 'sharp', 'tidy', 'vivid', 'wise', 'agile', 'curious',
27
- 'eager', 'gallant', 'mellow', 'patient', 'serene', 'steady', 'sturdy', 'sunny',
28
- ];
29
- const NOUNS_A = [
30
- 'bears', 'birds', 'cats', 'dogs', 'elks', 'foxes', 'goats', 'hawks',
31
- 'ibex', 'jays', 'koalas', 'lynx', 'moles', 'newts', 'otters', 'pumas',
32
- 'quails', 'rams', 'seals', 'tigers', 'voles', 'wolves', 'yaks', 'zebras',
33
- 'badgers', 'cranes', 'deer', 'eagles', 'finches', 'geese', 'herons', 'jaguars',
34
- 'lemurs', 'mice', 'orcas', 'pandas', 'ravens', 'sloths', 'tunas', 'wasps',
35
- ];
36
- const NOUNS_B = [
37
- 'dance', 'sing', 'leap', 'run', 'jump', 'climb', 'fly', 'swim',
38
- 'rest', 'wake', 'roam', 'greet', 'wander', 'gather', 'forage', 'travel',
39
- 'glide', 'sprint', 'tumble', 'wave', 'cheer', 'rally', 'parade', 'march',
40
- 'hop', 'frolic', 'caper', 'romp', 'zip', 'dart', 'snooze', 'munch',
41
- 'chatter', 'squeak', 'howl', 'bark', 'purr', 'roar', 'hum', 'click',
42
- ];
18
+ const SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
19
+ const SLUG_STOPWORDS = new Set(['a', 'an', 'the', 'and', 'or', 'of', 'to', 'for', 'in', 'on', 'is', 'are', 'now', 'its']);
20
+ const SLUG_MAX_WORDS = 7;
43
21
 
44
- function pick(arr) {
45
- return arr[Math.floor(Math.random() * arr.length)];
22
+ // Kebab-case slug from the bold lead sentence (or the first sentence), minus filler words.
23
+ function deriveFragmentSlug(body) {
24
+ const bold = /\*\*([^*]+)\*\*/.exec(body || '');
25
+ const lead = bold ? bold[1] : String(body || '').trim().split(/[.!?\n]/)[0];
26
+ const words = lead
27
+ .toLowerCase()
28
+ .replace(/'s\b/g, '')
29
+ .split(/[^a-z0-9]+/)
30
+ .filter((w) => w && !SLUG_STOPWORDS.has(w));
31
+ return words.slice(0, SLUG_MAX_WORDS).join('-') || 'change';
46
32
  }
47
33
 
48
- function generateFragmentName() {
49
- return `${pick(ADJECTIVES)}-${pick(NOUNS_A)}-${pick(NOUNS_B)}`;
34
+ function fragmentBaseName({ pr, body, name }) {
35
+ return `${pr}-${name || deriveFragmentSlug(body)}`;
50
36
  }
51
37
 
52
38
  // Allowed Keep-a-Changelog section types. Used by both scaffoldFragment
53
39
  // (sanitization at write time) and parse.cjs (validation at consume time).
54
40
  const ALLOWED_TYPES = new Set(['Added', 'Changed', 'Deprecated', 'Removed', 'Fixed', 'Security']);
55
41
 
56
- function scaffoldFragment({ repo, type, pr, body }) {
42
+ function scaffoldFragment({ repo, type, pr, body, name = null }) {
57
43
  // Sanitize: reject any type value not on the allowlist BEFORE embedding it
58
44
  // in frontmatter. A newline in `type` would corrupt the fragment; an
59
45
  // unrecognized value would be rejected later by parse.cjs but with a
@@ -63,28 +49,24 @@ function scaffoldFragment({ repo, type, pr, body }) {
63
49
  `scaffoldFragment: type=${JSON.stringify(type)} is not one of [${[...ALLOWED_TYPES].join(', ')}]`,
64
50
  );
65
51
  }
52
+ if (name !== null && !SLUG_RE.test(name)) {
53
+ throw new Error(`scaffoldFragment: name=${JSON.stringify(name)} is not kebab-case (a-z, 0-9, hyphens)`);
54
+ }
66
55
  const dir = path.join(repo, '.changeset');
67
56
  fs.mkdirSync(dir, { recursive: true });
68
57
  const content = `---\ntype: ${type}\npr: ${pr}\n---\n${body}\n`;
69
- // Atomic create: writeFileSync with `flag: 'wx'` fails (EEXIST) when the
70
- // file already exists, so concurrent invocations can't race past
71
- // `existsSync` and overwrite each other. Re-roll the random name on
72
- // collision; fail loudly after exhausting the retry budget.
73
- for (let i = 0; i < 16; i++) {
74
- const name = generateFragmentName();
75
- const target = path.join(dir, `${name}.md`);
58
+ // flag 'wx' fails with EEXIST instead of overwriting, so concurrent runs cannot clobber a fragment.
59
+ const base = fragmentBaseName({ pr, body, name });
60
+ for (let i = 1; i <= 50; i++) {
61
+ const target = path.join(dir, `${i === 1 ? base : `${base}-${i}`}.md`);
76
62
  try {
77
63
  fs.writeFileSync(target, content, { flag: 'wx' });
78
64
  return target;
79
65
  } catch (e) {
80
66
  if (e.code !== 'EEXIST') throw e;
81
- // collision — try another random draw
82
67
  }
83
68
  }
84
- throw new Error(
85
- 'scaffoldFragment: 16 random filename draws all collided; ' +
86
- 'expand the word lists or investigate corrupted .changeset/ state',
87
- );
69
+ throw new Error(`scaffoldFragment: ${base}.md and 49 numbered variants already exist; pass --name`);
88
70
  }
89
71
 
90
72
  function parseArgs(argv) {
@@ -102,7 +84,7 @@ function parseArgs(argv) {
102
84
 
103
85
  for (let i = 0; i < argv.length; i++) {
104
86
  const a = argv[i];
105
- if (a === '--type' || a === '--pr' || a === '--body' || a === '--repo') {
87
+ if (a === '--type' || a === '--pr' || a === '--body' || a === '--name' || a === '--repo') {
106
88
  const r = requireValue(a, i);
107
89
  if (!r.ok) return { ok: false, error: r.error };
108
90
  if (a === '--type') opts.type = r.value;
@@ -114,6 +96,7 @@ function parseArgs(argv) {
114
96
  const trimmed = r.value.trim();
115
97
  opts.pr = /^\d+$/.test(trimmed) ? Number(trimmed) : NaN;
116
98
  } else if (a === '--body') opts.body = r.value;
99
+ else if (a === '--name') opts.name = r.value;
117
100
  else if (a === '--repo') opts.repo = r.value;
118
101
  i++;
119
102
  continue;
@@ -127,7 +110,7 @@ function main() {
127
110
  const parsed = parseArgs(process.argv.slice(2));
128
111
  if (!parsed.ok) {
129
112
  process.stderr.write(`${parsed.error}\n`);
130
- process.stderr.write('usage: changeset/new.cjs --type <Fixed|Added|...> --pr NNNN --body "..."\n');
113
+ process.stderr.write('usage: changeset/new.cjs --type <Fixed|Added|...> --pr NNNN --body "..." [--name <slug>]\n');
131
114
  throw new ExitError(2);
132
115
  }
133
116
  const { opts } = parsed;
@@ -140,7 +123,7 @@ function main() {
140
123
  const prMissing = opts.pr === null;
141
124
  const prNaN = typeof opts.pr === 'number' && Number.isNaN(opts.pr);
142
125
  if (!opts.type || prMissing || prNaN || !opts.body) {
143
- throw new ExitError(2, 'usage: changeset/new.cjs --type <Fixed|Added|...> --pr NNNN --body "..."');
126
+ throw new ExitError(2, 'usage: changeset/new.cjs --type <Fixed|Added|...> --pr NNNN --body "..." [--name <slug>]');
144
127
  }
145
128
  const file = scaffoldFragment(opts);
146
129
  process.stdout.write(`${path.relative(process.cwd(), file)}\n`);
@@ -148,4 +131,4 @@ function main() {
148
131
 
149
132
  if (require.main === module) runMain(main);
150
133
 
151
- module.exports = { generateFragmentName, scaffoldFragment, parseArgs, ALLOWED_TYPES };
134
+ module.exports = { deriveFragmentSlug, fragmentBaseName, scaffoldFragment, parseArgs, ALLOWED_TYPES };
@@ -2,16 +2,16 @@
2
2
  'use strict';
3
3
 
4
4
  /**
5
- * dev-env.cjs — keep a stable pp-core install and a development build apart.
5
+ * dev-env.cjs — keep the global pp-core install and a development build apart.
6
+ * FORK-ONLY (PBI 74336)
6
7
  *
7
- * stable [<tag>] [--dir <dir>] [--runtime <id>] worktree on the latest release tag, installed --global
8
+ * setup [--runtime <id>] build this checkout on its current branch, installed --global
8
9
  * dev-setup [--config-dir <dir>] [--no-shell] npm ci, install the dev profile, put claude-dev on PATH
9
10
  * install [--config-dir <dir>] [--no-build] build, install into a separate Claude profile
10
11
  * install --local [<dir>] [--no-build] build, install into a sandbox project
11
12
  * claude [--config-dir <dir>] [-- <args>] start the Claude CLI on the dev profile
12
13
  *
13
- * Defaults: the worktree is pp-core-stable next to the main checkout; the profile is
14
- * PP_DEV_CONFIG_DIR, else ~/.claude-dev; the sandbox is ../pp-sandbox.
14
+ * Defaults: the profile is PP_DEV_CONFIG_DIR, else ~/.claude-dev; the sandbox is ../pp-sandbox.
15
15
  */
16
16
 
17
17
  const fs = require('node:fs');
@@ -25,27 +25,21 @@ const INSTALLER = path.join(ROOT, 'bin', 'install.js');
25
25
 
26
26
  const USAGE = [
27
27
  'Usage:',
28
- ' npm run setup [-- [<tag>] [--dir <dir>] [--runtime <id>]]',
28
+ ' npm run setup [-- --runtime <id>]',
29
29
  ' npm run dev:setup [-- [--config-dir <dir>] [--no-shell]]',
30
30
  ' npm run dev:install [-- [--config-dir <dir>] [--no-build]]',
31
31
  ' npm run dev:install -- --local [<dir>] [--no-build]',
32
32
  ' npm run dev:claude [-- <claude args>]',
33
33
  ].join('\n');
34
34
 
35
- function parseStableArgs(rest) {
36
- const opts = { command: 'stable', tag: null, dir: null, runtime: 'claude' };
35
+ function parseSetupArgs(rest) {
36
+ const opts = { command: 'setup', runtime: 'claude' };
37
37
  for (let i = 0; i < rest.length; i++) {
38
38
  const arg = rest[i];
39
- if (arg === '--dir') {
40
- const value = rest[++i];
41
- if (!value) return { error: '--dir requires a path' };
42
- opts.dir = value;
43
- } else if (arg === '--runtime') {
39
+ if (arg === '--runtime') {
44
40
  const value = rest[++i];
45
41
  if (!value) return { error: '--runtime requires a runtime id' };
46
42
  opts.runtime = value;
47
- } else if (!arg.startsWith('-') && opts.tag === null) {
48
- opts.tag = arg;
49
43
  } else {
50
44
  return { error: `unknown option: ${arg}` };
51
45
  }
@@ -72,7 +66,7 @@ function parseDevSetupArgs(rest) {
72
66
 
73
67
  function parseArgs(argv) {
74
68
  const [command, ...rest] = argv;
75
- if (command === 'stable') return parseStableArgs(rest);
69
+ if (command === 'setup') return parseSetupArgs(rest);
76
70
  if (command === 'dev-setup') return parseDevSetupArgs(rest);
77
71
  if (command !== 'install' && command !== 'claude') {
78
72
  return { error: `unknown command: ${command === undefined ? '(none)' : command}` };
@@ -133,7 +127,7 @@ function resolveLocalDir(opts, root = ROOT) {
133
127
  return path.resolve(opts.localDir || path.join(root, '..', 'pp-sandbox'));
134
128
  }
135
129
 
136
- // The stable profile is ~/.claude; overwriting it with a dev build is what this script exists to avoid.
130
+ // ~/.claude holds the global install from npm run setup; the dev routes never write there.
137
131
  function assertNotStableProfile(dir, home = os.homedir()) {
138
132
  if (samePath(dir, path.join(home, '.claude'))) {
139
133
  throw new ExitError(1, `Refusing to install a dev build into the stable profile ${dir}.`);
@@ -167,23 +161,6 @@ function git(args, cwd) {
167
161
  return execFileSync('git', args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
168
162
  }
169
163
 
170
- // Highest vX.Y.Z (or X.Y.Z) tag; pre-release tags are skipped.
171
- function pickLatestTag(tags) {
172
- const parsed = tags
173
- .map((tag) => ({ tag, m: /^v?(\d+)\.(\d+)\.(\d+)$/.exec(tag) }))
174
- .filter((t) => t.m)
175
- .map((t) => ({ tag: t.tag, v: t.m.slice(1).map(Number) }));
176
- parsed.sort((a, b) => b.v[0] - a.v[0] || b.v[1] - a.v[1] || b.v[2] - a.v[2]);
177
- return parsed.length ? parsed[0].tag : null;
178
- }
179
-
180
- // Next to the main checkout, so running this from any worktree finds the same directory.
181
- function resolveStableDir(opts, repo = ROOT) {
182
- if (opts.dir) return path.resolve(opts.dir);
183
- const commonDir = path.resolve(repo, git(['rev-parse', '--git-common-dir'], repo));
184
- return path.join(path.dirname(commonDir), '..', 'pp-core-stable');
185
- }
186
-
187
164
  // The runtimes install.js takes as --<id>, read from the capability manifests so no build is needed.
188
165
  function listRuntimes(root = ROOT) {
189
166
  const capDir = path.join(root, 'capabilities');
@@ -206,57 +183,22 @@ function resolveRuntime(id, root = ROOT) {
206
183
  return found;
207
184
  }
208
185
 
209
- function listWorktrees(repo) {
210
- return git(['worktree', 'list', '--porcelain'], repo)
211
- .split(/\r?\n/)
212
- .filter((line) => line.startsWith('worktree '))
213
- .map((line) => path.resolve(line.slice('worktree '.length)));
214
- }
215
-
216
- // Adds the worktree at the tag, or moves an existing clean one to it; never touches your own checkout.
217
- function prepareStableWorktree({ repo, dir, tag }) {
218
- try {
219
- git(['rev-parse', '--verify', '--quiet', `refs/tags/${tag}`], repo);
220
- } catch {
221
- throw new ExitError(1, `Tag ${tag} does not exist in ${repo}.`);
222
- }
223
- let known = listWorktrees(repo).some((w) => samePath(w, dir));
224
- // A worktree folder deleted by hand stays registered; prune it so it is added again.
225
- if (known && !fs.existsSync(dir)) {
226
- git(['worktree', 'prune'], repo);
227
- known = false;
228
- }
229
- if (!known) {
230
- if (fs.existsSync(dir)) {
231
- throw new ExitError(1, `${dir} exists but is not a worktree of ${repo}; pass --dir <other dir>.`);
232
- }
233
- git(['worktree', 'add', '--detach', dir, tag], repo);
234
- return;
235
- }
236
- if (git(['status', '--porcelain'], dir) !== '') {
237
- throw new ExitError(1, `The stable worktree ${dir} has local changes; commit or discard them first.`);
238
- }
239
- git(['checkout', '--quiet', '--detach', tag], dir);
240
- }
241
-
242
- function installStable(opts) {
186
+ // Installs the checkout this runs from, so the global install follows its current branch.
187
+ function installSetup(opts) {
243
188
  const runtime = resolveRuntime(opts.runtime);
244
189
  npmCliPath();
245
- const dir = resolveStableDir(opts);
246
- try {
247
- git(['fetch', '--tags', '--quiet', 'origin'], ROOT);
248
- } catch {
249
- process.stderr.write('Could not fetch tags from origin; using the tags already in this clone.\n');
250
- }
251
- const tag = opts.tag || pickLatestTag(git(['tag', '--list'], ROOT).split(/\r?\n/));
252
- if (!tag) throw new ExitError(1, 'No release tag found; pass one: npm run setup -- <tag>.');
253
- prepareStableWorktree({ repo: ROOT, dir, tag });
254
- process.stdout.write(`Stable worktree ${dir} is on ${tag}.\n`);
255
- runNpm(['ci'], dir);
256
- runNpm(['run', 'build'], dir);
257
- execFileSync(process.execPath, [path.join(dir, 'bin', 'install.js'), `--${runtime.id}`, '--global'], { cwd: dir, stdio: 'inherit' });
190
+ runNpm(['ci'], ROOT);
191
+ runBuild();
192
+ execFileSync(process.execPath, [INSTALLER, `--${runtime.id}`, '--global'], { cwd: ROOT, stdio: 'inherit' });
258
193
  const again = runtime.id === 'claude' ? 'npm run setup' : `npm run setup -- --runtime ${runtime.id}`;
259
- process.stdout.write(`\npp-core ${tag} is installed as the global pp-core for ${runtime.title}. Update later with: ${again}\n`);
194
+ process.stdout.write(`
195
+ pp-core from ${describeCheckout(devBuildInfo())} is installed as the global pp-core for ${runtime.title}. Update later with: ${again}
196
+ `);
197
+ }
198
+
199
+ function describeCheckout(info) {
200
+ const ref = info.commit ? `${info.branch || 'detached HEAD'}@${info.commit}` : info.checkout;
201
+ return info.dirty ? `${ref} (with uncommitted changes)` : ref;
260
202
  }
261
203
 
262
204
  // The statusline shows a DEV badge with this build's branch and commit when this file is present.
@@ -429,7 +371,7 @@ function main(argv = process.argv.slice(2)) {
429
371
  if (opts.error) throw new ExitError(2, `${opts.error}\n${USAGE}`);
430
372
  if (opts.command === 'claude') return launchClaude(opts);
431
373
  if (opts.command === 'dev-setup') devSetup(opts);
432
- else if (opts.command === 'stable') installStable(opts);
374
+ else if (opts.command === 'setup') installSetup(opts);
433
375
  else if (opts.local) installSandbox(opts);
434
376
  else installProfile(opts);
435
377
  return 0;
@@ -441,9 +383,9 @@ module.exports = {
441
383
  parseArgs,
442
384
  resolveConfigDir,
443
385
  resolveLocalDir,
444
- resolveStableDir,
445
386
  resolveLaunchDir,
446
387
  devBuildInfo,
388
+ describeCheckout,
447
389
  renderRcLauncher,
448
390
  upsertLauncher,
449
391
  windowsLauncherDir,
@@ -452,10 +394,8 @@ module.exports = {
452
394
  writeWindowsLaunchers,
453
395
  pathHasEntry,
454
396
  appendPathEntry,
455
- pickLatestTag,
456
397
  listRuntimes,
457
398
  resolveRuntime,
458
- prepareStableWorktree,
459
399
  assertNotStableProfile,
460
400
  assertOutsideRepo,
461
401
  samePath,