@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.
- package/.claude-plugin/plugin.json +3 -3
- package/README.md +31 -12
- package/agents/pp-code-fixer.md +0 -11
- package/agents/pp-code-reviewer.md +1 -77
- package/agents/pp-debug-session-manager.md +9 -6
- package/agents/pp-executor.md +12 -29
- package/agents/pp-plan-checker.md +4 -4
- package/agents/pp-planner.md +34 -20
- package/agents/pp-vault-reviewer.md +100 -23
- package/agents/pp-verifier.md +1 -3
- package/bin/install.js +10 -3
- package/commands/pp-execute-phase.md +1 -3
- package/commands/pp-ns-manage.md +1 -1
- package/commands/pp-ns-review.md +1 -1
- package/commands/pp-plan-phase.md +1 -2
- package/commands/pp-vault-promote.md +7 -8
- package/commands/pp-vault-review.md +6 -5
- package/commands/pp-verify-work.md +1 -2
- package/gemini-extension.json +1 -1
- package/hooks/dist/pp-check-update.js +0 -0
- package/hooks/dist/pp-cursor-session-start.js +1 -1
- package/hooks/dist/pp-statusline.js +1 -1
- package/hooks/dist/pp-update-banner.js +0 -0
- package/hooks/pp-check-update.js +0 -0
- package/hooks/pp-cursor-session-start.js +1 -1
- package/hooks/pp-statusline.js +1 -1
- package/hooks/pp-update-banner.js +0 -0
- package/package.json +10 -18
- package/pareto-skills/_shared/create-project-board.sh +0 -0
- package/pareto-skills/pp-project-setup/SKILL.md +1 -1
- package/pp-core/bin/check-latest-version.cjs +0 -0
- package/pp-core/bin/detect-build-commands.sh +2 -2
- package/pp-core/bin/lib/capability-activation.cjs +83 -9
- package/pp-core/bin/lib/capability-registry.cjs +201 -229
- package/pp-core/bin/lib/capability-state.cjs +93 -1
- package/pp-core/bin/lib/capability-writer.cjs +3 -3
- package/pp-core/bin/lib/check-command-router.cjs +1906 -1514
- package/pp-core/bin/lib/clusters.cjs +3 -0
- package/pp-core/bin/lib/command-aliases.cjs +0 -35
- package/pp-core/bin/lib/commands.cjs +9 -4
- package/pp-core/bin/lib/compose-bindings.cjs +251 -88
- package/pp-core/bin/lib/config-loader.cjs +7 -103
- package/pp-core/bin/lib/config-schema.cjs +1 -1
- package/pp-core/bin/lib/config.cjs +151 -62
- package/pp-core/bin/lib/decisions.cjs +4 -4
- package/pp-core/bin/lib/discovery-vault-check.cjs +246 -0
- package/pp-core/bin/lib/frontmatter.cjs +1 -38
- package/pp-core/bin/lib/git-base-branch.cjs +11 -12
- package/pp-core/bin/lib/git-provider.cjs +9 -10
- package/pp-core/bin/lib/init-command-router.cjs +5 -8
- package/pp-core/bin/lib/init-contract.cjs +620 -738
- package/pp-core/bin/lib/init-interactive.cjs +62 -4
- package/pp-core/bin/lib/init.cjs +24 -51
- package/pp-core/bin/lib/install-profiles.cjs +1 -1
- package/pp-core/bin/lib/io.cjs +4 -2
- package/pp-core/bin/lib/loop-resolver.cjs +165 -129
- package/pp-core/bin/lib/org-profile.cjs +402 -161
- package/pp-core/bin/lib/package-identity.cjs +3 -3
- package/pp-core/bin/lib/phase-command-router.cjs +1 -29
- package/pp-core/bin/lib/phase-scope.cjs +1 -1
- package/pp-core/bin/lib/phase.cjs +11 -253
- package/pp-core/bin/lib/platform-bindings.cjs +40 -19
- package/pp-core/bin/lib/profile-output.cjs +1 -1
- package/pp-core/bin/lib/shell-command-projection.cjs +104 -1
- package/pp-core/bin/lib/state.cjs +11 -85
- package/pp-core/bin/lib/template.cjs +1 -1
- package/pp-core/bin/lib/vault-command-router.cjs +1682 -477
- package/pp-core/bin/lib/vault-coordinates.cjs +16 -7
- package/pp-core/bin/lib/vault-index.cjs +656 -0
- package/pp-core/bin/lib/vault-seal.cjs +1851 -0
- package/pp-core/bin/lib/vault-standards-promote.cjs +577 -0
- package/pp-core/bin/lib/vault-standards-review.cjs +293 -0
- package/pp-core/bin/lib/vault-standards.cjs +1504 -0
- package/pp-core/bin/lib/vault-trace.cjs +155 -2778
- package/pp-core/bin/lib/vault-update.cjs +10 -59
- package/pp-core/bin/lib/vault.cjs +2570 -950
- package/pp-core/bin/lib/verify.cjs +0 -11
- package/pp-core/bin/portable-timeout.sh +1 -1
- package/pp-core/bin/pp-tools.cjs +157 -54
- package/pp-core/bin/shared/config-defaults.manifest.json +0 -7
- package/pp-core/bin/shared/config-schema.manifest.json +0 -3
- package/pp-core/bin/shared/model-catalog.json +2 -2
- package/pp-core/bin/verify-reapply-patches.cjs +0 -0
- package/pp-core/org-profile.json +26 -0
- package/pp-core/references/autonomous-best-effort.md +8 -7
- package/pp-core/references/loop-hook-dispatch.md +0 -25
- package/pp-core/references/planner-gap-closure.md +0 -1
- package/pp-core/references/planner-tdd.md +0 -1
- package/pp-core/references/planner-vault-standards.md +311 -0
- package/pp-core/references/planning-config.md +0 -8
- package/pp-core/references/tdd.md +0 -1
- package/pp-core/templates/README.md +1 -0
- package/pp-core/templates/VAULT-REVIEW.md +56 -0
- package/pp-core/templates/phase-prompt.md +2 -6
- package/pp-core/workflows/_runtime-launcher.snippet.sh +0 -0
- package/pp-core/workflows/add-tests.md +55 -4
- package/pp-core/workflows/audit-fix.md +63 -2
- package/pp-core/workflows/audit-milestone.md +1 -1
- package/pp-core/workflows/autonomous.md +85 -11
- package/pp-core/workflows/check-todos.md +13 -2
- package/pp-core/workflows/code-review-fix.md +59 -3
- package/pp-core/workflows/code-review.md +10 -57
- package/pp-core/workflows/complete-milestone.md +1 -1
- package/pp-core/workflows/debug.md +54 -4
- package/pp-core/workflows/discovery-sprint/prefill.md +19 -7
- package/pp-core/workflows/discovery-sprint/prototype.md +46 -49
- package/pp-core/workflows/discovery-sprint/session-13-gates.md +2 -0
- package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/02-as-is.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/03-to-be.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/04-ux-design.md +20 -24
- package/pp-core/workflows/discovery-sprint/sessions/05-architecture.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/06-security.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/07-identity.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/08-integration.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/09-qa.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/10-infrastructure.md +1 -6
- package/pp-core/workflows/discovery-sprint/sessions/11-roadmap.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/12-delivery-model.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +1 -3
- package/pp-core/workflows/discovery-sprint/templates/context-template.md +1 -1
- package/pp-core/workflows/discovery-sprint/vault-context.md +207 -0
- package/pp-core/workflows/discovery-sprint.md +7 -3
- package/pp-core/workflows/discovery-to-build/steps/architecture-deliverables.md +4 -4
- package/pp-core/workflows/discovery-to-build/templates/pp-handoff.md +43 -1
- package/pp-core/workflows/discovery-to-build.md +10 -2
- package/pp-core/workflows/discuss-phase.md +3 -10
- package/pp-core/workflows/execute-phase/steps/check-query-gate.md +327 -0
- package/pp-core/workflows/execute-phase/steps/client-context-gate.md +3 -2
- package/pp-core/workflows/execute-phase/steps/cross-ai-delegation.md +1 -1
- package/pp-core/workflows/execute-phase/steps/execute-pre-dispatch.md +78 -0
- package/pp-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +94 -0
- package/pp-core/workflows/execute-phase/steps/post-merge-gate.md +6 -8
- package/pp-core/workflows/execute-phase/steps/vault-fetch.md +115 -14
- package/pp-core/workflows/execute-phase/steps/vault-load-gate.md +219 -20
- package/pp-core/workflows/execute-phase/steps/verify-post-gate.md +147 -0
- package/pp-core/workflows/execute-phase.md +152 -128
- package/pp-core/workflows/execute-plan.md +54 -6
- package/pp-core/workflows/fast.md +53 -4
- package/pp-core/workflows/help/modes/full.md +3 -3
- package/pp-core/workflows/init/steps/client-context-capture.md +101 -0
- package/pp-core/workflows/init/steps/gitlab-provisioning.md +130 -0
- package/pp-core/workflows/init/steps/project-md-deferral-narration.md +66 -0
- package/pp-core/workflows/init/steps/reachability-outcome.md +70 -0
- package/pp-core/workflows/init.md +232 -272
- package/pp-core/workflows/new-project.md +31 -23
- package/pp-core/workflows/plan-phase.md +98 -45
- package/pp-core/workflows/quick.md +117 -3
- package/pp-core/workflows/secure-phase.md +1 -1
- package/pp-core/workflows/ship.md +57 -28
- package/pp-core/workflows/undo.md +1 -1
- package/pp-core/workflows/update.md +3 -3
- package/pp-core/workflows/validate-phase.md +58 -3
- package/pp-core/workflows/vault-review.md +161 -23
- package/pp-core/workflows/vault-write-back.md +73 -110
- package/pp-core/workflows/verify-work.md +12 -14
- package/scripts/changeset/cli.cjs +6 -5
- package/scripts/changeset/lint.cjs +0 -0
- package/scripts/changeset/new.cjs +33 -50
- package/scripts/dev-env.cjs +25 -85
- package/scripts/gen-capability-registry.cjs +120 -13
- package/scripts/lib/allowlist-ratchet.cjs +1 -103
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -2
- package/scripts/lint-docs-required.cjs +0 -0
- package/scripts/lint-pr-check-project-dir.cjs +1 -3
- package/scripts/lint-test-file-count.allowlist.json +3 -19
- package/scripts/lint-test-file-count.cjs +5 -6
- package/scripts/prompt-injection-scan.sh +19 -17
- package/scripts/release-channel.cjs +0 -0
- package/scripts/sync-manifest-versions.cjs +5 -4
- package/scripts/sync-rulesets.sh +1 -1
- package/scripts/update-size-baseline.cjs +30 -274
- package/scripts/verify-opengsd-sweep.cjs +3 -5
- package/scripts/workflow-size.cjs +9 -61
- package/pp-core/bin/lib/dispatch-plan.cjs +0 -483
- package/pp-core/bin/lib/install-run.cjs +0 -536
- package/pp-core/bin/lib/loop-gates.cjs +0 -213
- package/pp-core/bin/lib/phase-manifest.cjs +0 -527
- package/pp-core/bin/lib/phase-tier.cjs +0 -480
- package/pp-core/bin/lib/plan-graph.cjs +0 -368
- package/pp-core/bin/lib/practices-review-check.cjs +0 -938
- package/pp-core/bin/lib/vault-audit.cjs +0 -2070
- package/pp-core/bin/lib/vault-practices.cjs +0 -1990
- package/pp-core/bin/lib/vault-review.cjs +0 -2359
- package/pp-core/bin/lib/vault-write-back.cjs +0 -877
- package/pp-core/references/executor-vault-trace.md +0 -47
- package/pp-core/references/planner-plan-graph.md +0 -66
- package/pp-core/references/planner-vault-practices.md +0 -112
- package/pp-core/templates/practices/README.md +0 -251
- package/pp-core/templates/practices/error-handling.md +0 -18
- package/pp-core/templates/practices/minimal-comments.md +0 -14
- package/pp-core/templates/practices/naming-convention.md +0 -18
- package/pp-core/templates/practices/practice-template.md +0 -23
- package/pp-core/workflows/discovery-sprint/vault-fetch.md +0 -37
- package/pp-core/workflows/execute-phase/steps/checkpoint-handling.md +0 -45
- package/pp-core/workflows/execute-phase/steps/install-run.md +0 -29
- package/pp-core/workflows/execute-phase/steps/phase-close-rebaseline.md +0 -23
- package/pp-core/workflows/execute-phase/steps/serial-group-dispatch.md +0 -73
- package/pp-core/workflows/execute-phase/steps/vault-door-gate.md +0 -19
- package/pp-core/workflows/plan-phase/steps/phase-tier.md +0 -43
- package/scripts/lib/size-budget.cjs +0 -311
- package/scripts/lint-neutrality-names.cjs +0 -694
- package/scripts/lint-org-coordinates.allowlist.json +0 -35
- package/scripts/lint-org-coordinates.cjs +0 -426
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
<purpose>
|
|
2
|
-
Take one local
|
|
3
|
-
vault
|
|
4
|
-
irreversible outward action: it shows the operator the complete preview, asks once
|
|
5
|
-
to proceed, and only on explicit approval re-invokes the same guarded CLI
|
|
6
|
-
mode. It never authors, edits
|
|
7
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
workflow
|
|
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
|
|
37
|
-
none was given, ask the operator for one with a plain prompt:
|
|
24
|
+
Take from `$ARGUMENTS`:
|
|
38
25
|
|
|
39
|
-
|
|
40
|
-
Path to the local
|
|
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,
|
|
44
|
-
|
|
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
|
-
|
|
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
|
|
54
|
-
|
|
55
|
-
|
|
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
|
|
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
|
|
66
|
-
|
|
67
|
-
|
|
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,
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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('
|
|
102
|
-
put('sharedShadow', v.sharedShadow ? 'true' : 'false');
|
|
103
|
-
put('sharedShadowNote', v.sharedShadowNote);
|
|
85
|
+
put('indexChange', v.indexChange);
|
|
104
86
|
put('expect', v.expect);
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
5.
|
|
128
|
-
|
|
129
|
-
|
|
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
|
|
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
|
|
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
|
|
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:**
|
|
156
|
-
|
|
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
|
|
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
|
|
163
|
-
|
|
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
|
|
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
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
188
|
-
|
|
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
|
-
|
|
204
|
-
and
|
|
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}
|
|
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`, `
|
|
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
|
|
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
|
|
464
|
-
|
|
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
|
-
|
|
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
|
|
678
|
+
**Iterate planner ↔ checker until plans pass (max 3):**
|
|
681
679
|
|
|
682
|
-
**If iteration_count <
|
|
680
|
+
**If iteration_count < 3:**
|
|
683
681
|
|
|
684
|
-
Display: `Sending back to planner for revision... (iteration {N}/
|
|
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 >=
|
|
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
|
-
//
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
//
|
|
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/<
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
45
|
-
|
|
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
|
|
49
|
-
return `${
|
|
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
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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 = {
|
|
134
|
+
module.exports = { deriveFragmentSlug, fragmentBaseName, scaffoldFragment, parseArgs, ALLOWED_TYPES };
|