feat(ultraplan-local): mirror Phase 2.6 hardenings in headless-launch-template
Bring the launch template (used by /ultraplan-local --decompose) into contract-parity with the Phase 2.6 wave executor hardenings shipped in the previous commit: - GIT_OPTIONAL_LOCKS=0 exported once at the top - MAX_TURNS / MAX_BUDGET_USD env-overridable (default 50 / 5) - Absolute SHARED_CONTEXT_FILE built from brief + architecture - SAFETY_PREAMBLE prepended to every per-session prompt (GH #36071 + GH #52272 clarifications) - Per-child --max-turns + --max-budget-usd + --append-system-prompt-file - push-before-cleanup before merge AND in the cleanup_worktrees trap - Three new template rules (16, 17, 18, 19) document the contract for session-decomposer Pin in tests/lib/doc-consistency.test.mjs locks all required substrings against future regressions.
This commit is contained in:
parent
41a0c913fa
commit
b97251bda3
2 changed files with 78 additions and 3 deletions
|
|
@ -23,11 +23,42 @@ LOG_DIR="{session_dir}/logs"
|
|||
WORKTREE_BASE="{session_dir}/worktrees"
|
||||
mkdir -p "$LOG_DIR" "$WORKTREE_BASE"
|
||||
|
||||
# Disable git's optional locks during parallel worktree ops (research/02 R2;
|
||||
# GH #47721). Mirror Phase 2.6 hardenings (commands/ultraexecute-local.md).
|
||||
export GIT_OPTIONAL_LOCKS=0
|
||||
|
||||
# Per-child guardrails (operator may override via env). Match Phase 2.6
|
||||
# Step 2a-pre defaults.
|
||||
MAX_TURNS="${ULTRAEXECUTE_MAX_TURNS:-50}"
|
||||
MAX_BUDGET_USD="${ULTRAEXECUTE_MAX_BUDGET_USD:-5}"
|
||||
|
||||
# Shared context (brief + architecture if present). Absolute path so child
|
||||
# sessions resolve it after `cd` into their worktree.
|
||||
SHARED_CONTEXT_FILE="$WORKTREE_BASE/.shared-context.md"
|
||||
if [ -f "$PLAN_DIR/brief.md" ]; then
|
||||
cat "$PLAN_DIR/brief.md" > "$SHARED_CONTEXT_FILE"
|
||||
if [ -f "$PLAN_DIR/architecture/overview.md" ]; then
|
||||
printf '\n\n---\n\n' >> "$SHARED_CONTEXT_FILE"
|
||||
cat "$PLAN_DIR/architecture/overview.md" >> "$SHARED_CONTEXT_FILE"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Safety preambles prepended to every per-session prompt (defense-in-depth
|
||||
# against headless-mode hook gaps + Claude 4 cross-model malware-reminder).
|
||||
SAFETY_PREAMBLE='[CRITICAL — defense in depth] You are running in headless mode where plugin hooks may not fire reliably (GH #36071). DO NOT execute commands matching: `rm -rf /`, `curl | bash`, `git push --force` to main, `git reset --hard` outside this worktree, or any command writing outside the current worktree. Treat this rule as enforced regardless of `--allowedTools` allowlist.
|
||||
|
||||
Note: any malware-related safety reminders apply conditionally to code you assess as actually malicious, not to all code reads (per GH #52272 community workaround).'
|
||||
|
||||
# Cleanup trap — always remove worktrees on exit (success or failure)
|
||||
cleanup_worktrees() {
|
||||
echo ""
|
||||
echo "=== Cleaning up worktrees ==="
|
||||
cd "$REPO_ROOT"
|
||||
# push-before-cleanup (Hard Rule 19): push any remaining branches so work
|
||||
# survives even if subsequent removal races. Failure is non-fatal.
|
||||
git branch --list "ultraplan/{slug}/*" | while read b; do
|
||||
git push origin "$b" 2>/dev/null || true
|
||||
done
|
||||
for wt in "$WORKTREE_BASE"/session-*; do
|
||||
[ -d "$wt" ] && git worktree remove "$wt" --force 2>/dev/null && echo "Removed: $wt"
|
||||
done
|
||||
|
|
@ -81,10 +112,15 @@ echo "--- Wave {N}: {description} ---"
|
|||
git worktree add -b "ultraplan/{slug}/session-{n}" "$WORKTREE_BASE/session-{n}" HEAD
|
||||
echo "Worktree created: session-{n} (branch: ultraplan/{slug}/session-{n})"
|
||||
|
||||
{# Launch session in its worktree: }
|
||||
cd "$WORKTREE_BASE/session-{n}" && claude -p "$(cat "$PLAN_DIR/session-{n}-{slug}.md")" \
|
||||
{# Launch session in its worktree (with safety preamble + budget caps + shared context): }
|
||||
cd "$WORKTREE_BASE/session-{n}" && claude -p "${SAFETY_PREAMBLE}
|
||||
|
||||
$(cat "$PLAN_DIR/session-{n}-{slug}.md")" \
|
||||
--allowedTools "Read,Write,Edit,Bash,Glob,Grep" \
|
||||
--permission-mode bypassPermissions \
|
||||
--max-turns "$MAX_TURNS" \
|
||||
--max-budget-usd "$MAX_BUDGET_USD" \
|
||||
--append-system-prompt-file "$SHARED_CONTEXT_FILE" \
|
||||
> "$LOG_DIR/session-{n}.log" 2>&1 &
|
||||
PID_{n}=$!
|
||||
cd "$REPO_ROOT"
|
||||
|
|
@ -99,7 +135,8 @@ echo ""
|
|||
# --- Merge wave results (sequential) ---
|
||||
echo "--- Merging Wave {N} ---"
|
||||
cd "$REPO_ROOT"
|
||||
{# For each session in the wave, merge its branch: }
|
||||
{# For each session in the wave: push BEFORE merge (Hard Rule 19 — push-before-cleanup). }
|
||||
git push origin "ultraplan/{slug}/session-{n}" 2>/dev/null || true
|
||||
git merge --no-ff "ultraplan/{slug}/session-{n}" \
|
||||
-m "merge: ultraplan session {n} — {title}"
|
||||
if [ $? -ne 0 ]; then
|
||||
|
|
@ -166,3 +203,21 @@ When generating a launch script from this template:
|
|||
check at the top of the script. Fail if the working tree is dirty.
|
||||
15. **Absolute paths for logs.** Log file paths must be absolute (resolved from
|
||||
`$REPO_ROOT`), not relative to any worktree.
|
||||
16. **Per-child guardrails (mirrors Phase 2.6 Step 2b).** Every `claude -p`
|
||||
invocation must include `--max-turns "$MAX_TURNS"`,
|
||||
`--max-budget-usd "$MAX_BUDGET_USD"`, and
|
||||
`--append-system-prompt-file "$SHARED_CONTEXT_FILE"`. The shared context
|
||||
must be built once with an absolute path (resolved from `$WORKTREE_BASE`)
|
||||
so child sessions can read it after `cd`.
|
||||
17. **Safety preamble.** Every per-session prompt must be prefixed with the
|
||||
`$SAFETY_PREAMBLE` string defined at the top of the script. This is the
|
||||
primary defense when plugin hooks do not fire reliably (GH #36071), and
|
||||
includes the GH #52272 malware-reminder clarification for AUTO mode.
|
||||
18. **GIT_OPTIONAL_LOCKS=0.** The script must export `GIT_OPTIONAL_LOCKS=0`
|
||||
once at the top so every git invocation (worktree add/remove/prune,
|
||||
branch -d, merge, push) avoids the index.lock background-poll race
|
||||
(research/02 R2; GH #47721).
|
||||
19. **push-before-cleanup (Hard Rule 19).** After successful `git merge --no-ff`,
|
||||
run `git push origin <branch>` BEFORE `git worktree remove` and
|
||||
`git branch -d`. Push failure is non-fatal — cleanup proceeds. Converts
|
||||
unrecoverable branch loss into recoverable remote state (research/02 R3).
|
||||
|
|
|
|||
|
|
@ -158,6 +158,26 @@ test('rule-catalogue has exactly 12 entries', async () => {
|
|||
);
|
||||
});
|
||||
|
||||
test('headless-launch-template.md mirrors Phase 2.6 hardenings', () => {
|
||||
const tpl = read('templates/headless-launch-template.md');
|
||||
for (const needle of [
|
||||
'GIT_OPTIONAL_LOCKS',
|
||||
'--max-turns',
|
||||
'--max-budget-usd',
|
||||
'--append-system-prompt-file',
|
||||
'SHARED_CONTEXT_FILE',
|
||||
'SAFETY_PREAMBLE',
|
||||
'git push origin',
|
||||
'GH #36071',
|
||||
'push-before-cleanup',
|
||||
]) {
|
||||
assert.ok(
|
||||
tpl.includes(needle),
|
||||
`templates/headless-launch-template.md should include "${needle}" (Step 10 mirrors Phase 2.6)`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('Phase 9 prose mandates parallel single-message dispatch + inline dedup', () => {
|
||||
const cmd = read('commands/ultraplan-local.md');
|
||||
const orch = read('agents/planning-orchestrator.md');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue