@pareto4/pp-core 1.13.0 → 1.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +3 -3
- package/README.md +26 -9
- package/agents/pp-code-reviewer.md +2 -1
- package/agents/pp-debug-session-manager.md +9 -6
- package/agents/pp-executor.md +10 -13
- package/agents/pp-plan-checker.md +4 -4
- package/agents/pp-planner.md +23 -9
- package/agents/pp-vault-reviewer.md +244 -21
- package/agents/pp-verifier.md +7 -3
- package/bin/install.js +10 -3
- package/commands/pp-execute-phase.md +1 -3
- package/commands/pp-ns-review.md +1 -1
- package/commands/pp-plan-phase.md +1 -2
- package/commands/pp-vault-review.md +5 -3
- 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 +14 -19
- 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 +396 -82
- package/pp-core/bin/lib/capability-state.cjs +160 -4
- package/pp-core/bin/lib/capability-writer.cjs +19 -5
- package/pp-core/bin/lib/check-command-router.cjs +3334 -946
- 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 +154 -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 +13 -11
- package/pp-core/bin/lib/git-provider.cjs +4 -4
- package/pp-core/bin/lib/init-command-router.cjs +5 -8
- package/pp-core/bin/lib/init-contract.cjs +648 -738
- package/pp-core/bin/lib/init-interactive.cjs +76 -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 +8 -0
- package/pp-core/bin/lib/loop-resolver.cjs +189 -129
- package/pp-core/bin/lib/org-profile.cjs +469 -152
- 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/practices-review-check.cjs +15 -9
- 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-audit.cjs +650 -126
- package/pp-core/bin/lib/vault-command-router.cjs +2604 -367
- package/pp-core/bin/lib/vault-coordinates.cjs +13 -4
- package/pp-core/bin/lib/vault-index.cjs +699 -0
- package/pp-core/bin/lib/vault-mode.cjs +235 -0
- package/pp-core/bin/lib/vault-practices.cjs +139 -72
- package/pp-core/bin/lib/vault-review.cjs +194 -451
- package/pp-core/bin/lib/vault-seal.cjs +1936 -0
- package/pp-core/bin/lib/vault-standards.cjs +1505 -0
- package/pp-core/bin/lib/vault-trace.cjs +607 -254
- package/pp-core/bin/lib/vault-update.cjs +9 -33
- package/pp-core/bin/lib/vault-write-back.cjs +70 -42
- package/pp-core/bin/lib/vault.cjs +2734 -920
- 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 +184 -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-practices.md +17 -34
- package/pp-core/references/planner-vault-standards.md +320 -0
- package/pp-core/references/planning-config.md +0 -8
- package/pp-core/references/practice-arithmetic-contract.md +121 -0
- package/pp-core/references/tdd.md +0 -1
- package/pp-core/references/verifier-practice-verdicts.md +263 -0
- package/pp-core/templates/README.md +1 -0
- package/pp-core/templates/VAULT-REVIEW.md +202 -0
- package/pp-core/templates/phase-prompt.md +2 -6
- package/pp-core/templates/practices/README.md +20 -23
- 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 -1
- 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 +213 -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 +329 -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 +98 -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 +322 -20
- package/pp-core/workflows/execute-phase/steps/verifier-practice-payload.md +165 -0
- package/pp-core/workflows/execute-phase/steps/verify-post-gate.md +163 -0
- package/pp-core/workflows/execute-phase.md +155 -128
- package/pp-core/workflows/execute-plan.md +56 -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 +282 -22
- 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 +199 -11
- package/scripts/gen-practice-arithmetic-contract.cjs +412 -0
- 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 +12 -10
- package/scripts/practice-arithmetic-contract.allow.json +35 -0
- package/scripts/prompt-injection-scan.sh +22 -17
- package/scripts/record-vault-mode-golden.cjs +89 -0
- 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/vault-calibrate.cjs +996 -0
- 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/references/executor-vault-trace.md +0 -47
- package/pp-core/references/planner-plan-graph.md +0 -66
- 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
|
@@ -16,14 +16,11 @@ only when it resolves to `gitlab`; under any other provider (`azure` today) it i
|
|
|
16
16
|
**skipped, not failed**, and the run falls through to this same generic remote-wiring
|
|
17
17
|
path so the user still leaves with a wired remote (Phase 34 INIT-03).
|
|
18
18
|
|
|
19
|
-
Step 2
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
`AskUserQuestion` wizard for the question set (`34-VALIDATION.md`). The interaction now
|
|
25
|
-
lives entirely inside `pp-tools init` itself — a human types answers directly into the
|
|
26
|
-
process that acts on them — so this document has nothing left to compress, reorder or drop.
|
|
19
|
+
Step 2 relays the `init-contract` session: the engine returns one step at a time and the
|
|
20
|
+
agent shows each prompt verbatim and passes the reply back, adding no questions of its own
|
|
21
|
+
(earlier relays failed exactly there, `34-VALIDATION.md`). With the embedded org profile the
|
|
22
|
+
session asks only the project name, the description and the compose confirmation.
|
|
23
|
+
`pp-tools init` in a terminal is the same session for a user who prefers it.
|
|
27
24
|
|
|
28
25
|
Inert of all Pareto/GitLab behavior when no .platform-bindings.json is present.
|
|
29
26
|
</purpose>
|
|
@@ -67,23 +64,9 @@ _PP_SHIM_NAME="pp-tools.cjs"; _pp_root() { ( for _v in $(env | sed -n 's/^\(GIT_
|
|
|
67
64
|
# Gate on presence via reader — absent file → {present:false} exit 0 (inert, step skipped, no jq)
|
|
68
65
|
if [ "$(pp_run query platform-bindings --pick present 2>/dev/null)" = "true" ]; then
|
|
69
66
|
PINNED=$(pp_run query platform-bindings --pick platform.tag)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
PLATFORM_REPO_URL="https://${PLATFORM_HOST}/${REPO}.git"
|
|
74
|
-
# Resolve latest STABLE tag from remote — never hard-block if offline (empty LATEST → skip drift check)
|
|
75
|
-
# Keep --refs to strip ^{} annotated-tag rows; --sort=-v:refname sorts server-side
|
|
76
|
-
# Stable filter fixes prerelease mis-ordering: git ranks v1.4.0-rc.1 after v1.4.0 (wrong by semver)
|
|
77
|
-
# semver-compare.cjs is the single blessed source for comparison — do NOT reimplement
|
|
78
|
-
REMOTE_TAGS=$(git ls-remote --tags --refs --sort=-v:refname "$PLATFORM_REPO_URL" 'v*' 2>/dev/null | sed 's#.*/##')
|
|
79
|
-
# IN-03: pass the lib dir via env (PP_LIB_DIR) rather than interpolating ${PP_TOOLS%/*}
|
|
80
|
-
# into the JS program text — keeps the path out of the evaluated source.
|
|
81
|
-
LATEST=$(printf '%s\n' "$REMOTE_TAGS" | PP_LIB_DIR="${PP_TOOLS%/*}/lib" node -e "
|
|
82
|
-
const sc = require(process.env.PP_LIB_DIR + '/semver-compare.cjs');
|
|
83
|
-
const lines = require('fs').readFileSync(0, 'utf-8').split('\n').map(s=>s.trim()).filter(Boolean);
|
|
84
|
-
const stable = lines.filter(sc.isStableTripletSemver).sort(sc.compareSemverCore);
|
|
85
|
-
process.stdout.write((stable[stable.length-1] || '') + '\n');
|
|
86
|
-
" 2>/dev/null)
|
|
67
|
+
# The owner is compose-bindings resolveLatestPlatformTag — the same lookup pp-tools init and check ship.platform-drift use — so the URL is never composed in shell.
|
|
68
|
+
# Tagless, offline and unreachable all print as empty: latest=<unresolved> and the drift comparison is skipped (never hard-block).
|
|
69
|
+
LATEST=$(pp_run query platform-latest-tag --pick latest 2>/dev/null)
|
|
87
70
|
echo "platform preflight: pinned=${PINNED:-<none>} latest=${LATEST:-<unresolved>}"
|
|
88
71
|
if [ -n "$LATEST" ] && [ -n "$PINNED" ] && [ "$PINNED" != "$LATEST" ]; then
|
|
89
72
|
echo "PP_PLATFORM_DRIFT=1"
|
|
@@ -100,8 +83,8 @@ tag. STOP before Step 1 and use `AskUserQuestion` to let the user choose:
|
|
|
100
83
|
- **Proceed on the older pin** — continue knowingly; record the deferral in `PROJECT.md`
|
|
101
84
|
so it is a conscious decision, not silent drift.
|
|
102
85
|
|
|
103
|
-
If `LATEST` was unresolved (
|
|
104
|
-
initiation on an
|
|
86
|
+
If `LATEST` was unresolved (no stable release tag, offline, or an unreachable remote), note it
|
|
87
|
+
and proceed — never hard-block initiation on an unresolved tag. For non-Pareto repos the block is a no-op and this
|
|
105
88
|
step is skipped entirely.
|
|
106
89
|
|
|
107
90
|
---
|
|
@@ -125,7 +108,8 @@ Apply the `#3491` guard (never nest `.git` inside an existing worktree):
|
|
|
125
108
|
- If `has_git` is `true` and `in_nested_subdir` is `true`: **skip `git init`**; warn the
|
|
126
109
|
user: "Initializing inside an existing worktree (`${git_worktree_root}`); planning files
|
|
127
110
|
will track to the outer repo." Do not call `git init`. This case pairs with the
|
|
128
|
-
|
|
111
|
+
skip-git-init flag the gated GitLab provisioning path's locked create recipe
|
|
112
|
+
carries (G-4).
|
|
129
113
|
- If `has_git` is `true` and `in_nested_subdir` is `false`: **skip `git init`** (already
|
|
130
114
|
at the worktree root).
|
|
131
115
|
- If `has_git` is `false`: run `git init` to initialize a new repository.
|
|
@@ -135,50 +119,42 @@ Step 0). Continue honoring it for every `AskUserQuestion` call in this and later
|
|
|
135
119
|
|
|
136
120
|
---
|
|
137
121
|
|
|
138
|
-
## 2. Identity, provider and binding —
|
|
122
|
+
## 2. Identity, provider and binding — relay the `init-contract` session (D-05, D-07, INIT-01, INIT-02, INIT-03)
|
|
139
123
|
|
|
140
|
-
|
|
141
|
-
|
|
124
|
+
`pp-tools init` is a loop over this same session, so relaying it here makes the same writes
|
|
125
|
+
(`.planning/config.json`, `vault.provider`, `vault.enabled`, `.platform-bindings.json`). The
|
|
126
|
+
engine owns which questions exist, their order and every write; you only relay. Repeat until
|
|
127
|
+
the step is `done`:
|
|
142
128
|
|
|
143
|
-
```
|
|
144
|
-
|
|
129
|
+
```bash
|
|
130
|
+
pp_run query init-contract --session-next
|
|
145
131
|
```
|
|
146
132
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
**
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
133
|
+
Act on the JSON it prints and on nothing else. Ask in plain chat text, never with
|
|
134
|
+
`AskUserQuestion`: the answers are free text, and invented options or extra questions are the
|
|
135
|
+
failure this step guards against.
|
|
136
|
+
|
|
137
|
+
- **A question** (`step` is a question id): first list each new `autoAnswered` entry once as
|
|
138
|
+
`id = value (source)`, then ask `prompt` word for word. With `optionalAnswer: true` the user
|
|
139
|
+
may answer `none`. Pass the reply unchanged, in single quotes (a `'` written as `'\''`):
|
|
140
|
+
`pp_run query init-contract --session-answer '<step>=<reply>'`. On an error, show its
|
|
141
|
+
message and ask the same `prompt` again.
|
|
142
|
+
- **`compose_confirm`**: show `info.provenance` as a field / value / source table, ask
|
|
143
|
+
`prompt`, and pass the reply the same way.
|
|
144
|
+
- **`verify`**: show each violation's code and message and stop; the user fixes the cause, or
|
|
145
|
+
runs `pp_run query init-contract --session-reset` and Step 2 starts over.
|
|
146
|
+
- **`done`**: continue to Step 2.6.
|
|
147
|
+
|
|
148
|
+
A user who would rather type into the engine directly can run
|
|
149
|
+
`node "<dir>/bin/pp-tools.cjs" init` in their own terminal instead, `<dir>` being the directory
|
|
150
|
+
on this run's `pp-core answered from:` line (a bare `pp-tools` resolves through `PATH` and can
|
|
151
|
+
be another install); wait for them to confirm before Step 2.6.
|
|
164
152
|
|
|
165
153
|
**If this step is skipped, `/pp-init` has not completed** — no identity config, no
|
|
166
154
|
`vault.provider`, and no binding are produced without it. `pp_run query init-contract
|
|
167
155
|
--verify` reports exactly what is still missing if the operator wants to confirm before
|
|
168
156
|
continuing.
|
|
169
157
|
|
|
170
|
-
**Then arm and probe the client vault** (after `pp-tools init` finishes):
|
|
171
|
-
|
|
172
|
-
```bash
|
|
173
|
-
pp_run query init-contract --arm-vault
|
|
174
|
-
pp_run vault probe
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
Show both outputs verbatim. `--arm-vault` sets `vault.enabled: true`, and then `vault.required:
|
|
178
|
-
true` and `vault.practices_enabled: true`, each only where the key is absent (an explicit value is kept); `ok:false` means no client vault is bound — re-run
|
|
179
|
-
`pp-tools init`. The probe line says whether the client vault is reachable at its pinned ref;
|
|
180
|
-
it prints nothing when the vault is disabled. Neither ever blocks this step.
|
|
181
|
-
|
|
182
158
|
**Scope boundary:** this step writes identity, provider and the platform binding only. All
|
|
183
159
|
coding-standards, architecture context, client-engagement CLAUDE.md blocks, and
|
|
184
160
|
discovery-session scaffolding are out of scope here. Phase 9 owns those; /pp-discovery
|
|
@@ -186,6 +162,188 @@ self-generates its own session files (D-03).
|
|
|
186
162
|
|
|
187
163
|
---
|
|
188
164
|
|
|
165
|
+
## 2.6. Reachability proof (D-07-01, D-07-05, INIT-01, INIT-02, INIT-03)
|
|
166
|
+
|
|
167
|
+
Runs immediately after the Step 2 session is `done`, before any remote wiring. Three gate
|
|
168
|
+
signals decide whether it does anything at all, in this order: `vault.enabled` is the
|
|
169
|
+
literal `true` (OD-1 — `pp-tools init` writes it once a profile resolved and composition
|
|
170
|
+
succeeded); an org profile resolves (D-07-01); then a platform binding is present ⇒ probe,
|
|
171
|
+
absent ⇒ the loud failure below (D-07-02). A generic or non-vault project fails the first
|
|
172
|
+
two signals, and this whole step then prints **nothing at all** — no probe, no cache, no
|
|
173
|
+
record, no side effect of any kind (INIT-01).
|
|
174
|
+
|
|
175
|
+
The probe's exit code is `0` on every path by contract (STRICT-02): the verdict lives in
|
|
176
|
+
its booleans, never in `$?`, and its `error` prose is never echoed (D-07-07). The fetch
|
|
177
|
+
runs only *after* a clean probe, so a project that cannot reach its vault never ends up
|
|
178
|
+
with a half-populated cache (D-07-05).
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
# D-07-01 + OD-1 + INIT-01: three signals, all read through the resolvers every other path
|
|
182
|
+
# uses. Anything but the literal "true" is a fail-inert default.
|
|
183
|
+
PP_INIT_PROBE=""
|
|
184
|
+
# 16-16 D-81: the gate answers for itself (isVaultGateActive). A string "true" and a
|
|
185
|
+
# flat-only key read OFF there, in --verify (15-04 D-32) and here alike. It reads no
|
|
186
|
+
# bindings, so the unbound arm below stays reachable.
|
|
187
|
+
VAULT_ON=$(pp_run loop render-hooks plan:pre --active-cap vault 2>/dev/null || echo false)
|
|
188
|
+
# --raw is load-bearing on the org-profile read: without it that arm prints prose.
|
|
189
|
+
PROFILE_RESOLVED=$(pp_run query vault org-profile --raw --pick profile.resolved 2>/dev/null || echo false)
|
|
190
|
+
if [ "$VAULT_ON" = "true" ] && [ "$PROFILE_RESOLVED" = "true" ]; then
|
|
191
|
+
PROFILE_NAME=$(pp_run query vault org-profile --raw --pick profile.name 2>/dev/null || echo "")
|
|
192
|
+
if [ "$(pp_run query platform-bindings --pick present 2>/dev/null)" != "true" ]; then
|
|
193
|
+
# D-07-02: bindings ABSENT on a profile-bound project is the loud failure, never a skip; a
|
|
194
|
+
# present but malformed file is named as such (D-47 b: the verb treats it as present).
|
|
195
|
+
PP_INIT_PROBE=unbound
|
|
196
|
+
PP_INIT_BINDINGS="is absent"; PP_INIT_WHY="no platform binding"; PP_INIT_SEEN="--verify cannot see an unbound stop without a record: it reports ok:true until .planning is writable and /pp-init is re-run"
|
|
197
|
+
[ -f .platform-bindings.json ] && { PP_INIT_BINDINGS="is present and will not parse"; PP_INIT_WHY="malformed platform binding"; PP_INIT_SEEN="--verify reports INIT_PROBE_NOT_RUN until a re-run records it"; }
|
|
198
|
+
printf '%s\n' "Reachability proof: NOT POSSIBLE — org profile \"${PROFILE_NAME}\" resolved, so a platform binding is expected, but .platform-bindings.json ${PP_INIT_BINDINGS}."
|
|
199
|
+
# WR-02: the remedy branches on a MEASUREMENT (D-07-07): `--reset` reaches the compose
|
|
200
|
+
# confirmation only when something is composable. Offline dry run, exit 0.
|
|
201
|
+
COMPOSABLE=$(pp_run query compose-bindings --offline --pick composable 2>/dev/null || echo false)
|
|
202
|
+
if [ "$COMPOSABLE" = "true" ]; then
|
|
203
|
+
printf '%s\n' "Nothing was probed. Run \"pp-tools init --reset\" to redo the question set and reach the compose confirmation, then re-run /pp-init."
|
|
204
|
+
else
|
|
205
|
+
printf '%s\n' "Nothing was probed, and \"pp-tools init --reset\" cannot help — the org profile seeds no binding to compose. Add platform.repo and client.vaultRepo / client.vaultRef to .platform-bindings.json by hand (or update the profile), then re-run /pp-init."
|
|
206
|
+
fi
|
|
207
|
+
# WR-02: nothing was probed, but the stop IS recorded, or --verify reports ok:true on a
|
|
208
|
+
# project that halted. R2-WR-04: a REFUSED write is a detail line (reachability-outcome.md
|
|
209
|
+
# c-unrecorded) stating the MEASURED consequence: owesAProof keys on the bindings file, so
|
|
210
|
+
# --verify sees the stop only when the file exists (the malformed case). Both arms' wording
|
|
211
|
+
# is re-measured by the Step 2.6 test file.
|
|
212
|
+
if ! pp_run query init-contract --record-probe "failed=${PP_INIT_WHY}; nothing could be probed" >/dev/null 2>&1; then
|
|
213
|
+
printf '%s\n' " record .planning/config.json not written — this stop is unrecorded, and ${PP_INIT_SEEN}"
|
|
214
|
+
fi
|
|
215
|
+
echo "PP_INIT_PROBE=unbound"
|
|
216
|
+
else
|
|
217
|
+
# D-07-05: probe first. ONE --raw capture; every read below parses this text, because
|
|
218
|
+
# each --pick would re-run the whole probe. The exit code is 0 on every path.
|
|
219
|
+
PROBE=$(pp_run query vault probe --raw 2>/dev/null || echo '{"targets":[]}')
|
|
220
|
+
# R3-CR-02 + R3-WR-02: ONE evaluation of the target set, read by every arm
|
|
221
|
+
# below. FOUR hand-copied de-dup pipelines stood here and had already
|
|
222
|
+
# diverged; the one that mattered inverted the proof. 07-18 excluded the
|
|
223
|
+
# fallback tier AFTER the de-dup, and targets arrive in the fixed order
|
|
224
|
+
# client, shared, platform — so a platform sharing a coordinate with the
|
|
225
|
+
# fallback survived under the FALLBACK'S NAME, the exclusion deleted the
|
|
226
|
+
# only representative of a REQUIRED tier, and Step 2.6 printed and durably
|
|
227
|
+
# recorded a platform reachable line for a target measured unreachable. The
|
|
228
|
+
# exclusion now runs BEFORE the de-dup; the DISPLAY de-dup keeps the REQUIRED
|
|
229
|
+
# tier's identity when two tiers name one coordinate (a fallback identical to
|
|
230
|
+
# the client is still one remote, not two). OD-5 Option A unchanged: the
|
|
231
|
+
# fallback tier stays out of the verdict and out of the note's sick list and
|
|
232
|
+
# keeps its "(fallback tier, not required)" line when its coordinate is its
|
|
233
|
+
# own. The all-down flag counts EVERY distinct remote, fallback included, ON
|
|
234
|
+
# PURPOSE: it drives the offline candidate below, and a fallback that answered
|
|
235
|
+
# is evidence the network is up. CR-02: a REQUIRED target with no coordinate
|
|
236
|
+
# gets its own "missing" line — without it the D-07-02 case printed a card
|
|
237
|
+
# whose every line read healthy under a fail verdict; measured, not a
|
|
238
|
+
# diagnosis (D-07-07). EMITS: line 1 verdict, 2 all-down, 3 note, 4+ card; a
|
|
239
|
+
# card line can never carry a newline of its own (redactCloneTarget collapses
|
|
240
|
+
# the control class at the source, R2-WR-03), so the split below is safe.
|
|
241
|
+
PROBE_EVAL=$(printf '%s' "$PROBE" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{const t=(JSON.parse(d).targets||[]).filter(x=>x&&x.configured===true);const key=x=>[x.repo,x.ref,x.host].join('|');const names=new Set(t.map(x=>x.name));const seen=new Set();const req=t.filter(x=>x.name!=='shared'&&!seen.has(key(x))&&seen.add(key(x)));const pick=new Map();for(const x of t){const k=key(x);const p=pick.get(k);if(!p||(p.name==='shared'&&x.name!=='shared'))pick.set(k,x);}const shown=t.filter(x=>pick.get(key(x))===x);const cfg={client:'client.vaultRepo',platform:'platform.repo'};const gone=Object.keys(cfg).filter(n=>!names.has(n));const ok=names.has('client')&&names.has('platform')&&req.length>0&&req.every(x=>x.reachable===true&&x.refFound===true);const sick=req.filter(x=>!(x.reachable===true&&x.refFound===true)).map(x=>String(x.name)+' '+String(x.repoSafe)+' @ '+String(x.ref)+' '+(x.reachable===true?'ref not found':'unreachable'));const all=gone.map(n=>n+' not configured').concat(sick);const lines=shown.map(x=>' '+String(x.name).padEnd(9)+String(x.repoSafe)+' @ '+((x.name==='platform'&&x.ref==='HEAD')?'(unpinned)':String(x.ref))+' '+(x.reachable===true?(x.refFound===true?'reachable, ref found':'ref not found'):'unreachable')+(x.name==='shared'?' (fallback tier, not required)':'')).concat(gone.map(n=>' '+n.padEnd(9)+'(not configured) missing — '+cfg[n]+' is absent from .platform-bindings.json'));process.stdout.write([ok?'pass':'fail',String(shown.length>0&&shown.every(x=>x.reachable!==true)),'failed='+(all.length?all.join('; '):'probe payload unreadable')].concat(lines).join('\n'));}catch{process.stdout.write('fail\nfalse\nfailed=probe payload unreadable');}")
|
|
242
|
+
PROBE_VERDICT=$(printf '%s' "$PROBE_EVAL" | sed -n '1p')
|
|
243
|
+
PROBE_ALL_DOWN=$(printf '%s' "$PROBE_EVAL" | sed -n '2p')
|
|
244
|
+
PROBE_NOTE=$(printf '%s' "$PROBE_EVAL" | sed -n '3p')
|
|
245
|
+
PROBE_LINES=$(printf '%s' "$PROBE_EVAL" | sed -n '4,$p')
|
|
246
|
+
FAIL_KIND=""
|
|
247
|
+
FAIL_DETAIL=""
|
|
248
|
+
FAIL_NOTE=""
|
|
249
|
+
if [ "$PROBE_VERDICT" = "pass" ]; then
|
|
250
|
+
# D-07-05: the fetch is what produces INIT-02's count, warm cache and SUMMARY.md.
|
|
251
|
+
FETCH=$(pp_run query vault fetch --raw 2>/dev/null || echo '{}')
|
|
252
|
+
FETCH_TIER=$(printf '%s' "$FETCH" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{process.stdout.write(String(JSON.parse(d).tier||''));}catch{process.stdout.write('');}")
|
|
253
|
+
FETCH_SOURCE=$(printf '%s' "$FETCH" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{process.stdout.write(String(JSON.parse(d).source||''));}catch{process.stdout.write('');}")
|
|
254
|
+
FETCH_REPO=$(printf '%s' "$FETCH" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{process.stdout.write(String(JSON.parse(d).repoSafe||''));}catch{process.stdout.write('');}")
|
|
255
|
+
FETCH_REF=$(printf '%s' "$FETCH" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{process.stdout.write(String(JSON.parse(d).ref||''));}catch{process.stdout.write('');}")
|
|
256
|
+
FETCH_SHA=$(printf '%s' "$FETCH" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{process.stdout.write(String(JSON.parse(d).resolvedSha||'').slice(0,8));}catch{process.stdout.write('');}")
|
|
257
|
+
FETCH_COUNT=$(printf '%s' "$FETCH" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{process.stdout.write(String((JSON.parse(d).files||[]).length));}catch{process.stdout.write('0');}")
|
|
258
|
+
FETCH_SUMMARY=$(printf '%s' "$FETCH" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{process.stdout.write(String(JSON.parse(d).summaryWritten===true));}catch{process.stdout.write('false');}")
|
|
259
|
+
PLATFORM_REPO=$(printf '%s' "$PROBE" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{const x=(JSON.parse(d).targets||[]).find(y=>y&&y.name==='platform');process.stdout.write(String((x&&x.repoSafe)||''));}catch{process.stdout.write('');}")
|
|
260
|
+
# Never the probe's internal HEAD placeholder — an unpinned platform says so.
|
|
261
|
+
PLATFORM_TAG=$(pp_run query platform-bindings --pick platform.tag 2>/dev/null)
|
|
262
|
+
if [ "$FETCH_TIER" = "client" ] && [ "$FETCH_SUMMARY" = "true" ] && { [ "$FETCH_SOURCE" = "fetched" ] || [ "$FETCH_SOURCE" = "cache" ]; }; then
|
|
263
|
+
# WR-03: the record write runs BEFORE the card and its exit status decides which
|
|
264
|
+
# card prints. A refused write used to be a warning under a PASSED card, and PASSED
|
|
265
|
+
# routes to "continue to Step 3" — so "unrecorded" was defined to continue. It is a
|
|
266
|
+
# failure of the step, kind FAIL_KIND=record. Markers unchanged: pass/unbound/fail.
|
|
267
|
+
if pp_run query init-contract --record-probe "passed=platform ${PLATFORM_REPO} @ ${PLATFORM_TAG:-(unpinned)}; vault ${FETCH_REPO} @ ${FETCH_REF} tier ${FETCH_TIER} ${FETCH_COUNT} files" >/dev/null 2>&1; then
|
|
268
|
+
PP_INIT_PROBE=pass
|
|
269
|
+
echo "Reachability proof: PASSED"
|
|
270
|
+
# OD-3 + CR-01: the DISPLAY-SAFE coordinate (repoSafe), ref, tier and booleans
|
|
271
|
+
# only. Credentials are stripped at the source, so a PAT in the binding reaches
|
|
272
|
+
# neither this card nor the record. Never url, never error.
|
|
273
|
+
printf '%s\n' " platform ${PLATFORM_REPO} @ ${PLATFORM_TAG:-(unpinned)} reachable"
|
|
274
|
+
printf '%s\n' " vault ${FETCH_REPO} @ ${FETCH_REF} reachable, ref found — tier ${FETCH_TIER}, sha ${FETCH_SHA}, ${FETCH_COUNT} files, SUMMARY.md written (${FETCH_SOURCE})"
|
|
275
|
+
printf '%s\n' "$PROBE_LINES" | grep '^ shared ' || true
|
|
276
|
+
echo "PP_INIT_PROBE=pass"
|
|
277
|
+
else
|
|
278
|
+
PP_INIT_PROBE=fail
|
|
279
|
+
FAIL_KIND=record
|
|
280
|
+
FAIL_NOTE=""
|
|
281
|
+
FAIL_DETAIL="$PROBE_LINES
|
|
282
|
+
record .planning/config.json not written — the proof is unrecorded; --verify reports it as never run"
|
|
283
|
+
fi
|
|
284
|
+
else
|
|
285
|
+
PP_INIT_PROBE=fail
|
|
286
|
+
FAIL_KIND=fetch
|
|
287
|
+
FAIL_DETAIL=" The probe answered, but the fetch did not land on the client vault: tier ${FETCH_TIER:-none}, source ${FETCH_SOURCE:-none}, SUMMARY.md written ${FETCH_SUMMARY:-false}."
|
|
288
|
+
FAIL_NOTE="failed=fetch tier ${FETCH_TIER:-none} source ${FETCH_SOURCE:-none} summaryWritten ${FETCH_SUMMARY:-false}"
|
|
289
|
+
fi
|
|
290
|
+
else
|
|
291
|
+
PP_INIT_PROBE=fail
|
|
292
|
+
FAIL_KIND=probe
|
|
293
|
+
FAIL_DETAIL="$PROBE_LINES"
|
|
294
|
+
# R3-WR-02: the recorded miss comes from the SAME evaluation the verdict and
|
|
295
|
+
# the card came from — recomputing it here is how the four copies drifted.
|
|
296
|
+
# CR-02: filtering the RENDERING was the original defect (an ABSENT target
|
|
297
|
+
# left the note a bare failed=, which recordProbeOutcome refused).
|
|
298
|
+
FAIL_NOTE="$PROBE_NOTE"
|
|
299
|
+
fi
|
|
300
|
+
# INIT-03 + D-07-07: ONE failure block for both fail arms — the header, the candidate
|
|
301
|
+
# heading and the escape sentence have a single home, so they cannot drift apart.
|
|
302
|
+
if [ "$PP_INIT_PROBE" = "fail" ]; then
|
|
303
|
+
echo "Reachability proof: FAILED — init is not complete."
|
|
304
|
+
printf '%s\n' "$FAIL_DETAIL"
|
|
305
|
+
echo "Things to check (candidates, not diagnoses):"
|
|
306
|
+
if [ "$FAIL_KIND" = "record" ]; then
|
|
307
|
+
echo " - .planning/config.json or its directory is not writable (permissions, a read-only mount, a full disk)"
|
|
308
|
+
echo " - re-run /pp-init once it is — the probe and fetch are cheap to repeat"
|
|
309
|
+
elif [ "$FAIL_KIND" = "fetch" ]; then
|
|
310
|
+
echo " - the client vault ref exists at the remote but its content could not be cloned (sparse paths, permissions)"
|
|
311
|
+
echo " - a fresh cache from an earlier run recorded a fallback tier — remove .planning/vault and re-run /pp-init"
|
|
312
|
+
echo " - .planning/vault/SUMMARY.md could not be written (directory permissions)"
|
|
313
|
+
else
|
|
314
|
+
if [ "$PROBE_ALL_DOWN" = "true" ]; then
|
|
315
|
+
echo " - no network / offline — every distinct remote was unreachable (record a deferral if you are offline; re-run /pp-init when back)"
|
|
316
|
+
fi
|
|
317
|
+
echo " - SSH key not loaded in the agent (scp-style targets)"
|
|
318
|
+
echo " - HTTPS credentials not configured for this host — git refused to prompt (GIT_TERMINAL_PROMPT=0)"
|
|
319
|
+
echo " - az login / host CLI session expired"
|
|
320
|
+
echo " - the ref does not exist at the remote (a 'ref not found' line above)"
|
|
321
|
+
echo " - a typo in the org profile or in .platform-bindings.json"
|
|
322
|
+
echo " - a global git url.<base>.insteadOf rewrite sending git to a different host than the one printed"
|
|
323
|
+
fi
|
|
324
|
+
echo "Next: fix and re-run /pp-init, or record an attributed deferral (see below)."
|
|
325
|
+
# Skipped when the failure IS the record write — it just failed (FAIL_KIND=record).
|
|
326
|
+
if [ "$FAIL_KIND" != "record" ]; then
|
|
327
|
+
pp_run query init-contract --record-probe "$FAIL_NOTE" >/dev/null 2>&1 || echo "warning: the probe outcome could not be recorded in .planning/config.json — --verify will report INIT_PROBE_NOT_RUN until a re-run records it"
|
|
328
|
+
fi
|
|
329
|
+
echo "PP_INIT_PROBE=fail"
|
|
330
|
+
fi
|
|
331
|
+
fi
|
|
332
|
+
fi
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
**What the block printed decides what happens next.** Nothing at all ⇒ Step 2.6 was inert
|
|
336
|
+
(generic project, vault off, or no org profile resolved): continue to Step 3 without
|
|
337
|
+
comment, and do not mention the vault, the probe or this step to the user. On any other
|
|
338
|
+
outcome — `PP_INIT_PROBE=pass`, `PP_INIT_PROBE=unbound` or `PP_INIT_PROBE=fail` — **Read
|
|
339
|
+
`pp-core/workflows/init/steps/reachability-outcome.md` now and follow it.** That file owns
|
|
340
|
+
the pass narration, the INIT-03 stop, the two-option `AskUserQuestion`, the
|
|
341
|
+
`init-contract --defer-probe` escape and its three reason rules, and D-07-06 (being offline
|
|
342
|
+
is not a reason to skip). Do not invent a fourth outcome, and do not re-run the probe
|
|
343
|
+
hoping for a different answer.
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
189
347
|
## 3. Remote wiring — provider-agnostic (non-Pareto / no bindings)
|
|
190
348
|
|
|
191
349
|
**Gate (D-06, D-13) — bindings presence ONLY, never the provider answer.** Step 3 always
|
|
@@ -305,6 +463,12 @@ instead — do not fail, do not error, do not prompt for a missing CLI. "Skipped
|
|
|
305
463
|
failed" is satisfied *and* the user still leaves with a wired remote, which a bare skip
|
|
306
464
|
would not give them; pp-core makes zero provider-API calls it has no client for.
|
|
307
465
|
|
|
466
|
+
When both conditions hold, **Read `pp-core/workflows/init/steps/gitlab-provisioning.md` and
|
|
467
|
+
follow it.** That file owns G-2 (auth verify-only), G-3 (existence probe →
|
|
468
|
+
adopt-if-exists), G-4 (per-action confirmation, then the locked `--private` create) and G-5
|
|
469
|
+
(post-create reconciliation). Under any other provider do not read it at all — fall through
|
|
470
|
+
to Step 3's generic remote wiring above.
|
|
471
|
+
|
|
308
472
|
**Security invariants — landmine guards:**
|
|
309
473
|
- Always use the group-qualified `$GROUP/$SLUG` path — never a bare slug (T-08-04, D-09).
|
|
310
474
|
A bare slug creates under the user's personal namespace, not the client group. G-1's
|
|
@@ -318,126 +482,6 @@ would not give them; pp-core makes zero provider-API calls it has no client for.
|
|
|
318
482
|
- Always reconcile create with a `glab repo view` probe — a non-zero create exit does
|
|
319
483
|
NOT mean nothing happened (T-08-09, D-09).
|
|
320
484
|
|
|
321
|
-
#### G-2. Auth verify-only (D-11)
|
|
322
|
-
|
|
323
|
-
`/pp-init` **never establishes auth and never reads or writes the user's token.**
|
|
324
|
-
Secrets stay in the environment or OS keyring. Only verify that a session exists:
|
|
325
|
-
|
|
326
|
-
```bash
|
|
327
|
-
glab auth status >/dev/null 2>&1 || {
|
|
328
|
-
echo "Not authenticated to GitLab. Run: glab auth login"
|
|
329
|
-
exit 1
|
|
330
|
-
}
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
If this check fails, stop immediately. Do not proceed to probe or create.
|
|
334
|
-
Never run `glab auth login` from within `/pp-init`.
|
|
335
|
-
|
|
336
|
-
#### G-3. Existence probe → adopt-if-exists (D-09, D-12)
|
|
337
|
-
|
|
338
|
-
Pre-check with a read-only probe before any create attempt. `glab repo create` is **not
|
|
339
|
-
idempotent** — re-running it against an existing project errors but may still produce side
|
|
340
|
-
effects (the GitLab project is created before the remote-add step that then fails). The
|
|
341
|
-
probe avoids this landmine.
|
|
342
|
-
|
|
343
|
-
This is one self-contained block: it sets `PROJECT_EXISTS` and, on the exists path,
|
|
344
|
-
adopts (links the remote). It NEVER creates — create lives in its own G-4 block, gated
|
|
345
|
-
on `PROJECT_EXISTS=false`, so the create invocation can never fall inside the exists
|
|
346
|
-
branch.
|
|
347
|
-
|
|
348
|
-
```bash
|
|
349
|
-
if glab repo view "$GROUP/$SLUG" -F json >/dev/null 2>&1; then
|
|
350
|
-
# Project already exists — ADOPT: link remote only, skip create (D-12)
|
|
351
|
-
PROJECT_EXISTS=true
|
|
352
|
-
echo "GitLab project $GROUP/$SLUG already exists — adopting (linking remote, skipping create)."
|
|
353
|
-
# IN-01: match the remote scheme glab itself uses on the create path — honor the
|
|
354
|
-
# user's `glab config get git_protocol` (https default) instead of hardcoding SSH,
|
|
355
|
-
# so adopt and create produce a consistent origin URL.
|
|
356
|
-
GIT_PROTO=$(glab config get git_protocol 2>/dev/null || echo https)
|
|
357
|
-
if [ "$GIT_PROTO" = "ssh" ]; then
|
|
358
|
-
ADOPT_URL="git@${HOST:-gitlab.com}:$GROUP/$SLUG.git"
|
|
359
|
-
else
|
|
360
|
-
ADOPT_URL="https://${HOST:-gitlab.com}/$GROUP/$SLUG.git"
|
|
361
|
-
fi
|
|
362
|
-
git remote get-url origin >/dev/null 2>&1 || git remote add origin "$ADOPT_URL"
|
|
363
|
-
echo "Remote 'origin' → $ADOPT_URL"
|
|
364
|
-
else
|
|
365
|
-
# Project not found — defer to G-4 for confirm-then-create
|
|
366
|
-
PROJECT_EXISTS=false
|
|
367
|
-
echo "GitLab project $GROUP/$SLUG not found — will confirm before create."
|
|
368
|
-
fi
|
|
369
|
-
```
|
|
370
|
-
|
|
371
|
-
If the project exists (`PROJECT_EXISTS=true`), the remote is already linked above
|
|
372
|
-
(skipping create entirely) — proceed straight to G-5 reconciliation. The adopt path does
|
|
373
|
-
not show a create confirmation — it only links the remote, which is a local-only
|
|
374
|
-
operation and can be undone with `git remote remove origin`.
|
|
375
|
-
|
|
376
|
-
#### G-4. Per-action confirmation, then create (D-05, D-09, D-10, SAFE-01)
|
|
377
|
-
|
|
378
|
-
**Only run this step when `PROJECT_EXISTS=false`** (the existence probe in G-3 found no
|
|
379
|
-
project). If `PROJECT_EXISTS=true`, skip G-4 entirely — the project was already adopted —
|
|
380
|
-
and go to G-5. When creating, **show the exact command and target before firing**. The
|
|
381
|
-
user must confirm explicitly. Nothing is batched. (D-05)
|
|
382
|
-
|
|
383
|
-
Present to the user (via `AskUserQuestion` or TEXT_MODE numbered list):
|
|
384
|
-
|
|
385
|
-
```text
|
|
386
|
-
About to create GitLab project:
|
|
387
|
-
Command: glab repo create "$GROUP/$SLUG"
|
|
388
|
-
Group path: $GROUP/$SLUG
|
|
389
|
-
Visibility: private (enforced --private; never the glab default --internal)
|
|
390
|
-
Requested: $VIS (from bindings gitlab.visibility — see note below)
|
|
391
|
-
Default branch: main
|
|
392
|
-
Skip git init: yes (local repo already initialized — --skipGitInit)
|
|
393
|
-
Remote name: origin
|
|
394
|
-
|
|
395
|
-
Proceed? [y/N]
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
`$VIS` (from G-1) is surfaced in the confirmation so the binding's requested visibility is
|
|
399
|
-
never silently ignored (WR-01). pp-core always **creates private** regardless; a
|
|
400
|
-
non-private binding must be applied as a deliberate, separate post-create change.
|
|
401
|
-
|
|
402
|
-
Only after the user confirms — and only with `PROJECT_EXISTS=false` — run the locked
|
|
403
|
-
create recipe (D-09, D-10). This is a standalone block: it carries no `if`/`else`/`fi`, so
|
|
404
|
-
it can only ever run as the deliberate, confirmed not-exists path:
|
|
405
|
-
|
|
406
|
-
```bash
|
|
407
|
-
glab repo create "$GROUP/$SLUG" \
|
|
408
|
-
--description "$DESC" \
|
|
409
|
-
--private \
|
|
410
|
-
--defaultBranch main \
|
|
411
|
-
--skipGitInit \
|
|
412
|
-
--remoteName origin
|
|
413
|
-
```
|
|
414
|
-
|
|
415
|
-
**Visibility hard-default (D-10):** the create invocation always carries `--private`.
|
|
416
|
-
Never omit it and let `glab` silently apply `--internal`. `VIS` from bindings is read in
|
|
417
|
-
G-1 and **surfaced in the G-4 confirmation** (WR-01) so a `public`/`internal` request is
|
|
418
|
-
visible to the user — but pp-core still creates the project **private**. Applying a
|
|
419
|
-
non-private visibility is a deliberate, separate post-create action (e.g. `glab repo edit
|
|
420
|
-
--visibility …`) the operator performs knowingly; `/pp-init` never auto-relaxes visibility.
|
|
421
|
-
|
|
422
|
-
#### G-5. Post-create reconciliation (D-09)
|
|
423
|
-
|
|
424
|
-
After the create (or after the adopt path), re-probe existence. A non-zero exit from
|
|
425
|
-
`glab repo create` does **not** mean the project was not created — `glab` exits non-zero
|
|
426
|
-
(code 3) when the GitLab project was created but the subsequent `git remote add` step
|
|
427
|
-
then failed (e.g., because `origin` already existed). The `repo view` probe is the source
|
|
428
|
-
of truth, not the exit code (research §1 "Remote-add caveat — VERIFIED"):
|
|
429
|
-
|
|
430
|
-
```bash
|
|
431
|
-
if glab repo view "$GROUP/$SLUG" -F json >/dev/null 2>&1; then
|
|
432
|
-
echo "Reconciled: GitLab project $GROUP/$SLUG confirmed."
|
|
433
|
-
else
|
|
434
|
-
echo "WARN: glab repo view $GROUP/$SLUG returned non-zero — verify the project was created before continuing."
|
|
435
|
-
fi
|
|
436
|
-
```
|
|
437
|
-
|
|
438
|
-
If reconciliation warns, do not auto-retry. Prompt the user to check GitLab directly
|
|
439
|
-
before proceeding to Step 4 (handoff routing).
|
|
440
|
-
|
|
441
485
|
### Gated client-context capture (Pareto projects — bindings present)
|
|
442
486
|
|
|
443
487
|
This step runs **only when** `pp_run query platform-bindings --pick present 2>/dev/null` equals
|
|
@@ -445,96 +489,12 @@ This step runs **only when** `pp_run query platform-bindings --pick present 2>/d
|
|
|
445
489
|
success criterion #4). All capture is **offline-only** — no Obsidian, no MCP, no network (D-05 /
|
|
446
490
|
T-09-05).
|
|
447
491
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
if [ "$EXISTING_PRESENT" = "true" ]; then
|
|
455
|
-
echo ""
|
|
456
|
-
echo "Existing client-context block found. Press Enter to keep each value, or type to replace it."
|
|
457
|
-
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)
|
|
458
|
-
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)
|
|
459
|
-
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)
|
|
460
|
-
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)
|
|
461
|
-
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)
|
|
462
|
-
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)
|
|
463
|
-
fi
|
|
464
|
-
fi
|
|
465
|
-
```
|
|
466
|
-
|
|
467
|
-
**Field capture — one field at a time (D-01, D-02):** Prompt the user for each of the five fields
|
|
468
|
-
sequentially. All five are required — **re-prompt on empty input until a non-empty value is given**
|
|
469
|
-
(no skip, no catch-all). Multi-line values are accepted: instruct the user to type or paste, then
|
|
470
|
-
enter a blank line (or a sentinel line such as `EOF`) to signal end-of-field. Do not call any
|
|
471
|
-
network resource during capture.
|
|
472
|
-
|
|
473
|
-
Capture these fields, in this order:
|
|
474
|
-
|
|
475
|
-
1. **Client name** — the human-readable client or project name (e.g. "Acme Corp").
|
|
476
|
-
- On re-run: show the current value (`$EXISTING_CLIENT_NAME`) and let the user keep or replace.
|
|
477
|
-
- After capturing, derive a URL-safe slug via `pp_run query generate-slug` on the name; present
|
|
478
|
-
the suggested slug and let the user confirm or override. Store as `CLIENT_SLUG`.
|
|
479
|
-
|
|
480
|
-
2. **Coding standards** — the conventions and rules the Build agent must follow (e.g. language,
|
|
481
|
-
style guide, patterns to use/avoid). Multi-line OK.
|
|
482
|
-
- On re-run: show `$EXISTING_CODING`.
|
|
483
|
-
|
|
484
|
-
3. **Architecture patterns** — the structural patterns and constraints the Build agent must respect
|
|
485
|
-
(e.g. layering, module boundaries, framework idioms). Multi-line OK.
|
|
486
|
-
- On re-run: show `$EXISTING_ARCH`.
|
|
487
|
-
|
|
488
|
-
4. **UI / brand conventions** — visual and component standards the Build agent must apply (e.g.
|
|
489
|
-
design system, color tokens, typography rules). Multi-line OK.
|
|
490
|
-
- On re-run: show `$EXISTING_UI`.
|
|
491
|
-
|
|
492
|
-
5. **Definition of done** — the quality gates that must pass before any task is marked complete
|
|
493
|
-
(e.g. tests green, lint clean, accessibility checked). Multi-line OK.
|
|
494
|
-
- On re-run: show `$EXISTING_DOD`.
|
|
495
|
-
|
|
496
|
-
**Write block** — after all five fields are captured, write them to a temp file and invoke the
|
|
497
|
-
managed-block writer:
|
|
498
|
-
|
|
499
|
-
```bash
|
|
500
|
-
if [ "$(pp_run query platform-bindings --pick present 2>/dev/null)" = "true" ]; then
|
|
501
|
-
# Write collected values to a temp JSON file using node + JSON.stringify so that
|
|
502
|
-
# multi-line values, embedded quotes, backslashes, and $(...) sequences are encoded
|
|
503
|
-
# correctly (CR-02: an unquoted heredoc would produce invalid JSON for any multi-line
|
|
504
|
-
# field, breaking cmdGenerateClientContext with "Failed to parse JSON input").
|
|
505
|
-
TMP_CTX=$(mktemp /tmp/pp-client-ctx-XXXXXX.json)
|
|
506
|
-
CLIENT_NAME="$CLIENT_NAME" \
|
|
507
|
-
CLIENT_SLUG="$CLIENT_SLUG" \
|
|
508
|
-
CODING_STANDARDS="$CODING_STANDARDS" \
|
|
509
|
-
ARCHITECTURE_PATTERNS="$ARCHITECTURE_PATTERNS" \
|
|
510
|
-
UI_BRAND_CONVENTIONS="$UI_BRAND_CONVENTIONS" \
|
|
511
|
-
DEFINITION_OF_DONE="$DEFINITION_OF_DONE" \
|
|
512
|
-
node -e "
|
|
513
|
-
const e = process.env;
|
|
514
|
-
const fs = require('fs');
|
|
515
|
-
fs.writeFileSync(process.argv[1], JSON.stringify({
|
|
516
|
-
client_name: e.CLIENT_NAME,
|
|
517
|
-
client_slug: e.CLIENT_SLUG,
|
|
518
|
-
coding_standards: e.CODING_STANDARDS,
|
|
519
|
-
architecture_patterns: e.ARCHITECTURE_PATTERNS,
|
|
520
|
-
ui_brand_conventions: e.UI_BRAND_CONVENTIONS,
|
|
521
|
-
definition_of_done: e.DEFINITION_OF_DONE
|
|
522
|
-
}));
|
|
523
|
-
" "$TMP_CTX"
|
|
524
|
-
|
|
525
|
-
# Invoke the Plan 09-01 writer — routes through updateSection, marker-bounded, idempotent
|
|
526
|
-
pp_run query generate-client-context --from-json "$TMP_CTX"
|
|
527
|
-
rm -f "$TMP_CTX"
|
|
528
|
-
echo "Client context written to CLAUDE.md (<!-- PP:client-context-start --> block)."
|
|
529
|
-
fi
|
|
530
|
-
```
|
|
531
|
-
|
|
532
|
-
This writes a `## Client Context & Patterns` block bounded by
|
|
533
|
-
`<!-- PP:client-context-start -->` … `<!-- PP:client-context-end -->` markers into the local
|
|
534
|
-
CLAUDE.md (config-driven path, default `./.claude/CLAUDE.md`). The write is idempotent —
|
|
535
|
-
re-running `/pp-init` replaces the bounded block, never duplicates it. All values stay local;
|
|
536
|
-
no network or external service is involved.
|
|
537
|
-
|
|
492
|
+
When — and only when — that gate answers `"true"`, **Read
|
|
493
|
+
`pp-core/workflows/init/steps/client-context-capture.md` and follow it.** That file owns
|
|
494
|
+
the re-run edit path (D-05), the five required fields with their re-prompt-on-empty and
|
|
495
|
+
multi-line rules (D-01, D-02), and the marker-bounded `generate-client-context` write into
|
|
496
|
+
the local CLAUDE.md. On a non-Pareto project do not read it at all — skip straight to
|
|
497
|
+
Step 4, leaving no prompt fired and no block written.
|
|
538
498
|
---
|
|
539
499
|
|
|
540
500
|
## 4. Handoff routing (D-04)
|