@pareto4/pp-core 1.5.4 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/agents/pp-code-fixer.md +3 -3
- package/bin/install.js +0 -6
- package/commands/pp-init.md +36 -0
- package/commands/pp-ns-project.md +2 -1
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/pp-core/bin/lib/agent-install-check.cjs +1 -1
- package/pp-core/bin/lib/audit-command-router.cjs +3 -3
- package/pp-core/bin/lib/audit.cjs +1 -1
- package/pp-core/bin/lib/capability-activation.cjs +1 -1
- package/pp-core/bin/lib/capability-registry.cjs +138 -5
- package/pp-core/bin/lib/capability-state.cjs +1 -1
- package/pp-core/bin/lib/capability-writer.cjs +1 -1
- package/pp-core/bin/lib/check-command-router.cjs +190 -8
- package/pp-core/bin/lib/cjs-command-router-adapter.cjs +1 -1
- package/pp-core/bin/lib/clusters.cjs +1 -0
- package/pp-core/bin/lib/code-review-flags.cjs +1 -1
- package/pp-core/bin/lib/command-aliases.cjs +7 -0
- package/pp-core/bin/lib/config-loader.cjs +1 -1
- package/pp-core/bin/lib/config.cjs +1 -1
- package/pp-core/bin/lib/context-utilization.cjs +1 -1
- package/pp-core/bin/lib/docs.cjs +1 -1
- package/pp-core/bin/lib/frontmatter.cjs +1 -1
- package/pp-core/bin/lib/git-base-branch.cjs +2 -2
- package/pp-core/bin/lib/graphify-command-router.cjs +4 -4
- package/pp-core/bin/lib/graphify.cjs +1 -1
- package/pp-core/bin/lib/gsd2-import.cjs +1 -1
- package/pp-core/bin/lib/init-command-router.cjs +1 -1
- package/pp-core/bin/lib/init.cjs +1 -1
- package/pp-core/bin/lib/intel-command-router.cjs +5 -5
- package/pp-core/bin/lib/intel.cjs +1 -1
- package/pp-core/bin/lib/io.cjs +4 -2
- package/pp-core/bin/lib/loop-resolver.cjs +1 -1
- package/pp-core/bin/lib/phase-command-router.cjs +1 -1
- package/pp-core/bin/lib/phases-command-router.cjs +1 -1
- package/pp-core/bin/lib/plan-drift-guard.cjs +1 -1
- package/pp-core/bin/lib/platform-bindings.cjs +336 -0
- package/pp-core/bin/lib/profile-output.cjs +216 -1
- package/pp-core/bin/lib/prohibition-enforcement.cjs +2 -2
- package/pp-core/bin/lib/roadmap-command-router.cjs +2 -2
- package/pp-core/bin/lib/roadmap-parser.cjs +1 -1
- package/pp-core/bin/lib/state-command-router.cjs +1 -1
- package/pp-core/bin/lib/state.cjs +2 -2
- package/pp-core/bin/lib/validate-command-router.cjs +1 -1
- package/pp-core/bin/lib/verify-command-router.cjs +1 -1
- package/pp-core/bin/lib/verify.cjs +1 -1
- package/pp-core/bin/lib/worktree-base-ref.cjs +2 -2
- package/pp-core/bin/lib/worktree-safety.cjs +1 -1
- package/pp-core/bin/pp-tools.cjs +61 -24
- package/pp-core/workflows/discovery-sprint.md +27 -10
- package/pp-core/workflows/execute-phase/steps/client-context-gate.md +37 -0
- package/pp-core/workflows/execute-phase.md +4 -0
- package/pp-core/workflows/init.md +530 -0
- package/pp-core/workflows/new-project.md +54 -1
- package/pp-core/workflows/ship.md +24 -1
- package/pp-core/workflows/verify-work.md +1 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +1 -0
- package/assets/gsd-logo-2000-transparent.png +0 -0
- package/assets/gsd-logo-2000-transparent.svg +0 -17
- package/assets/gsd-logo-2000.png +0 -0
- package/assets/gsd-logo-2000.svg +0 -21
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
<purpose>
|
|
2
|
+
Universal front door for initializing any project with Pareto. Takes any directory — empty
|
|
3
|
+
or containing an existing codebase — from zero to: git-initialized, minimal identity
|
|
4
|
+
captured, remote wired, and handed off to either /pp-discovery or /pp-new-project.
|
|
5
|
+
|
|
6
|
+
Implements decisions: D-02 (no Obsidian/board scaffolding), D-03 (no discovery session
|
|
7
|
+
scaffolding here — /pp-discovery self-generates them), D-04 (ASK handoff with smart
|
|
8
|
+
default), D-06 (generic remote wiring: prompt URL → git remote add → confirmed push,
|
|
9
|
+
zero provider API), D-07 (minimal identity only — no CLAUDE.md client-context block),
|
|
10
|
+
D-13 (gate on .platform-bindings.json via the Phase 7 reader).
|
|
11
|
+
|
|
12
|
+
Inert of all Pareto/GitLab behavior when no .platform-bindings.json is present.
|
|
13
|
+
</purpose>
|
|
14
|
+
|
|
15
|
+
<required_reading>
|
|
16
|
+
Read all files referenced by the invoking prompt's execution_context before starting.
|
|
17
|
+
</required_reading>
|
|
18
|
+
|
|
19
|
+
<process>
|
|
20
|
+
|
|
21
|
+
## Shim hoist
|
|
22
|
+
|
|
23
|
+
Resolve `pp_run` before any step — required for the platform-bindings preflight (D-13).
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Text mode (resolve before any prompt — WR-02)
|
|
29
|
+
|
|
30
|
+
Resolve `TEXT_MODE` here, **before Step 0**, because the Step 0 drift prompt is the first
|
|
31
|
+
`AskUserQuestion` a non-Claude runtime hits. Set `TEXT_MODE=true` if `--text` is present in
|
|
32
|
+
`$ARGUMENTS`. When `TEXT_MODE` is active, replace every `AskUserQuestion` call (including the
|
|
33
|
+
Step 0 drift prompt) with a plain-text numbered list and ask the user to type their choice
|
|
34
|
+
number. This is required for non-Claude runtimes where `AskUserQuestion` is unavailable.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 0. Platform-version preflight (Pareto satellite repos)
|
|
39
|
+
|
|
40
|
+
**Runs before Step 1. Inert for non-Pareto projects** — gated entirely on
|
|
41
|
+
`.platform-bindings.json` existing (D-13), so generic projects skip it with no effect.
|
|
42
|
+
|
|
43
|
+
A repo scaffolded by `/pareto-new-app` pins the Pareto platform at a specific `vX.Y.Z` tag
|
|
44
|
+
(module `?ref=` pins, the `.gitlab-ci.yml` include `ref:`, and the canonical
|
|
45
|
+
`.platform-bindings.json` → `platform.tag`). Project initiation is the moment to confirm
|
|
46
|
+
the app is on the **latest** platform before any building starts — bumping after a
|
|
47
|
+
milestone is built is far more expensive.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
_PP_SHIM_NAME="pp-tools.cjs"; _PP_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; PP_TOOLS="${_PP_RUNTIME_ROOT}/pp-core/bin/${_PP_SHIM_NAME}"; if [ -f "$PP_TOOLS" ]; then pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${_PP_RUNTIME_ROOT}/.claude/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${_PP_RUNTIME_ROOT}/.claude/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${_PP_RUNTIME_ROOT}/.codex/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${_PP_RUNTIME_ROOT}/.codex/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif command -v pp-tools >/dev/null 2>&1; then PP_TOOLS="$(command -v pp-tools)"; pp_run() { "$PP_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="$HOME/.claude/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${HERMES_HOME:-$HOME/.hermes}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CODEX_HOME:-$HOME/.codex}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; else echo "ERROR: pp-tools.cjs not found at $PP_TOOLS and pp-tools is not on PATH. Run: npx -y @pareto4/pp-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${PP_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${PP_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
|
|
51
|
+
# Gate on presence via reader — absent file → {present:false} exit 0 (inert, step skipped, no jq)
|
|
52
|
+
if [ "$(pp_run query platform-bindings --pick present 2>/dev/null)" = "true" ]; then
|
|
53
|
+
PINNED=$(pp_run query platform-bindings --pick platform.tag)
|
|
54
|
+
REPO=$(pp_run query platform-bindings --pick platform.repo)
|
|
55
|
+
HOST_FROM_BINDINGS=$(pp_run query platform-bindings --pick platform.host 2>/dev/null)
|
|
56
|
+
PLATFORM_HOST="${HOST_FROM_BINDINGS:-${HOST:-gitlab.com}}"
|
|
57
|
+
PLATFORM_REPO_URL="https://${PLATFORM_HOST}/${REPO}.git"
|
|
58
|
+
# Resolve latest STABLE tag from remote — never hard-block if offline (empty LATEST → skip drift check)
|
|
59
|
+
# Keep --refs to strip ^{} annotated-tag rows; --sort=-v:refname sorts server-side
|
|
60
|
+
# Stable filter fixes prerelease mis-ordering: git ranks v1.4.0-rc.1 after v1.4.0 (wrong by semver)
|
|
61
|
+
# semver-compare.cjs is the single blessed source for comparison — do NOT reimplement
|
|
62
|
+
REMOTE_TAGS=$(git ls-remote --tags --refs --sort=-v:refname "$PLATFORM_REPO_URL" 'v*' 2>/dev/null | sed 's#.*/##')
|
|
63
|
+
# IN-03: pass the lib dir via env (PP_LIB_DIR) rather than interpolating ${PP_TOOLS%/*}
|
|
64
|
+
# into the JS program text — keeps the path out of the evaluated source.
|
|
65
|
+
LATEST=$(printf '%s\n' "$REMOTE_TAGS" | PP_LIB_DIR="${PP_TOOLS%/*}/lib" node -e "
|
|
66
|
+
const sc = require(process.env.PP_LIB_DIR + '/semver-compare.cjs');
|
|
67
|
+
const lines = require('fs').readFileSync(0, 'utf-8').split('\n').map(s=>s.trim()).filter(Boolean);
|
|
68
|
+
const stable = lines.filter(sc.isStableTripletSemver).sort(sc.compareSemverCore);
|
|
69
|
+
process.stdout.write((stable[stable.length-1] || '') + '\n');
|
|
70
|
+
" 2>/dev/null)
|
|
71
|
+
echo "platform preflight: pinned=${PINNED:-<none>} latest=${LATEST:-<unresolved>}"
|
|
72
|
+
if [ -n "$LATEST" ] && [ -n "$PINNED" ] && [ "$PINNED" != "$LATEST" ]; then
|
|
73
|
+
echo "PP_PLATFORM_DRIFT=1"
|
|
74
|
+
fi
|
|
75
|
+
fi
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**If the block printed `PP_PLATFORM_DRIFT=1`:** the app is pinned behind the latest platform
|
|
79
|
+
tag. STOP before Step 1 and use `AskUserQuestion` to let the user choose:
|
|
80
|
+
- **Bump first (recommended)** — pause initiation; bump the module `?ref=` pins, the
|
|
81
|
+
`.gitlab-ci.yml` include `ref:`, and `.platform-bindings.json` `platform.tag` to
|
|
82
|
+
`${LATEST}` (review `CHANGELOG.md` between the two tags for breaking changes), then
|
|
83
|
+
re-run `/pp-init`.
|
|
84
|
+
- **Proceed on the older pin** — continue knowingly; record the deferral in `PROJECT.md`
|
|
85
|
+
so it is a conscious decision, not silent drift.
|
|
86
|
+
|
|
87
|
+
If `LATEST` was unresolved (offline / no network), note it and proceed — never hard-block
|
|
88
|
+
initiation on an unreachable remote. For non-Pareto repos the block is a no-op and this
|
|
89
|
+
step is skipped entirely.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## 1. Worktree-aware git init
|
|
94
|
+
|
|
95
|
+
Parse the init signals by running the query below. `init.execute-phase` is reused here
|
|
96
|
+
deliberately (IN-02): it is the shared verb that emits the generic git-state signals
|
|
97
|
+
(`has_git`, `in_nested_subdir`, `git_worktree_root`) this step needs — no phase work is
|
|
98
|
+
triggered when called with an empty phase argument.
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
INIT=$(pp_run query init.execute-phase "" 2>/dev/null || echo '{}')
|
|
102
|
+
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Extract from the JSON: `has_git`, `in_nested_subdir`, `git_worktree_root`.
|
|
106
|
+
|
|
107
|
+
Apply the `#3491` guard (never nest `.git` inside an existing worktree):
|
|
108
|
+
|
|
109
|
+
- If `has_git` is `true` and `in_nested_subdir` is `true`: **skip `git init`**; warn the
|
|
110
|
+
user: "Initializing inside an existing worktree (`${git_worktree_root}`); planning files
|
|
111
|
+
will track to the outer repo." Do not call `git init`. This case pairs with the
|
|
112
|
+
`--skipGitInit` flag used by the gated GitLab provisioning path in Plan 02.
|
|
113
|
+
- If `has_git` is `true` and `in_nested_subdir` is `false`: **skip `git init`** (already
|
|
114
|
+
at the worktree root).
|
|
115
|
+
- If `has_git` is `false`: run `git init` to initialize a new repository.
|
|
116
|
+
|
|
117
|
+
**Text mode:** `TEXT_MODE` was already resolved in the "Text mode" section above (before
|
|
118
|
+
Step 0). Continue honoring it for every `AskUserQuestion` call in this and later steps.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## 2. Minimal identity capture (D-07)
|
|
123
|
+
|
|
124
|
+
Capture ONLY the five identity fields below. Persist them as a stub in
|
|
125
|
+
`.planning/config.json` under a top-level `"project"` key, merged with any existing
|
|
126
|
+
config. These fields are used by the gated GitLab provisioning path (Plan 02) for
|
|
127
|
+
`group/repo-slug` and `visibility`; they do NOT produce a `CLAUDE.md` client-context
|
|
128
|
+
block — that is Phase 9 (CTX-01..03) territory. Do NOT write a coding-standards,
|
|
129
|
+
architecture, or DoD block here; executor and Phase 9 must not double-capture these.
|
|
130
|
+
|
|
131
|
+
Ask the user with `AskUserQuestion` (or plain-text prompts in TEXT_MODE):
|
|
132
|
+
|
|
133
|
+
```text
|
|
134
|
+
1. Project name (human-readable, e.g. "Acme Dashboard")
|
|
135
|
+
2. Project slug (kebab-case, e.g. "acme-dashboard")
|
|
136
|
+
3. Client name (company / team, e.g. "Acme Corp") — type "internal" if none
|
|
137
|
+
4. Client slug (kebab-case, e.g. "acme") — type "internal" if none
|
|
138
|
+
5. One-line description (what this project does)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Also ask — used only by the gated GitLab path (D-13, Plan 02):
|
|
142
|
+
|
|
143
|
+
```text
|
|
144
|
+
6. Provider (gitlab / github / other / none)
|
|
145
|
+
7. Visibility (private / public / internal)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Write these to `.planning/config.json`:
|
|
149
|
+
|
|
150
|
+
```json
|
|
151
|
+
{
|
|
152
|
+
"project": {
|
|
153
|
+
"name": "<value>",
|
|
154
|
+
"slug": "<value>",
|
|
155
|
+
"client_name": "<value>",
|
|
156
|
+
"client_slug": "<value>",
|
|
157
|
+
"description": "<value>",
|
|
158
|
+
"provider": "<value>",
|
|
159
|
+
"visibility": "<value>"
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
`mkdir -p .planning` before writing if the directory does not exist.
|
|
165
|
+
|
|
166
|
+
**Scope boundary:** this step writes identity only. All coding-standards, architecture
|
|
167
|
+
context, client-engagement CLAUDE.md blocks, and discovery-session scaffolding are out of
|
|
168
|
+
scope here. Phase 9 owns those; /pp-discovery self-generates its own session files (D-03).
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## 3. Remote wiring — provider-agnostic (non-Pareto / no bindings)
|
|
173
|
+
|
|
174
|
+
**Gate (D-06, D-13):** this step is the active path when
|
|
175
|
+
`pp_run query platform-bindings --pick present 2>/dev/null` does NOT equal `"true"`.
|
|
176
|
+
When bindings ARE present, see the gated GitLab provisioning section below instead.
|
|
177
|
+
|
|
178
|
+
On the generic path:
|
|
179
|
+
|
|
180
|
+
1. Ask the user for the remote URL (any host — GitHub, GitLab, Gitea, Bitbucket, etc.):
|
|
181
|
+
|
|
182
|
+
```text
|
|
183
|
+
Remote URL for this repository (e.g. https://github.com/org/repo.git):
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
2. Show the exact command and target, then confirm before firing:
|
|
187
|
+
|
|
188
|
+
```text
|
|
189
|
+
About to run:
|
|
190
|
+
git remote add origin <url>
|
|
191
|
+
|
|
192
|
+
Proceed? [y/N]
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
If confirmed, run `git remote add origin <url>`. If a remote named `origin` already
|
|
196
|
+
exists, offer to update it with `git remote set-url origin <url>` instead (show the
|
|
197
|
+
same per-action confirm pattern).
|
|
198
|
+
|
|
199
|
+
3. Offer a first push. Show the exact command and target before firing:
|
|
200
|
+
|
|
201
|
+
```text
|
|
202
|
+
About to run:
|
|
203
|
+
git push -u origin <current-branch>
|
|
204
|
+
|
|
205
|
+
Proceed? [y/N]
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
If confirmed, run `git push -u origin <current-branch>`. If the user declines, skip
|
|
209
|
+
the push and note that they can run `git push -u origin <branch>` later.
|
|
210
|
+
|
|
211
|
+
**Security (T-08-01, T-08-02):** every outward action (`git remote add`, `git push`)
|
|
212
|
+
gets its own per-action confirmation immediately before it fires — nothing batched. This
|
|
213
|
+
step makes **ZERO `gh`/`glab`/provider-API calls**. The push target is the user-supplied
|
|
214
|
+
URL only.
|
|
215
|
+
|
|
216
|
+
### Gated GitLab provisioning (Pareto projects — bindings present)
|
|
217
|
+
|
|
218
|
+
This path runs **instead of** the generic remote-wiring path above when
|
|
219
|
+
`pp_run query platform-bindings --pick present 2>/dev/null` equals `"true"`. Implements
|
|
220
|
+
D-05 (per-action confirmation), D-09 (existence probe → adopt-if-exists), D-10 (private
|
|
221
|
+
by default), D-11 (auth verify-only), D-12 (adopt-if-exists reconciliation), D-13
|
|
222
|
+
(bindings gate). Covers INIT-02 and the cross-cutting SAFE-01 invariant.
|
|
223
|
+
|
|
224
|
+
**Security invariants — landmine guards:**
|
|
225
|
+
- Always use the group-qualified `$GROUP/$SLUG` path — never a bare slug (T-08-04, D-09).
|
|
226
|
+
A bare slug creates under the user's personal namespace, not the client group.
|
|
227
|
+
- Always pass `--private` explicitly — never inherit `glab`'s default `--internal`
|
|
228
|
+
visibility (T-08-05, D-10). The `--internal` flag is what makes this dangerous; the
|
|
229
|
+
locked recipe must carry `--private` and must NOT emit `--internal`.
|
|
230
|
+
- Always confirm immediately before each outward action — nothing batched (T-08-06, D-05).
|
|
231
|
+
- Verify-only auth via `glab auth status` — never run `glab auth login`, never read
|
|
232
|
+
or write the user's token (T-08-08, D-11). Bindings hold only non-secret routing data.
|
|
233
|
+
- Always reconcile create with a `glab repo view` probe — a non-zero create exit does
|
|
234
|
+
NOT mean nothing happened (T-08-09, D-09).
|
|
235
|
+
|
|
236
|
+
#### G-1. Config reads from the bindings reader (Pattern C, D-09, D-13)
|
|
237
|
+
|
|
238
|
+
All values come from the Phase 7 bindings reader. **Never hardcoded.**
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
GROUP=$(pp_run query platform-bindings --pick gitlab.group 2>/dev/null)
|
|
242
|
+
SLUG=$(pp_run query platform-bindings --pick gitlab.repo_slug 2>/dev/null)
|
|
243
|
+
VIS_RAW=$(pp_run query platform-bindings --pick gitlab.visibility 2>/dev/null)
|
|
244
|
+
HOST=$(pp_run query platform-bindings --pick gitlab.host 2>/dev/null)
|
|
245
|
+
# D-10: hard-default to private when field is absent or empty
|
|
246
|
+
VIS="${VIS_RAW:-private}"
|
|
247
|
+
DESC=$(pp_run query config-get project.description 2>/dev/null || echo "")
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
The `gitlab.*` namespace is provisional — the bindings reader is lenient on unknown fields
|
|
251
|
+
(it only hard-validates `schemaVersion`, `platform.repo`, and `platform.tag`). Treat any
|
|
252
|
+
absent `gitlab.*` field gracefully: fall back to safe defaults, never fail hard on a
|
|
253
|
+
missing key. `HOST` defaults to `gitlab.com` in remote URLs when empty.
|
|
254
|
+
|
|
255
|
+
#### G-2. Auth verify-only (D-11)
|
|
256
|
+
|
|
257
|
+
`/pp-init` **never establishes auth and never reads or writes the user's token.**
|
|
258
|
+
Secrets stay in the environment or OS keyring. Only verify that a session exists:
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
glab auth status >/dev/null 2>&1 || {
|
|
262
|
+
echo "Not authenticated to GitLab. Run: glab auth login"
|
|
263
|
+
exit 1
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
If this check fails, stop immediately. Do not proceed to probe or create.
|
|
268
|
+
Never run `glab auth login` from within `/pp-init`.
|
|
269
|
+
|
|
270
|
+
#### G-3. Existence probe → adopt-if-exists (D-09, D-12)
|
|
271
|
+
|
|
272
|
+
Pre-check with a read-only probe before any create attempt. `glab repo create` is **not
|
|
273
|
+
idempotent** — re-running it against an existing project errors but may still produce side
|
|
274
|
+
effects (the GitLab project is created before the remote-add step that then fails). The
|
|
275
|
+
probe avoids this landmine.
|
|
276
|
+
|
|
277
|
+
This is one self-contained block: it sets `PROJECT_EXISTS` and, on the exists path,
|
|
278
|
+
adopts (links the remote). It NEVER creates — create lives in its own G-4 block, gated
|
|
279
|
+
on `PROJECT_EXISTS=false`, so the create invocation can never fall inside the exists
|
|
280
|
+
branch.
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
if glab repo view "$GROUP/$SLUG" -F json >/dev/null 2>&1; then
|
|
284
|
+
# Project already exists — ADOPT: link remote only, skip create (D-12)
|
|
285
|
+
PROJECT_EXISTS=true
|
|
286
|
+
echo "GitLab project $GROUP/$SLUG already exists — adopting (linking remote, skipping create)."
|
|
287
|
+
# IN-01: match the remote scheme glab itself uses on the create path — honor the
|
|
288
|
+
# user's `glab config get git_protocol` (https default) instead of hardcoding SSH,
|
|
289
|
+
# so adopt and create produce a consistent origin URL.
|
|
290
|
+
GIT_PROTO=$(glab config get git_protocol 2>/dev/null || echo https)
|
|
291
|
+
if [ "$GIT_PROTO" = "ssh" ]; then
|
|
292
|
+
ADOPT_URL="git@${HOST:-gitlab.com}:$GROUP/$SLUG.git"
|
|
293
|
+
else
|
|
294
|
+
ADOPT_URL="https://${HOST:-gitlab.com}/$GROUP/$SLUG.git"
|
|
295
|
+
fi
|
|
296
|
+
git remote get-url origin >/dev/null 2>&1 || git remote add origin "$ADOPT_URL"
|
|
297
|
+
echo "Remote 'origin' → $ADOPT_URL"
|
|
298
|
+
else
|
|
299
|
+
# Project not found — defer to G-4 for confirm-then-create
|
|
300
|
+
PROJECT_EXISTS=false
|
|
301
|
+
echo "GitLab project $GROUP/$SLUG not found — will confirm before create."
|
|
302
|
+
fi
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
If the project exists (`PROJECT_EXISTS=true`), the remote is already linked above
|
|
306
|
+
(skipping create entirely) — proceed straight to G-5 reconciliation. The adopt path does
|
|
307
|
+
not show a create confirmation — it only links the remote, which is a local-only
|
|
308
|
+
operation and can be undone with `git remote remove origin`.
|
|
309
|
+
|
|
310
|
+
#### G-4. Per-action confirmation, then create (D-05, D-09, D-10, SAFE-01)
|
|
311
|
+
|
|
312
|
+
**Only run this step when `PROJECT_EXISTS=false`** (the existence probe in G-3 found no
|
|
313
|
+
project). If `PROJECT_EXISTS=true`, skip G-4 entirely — the project was already adopted —
|
|
314
|
+
and go to G-5. When creating, **show the exact command and target before firing**. The
|
|
315
|
+
user must confirm explicitly. Nothing is batched. (D-05)
|
|
316
|
+
|
|
317
|
+
Present to the user (via `AskUserQuestion` or TEXT_MODE numbered list):
|
|
318
|
+
|
|
319
|
+
```text
|
|
320
|
+
About to create GitLab project:
|
|
321
|
+
Command: glab repo create "$GROUP/$SLUG"
|
|
322
|
+
Group path: $GROUP/$SLUG
|
|
323
|
+
Visibility: private (enforced --private; never the glab default --internal)
|
|
324
|
+
Requested: $VIS (from bindings gitlab.visibility — see note below)
|
|
325
|
+
Default branch: main
|
|
326
|
+
Skip git init: yes (local repo already initialized — --skipGitInit)
|
|
327
|
+
Remote name: origin
|
|
328
|
+
|
|
329
|
+
Proceed? [y/N]
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
`$VIS` (from G-1) is surfaced in the confirmation so the binding's requested visibility is
|
|
333
|
+
never silently ignored (WR-01). pp-core always **creates private** regardless; a
|
|
334
|
+
non-private binding must be applied as a deliberate, separate post-create change.
|
|
335
|
+
|
|
336
|
+
Only after the user confirms — and only with `PROJECT_EXISTS=false` — run the locked
|
|
337
|
+
create recipe (D-09, D-10). This is a standalone block: it carries no `if`/`else`/`fi`, so
|
|
338
|
+
it can only ever run as the deliberate, confirmed not-exists path:
|
|
339
|
+
|
|
340
|
+
```bash
|
|
341
|
+
glab repo create "$GROUP/$SLUG" \
|
|
342
|
+
--description "$DESC" \
|
|
343
|
+
--private \
|
|
344
|
+
--defaultBranch main \
|
|
345
|
+
--skipGitInit \
|
|
346
|
+
--remoteName origin
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
**Visibility hard-default (D-10):** the create invocation always carries `--private`.
|
|
350
|
+
Never omit it and let `glab` silently apply `--internal`. `VIS` from bindings is read in
|
|
351
|
+
G-1 and **surfaced in the G-4 confirmation** (WR-01) so a `public`/`internal` request is
|
|
352
|
+
visible to the user — but pp-core still creates the project **private**. Applying a
|
|
353
|
+
non-private visibility is a deliberate, separate post-create action (e.g. `glab repo edit
|
|
354
|
+
--visibility …`) the operator performs knowingly; `/pp-init` never auto-relaxes visibility.
|
|
355
|
+
|
|
356
|
+
#### G-5. Post-create reconciliation (D-09)
|
|
357
|
+
|
|
358
|
+
After the create (or after the adopt path), re-probe existence. A non-zero exit from
|
|
359
|
+
`glab repo create` does **not** mean the project was not created — `glab` exits non-zero
|
|
360
|
+
(code 3) when the GitLab project was created but the subsequent `git remote add` step
|
|
361
|
+
then failed (e.g., because `origin` already existed). The `repo view` probe is the source
|
|
362
|
+
of truth, not the exit code (research §1 "Remote-add caveat — VERIFIED"):
|
|
363
|
+
|
|
364
|
+
```bash
|
|
365
|
+
if glab repo view "$GROUP/$SLUG" -F json >/dev/null 2>&1; then
|
|
366
|
+
echo "Reconciled: GitLab project $GROUP/$SLUG confirmed."
|
|
367
|
+
else
|
|
368
|
+
echo "WARN: glab repo view $GROUP/$SLUG returned non-zero — verify the project was created before continuing."
|
|
369
|
+
fi
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
If reconciliation warns, do not auto-retry. Prompt the user to check GitLab directly
|
|
373
|
+
before proceeding to Step 4 (handoff routing).
|
|
374
|
+
|
|
375
|
+
### Gated client-context capture (Pareto projects — bindings present)
|
|
376
|
+
|
|
377
|
+
This step runs **only when** `pp_run query platform-bindings --pick present 2>/dev/null` equals
|
|
378
|
+
`"true"`. Non-Pareto projects are **fully inert**: no prompts fire and no block is written (D-04,
|
|
379
|
+
success criterion #4). All capture is **offline-only** — no Obsidian, no MCP, no network (D-05 /
|
|
380
|
+
T-09-05).
|
|
381
|
+
|
|
382
|
+
```bash
|
|
383
|
+
if [ "$(pp_run query platform-bindings --pick present 2>/dev/null)" = "true" ]; then
|
|
384
|
+
# ── D-05: edit-on-re-run — read existing block first ──────────────────────────
|
|
385
|
+
EXISTING=$(pp_run query generate-client-context --read 2>/dev/null || echo '{"present":false}')
|
|
386
|
+
EXISTING_PRESENT=$(echo "$EXISTING" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{const o=JSON.parse(d);process.stdout.write(String(o.present||'false'));}catch{process.stdout.write('false');}")
|
|
387
|
+
|
|
388
|
+
if [ "$EXISTING_PRESENT" = "true" ]; then
|
|
389
|
+
echo ""
|
|
390
|
+
echo "Existing client-context block found. Press Enter to keep each value, or type to replace it."
|
|
391
|
+
EXISTING_CLIENT_NAME=$(echo "$EXISTING" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{const o=JSON.parse(d);process.stdout.write(o.client_name||'');}catch{}" 2>/dev/null)
|
|
392
|
+
EXISTING_CLIENT_SLUG=$(echo "$EXISTING" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{const o=JSON.parse(d);process.stdout.write(o.client_slug||'');}catch{}" 2>/dev/null)
|
|
393
|
+
EXISTING_CODING=$(echo "$EXISTING" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{const o=JSON.parse(d);process.stdout.write(o.coding_standards||'');}catch{}" 2>/dev/null)
|
|
394
|
+
EXISTING_ARCH=$(echo "$EXISTING" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{const o=JSON.parse(d);process.stdout.write(o.architecture_patterns||'');}catch{}" 2>/dev/null)
|
|
395
|
+
EXISTING_UI=$(echo "$EXISTING" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{const o=JSON.parse(d);process.stdout.write(o.ui_brand_conventions||'');}catch{}" 2>/dev/null)
|
|
396
|
+
EXISTING_DOD=$(echo "$EXISTING" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{const o=JSON.parse(d);process.stdout.write(o.definition_of_done||'');}catch{}" 2>/dev/null)
|
|
397
|
+
fi
|
|
398
|
+
fi
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
**Field capture — one field at a time (D-01, D-02):** Prompt the user for each of the five fields
|
|
402
|
+
sequentially. All five are required — **re-prompt on empty input until a non-empty value is given**
|
|
403
|
+
(no skip, no catch-all). Multi-line values are accepted: instruct the user to type or paste, then
|
|
404
|
+
enter a blank line (or a sentinel line such as `EOF`) to signal end-of-field. Do not call any
|
|
405
|
+
network resource during capture.
|
|
406
|
+
|
|
407
|
+
Capture these fields, in this order:
|
|
408
|
+
|
|
409
|
+
1. **Client name** — the human-readable client or project name (e.g. "Acme Corp").
|
|
410
|
+
- On re-run: show the current value (`$EXISTING_CLIENT_NAME`) and let the user keep or replace.
|
|
411
|
+
- After capturing, derive a URL-safe slug via `pp_run query generate-slug` on the name; present
|
|
412
|
+
the suggested slug and let the user confirm or override. Store as `CLIENT_SLUG`.
|
|
413
|
+
|
|
414
|
+
2. **Coding standards** — the conventions and rules the Build agent must follow (e.g. language,
|
|
415
|
+
style guide, patterns to use/avoid). Multi-line OK.
|
|
416
|
+
- On re-run: show `$EXISTING_CODING`.
|
|
417
|
+
|
|
418
|
+
3. **Architecture patterns** — the structural patterns and constraints the Build agent must respect
|
|
419
|
+
(e.g. layering, module boundaries, framework idioms). Multi-line OK.
|
|
420
|
+
- On re-run: show `$EXISTING_ARCH`.
|
|
421
|
+
|
|
422
|
+
4. **UI / brand conventions** — visual and component standards the Build agent must apply (e.g.
|
|
423
|
+
design system, color tokens, typography rules). Multi-line OK.
|
|
424
|
+
- On re-run: show `$EXISTING_UI`.
|
|
425
|
+
|
|
426
|
+
5. **Definition of done** — the quality gates that must pass before any task is marked complete
|
|
427
|
+
(e.g. tests green, lint clean, accessibility checked). Multi-line OK.
|
|
428
|
+
- On re-run: show `$EXISTING_DOD`.
|
|
429
|
+
|
|
430
|
+
**Write block** — after all five fields are captured, write them to a temp file and invoke the
|
|
431
|
+
managed-block writer:
|
|
432
|
+
|
|
433
|
+
```bash
|
|
434
|
+
if [ "$(pp_run query platform-bindings --pick present 2>/dev/null)" = "true" ]; then
|
|
435
|
+
# Write collected values to a temp JSON file using node + JSON.stringify so that
|
|
436
|
+
# multi-line values, embedded quotes, backslashes, and $(...) sequences are encoded
|
|
437
|
+
# correctly (CR-02: an unquoted heredoc would produce invalid JSON for any multi-line
|
|
438
|
+
# field, breaking cmdGenerateClientContext with "Failed to parse JSON input").
|
|
439
|
+
TMP_CTX=$(mktemp /tmp/pp-client-ctx-XXXXXX.json)
|
|
440
|
+
CLIENT_NAME="$CLIENT_NAME" \
|
|
441
|
+
CLIENT_SLUG="$CLIENT_SLUG" \
|
|
442
|
+
CODING_STANDARDS="$CODING_STANDARDS" \
|
|
443
|
+
ARCHITECTURE_PATTERNS="$ARCHITECTURE_PATTERNS" \
|
|
444
|
+
UI_BRAND_CONVENTIONS="$UI_BRAND_CONVENTIONS" \
|
|
445
|
+
DEFINITION_OF_DONE="$DEFINITION_OF_DONE" \
|
|
446
|
+
node -e "
|
|
447
|
+
const e = process.env;
|
|
448
|
+
const fs = require('fs');
|
|
449
|
+
fs.writeFileSync(process.argv[1], JSON.stringify({
|
|
450
|
+
client_name: e.CLIENT_NAME,
|
|
451
|
+
client_slug: e.CLIENT_SLUG,
|
|
452
|
+
coding_standards: e.CODING_STANDARDS,
|
|
453
|
+
architecture_patterns: e.ARCHITECTURE_PATTERNS,
|
|
454
|
+
ui_brand_conventions: e.UI_BRAND_CONVENTIONS,
|
|
455
|
+
definition_of_done: e.DEFINITION_OF_DONE
|
|
456
|
+
}));
|
|
457
|
+
" "$TMP_CTX"
|
|
458
|
+
|
|
459
|
+
# Invoke the Plan 09-01 writer — routes through updateSection, marker-bounded, idempotent
|
|
460
|
+
pp_run query generate-client-context --from-json "$TMP_CTX"
|
|
461
|
+
rm -f "$TMP_CTX"
|
|
462
|
+
echo "Client context written to CLAUDE.md (<!-- PP:client-context-start --> block)."
|
|
463
|
+
fi
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
This writes a `## Client Context & Patterns` block bounded by
|
|
467
|
+
`<!-- PP:client-context-start -->` … `<!-- PP:client-context-end -->` markers into the local
|
|
468
|
+
CLAUDE.md (config-driven path, default `./.claude/CLAUDE.md`). The write is idempotent —
|
|
469
|
+
re-running `/pp-init` replaces the bounded block, never duplicates it. All values stay local;
|
|
470
|
+
no network or external service is involved.
|
|
471
|
+
|
|
472
|
+
---
|
|
473
|
+
|
|
474
|
+
## 4. Handoff routing (D-04)
|
|
475
|
+
|
|
476
|
+
After git init, metadata, and remote wiring are complete, ask the user which workflow to
|
|
477
|
+
continue with. `/pp-init` does NOT scaffold discovery session files itself — `/pp-discovery`
|
|
478
|
+
self-generates them (D-03). Do not silently auto-chain to either option.
|
|
479
|
+
|
|
480
|
+
Determine the smart default from signals captured in Steps 0–2:
|
|
481
|
+
- `platform-bindings --pick present` == `true`, OR `provider` == `"gitlab"`, OR
|
|
482
|
+
`client_slug` is not `"internal"` → pre-select **`/pp-discovery`** (Pareto engagement
|
|
483
|
+
or client work).
|
|
484
|
+
- Otherwise (internal project, known stack, no bindings) → pre-select
|
|
485
|
+
**`/pp-new-project`** (freeform planning, requirements, roadmap).
|
|
486
|
+
|
|
487
|
+
Present to the user with `AskUserQuestion`:
|
|
488
|
+
|
|
489
|
+
```text
|
|
490
|
+
AskUserQuestion([
|
|
491
|
+
{
|
|
492
|
+
header: "Next step",
|
|
493
|
+
question: "Where would you like to go next?",
|
|
494
|
+
multiSelect: false,
|
|
495
|
+
options: [
|
|
496
|
+
{
|
|
497
|
+
label: "/pp-discovery (Recommended for client/Pareto projects)",
|
|
498
|
+
description: "Run the 13-session Discovery Sprint — self-generates all session files"
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
label: "/pp-new-project",
|
|
502
|
+
description: "Gather requirements and generate a phased roadmap for a known-stack project"
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
label: "Stop here",
|
|
506
|
+
description: "I will continue manually — the repo is initialized"
|
|
507
|
+
}
|
|
508
|
+
]
|
|
509
|
+
}
|
|
510
|
+
])
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
Pre-select the option matching the smart default computed above (indicate it with
|
|
514
|
+
`(Recommended)` in the label, as shown). When the user confirms or overrides, invoke the
|
|
515
|
+
chosen command or stop cleanly.
|
|
516
|
+
|
|
517
|
+
---
|
|
518
|
+
|
|
519
|
+
## 5. Commit
|
|
520
|
+
|
|
521
|
+
Commit the `.planning/config.json` identity stub created in Step 2:
|
|
522
|
+
|
|
523
|
+
```bash
|
|
524
|
+
pp_run query commit "docs: initialize project" --files .planning/config.json
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
If `.planning/config.json` is the only new file, stage only that file. If other planning
|
|
528
|
+
artifacts were created (e.g. `.planning/` directory structure), include them.
|
|
529
|
+
|
|
530
|
+
</process>
|
|
@@ -52,12 +52,65 @@ The document should describe what you want to build.
|
|
|
52
52
|
|
|
53
53
|
<process>
|
|
54
54
|
|
|
55
|
+
## 0. Platform-version preflight (Pareto satellite repos)
|
|
56
|
+
|
|
57
|
+
**Runs before Setup. Inert for non-Pareto projects** — gated entirely on
|
|
58
|
+
`.platform-bindings.json` existing, so generic projects skip it with no effect.
|
|
59
|
+
|
|
60
|
+
A repo scaffolded by `/pareto-new-app` pins the Pareto platform at a specific
|
|
61
|
+
`vX.Y.Z` tag (module `?ref=` pins, the `.gitlab-ci.yml` include `ref:`, and the
|
|
62
|
+
canonical `.platform-bindings.json` → `platform.tag`). Project initiation is the
|
|
63
|
+
moment to confirm the app is on the **latest** platform before any building
|
|
64
|
+
starts — bumping after a milestone is built is far more expensive.
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
_PP_SHIM_NAME="pp-tools.cjs"; _PP_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; PP_TOOLS="${_PP_RUNTIME_ROOT}/pp-core/bin/${_PP_SHIM_NAME}"; if [ -f "$PP_TOOLS" ]; then pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${_PP_RUNTIME_ROOT}/.claude/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${_PP_RUNTIME_ROOT}/.claude/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${_PP_RUNTIME_ROOT}/.codex/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${_PP_RUNTIME_ROOT}/.codex/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif command -v pp-tools >/dev/null 2>&1; then PP_TOOLS="$(command -v pp-tools)"; pp_run() { "$PP_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="$HOME/.claude/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${HERMES_HOME:-$HOME/.hermes}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CODEX_HOME:-$HOME/.codex}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; else echo "ERROR: pp-tools.cjs not found at $PP_TOOLS and pp-tools is not on PATH. Run: npx -y @pareto4/pp-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${PP_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${PP_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
|
|
68
|
+
# Resolve pp_run before Step 1 — reader required for platform-bindings preflight (BIND-04)
|
|
69
|
+
|
|
70
|
+
# Gate on presence via reader — absent file → {present:false} exit 0 (inert, step skipped, no jq)
|
|
71
|
+
if [ "$(pp_run query platform-bindings --pick present 2>/dev/null)" = "true" ]; then
|
|
72
|
+
PINNED=$(pp_run query platform-bindings --pick platform.tag)
|
|
73
|
+
REPO=$(pp_run query platform-bindings --pick platform.repo)
|
|
74
|
+
HOST_FROM_BINDINGS=$(pp_run query platform-bindings --pick platform.host 2>/dev/null)
|
|
75
|
+
PLATFORM_HOST="${HOST_FROM_BINDINGS:-${HOST:-gitlab.com}}"
|
|
76
|
+
PLATFORM_REPO_URL="https://${PLATFORM_HOST}/${REPO}.git"
|
|
77
|
+
# Resolve latest STABLE tag from remote — never hard-block if offline (empty LATEST → skip drift check)
|
|
78
|
+
# Keep --refs to strip ^{} annotated-tag rows; --sort=-v:refname sorts server-side
|
|
79
|
+
# Stable filter fixes prerelease mis-ordering: git ranks v1.4.0-rc.1 after v1.4.0 (wrong by semver)
|
|
80
|
+
# semver-compare.cjs is the single blessed source for comparison — do NOT reimplement
|
|
81
|
+
REMOTE_TAGS=$(git ls-remote --tags --refs --sort=-v:refname "$PLATFORM_REPO_URL" 'v*' 2>/dev/null | sed 's#.*/##')
|
|
82
|
+
LATEST=$(printf '%s\n' "$REMOTE_TAGS" | node -e "
|
|
83
|
+
const sc = require('${PP_TOOLS%/*}/lib/semver-compare.cjs');
|
|
84
|
+
const lines = require('fs').readFileSync(0, 'utf-8').split('\n').map(s=>s.trim()).filter(Boolean);
|
|
85
|
+
const stable = lines.filter(sc.isStableTripletSemver).sort(sc.compareSemverCore);
|
|
86
|
+
process.stdout.write((stable[stable.length-1] || '') + '\n');
|
|
87
|
+
" 2>/dev/null)
|
|
88
|
+
echo "platform preflight: pinned=${PINNED:-<none>} latest=${LATEST:-<unresolved>}"
|
|
89
|
+
if [ -n "$LATEST" ] && [ -n "$PINNED" ] && [ "$PINNED" != "$LATEST" ]; then
|
|
90
|
+
echo "PP_PLATFORM_DRIFT=1"
|
|
91
|
+
fi
|
|
92
|
+
fi
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**If the block printed `PP_PLATFORM_DRIFT=1`:** the app is pinned behind the
|
|
96
|
+
latest platform tag. STOP before Setup and use `AskUserQuestion` to let the user
|
|
97
|
+
choose:
|
|
98
|
+
- **Bump first (recommended)** — pause initiation; bump the module `?ref=` pins,
|
|
99
|
+
the `.gitlab-ci.yml` include `ref:`, and `.platform-bindings.json` `platform.tag`
|
|
100
|
+
to `${LATEST}` (review `CHANGELOG.md` between the two tags for breaking changes),
|
|
101
|
+
then re-run `/pp-new-project`.
|
|
102
|
+
- **Proceed on the older pin** — continue knowingly; record the deferral in
|
|
103
|
+
`PROJECT.md` so it's a conscious decision, not silent drift.
|
|
104
|
+
|
|
105
|
+
If `LATEST` was unresolved (offline / no network), note it and proceed — never
|
|
106
|
+
hard-block initiation on an unreachable remote. For non-Pareto repos the block is
|
|
107
|
+
a no-op and this step is skipped entirely.
|
|
108
|
+
|
|
55
109
|
## 1. Setup
|
|
56
110
|
|
|
57
111
|
**MANDATORY FIRST STEP — Execute these checks before ANY user interaction:**
|
|
58
112
|
|
|
59
113
|
```bash
|
|
60
|
-
_PP_SHIM_NAME="pp-tools.cjs"; _PP_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; PP_TOOLS="${_PP_RUNTIME_ROOT}/pp-core/bin/${_PP_SHIM_NAME}"; if [ -f "$PP_TOOLS" ]; then pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${_PP_RUNTIME_ROOT}/.claude/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${_PP_RUNTIME_ROOT}/.claude/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${_PP_RUNTIME_ROOT}/.codex/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${_PP_RUNTIME_ROOT}/.codex/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif command -v pp-tools >/dev/null 2>&1; then PP_TOOLS="$(command -v pp-tools)"; pp_run() { "$PP_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="$HOME/.claude/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${HERMES_HOME:-$HOME/.hermes}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CODEX_HOME:-$HOME/.codex}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/pp-core/bin/${_PP_SHIM_NAME}"; pp_run() { node "$PP_TOOLS" "$@"; }; else echo "ERROR: pp-tools.cjs not found at $PP_TOOLS and pp-tools is not on PATH. Run: npx -y @pareto4/pp-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${PP_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${PP_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
|
|
61
114
|
INIT=$(pp_run query init.new-project)
|
|
62
115
|
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
|
63
116
|
AGENT_SKILLS_RESEARCHER=$(pp_run query agent-skills pp-project-researcher)
|