@pareto4/pp-core 1.18.0 → 1.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/agents/pp-code-fixer.md +9 -5
- package/agents/pp-code-reviewer.md +1 -0
- package/agents/pp-codebase-mapper.md +3 -0
- package/agents/pp-debugger.md +3 -2
- package/agents/pp-executor.md +16 -7
- package/agents/pp-integration-checker.md +1 -0
- package/agents/pp-phase-researcher.md +3 -1
- package/agents/pp-plan-checker.md +3 -0
- package/agents/pp-planner.md +5 -1
- package/agents/pp-research-synthesizer.md +1 -1
- package/agents/pp-ui-auditor.md +1 -0
- package/agents/pp-ui-researcher.md +3 -1
- package/agents/pp-verifier.md +10 -2
- package/commands/pp-add-tests.md +1 -1
- package/commands/pp-debug.md +1 -0
- package/commands/pp-init.md +4 -3
- package/commands/pp-quick.md +3 -0
- package/commands/pp-review-backlog.md +2 -1
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/pp-core/bin/lib/capability-registry.cjs +2 -2
- package/pp-core/bin/lib/check-command-router.cjs +60 -20
- package/pp-core/bin/lib/commands.cjs +79 -4
- package/pp-core/bin/lib/compose-bindings.cjs +97 -14
- package/pp-core/bin/lib/discovery-rollup.cjs +9 -6
- package/pp-core/bin/lib/discovery-screens.cjs +20 -2
- package/pp-core/bin/lib/discovery-sweep.cjs +12 -3
- package/pp-core/bin/lib/discovery-vault-check.cjs +37 -3
- package/pp-core/bin/lib/init-contract.cjs +48 -7
- package/pp-core/bin/lib/vault-coordinates.cjs +1 -1
- package/pp-core/bin/lib/vault-index.cjs +18 -5
- package/pp-core/bin/lib/vault-update.cjs +4 -0
- package/pp-core/bin/lib/vault.cjs +2 -2
- package/pp-core/bin/pp-tools.cjs +10 -4
- package/pp-core/org-profile.json +1 -1
- package/pp-core/references/autonomous-best-effort.md +3 -2
- package/pp-core/references/autonomous-lifecycle.md +1 -0
- package/pp-core/references/autonomous-smart-discuss.md +5 -1
- package/pp-core/references/discovery-id-conventions.md +35 -2
- package/pp-core/references/execute-mvp-tdd.md +3 -3
- package/pp-core/references/git-integration.md +57 -33
- package/pp-core/references/git-planning-commit.md +31 -9
- package/pp-core/references/phase-argument-parsing.md +1 -0
- package/pp-core/references/planner-gap-closure.md +1 -0
- package/pp-core/references/planner-revision.md +3 -1
- package/pp-core/references/planner-tdd.md +4 -4
- package/pp-core/references/planning-config.md +3 -3
- package/pp-core/references/shell-portability.md +28 -0
- package/pp-core/references/tdd.md +20 -16
- package/pp-core/references/worktree-path-safety.md +1 -0
- package/pp-core/templates/milestone.md +5 -5
- package/pp-core/workflows/add-backlog.md +1 -1
- package/pp-core/workflows/add-tests.md +5 -2
- package/pp-core/workflows/add-todo.md +3 -1
- package/pp-core/workflows/ai-integration-phase.md +2 -1
- package/pp-core/workflows/audit-fix.md +4 -3
- package/pp-core/workflows/audit-milestone.md +1 -0
- package/pp-core/workflows/autonomous.md +6 -0
- package/pp-core/workflows/check-todos.md +2 -1
- package/pp-core/workflows/cleanup.md +3 -1
- package/pp-core/workflows/code-review-fix.md +12 -5
- package/pp-core/workflows/code-review.md +15 -1
- package/pp-core/workflows/complete-milestone.md +13 -3
- package/pp-core/workflows/debug.md +2 -0
- package/pp-core/workflows/diagnose-issues.md +1 -1
- package/pp-core/workflows/discovery-sprint/session-13-gates.md +1 -1
- package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +51 -7
- package/pp-core/workflows/discovery-sprint/sessions/02-as-is.md +5 -3
- package/pp-core/workflows/discovery-sprint/sessions/03-to-be.md +10 -8
- package/pp-core/workflows/discovery-sprint/sessions/04-ux-design.md +11 -2
- package/pp-core/workflows/discovery-sprint/sessions/05-architecture.md +5 -5
- package/pp-core/workflows/discovery-sprint/sessions/06-security.md +3 -3
- package/pp-core/workflows/discovery-sprint/sessions/07-identity.md +2 -2
- package/pp-core/workflows/discovery-sprint/sessions/08-integration.md +14 -2
- package/pp-core/workflows/discovery-sprint/sessions/09-qa.md +2 -2
- package/pp-core/workflows/discovery-sprint/sessions/10-infrastructure.md +10 -10
- package/pp-core/workflows/discovery-sprint/sessions/11-roadmap.md +11 -8
- package/pp-core/workflows/discovery-sprint/sessions/12-delivery-model.md +21 -18
- package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +5 -3
- package/pp-core/workflows/discovery-sprint/templates/context-template.md +9 -3
- package/pp-core/workflows/discovery-sprint/vault-context.md +14 -3
- package/pp-core/workflows/discovery-sprint.md +11 -8
- package/pp-core/workflows/discovery-to-build/steps/architecture-deliverables.md +1 -0
- package/pp-core/workflows/discuss-phase-assumptions.md +5 -3
- package/pp-core/workflows/discuss-phase.md +8 -3
- package/pp-core/workflows/docs-update.md +1 -0
- package/pp-core/workflows/eval-review.md +2 -1
- package/pp-core/workflows/execute-phase/steps/check-query-gate.md +7 -6
- package/pp-core/workflows/execute-phase/steps/cross-ai-delegation.md +1 -0
- package/pp-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -0
- package/pp-core/workflows/execute-phase/steps/vault-fetch.md +12 -0
- package/pp-core/workflows/execute-phase.md +19 -7
- package/pp-core/workflows/execute-plan.md +18 -7
- package/pp-core/workflows/explore.md +1 -1
- package/pp-core/workflows/forensics.md +4 -2
- package/pp-core/workflows/health.md +1 -0
- package/pp-core/workflows/import.md +2 -1
- package/pp-core/workflows/ingest-docs.md +1 -0
- package/pp-core/workflows/init/steps/client-context-capture.md +1 -0
- package/pp-core/workflows/init/steps/github-provisioning.md +145 -0
- package/pp-core/workflows/init/steps/gitlab-provisioning.md +18 -4
- package/pp-core/workflows/init/steps/reachability-outcome.md +3 -1
- package/pp-core/workflows/init.md +56 -66
- package/pp-core/workflows/map-codebase.md +5 -1
- package/pp-core/workflows/milestone-summary.md +2 -1
- package/pp-core/workflows/mvp-phase.md +1 -0
- package/pp-core/workflows/new-milestone.md +9 -5
- package/pp-core/workflows/new-project.md +9 -8
- package/pp-core/workflows/new-workspace.md +1 -0
- package/pp-core/workflows/next.md +2 -1
- package/pp-core/workflows/pause-work.md +6 -4
- package/pp-core/workflows/plan-milestone-gaps.md +3 -1
- package/pp-core/workflows/plan-phase/steps/chunked-planning.md +1 -1
- package/pp-core/workflows/plan-phase.md +21 -6
- package/pp-core/workflows/plan-review-convergence.md +2 -0
- package/pp-core/workflows/plant-seed.md +4 -2
- package/pp-core/workflows/pr-branch.md +2 -0
- package/pp-core/workflows/progress.md +5 -0
- package/pp-core/workflows/quick.md +11 -4
- package/pp-core/workflows/reapply-patches.md +3 -0
- package/pp-core/workflows/remove-phase.md +1 -1
- package/pp-core/workflows/resume-project.md +3 -0
- package/pp-core/workflows/review.md +7 -1
- package/pp-core/workflows/secure-phase.md +2 -1
- package/pp-core/workflows/session-report.md +1 -0
- package/pp-core/workflows/ship.md +8 -1
- package/pp-core/workflows/sketch-wrap-up.md +1 -1
- package/pp-core/workflows/sketch.md +3 -2
- package/pp-core/workflows/spec-phase.md +2 -1
- package/pp-core/workflows/spike-wrap-up.md +1 -1
- package/pp-core/workflows/spike.md +4 -3
- package/pp-core/workflows/sync-skills.md +5 -2
- package/pp-core/workflows/thread.md +3 -2
- package/pp-core/workflows/transition.md +6 -0
- package/pp-core/workflows/ui-phase.md +3 -1
- package/pp-core/workflows/ui-review.md +2 -1
- package/pp-core/workflows/ultraplan-phase.md +1 -0
- package/pp-core/workflows/undo.md +14 -10
- package/pp-core/workflows/update.md +5 -0
- package/pp-core/workflows/validate-phase.md +6 -3
- package/pp-core/workflows/vault-review.md +2 -1
- package/pp-core/workflows/vault-write-back.md +5 -0
- package/pp-core/workflows/verify-phase.md +5 -0
- package/pp-core/workflows/verify-work.md +8 -1
- package/scripts/prompt-injection-scan.sh +1 -1
- package/scripts/sync-runtime-launcher.cjs +115 -5
|
@@ -38,6 +38,7 @@ Extract from init JSON: `phase_dir`, `phase_number`, `phase_name`, `has_plans`,
|
|
|
38
38
|
|
|
39
39
|
Then load phase details and list plans/summaries:
|
|
40
40
|
```bash
|
|
41
|
+
[ -z "${ZSH_VERSION:-}" ] || emulate -R sh
|
|
41
42
|
pp_run query roadmap.get-phase "${phase_number}"
|
|
42
43
|
grep -E "^| ${phase_number}" .planning/REQUIREMENTS.md 2>/dev/null || true
|
|
43
44
|
ls "$phase_dir"/*-SUMMARY.md "$phase_dir"/*-PLAN.md 2>/dev/null || true
|
|
@@ -57,6 +58,7 @@ Extract **phase goal** from ROADMAP.md (the outcome to verify, not tasks), **req
|
|
|
57
58
|
Use `pp-tools.cjs query` verify handlers (or legacy pp-tools) to extract must_haves from each PLAN:
|
|
58
59
|
|
|
59
60
|
```bash
|
|
61
|
+
[ -z "${ZSH_VERSION:-}" ] || emulate -R sh
|
|
60
62
|
for plan in "$PHASE_DIR"/*-PLAN.md; do
|
|
61
63
|
MUST_HAVES=$(pp_run query frontmatter.get "$plan" --field must_haves)
|
|
62
64
|
echo "=== $plan ===" && echo "$MUST_HAVES"
|
|
@@ -124,6 +126,7 @@ For each truth: identify supporting artifacts → check artifact status → chec
|
|
|
124
126
|
Use `pp-tools.cjs query verify.artifacts` (or legacy pp-tools) for artifact verification against must_haves in each PLAN:
|
|
125
127
|
|
|
126
128
|
```bash
|
|
129
|
+
[ -z "${ZSH_VERSION:-}" ] || emulate -R sh
|
|
127
130
|
for plan in "$PHASE_DIR"/*-PLAN.md; do
|
|
128
131
|
ARTIFACT_RESULT=$(pp_run query verify.artifacts "$plan")
|
|
129
132
|
echo "=== $plan ===" && echo "$ARTIFACT_RESULT"
|
|
@@ -167,6 +170,7 @@ wiring or leftover code from plan revisions.
|
|
|
167
170
|
Use `pp-tools.cjs query verify.key-links` (or legacy pp-tools) for key link verification against must_haves in each PLAN:
|
|
168
171
|
|
|
169
172
|
```bash
|
|
173
|
+
[ -z "${ZSH_VERSION:-}" ] || emulate -R sh
|
|
170
174
|
for plan in "$PHASE_DIR"/*-PLAN.md; do
|
|
171
175
|
LINKS_RESULT=$(pp_run query verify.key-links "$plan")
|
|
172
176
|
echo "=== $plan ===" && echo "$LINKS_RESULT"
|
|
@@ -223,6 +227,7 @@ phase.
|
|
|
223
227
|
no `<decisions>` block.
|
|
224
228
|
|
|
225
229
|
```bash
|
|
230
|
+
[ -z "${ZSH_VERSION:-}" ] || emulate -R sh
|
|
226
231
|
GATE_CFG=$(pp_run query config-get workflow.context_coverage_gate 2>/dev/null || echo "true")
|
|
227
232
|
if [ "$GATE_CFG" != "false" ]; then
|
|
228
233
|
# Discover the phase CONTEXT.md via glob expansion rather than `ls | head`
|
|
@@ -38,6 +38,7 @@ If $ARGUMENTS contains a phase number, load context:
|
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
40
|
_PP_SHIM_NAME="pp-tools.cjs"; _pp_root() { ( for _v in $(env | sed -n 's/^\(GIT_[A-Za-z0-9_]*\)=.*/\1/p'); do unset "$_v"; done; _c=$(git rev-parse --git-common-dir 2>/dev/null); if [ "${_c##*/}" = .git ] && cd "$_c/.." 2>/dev/null; then pwd -P; else git rev-parse --show-toplevel 2>/dev/null || pwd; fi ); }; pp_run() { case "$PP_TOOLS" in *.cjs) node "$PP_TOOLS" "$@";; *) "$PP_TOOLS" "$@";; esac; }; _PP_RUNTIME_ROOT="${RUNTIME_DIR:-$(_pp_root)}"; PP_TOOLS="${_PP_RUNTIME_ROOT}/pp-core/bin/${_PP_SHIM_NAME}"; if [ -f "$PP_TOOLS" ]; then :; elif [ -f "${_PP_RUNTIME_ROOT}/.claude/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${_PP_RUNTIME_ROOT}/.claude/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${_PP_RUNTIME_ROOT}/.codex/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${_PP_RUNTIME_ROOT}/.codex/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "$HOME/.claude/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="$HOME/.claude/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${HERMES_HOME:-$HOME/.hermes}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CODEX_HOME:-$HOME/.codex}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CODEX_HOME:-$HOME/.codex}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/pp-core/bin/${_PP_SHIM_NAME}"; elif command -v pp-tools >/dev/null 2>&1; then PP_TOOLS="$(command -v pp-tools)"; else echo "ERROR: pp-tools.cjs not found at $PP_TOOLS and pp-tools is not on PATH. Run: npx -y @pareto4/pp-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${PP_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${PP_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
|
|
41
|
+
[ -z "${ZSH_VERSION:-}" ] || emulate -R sh
|
|
41
42
|
GSD_WS=""
|
|
42
43
|
echo "$ARGUMENTS" | grep -qE -- '--ws[[:space:]]+[^[:space:]]+' && GSD_WS=$(echo "$ARGUMENTS" | grep -oE -- '--ws[[:space:]]+[^[:space:]]+')
|
|
43
44
|
PHASE_ARG=$(echo "$ARGUMENTS" | sed -E 's/--ws[[:space:]]+[^[:space:]]+//g' | xargs)
|
|
@@ -51,6 +52,7 @@ AGENT_SKILLS_CHECKER=$(pp_run query agent-skills pp-plan-checker)
|
|
|
51
52
|
Parse JSON for: `planner_model`, `checker_model`, `commit_docs`, `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `has_verification`, `uat_path`.
|
|
52
53
|
|
|
53
54
|
```bash
|
|
55
|
+
[ -z "${ZSH_VERSION:-}" ] || emulate -R sh
|
|
54
56
|
# MVP mode detection via the centralized phase.mvp-mode resolver.
|
|
55
57
|
# verify-work has no --mvp CLI flag (mode is inherited from the planned phase),
|
|
56
58
|
# so we omit --cli-flag — the verb falls through roadmap → config → false.
|
|
@@ -111,6 +113,7 @@ Continue to `create_uat_file`.
|
|
|
111
113
|
Before UAT, check UI capability activation and whether Playwright/Puppeteer MCP tools are available.
|
|
112
114
|
|
|
113
115
|
```bash
|
|
116
|
+
[ -z "${ZSH_VERSION:-}" ] || emulate -R sh
|
|
114
117
|
PLAN_HOOKS_JSON=$(pp_run loop render-hooks plan:pre --raw)
|
|
115
118
|
UI_SPEC_FILE=$(ls "${PHASE_DIR}"/*-UI-SPEC.md 2>/dev/null | head -1)
|
|
116
119
|
```
|
|
@@ -148,6 +151,7 @@ UI checkpoints: {N} auto-verified, {M} queued for manual review
|
|
|
148
151
|
Use `phase_dir` from init (or run init if not already done).
|
|
149
152
|
|
|
150
153
|
```bash
|
|
154
|
+
[ -z "${ZSH_VERSION:-}" ] || emulate -R sh
|
|
151
155
|
ls "$phase_dir"/*-SUMMARY.md 2>/dev/null || true
|
|
152
156
|
```
|
|
153
157
|
|
|
@@ -167,6 +171,7 @@ When `MVP_MODE=false` (mode is null, absent, or the phase has no `**Mode:**` lin
|
|
|
167
171
|
**User-story format guard.** When `MVP_MODE=true`, also verify the phase's goal is in User Story format via the centralized validator:
|
|
168
172
|
|
|
169
173
|
```bash
|
|
174
|
+
[ -z "${ZSH_VERSION:-}" ] || emulate -R sh
|
|
170
175
|
PHASE_GOAL=$(pp_run query roadmap.get-phase "${phase_number}" ${GSD_WS} --pick goal)
|
|
171
176
|
USER_STORY_VALID=$(pp_run query user-story.validate --story "$PHASE_GOAL" --pick valid)
|
|
172
177
|
if [ "$USER_STORY_VALID" != "true" ]; then
|
|
@@ -434,7 +439,7 @@ Clear Current Test section:
|
|
|
434
439
|
|
|
435
440
|
Commit the UAT file:
|
|
436
441
|
```bash
|
|
437
|
-
pp_run query commit "
|
|
442
|
+
pp_run query commit "docs(planning): complete UAT - {passed} passed, {issues} issues" --phase "{phase_num}" --files ".planning/phases/XX-name/{phase_num}-UAT.md"
|
|
438
443
|
```
|
|
439
444
|
|
|
440
445
|
Present summary:
|
|
@@ -458,6 +463,7 @@ Present summary:
|
|
|
458
463
|
**If issues == 0:**
|
|
459
464
|
|
|
460
465
|
```bash
|
|
466
|
+
[ -z "${ZSH_VERSION:-}" ] || emulate -R sh
|
|
461
467
|
VERIFY_POST_HOOKS_JSON=$(pp_run loop render-hooks verify:post --raw)
|
|
462
468
|
printf '%s' "$VERIFY_POST_HOOKS_JSON" | node -e "let h=[];try{h=JSON.parse(require('fs').readFileSync(0,'utf-8')).activeHooks||[]}catch{}console.log('VERIFY_POST_HOOKS_JSON='+JSON.stringify({activeHooks:h}))"
|
|
463
469
|
SECURITY_FILE=$(ls "${PHASE_DIR}"/*-SECURITY.md 2>/dev/null | head -1)
|
|
@@ -478,6 +484,7 @@ Skill(skill="pp-${ref.skill}", args="{phase}")
|
|
|
478
484
|
After the skill returns, refresh `SECURITY_FILE`:
|
|
479
485
|
|
|
480
486
|
```bash
|
|
487
|
+
[ -z "${ZSH_VERSION:-}" ] || emulate -R sh
|
|
481
488
|
SECURITY_FILE=$(ls "${PHASE_DIR}"/*-SECURITY.md 2>/dev/null | head -1)
|
|
482
489
|
```
|
|
483
490
|
|
|
@@ -84,7 +84,7 @@ PATTERNS=(
|
|
|
84
84
|
ALLOWLIST=(
|
|
85
85
|
'scripts/prompt-injection-scan.sh::the PATTERNS array above is the literal source of every phrase this scanner looks for, so the file matches itself by construction; measured 4 pattern hits at HEAD on 2026-09-19'
|
|
86
86
|
'scripts/base64-scan.sh::decodes base64 blobs and re-scans the recovered plaintext against the same injection vocabulary, so it names those phrases inline; measured 2 pattern hits at HEAD on 2026-09-19'
|
|
87
|
-
'scripts/secret-scan.sh::third of the three diff-mode scan scripts CI runs together in the
|
|
87
|
+
'scripts/secret-scan.sh::third of the three diff-mode scan scripts CI runs together in the pr_gates job of .github/workflows/ci.yml; measured 0 pattern hits at HEAD on 2026-09-19, retained because its own exemption list at line 223 already names security-prompt-injection.security.test.cjs and grows with that vocabulary'
|
|
88
88
|
'tests/security-scan.security.test.cjs::the behavioural suite for this very scanner, so every positive case embeds a real injection phrase as fixture input; measured 28 pattern hits at HEAD on 2026-09-19'
|
|
89
89
|
'tests/security.test.cjs::the adversarial suite for the library sanitisers, whose fixtures are real override and role-manipulation strings asserted to be neutralised; measured 54 pattern hits at HEAD on 2026-09-19'
|
|
90
90
|
'tests/prompt-injection-scan.security.test.cjs::drives scanForInjection in the compiled security library with crafted payloads, and carries a second allowlist of its own for that separate scanner; measured 11 pattern hits at HEAD on 2026-09-19'
|
|
@@ -12,6 +12,13 @@
|
|
|
12
12
|
* (document order) that contains a pp_run call. All other bash blocks
|
|
13
13
|
* keep their pp_run calls with NO preamble. (Define once per file,
|
|
14
14
|
* use across blocks — original footprint.)
|
|
15
|
+
* 4. Start every bash/sh/shell/zsh block that zsh would run differently
|
|
16
|
+
* from bash with ZSH_GUARD (right after the preamble where the block
|
|
17
|
+
* carries one), and drop the guard from a block that no longer needs it.
|
|
18
|
+
* Each block may run in its own shell, and an agent's shell is often zsh:
|
|
19
|
+
* the guard makes zsh read the block as sh (unmatched globs stay literal,
|
|
20
|
+
* `====` is a word, unquoted words split, arrays index from 0) and is a
|
|
21
|
+
* no-op under bash. See needsZshGuard for what counts.
|
|
15
22
|
*
|
|
16
23
|
* Run: node scripts/sync-runtime-launcher.cjs
|
|
17
24
|
*/
|
|
@@ -28,6 +35,57 @@ const SNIPPET_FILE = path.join(WORKFLOWS_DIR, '_runtime-launcher.snippet.sh');
|
|
|
28
35
|
*/
|
|
29
36
|
const LAUNCHER_HEAD = '_PP_SHIM_NAME="pp-tools.cjs"; ';
|
|
30
37
|
|
|
38
|
+
/**
|
|
39
|
+
* The first line of every shell block (issue #12 items 2-4). Exits 0 under
|
|
40
|
+
* bash, so a block's status never depends on it.
|
|
41
|
+
*/
|
|
42
|
+
const ZSH_GUARD = '[ -z "${ZSH_VERSION:-}" ] || emulate -R sh';
|
|
43
|
+
const GUARDED_LANGS = Object.freeze(['bash', 'sh', 'shell', 'zsh']);
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* True when zsh would run `lines` differently from bash: an unquoted glob
|
|
47
|
+
* character, a word starting with `=` plus more (`====`, `==`), an unquoted
|
|
48
|
+
* `$var` (zsh does not split it), or array syntax (zsh indexes from 1). Errs
|
|
49
|
+
* towards true — a needless guard costs a line, a missing one breaks a run.
|
|
50
|
+
* The launcher preamble is zsh-safe and is never inspected.
|
|
51
|
+
*/
|
|
52
|
+
function needsZshGuard(lines) {
|
|
53
|
+
for (const raw of lines) {
|
|
54
|
+
const line = raw.trim();
|
|
55
|
+
if (line === '' || line.startsWith('#') || isLauncherLine(line) || line === ZSH_GUARD) continue;
|
|
56
|
+
if (/\$\{#?\w+\[/.test(line) || /\b\w+\+?=\(/.test(line)) return true;
|
|
57
|
+
// Neither shell globs, splits or `=`-expands inside `[[ … ]]` (syntax, not
|
|
58
|
+
// a command), in a case pattern (`*)`), or on an assignment's right side.
|
|
59
|
+
const bare = line
|
|
60
|
+
.replace(/'[^']*'/g, "''")
|
|
61
|
+
.replace(/"(?:\\.|[^"\\])*"/g, '""')
|
|
62
|
+
.replace(/\[\[\s.*?\s\]\]/g, '[[ ]]')
|
|
63
|
+
.replace(/(^|[\s;(|])\*\)/g, '$1)')
|
|
64
|
+
.replace(/(^|[\s;&|])([A-Za-z_]\w*)=\$(?=[{\w])/g, '$1$2=');
|
|
65
|
+
if (/(^|[\s;|&(])=[^\s]/.test(bare)) return true;
|
|
66
|
+
if (/\$(\w|\{)/.test(bare)) return true;
|
|
67
|
+
if (/[*?]/.test(bare.replace(/\$[*?#@!$-]/g, ''))) return true;
|
|
68
|
+
if (/[^\s\[]\[[^\]\s]*\]/.test(bare)) return true;
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Make `lines` carry ZSH_GUARD exactly when needsZshGuard says so: at the top
|
|
75
|
+
* (after leading blanks, and after the preamble when the block starts with
|
|
76
|
+
* it), indented like the fence. Idempotent.
|
|
77
|
+
*/
|
|
78
|
+
function insertZshGuard(lines, preamble, indent) {
|
|
79
|
+
const rest = lines.filter((l) => l.trim() !== ZSH_GUARD);
|
|
80
|
+
if (!needsZshGuard(rest)) return rest.length === lines.length ? lines : rest;
|
|
81
|
+
let at = 0;
|
|
82
|
+
while (at < rest.length && rest[at].trim() === '') at++;
|
|
83
|
+
if (at === rest.length) return lines;
|
|
84
|
+
if (preamble.length > 0 && preamble.every((p, k) => rest[at + k] === p)) at += preamble.length;
|
|
85
|
+
const out = [...rest.slice(0, at), indent + ZSH_GUARD, ...rest.slice(at)];
|
|
86
|
+
return out.join('\n') === lines.join('\n') ? lines : out;
|
|
87
|
+
}
|
|
88
|
+
|
|
31
89
|
/**
|
|
32
90
|
* Launcher carriers that live OUTSIDE this script's two roots
|
|
33
91
|
* (pp-core/workflows/ and agents/).
|
|
@@ -387,7 +445,7 @@ function transformFile(content, preamble) {
|
|
|
387
445
|
if (i < allLines.length) i++;
|
|
388
446
|
|
|
389
447
|
if (isShellBlock) {
|
|
390
|
-
shellBlockRanges.push({ openLineIdx, contentStart, contentEnd, closingLineIdx });
|
|
448
|
+
shellBlockRanges.push({ openLineIdx, contentStart, contentEnd, closingLineIdx, lang, indent });
|
|
391
449
|
}
|
|
392
450
|
}
|
|
393
451
|
|
|
@@ -418,10 +476,9 @@ function transformFile(content, preamble) {
|
|
|
418
476
|
|
|
419
477
|
// Insert preamble into the first pp_run block only
|
|
420
478
|
const finalBlocks = strippedBlocks.map((stripped, bi) => {
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
return stripped;
|
|
479
|
+
const withPreamble = bi === firstPpRunBlockIdx ? insertPreamble(stripped, preamble) : stripped;
|
|
480
|
+
const range = shellBlockRanges[bi];
|
|
481
|
+
return GUARDED_LANGS.includes(range.lang) ? insertZshGuard(withPreamble, preamble, range.indent) : withPreamble;
|
|
425
482
|
});
|
|
426
483
|
|
|
427
484
|
// --- Pass 3: splice back into output (reverse order to preserve indices) ---
|
|
@@ -443,6 +500,43 @@ function transformFile(content, preamble) {
|
|
|
443
500
|
return outputLines.join('\n');
|
|
444
501
|
}
|
|
445
502
|
|
|
503
|
+
/**
|
|
504
|
+
* Guard-only pass for the roots whose launcher lines are kept in place
|
|
505
|
+
* (commands/, pp-core/references/): every non-empty bash/sh/shell/zsh block
|
|
506
|
+
* gets ZSH_GUARD, after a launcher line that opens it. pp-core/templates/ is
|
|
507
|
+
* never passed here — its blocks are text written into the user's project.
|
|
508
|
+
* Returns new content, or null when nothing changes.
|
|
509
|
+
*/
|
|
510
|
+
function guardOnlyTransform(content) {
|
|
511
|
+
const lines = content.split('\n');
|
|
512
|
+
const out = [];
|
|
513
|
+
let changed = false;
|
|
514
|
+
let i = 0;
|
|
515
|
+
while (i < lines.length) {
|
|
516
|
+
const open = lines[i].match(/^(\s*)```(\w+)?\s*$/);
|
|
517
|
+
out.push(lines[i]);
|
|
518
|
+
i++;
|
|
519
|
+
if (!open) continue;
|
|
520
|
+
const indent = open[1];
|
|
521
|
+
const lang = (open[2] || '').toLowerCase();
|
|
522
|
+
const closing = new RegExp('^' + escapeRegExp(indent) + '```\\s*$');
|
|
523
|
+
const body = [];
|
|
524
|
+
while (i < lines.length && !closing.test(lines[i])) body.push(lines[i++]);
|
|
525
|
+
let next = body;
|
|
526
|
+
if (GUARDED_LANGS.includes(lang)) {
|
|
527
|
+
const first = body.find((l) => l.trim() !== '');
|
|
528
|
+
next = insertZshGuard(body, first !== undefined && isLauncherLine(first) ? [first] : [], indent);
|
|
529
|
+
if (next !== body) changed = true;
|
|
530
|
+
}
|
|
531
|
+
out.push(...next);
|
|
532
|
+
if (i < lines.length) out.push(lines[i++]);
|
|
533
|
+
}
|
|
534
|
+
return changed ? out.join('\n') : null;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/** The roots that get only the guard pass. */
|
|
538
|
+
const GUARD_ONLY_DIRS = Object.freeze(['commands', path.join('pp-core', 'references')]);
|
|
539
|
+
|
|
446
540
|
function escapeRegExp(str) {
|
|
447
541
|
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
448
542
|
}
|
|
@@ -511,6 +605,18 @@ function main() {
|
|
|
511
605
|
}
|
|
512
606
|
}
|
|
513
607
|
|
|
608
|
+
for (const dir of GUARD_ONLY_DIRS) {
|
|
609
|
+
for (const f of collectFiles(path.join(repoRoot, dir))) {
|
|
610
|
+
const content = fs.readFileSync(f, 'utf8');
|
|
611
|
+
const result = guardOnlyTransform(content);
|
|
612
|
+
if (result !== null) {
|
|
613
|
+
fs.writeFileSync(f, result, 'utf8');
|
|
614
|
+
transformedCount++;
|
|
615
|
+
console.log(`transformed (guard): ${path.relative(repoRoot, f)}`);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
514
620
|
console.log(`\nDone. ${transformedCount} files transformed, ${unchangedCount} unchanged.`);
|
|
515
621
|
}
|
|
516
622
|
|
|
@@ -523,6 +629,10 @@ module.exports = {
|
|
|
523
629
|
replaceCanonicalLines,
|
|
524
630
|
EXTRA_CARRIERS,
|
|
525
631
|
LAUNCHER_HEAD,
|
|
632
|
+
ZSH_GUARD,
|
|
633
|
+
needsZshGuard,
|
|
634
|
+
guardOnlyTransform,
|
|
635
|
+
GUARD_ONLY_DIRS,
|
|
526
636
|
};
|
|
527
637
|
|
|
528
638
|
if (require.main === module) {
|