@pareto4/pp-core 1.13.0 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/.claude-plugin/plugin.json +3 -3
  2. package/README.md +31 -12
  3. package/agents/pp-code-fixer.md +0 -11
  4. package/agents/pp-code-reviewer.md +1 -77
  5. package/agents/pp-debug-session-manager.md +9 -6
  6. package/agents/pp-executor.md +12 -29
  7. package/agents/pp-plan-checker.md +4 -4
  8. package/agents/pp-planner.md +34 -20
  9. package/agents/pp-vault-reviewer.md +100 -23
  10. package/agents/pp-verifier.md +1 -3
  11. package/bin/install.js +10 -3
  12. package/commands/pp-execute-phase.md +1 -3
  13. package/commands/pp-ns-manage.md +1 -1
  14. package/commands/pp-ns-review.md +1 -1
  15. package/commands/pp-plan-phase.md +1 -2
  16. package/commands/pp-vault-promote.md +7 -8
  17. package/commands/pp-vault-review.md +6 -5
  18. package/commands/pp-verify-work.md +1 -2
  19. package/gemini-extension.json +1 -1
  20. package/hooks/dist/pp-check-update.js +0 -0
  21. package/hooks/dist/pp-cursor-session-start.js +1 -1
  22. package/hooks/dist/pp-statusline.js +1 -1
  23. package/hooks/dist/pp-update-banner.js +0 -0
  24. package/hooks/pp-check-update.js +0 -0
  25. package/hooks/pp-cursor-session-start.js +1 -1
  26. package/hooks/pp-statusline.js +1 -1
  27. package/hooks/pp-update-banner.js +0 -0
  28. package/package.json +10 -18
  29. package/pareto-skills/_shared/create-project-board.sh +0 -0
  30. package/pareto-skills/pp-project-setup/SKILL.md +1 -1
  31. package/pp-core/bin/check-latest-version.cjs +0 -0
  32. package/pp-core/bin/detect-build-commands.sh +2 -2
  33. package/pp-core/bin/lib/capability-activation.cjs +83 -9
  34. package/pp-core/bin/lib/capability-registry.cjs +201 -229
  35. package/pp-core/bin/lib/capability-state.cjs +93 -1
  36. package/pp-core/bin/lib/capability-writer.cjs +3 -3
  37. package/pp-core/bin/lib/check-command-router.cjs +1906 -1514
  38. package/pp-core/bin/lib/clusters.cjs +3 -0
  39. package/pp-core/bin/lib/command-aliases.cjs +0 -35
  40. package/pp-core/bin/lib/commands.cjs +9 -4
  41. package/pp-core/bin/lib/compose-bindings.cjs +251 -88
  42. package/pp-core/bin/lib/config-loader.cjs +7 -103
  43. package/pp-core/bin/lib/config-schema.cjs +1 -1
  44. package/pp-core/bin/lib/config.cjs +151 -62
  45. package/pp-core/bin/lib/decisions.cjs +4 -4
  46. package/pp-core/bin/lib/discovery-vault-check.cjs +246 -0
  47. package/pp-core/bin/lib/frontmatter.cjs +1 -38
  48. package/pp-core/bin/lib/git-base-branch.cjs +11 -12
  49. package/pp-core/bin/lib/git-provider.cjs +9 -10
  50. package/pp-core/bin/lib/init-command-router.cjs +5 -8
  51. package/pp-core/bin/lib/init-contract.cjs +620 -738
  52. package/pp-core/bin/lib/init-interactive.cjs +62 -4
  53. package/pp-core/bin/lib/init.cjs +24 -51
  54. package/pp-core/bin/lib/install-profiles.cjs +1 -1
  55. package/pp-core/bin/lib/io.cjs +4 -2
  56. package/pp-core/bin/lib/loop-resolver.cjs +165 -129
  57. package/pp-core/bin/lib/org-profile.cjs +402 -161
  58. package/pp-core/bin/lib/package-identity.cjs +3 -3
  59. package/pp-core/bin/lib/phase-command-router.cjs +1 -29
  60. package/pp-core/bin/lib/phase-scope.cjs +1 -1
  61. package/pp-core/bin/lib/phase.cjs +11 -253
  62. package/pp-core/bin/lib/platform-bindings.cjs +40 -19
  63. package/pp-core/bin/lib/profile-output.cjs +1 -1
  64. package/pp-core/bin/lib/shell-command-projection.cjs +104 -1
  65. package/pp-core/bin/lib/state.cjs +11 -85
  66. package/pp-core/bin/lib/template.cjs +1 -1
  67. package/pp-core/bin/lib/vault-command-router.cjs +1682 -477
  68. package/pp-core/bin/lib/vault-coordinates.cjs +16 -7
  69. package/pp-core/bin/lib/vault-index.cjs +656 -0
  70. package/pp-core/bin/lib/vault-seal.cjs +1851 -0
  71. package/pp-core/bin/lib/vault-standards-promote.cjs +577 -0
  72. package/pp-core/bin/lib/vault-standards-review.cjs +293 -0
  73. package/pp-core/bin/lib/vault-standards.cjs +1504 -0
  74. package/pp-core/bin/lib/vault-trace.cjs +155 -2778
  75. package/pp-core/bin/lib/vault-update.cjs +10 -59
  76. package/pp-core/bin/lib/vault.cjs +2570 -950
  77. package/pp-core/bin/lib/verify.cjs +0 -11
  78. package/pp-core/bin/portable-timeout.sh +1 -1
  79. package/pp-core/bin/pp-tools.cjs +157 -54
  80. package/pp-core/bin/shared/config-defaults.manifest.json +0 -7
  81. package/pp-core/bin/shared/config-schema.manifest.json +0 -3
  82. package/pp-core/bin/shared/model-catalog.json +2 -2
  83. package/pp-core/bin/verify-reapply-patches.cjs +0 -0
  84. package/pp-core/org-profile.json +26 -0
  85. package/pp-core/references/autonomous-best-effort.md +8 -7
  86. package/pp-core/references/loop-hook-dispatch.md +0 -25
  87. package/pp-core/references/planner-gap-closure.md +0 -1
  88. package/pp-core/references/planner-tdd.md +0 -1
  89. package/pp-core/references/planner-vault-standards.md +311 -0
  90. package/pp-core/references/planning-config.md +0 -8
  91. package/pp-core/references/tdd.md +0 -1
  92. package/pp-core/templates/README.md +1 -0
  93. package/pp-core/templates/VAULT-REVIEW.md +56 -0
  94. package/pp-core/templates/phase-prompt.md +2 -6
  95. package/pp-core/workflows/_runtime-launcher.snippet.sh +0 -0
  96. package/pp-core/workflows/add-tests.md +55 -4
  97. package/pp-core/workflows/audit-fix.md +63 -2
  98. package/pp-core/workflows/audit-milestone.md +1 -1
  99. package/pp-core/workflows/autonomous.md +85 -11
  100. package/pp-core/workflows/check-todos.md +13 -2
  101. package/pp-core/workflows/code-review-fix.md +59 -3
  102. package/pp-core/workflows/code-review.md +10 -57
  103. package/pp-core/workflows/complete-milestone.md +1 -1
  104. package/pp-core/workflows/debug.md +54 -4
  105. package/pp-core/workflows/discovery-sprint/prefill.md +19 -7
  106. package/pp-core/workflows/discovery-sprint/prototype.md +46 -49
  107. package/pp-core/workflows/discovery-sprint/session-13-gates.md +2 -0
  108. package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +1 -3
  109. package/pp-core/workflows/discovery-sprint/sessions/02-as-is.md +1 -3
  110. package/pp-core/workflows/discovery-sprint/sessions/03-to-be.md +1 -3
  111. package/pp-core/workflows/discovery-sprint/sessions/04-ux-design.md +20 -24
  112. package/pp-core/workflows/discovery-sprint/sessions/05-architecture.md +1 -3
  113. package/pp-core/workflows/discovery-sprint/sessions/06-security.md +1 -3
  114. package/pp-core/workflows/discovery-sprint/sessions/07-identity.md +1 -3
  115. package/pp-core/workflows/discovery-sprint/sessions/08-integration.md +1 -3
  116. package/pp-core/workflows/discovery-sprint/sessions/09-qa.md +1 -3
  117. package/pp-core/workflows/discovery-sprint/sessions/10-infrastructure.md +1 -6
  118. package/pp-core/workflows/discovery-sprint/sessions/11-roadmap.md +1 -3
  119. package/pp-core/workflows/discovery-sprint/sessions/12-delivery-model.md +1 -3
  120. package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +1 -3
  121. package/pp-core/workflows/discovery-sprint/templates/context-template.md +1 -1
  122. package/pp-core/workflows/discovery-sprint/vault-context.md +207 -0
  123. package/pp-core/workflows/discovery-sprint.md +7 -3
  124. package/pp-core/workflows/discovery-to-build/steps/architecture-deliverables.md +4 -4
  125. package/pp-core/workflows/discovery-to-build/templates/pp-handoff.md +43 -1
  126. package/pp-core/workflows/discovery-to-build.md +10 -2
  127. package/pp-core/workflows/discuss-phase.md +3 -10
  128. package/pp-core/workflows/execute-phase/steps/check-query-gate.md +327 -0
  129. package/pp-core/workflows/execute-phase/steps/client-context-gate.md +3 -2
  130. package/pp-core/workflows/execute-phase/steps/cross-ai-delegation.md +1 -1
  131. package/pp-core/workflows/execute-phase/steps/execute-pre-dispatch.md +78 -0
  132. package/pp-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +94 -0
  133. package/pp-core/workflows/execute-phase/steps/post-merge-gate.md +6 -8
  134. package/pp-core/workflows/execute-phase/steps/vault-fetch.md +115 -14
  135. package/pp-core/workflows/execute-phase/steps/vault-load-gate.md +219 -20
  136. package/pp-core/workflows/execute-phase/steps/verify-post-gate.md +147 -0
  137. package/pp-core/workflows/execute-phase.md +152 -128
  138. package/pp-core/workflows/execute-plan.md +54 -6
  139. package/pp-core/workflows/fast.md +53 -4
  140. package/pp-core/workflows/help/modes/full.md +3 -3
  141. package/pp-core/workflows/init/steps/client-context-capture.md +101 -0
  142. package/pp-core/workflows/init/steps/gitlab-provisioning.md +130 -0
  143. package/pp-core/workflows/init/steps/project-md-deferral-narration.md +66 -0
  144. package/pp-core/workflows/init/steps/reachability-outcome.md +70 -0
  145. package/pp-core/workflows/init.md +232 -272
  146. package/pp-core/workflows/new-project.md +31 -23
  147. package/pp-core/workflows/plan-phase.md +98 -45
  148. package/pp-core/workflows/quick.md +117 -3
  149. package/pp-core/workflows/secure-phase.md +1 -1
  150. package/pp-core/workflows/ship.md +57 -28
  151. package/pp-core/workflows/undo.md +1 -1
  152. package/pp-core/workflows/update.md +3 -3
  153. package/pp-core/workflows/validate-phase.md +58 -3
  154. package/pp-core/workflows/vault-review.md +161 -23
  155. package/pp-core/workflows/vault-write-back.md +73 -110
  156. package/pp-core/workflows/verify-work.md +12 -14
  157. package/scripts/changeset/cli.cjs +6 -5
  158. package/scripts/changeset/lint.cjs +0 -0
  159. package/scripts/changeset/new.cjs +33 -50
  160. package/scripts/dev-env.cjs +25 -85
  161. package/scripts/gen-capability-registry.cjs +120 -13
  162. package/scripts/lib/allowlist-ratchet.cjs +1 -103
  163. package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -2
  164. package/scripts/lint-docs-required.cjs +0 -0
  165. package/scripts/lint-pr-check-project-dir.cjs +1 -3
  166. package/scripts/lint-test-file-count.allowlist.json +3 -19
  167. package/scripts/lint-test-file-count.cjs +5 -6
  168. package/scripts/prompt-injection-scan.sh +19 -17
  169. package/scripts/release-channel.cjs +0 -0
  170. package/scripts/sync-manifest-versions.cjs +5 -4
  171. package/scripts/sync-rulesets.sh +1 -1
  172. package/scripts/update-size-baseline.cjs +30 -274
  173. package/scripts/verify-opengsd-sweep.cjs +3 -5
  174. package/scripts/workflow-size.cjs +9 -61
  175. package/pp-core/bin/lib/dispatch-plan.cjs +0 -483
  176. package/pp-core/bin/lib/install-run.cjs +0 -536
  177. package/pp-core/bin/lib/loop-gates.cjs +0 -213
  178. package/pp-core/bin/lib/phase-manifest.cjs +0 -527
  179. package/pp-core/bin/lib/phase-tier.cjs +0 -480
  180. package/pp-core/bin/lib/plan-graph.cjs +0 -368
  181. package/pp-core/bin/lib/practices-review-check.cjs +0 -938
  182. package/pp-core/bin/lib/vault-audit.cjs +0 -2070
  183. package/pp-core/bin/lib/vault-practices.cjs +0 -1990
  184. package/pp-core/bin/lib/vault-review.cjs +0 -2359
  185. package/pp-core/bin/lib/vault-write-back.cjs +0 -877
  186. package/pp-core/references/executor-vault-trace.md +0 -47
  187. package/pp-core/references/planner-plan-graph.md +0 -66
  188. package/pp-core/references/planner-vault-practices.md +0 -112
  189. package/pp-core/templates/practices/README.md +0 -251
  190. package/pp-core/templates/practices/error-handling.md +0 -18
  191. package/pp-core/templates/practices/minimal-comments.md +0 -14
  192. package/pp-core/templates/practices/naming-convention.md +0 -18
  193. package/pp-core/templates/practices/practice-template.md +0 -23
  194. package/pp-core/workflows/discovery-sprint/vault-fetch.md +0 -37
  195. package/pp-core/workflows/execute-phase/steps/checkpoint-handling.md +0 -45
  196. package/pp-core/workflows/execute-phase/steps/install-run.md +0 -29
  197. package/pp-core/workflows/execute-phase/steps/phase-close-rebaseline.md +0 -23
  198. package/pp-core/workflows/execute-phase/steps/serial-group-dispatch.md +0 -73
  199. package/pp-core/workflows/execute-phase/steps/vault-door-gate.md +0 -19
  200. package/pp-core/workflows/plan-phase/steps/phase-tier.md +0 -43
  201. package/scripts/lib/size-budget.cjs +0 -311
  202. package/scripts/lint-neutrality-names.cjs +0 -694
  203. package/scripts/lint-org-coordinates.allowlist.json +0 -35
  204. package/scripts/lint-org-coordinates.cjs +0 -426
@@ -1,11 +1,10 @@
1
1
  <purpose>
2
- Take one local practice file from preview to a confirmed, pushed promotion into the client
3
- vault — and nothing more. This workflow is the single human gate in front of an
4
- irreversible outward action: it shows the operator the complete preview, asks once whether
5
- to proceed, and only on explicit approval re-invokes the same guarded CLI arm in confirmed
6
- mode. It never authors, edits, or reformats the practice file, and it performs no direct git
7
- operation of its own — every outward action happens inside the promote verb, which this
8
- workflow only calls.
2
+ Take one local standards document from preview to a confirmed, pushed promotion into the client
3
+ vault, together with its INDEX.md entry, and nothing more. This workflow is the single human gate
4
+ in front of an irreversible outward action: it shows the operator the complete preview, asks once
5
+ whether to proceed, and only on explicit approval re-invokes the same guarded CLI verb in confirmed
6
+ mode. It never authors, edits or reformats the document, and it performs no git operation of its
7
+ own; every outward action happens inside `pp_run vault standards promote`.
9
8
  </purpose>
10
9
 
11
10
  <required_reading>
@@ -15,79 +14,64 @@ Read all files referenced by the invoking prompt's execution_context before star
15
14
  <process>
16
15
 
17
16
  <step name="text_mode">
18
- Resolve `TEXT_MODE` here, before the first question, mirroring the same resolution `init.md`
19
- does before its own first prompt. Set `TEXT_MODE=true` if `--text` is present in
20
- `$ARGUMENTS`. When `TEXT_MODE` is active, replace every `AskUserQuestion` call in this
21
- workflow — including the gate in Step 4 — with a plain-text numbered list, and ask the
22
- operator to type their choice number. This is required because non-Claude runtimes have no
23
- interactive question tool, and the confirmation in Step 4 is the entire point of this
24
- workflow — it must degrade to plain text, never disappear.
25
- </step>
26
-
27
- <step name="resolve_launcher">
28
- Resolve the `pp_run` launcher shim before any other step, so every call below goes through
29
- the repo-local or installed `pp-tools` binary rather than assuming a global install:
30
-
31
- ```bash
32
- ```
17
+ Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS`. When `TEXT_MODE` is active, replace
18
+ every `AskUserQuestion` call in this workflow, including the gate below, with a plain-text numbered
19
+ list, and ask the operator to type their choice number. The confirmation is the point of this
20
+ workflow: it must degrade to plain text, never disappear.
33
21
  </step>
34
22
 
35
23
  <step name="resolve_input">
36
- Take the practice file path from `$ARGUMENTS` (the first token that is not `--text`). When
37
- none was given, ask the operator for one with a plain prompt:
24
+ Take from `$ARGUMENTS`:
38
25
 
39
- ```text
40
- Path to the local practice file to promote (e.g. practices/my-practice.md):
41
- ```
26
+ - the document path: the first token that is not a flag (required; ask for it with a plain prompt
27
+ when it is missing: `Path to the local standards document to promote (e.g. docs/standards/api-errors.md):`);
28
+ - `--path <vault-path>`: where the document goes in the vault (optional; defaults to
29
+ `standards/<file name>`);
30
+ - `--entry "<what it is>; used when <…>"`: the document's INDEX.md entry text (required when the
31
+ vault's INDEX.md does not list the document yet; replaces the existing line when given for a
32
+ listed document);
33
+ - `--section <title>`: the INDEX.md `##` section a new entry goes under (optional).
34
+
35
+ When the document is new to the vault and no `--entry` was given, ask the operator for one plain
36
+ sentence of the form `<what the document is>; used when <when a plan should follow it>`. Never
37
+ invent it.
42
38
 
43
- Do not compose, author, rewrite, or reformat the practice file at this or any later step —
44
- authoring stays outside this guarded path entirely. Whatever bytes are on disk right now at
45
- that path are exactly the bytes that will be previewed and, if approved, pushed.
39
+ Do not compose, rewrite or reformat the document at this or any later step. Whatever bytes are on
40
+ disk at that path are exactly the bytes that will be previewed and, if approved, pushed.
46
41
 
47
42
  ```bash
48
- PRACTICE_FILE="<the resolved path>"
43
+ DOC_FILE="<the resolved path>"
44
+ PROMOTE_ARGS=() # add --path, --entry and --section with their values, as given
49
45
  ```
50
46
  </step>
51
47
 
52
48
  <step name="preview">
53
- Run the promote verb with the file path and `--raw`, capturing stdout, stderr, and the exit
54
- code. This invocation performs no outward action — it only clones into a disposable
55
- directory, computes the diff, and reports.
49
+ Run the promote verb without `--confirmed`, capturing stdout, stderr and the exit code. This
50
+ invocation performs no outward action: it clones the client vault into a disposable directory,
51
+ writes the change there, and reports the diff.
56
52
 
57
53
  ```bash
58
54
  _PP_SHIM_NAME="pp-tools.cjs"; _pp_root() { ( for _v in $(env | sed -n 's/^\(GIT_[A-Za-z0-9_]*\)=.*/\1/p'); do unset "$_v"; done; _c=$(git rev-parse --git-common-dir 2>/dev/null); if [ "${_c##*/}" = .git ] && cd "$_c/.." 2>/dev/null; then pwd -P; else git rev-parse --show-toplevel 2>/dev/null || pwd; fi ); }; pp_run() { case "$PP_TOOLS" in *.cjs) node "$PP_TOOLS" "$@";; *) "$PP_TOOLS" "$@";; esac; }; _PP_RUNTIME_ROOT="${RUNTIME_DIR:-$(_pp_root)}"; PP_TOOLS="${_PP_RUNTIME_ROOT}/pp-core/bin/${_PP_SHIM_NAME}"; if [ -f "$PP_TOOLS" ]; then :; 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}"; 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}"; elif [ -f "$HOME/.claude/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="$HOME/.claude/pp-core/bin/${_PP_SHIM_NAME}"; 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}"; 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}"; 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}"; 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}"; 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}"; 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}"; 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}"; 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}"; 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}"; 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}"; 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}"; 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}"; 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}"; 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}"; 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}"; elif command -v pp-tools >/dev/null 2>&1; then PP_TOOLS="$(command -v 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
59
55
  PROMOTE_PREVIEW_OUT=$(mktemp)
60
56
  PROMOTE_PREVIEW_ERR=$(mktemp)
61
- pp_run vault practices promote "$PRACTICE_FILE" --raw >"$PROMOTE_PREVIEW_OUT" 2>"$PROMOTE_PREVIEW_ERR"
57
+ pp_run vault standards promote "$DOC_FILE" "${PROMOTE_ARGS[@]}" --raw >"$PROMOTE_PREVIEW_OUT" 2>"$PROMOTE_PREVIEW_ERR"
62
58
  PREVIEW_EXIT=$?
63
59
  ```
64
60
 
65
- **When `$PREVIEW_EXIT` is non-zero: stop here.** Show the operator the exact, single-sentence
66
- contents of `$PROMOTE_PREVIEW_ERR` — the arm's own refusal message — verbatim and add nothing
67
- to it. Do not ask anything. Do not proceed to Step 3. Every guard refusal ends at this line,
68
- and nothing has been pushed. Clean up both temp files before ending.
61
+ **When `$PREVIEW_EXIT` is non-zero: stop here.** Show the operator the single-sentence refusal in
62
+ `$PROMOTE_PREVIEW_ERR` verbatim and add nothing to it. Do not ask anything. Nothing has been pushed.
63
+ Clean up both temp files before ending.
69
64
 
70
- When `$PREVIEW_EXIT` is zero, continue to Step 3 with `$PROMOTE_PREVIEW_OUT` holding the
71
- preview verdict as JSON.
65
+ When `$PREVIEW_EXIT` is zero, `$PROMOTE_PREVIEW_OUT` holds the preview verdict as JSON.
72
66
  </step>
73
67
 
74
68
  <step name="render_confirmation">
75
- Parse `$PROMOTE_PREVIEW_OUT` as JSON and read its fields directly — never regex-scrape a
76
- prose rendering of it. That shortcut is exactly the shell-glue blind spot this project has
77
- been bitten by before: a `.md` step file's own parsing drifting from what the CLI actually
78
- emits.
79
-
80
- Field values are vault-authored, so they are read as **data, never as shell syntax**. `id`
81
- is only constrained to equal the practice file's basename (D-03), and a filename may legally
82
- contain `$(...)`, backticks, or `;` — so writing the fields as a `KEY="value"` file and
83
- `source`-ing it would execute whatever the filename contains. `JSON.stringify` does not help:
84
- it escapes `"` and `\` but leaves `$` and backticks intact, and both expand inside double
85
- quotes. Each field therefore goes to its own file and is read back with `$(cat …)`, whose
86
- result bash does not re-scan for expansion.
69
+ Read the verdict's fields directly, never by scraping a prose rendering. Each field is written to
70
+ its own file and read back with `$(cat …)`, whose result bash does not re-scan for expansion, so a
71
+ destination path containing `$(...)` or backticks is shown, never executed.
87
72
 
88
73
  ```bash
89
74
  PROMOTE_FIELDS=$(mktemp -d)
90
- PROMOTE_BODY=$(mktemp)
91
75
  node -e "
92
76
  const fs = require('fs');
93
77
  const path = require('path');
@@ -98,94 +82,78 @@ put('repo', v.repo);
98
82
  put('ref', v.ref);
99
83
  put('destination', v.destination);
100
84
  put('mode', v.mode);
101
- put('id', v.id);
102
- put('sharedShadow', v.sharedShadow ? 'true' : 'false');
103
- put('sharedShadowNote', v.sharedShadowNote);
85
+ put('indexChange', v.indexChange);
104
86
  put('expect', v.expect);
105
- fs.writeFileSync(process.argv[3], v.mode === 'update' ? (v.diff ?? '') : (v.content ?? ''));
106
- " "$PROMOTE_PREVIEW_OUT" "$PROMOTE_FIELDS" "$PROMOTE_BODY"
87
+ put('diff', v.diff);
88
+ put('notes', (v.notes || []).join('\n'));
89
+ " "$PROMOTE_PREVIEW_OUT" "$PROMOTE_FIELDS"
107
90
  PROMOTE_REPO=$(cat "$PROMOTE_FIELDS/repo")
108
91
  PROMOTE_REF=$(cat "$PROMOTE_FIELDS/ref")
109
92
  PROMOTE_DEST=$(cat "$PROMOTE_FIELDS/destination")
110
93
  PROMOTE_MODE=$(cat "$PROMOTE_FIELDS/mode")
111
- PROMOTE_ID=$(cat "$PROMOTE_FIELDS/id")
112
- PROMOTE_SHARED=$(cat "$PROMOTE_FIELDS/sharedShadow")
113
- PROMOTE_SHARED_NOTE=$(cat "$PROMOTE_FIELDS/sharedShadowNote")
94
+ PROMOTE_INDEX=$(cat "$PROMOTE_FIELDS/indexChange")
114
95
  PROMOTE_EXPECT=$(cat "$PROMOTE_FIELDS/expect")
115
96
  ```
116
97
 
117
- Show the operator, in this exact order, reading every value from the parsed fields above —
118
- never truncated, summarized, or collapsed to "the first N lines":
98
+ Show the operator, in this order, never truncated or summarised:
119
99
 
120
100
  1. The target vault repository (`$PROMOTE_REPO`) and ref (`$PROMOTE_REF`).
121
- 2. The destination path inside that repository (`$PROMOTE_DEST`).
122
- 3. Whether this **creates** a new practice or **updates** an existing one
123
- (`$PROMOTE_MODE`, for id `$PROMOTE_ID`).
124
- 4. When `$PROMOTE_SHARED` is `true`, the shared-vault disclosure sentence verbatim from
125
- `$PROMOTE_SHARED_NOTE` — this id also exists in the shared Pareto vault, and promoting it
126
- here makes the client's version the one that applies for this client.
127
- 5. One line stating plainly that the secret scan already run inside the promote verb found
128
- nothing in this file — a file that failed that scan would have refused before a preview
129
- was ever produced, so reaching this line already proves the scan passed.
130
- 6. The complete contents of `$PROMOTE_BODY` — the full diff when `$PROMOTE_MODE` is
131
- `update`, or the full new-file content when it is `create`. Show every byte; the operator
132
- is about to approve exactly these bytes, so nothing here may be shortened.
101
+ 2. The destination path in the vault (`$PROMOTE_DEST`), and whether this **creates** a new
102
+ standards document or **updates** an existing one (`$PROMOTE_MODE`).
103
+ 3. What happens to INDEX.md (`$PROMOTE_INDEX`): `added` (a new line), `replaced` (its line gets the
104
+ new entry text) or `unchanged`.
105
+ 4. One line stating that the secret scan inside the promote verb found nothing; a document that
106
+ failed it would have been refused before any preview existed.
107
+ 5. Any notes in `$PROMOTE_FIELDS/notes`.
108
+ 6. The complete contents of `$PROMOTE_FIELDS/diff`: the document and the INDEX.md change, every
109
+ byte. The operator is about to approve exactly these bytes.
133
110
  </step>
134
111
 
135
112
  <step name="gate">
136
- Ask the operator with `AskUserQuestion` (or a numbered plain-text prompt when `TEXT_MODE` is
137
- active) whether to push this to the client vault. Offer exactly two outcomes — there is no
138
- default, no timeout, and no auto-advance, because this is an irreversible outward action and
139
- this confirmation is the whole review step:
113
+ Ask once whether to push. Exactly two outcomes, no default, no timeout, no auto-advance:
140
114
 
141
115
  ```text
142
116
  AskUserQuestion([
143
117
  {
144
118
  header: "Push to client vault",
145
- question: "Push this practice to the client vault now?",
119
+ question: "Push this standards document to the client vault now?",
146
120
  multiSelect: false,
147
121
  options: [
148
- { label: "Push it", description: "Push exactly the diff/content shown above" },
122
+ { label: "Push it", description: "Push exactly the diff shown above" },
149
123
  { label: "Stop", description: "Do not push anything — the vault stays unchanged" }
150
124
  ]
151
125
  }
152
126
  ])
153
127
  ```
154
128
 
155
- **When the operator chooses to stop:** print one sentence saying nothing was pushed and the
156
- client vault is unchanged, clean up the temp files, and end the workflow here.
129
+ **When the operator chooses to stop:** say in one sentence that nothing was pushed and the client
130
+ vault is unchanged, clean up the temp paths, and end.
157
131
 
158
- **When the operator chooses to push:** continue to Step 5.
132
+ **When the operator chooses to push:** continue.
159
133
  </step>
160
134
 
161
135
  <step name="push">
162
- Only inside this approved branch, run the promote verb again with the same file path, the
163
- confirmed flag, the expect token carried verbatim from the preview verdict, and `--raw`. This
164
- workflow never runs an outward git operation itself — the only thing it does here is invoke
165
- this verb a second time; every push, every commit, and every other outward action happens
166
- inside the verb, not in this shell block.
136
+ Only inside this approved branch, run the verb again with the same arguments, `--confirmed`, and the
137
+ expect token carried verbatim from the preview.
167
138
 
168
139
  ```bash
169
140
  PROMOTE_PUSH_OUT=$(mktemp)
170
141
  PROMOTE_PUSH_ERR=$(mktemp)
171
- pp_run vault practices promote "$PRACTICE_FILE" --confirmed --expect "$PROMOTE_EXPECT" --raw >"$PROMOTE_PUSH_OUT" 2>"$PROMOTE_PUSH_ERR"
142
+ pp_run vault standards promote "$DOC_FILE" "${PROMOTE_ARGS[@]}" --confirmed --expect "$PROMOTE_EXPECT" --raw >"$PROMOTE_PUSH_OUT" 2>"$PROMOTE_PUSH_ERR"
172
143
  PUSH_EXIT=$?
173
144
  ```
174
145
 
175
- **When `$PUSH_EXIT` is non-zero:** show the arm's own single-sentence refusal from
176
- `$PROMOTE_PUSH_ERR` verbatim and end — the arm has already guaranteed nothing was pushed on a
177
- refusal. A concurrent-edit refusal here is expected, ordinary behavior: it means someone else
178
- changed this same practice in the client vault between the preview in Step 2 and this push.
179
- The remedy is simply to run this workflow again from the top — the new preview will diff
180
- against their current version. Clean up all temp files before ending.
181
-
182
- When `$PUSH_EXIT` is zero, continue to Step 6 with `$PROMOTE_PUSH_OUT` holding the push
183
- verdict as JSON.
146
+ **When `$PUSH_EXIT` is non-zero:** show the verb's single-sentence refusal from `$PROMOTE_PUSH_ERR`
147
+ verbatim and end; a refusal guarantees nothing was pushed. A concurrent-edit refusal is ordinary:
148
+ someone changed the document or INDEX.md in the vault since the preview. The remedy is to run this
149
+ workflow again from the top. Clean up all temp paths before ending.
184
150
  </step>
185
151
 
186
152
  <step name="report">
187
- Parse `$PROMOTE_PUSH_OUT` the same way Step 3 parsed the preview verdict — direct field
188
- reads, never a prose scrape:
153
+ Read the push verdict's fields the same way, then show the operator the repository, the destination,
154
+ the new commit, what happened to INDEX.md, and whether the local vault cache was refreshed. When the
155
+ refresh did not succeed, say so plainly: the next vault fetch picks the change up; the push itself
156
+ succeeded.
189
157
 
190
158
  ```bash
191
159
  node -e "
@@ -195,19 +163,14 @@ process.stdout.write(
195
163
  'repo=' + (v.repo ?? '') + '\n' +
196
164
  'destination=' + (v.destination ?? '') + '\n' +
197
165
  'commit=' + (v.commit ?? '') + '\n' +
166
+ 'indexChange=' + (v.indexChange ?? '') + '\n' +
198
167
  'cacheRefreshed=' + String(!!v.cacheRefreshed) + '\n'
199
168
  );
200
169
  " "$PROMOTE_PUSH_OUT"
201
170
  ```
202
171
 
203
- Show the operator: the repository, the destination path, the new commit the push produced,
204
- and whether the local read cache of the vault was refreshed. When the local cache refresh did
205
- not succeed, say so plainly and note that the next vault fetch will pick up the change — this
206
- is a cosmetic staleness in the local cache only, not a sign the push failed.
207
-
208
- Clean up every temp path created in this workflow (`$PROMOTE_PREVIEW_OUT`,
209
- `$PROMOTE_PREVIEW_ERR`, `$PROMOTE_BODY`, `$PROMOTE_PUSH_OUT`, `$PROMOTE_PUSH_ERR`, and the
210
- `$PROMOTE_FIELDS` **directory**, which needs `rm -rf`) before ending.
172
+ Clean up every temp path created in this workflow (`$PROMOTE_PREVIEW_OUT`, `$PROMOTE_PREVIEW_ERR`,
173
+ `$PROMOTE_PUSH_OUT`, `$PROMOTE_PUSH_ERR`, and the `$PROMOTE_FIELDS` directory with `rm -rf`).
211
174
  </step>
212
175
 
213
176
  </process>
@@ -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 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 };