fix(#4830): re-land #4768's letter-suffix phase-id fix and its lint-phase-id-drift ratchets on current next (#4873)
* fix(#4830): re-land #4768's letter-suffix phase-id fix and its lint-phase-id-drift ratchets on current next Commit740ba0d8a(#4781) removed every change #4768 had merged for #4748: the first-non-digit split at execute-phase.md's two arithmetic sites, the init-emitted `padded_phase` the REVIEW.md lookup binds instead of `printf "%02d"`, the canonical-grammar extractions in autonomous.md and plan-review-convergence.md, the `.changeset/zesty-wolves-tumble.md` fragment, and the three lint-phase-id-drift ratchets with their tests. The guard and the code it guarded left together, so nothing went red. This is a cherry-pick of092d9256bonto current `next`, resolved against the #4683 threat-id fields on execute-phase.md's Parse-JSON line, with the changeset `pr:` reset to the placeholder and the compact-content benchmark baseline regenerated against the current base. (cherry picked from commit092d9256b8) Emitted-Drift-Ack-Growth: autonomous.md — restores #4768's canonical-grammar extraction and its explanatory comment for --from/--to/--only Emitted-Drift-Ack-Growth: execute-phase.md — restores #4768's first-non-digit split at two arithmetic sites and the padded_phase binding for the REVIEW.md lookup Emitted-Drift-Ack-Growth: plan-review-convergence.md — restores #4768's canonical-grammar phase extraction and its comment Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AX7LXxc3uAkGki6iaYiAMP * chore(#4830): set changeset fragment pr to 4873 --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
This commit is contained in:
@@ -21,19 +21,22 @@ Read all files referenced by the invoking prompt's execution_context before star
|
||||
Parse `$ARGUMENTS` for `--from N`, `--to N`, `--only N`, `--interactive`, `--converge`/`--cross-ai`, reviewer selector flags, and `--max-cycles N`:
|
||||
|
||||
```bash
|
||||
# #4748: the phase token is the canonical grammar (src/phase-id.cts) — digits,
|
||||
# an optional uppercase letter, any number of dotted segments — so `12A` and
|
||||
# `23.1.2` extract whole instead of truncating to `12` / `23.1`.
|
||||
FROM_PHASE=""
|
||||
if echo "$ARGUMENTS" | grep -qE '\-\-from\s+[0-9]'; then
|
||||
FROM_PHASE=$(echo "$ARGUMENTS" | grep -oE '\-\-from\s+[0-9]+\.?[0-9]*' | awk '{print $2}')
|
||||
FROM_PHASE=$(echo "$ARGUMENTS" | grep -oE '\-\-from\s+[0-9]+[A-Z]?(\.[0-9]+)*' | awk '{print $2}')
|
||||
fi
|
||||
|
||||
TO_PHASE=""
|
||||
if echo "$ARGUMENTS" | grep -qE '\-\-to\s+[0-9]'; then
|
||||
TO_PHASE=$(echo "$ARGUMENTS" | grep -oE '\-\-to\s+[0-9]+\.?[0-9]*' | awk '{print $2}')
|
||||
TO_PHASE=$(echo "$ARGUMENTS" | grep -oE '\-\-to\s+[0-9]+[A-Z]?(\.[0-9]+)*' | awk '{print $2}')
|
||||
fi
|
||||
|
||||
ONLY_PHASE=""
|
||||
if echo "$ARGUMENTS" | grep -qE '\-\-only\s+[0-9]'; then
|
||||
ONLY_PHASE=$(echo "$ARGUMENTS" | grep -oE '\-\-only\s+[0-9]+\.?[0-9]*' | awk '{print $2}')
|
||||
ONLY_PHASE=$(echo "$ARGUMENTS" | grep -oE '\-\-only\s+[0-9]+[A-Z]?(\.[0-9]+)*' | awk '{print $2}')
|
||||
FROM_PHASE="$ONLY_PHASE"
|
||||
fi
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
AGENT_SKILLS=$(gsd_run query agent-skills gsd-executor)
|
||||
```
|
||||
|
||||
Parse JSON for: `executor_model`, `verifier_model`, `commit_docs`, `parallelization`, `branching_strategy`, `branch_name`, `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `plans`, `incomplete_plans`, `plan_count`, `incomplete_count`, `state_exists`, `roadmap_exists`, `phase_req_ids`, `response_language`, `requirements_path`, `section_manifest`, `threat_id_duplicate_count`.
|
||||
Parse JSON for: `executor_model`, `verifier_model`, `commit_docs`, `parallelization`, `branching_strategy`, `branch_name`, `phase_found`, `phase_dir`, `phase_number`, `padded_phase`, `phase_name`, `phase_slug`, `plans`, `incomplete_plans`, `plan_count`, `incomplete_count`, `state_exists`, `roadmap_exists`, `phase_req_ids`, `response_language`, `requirements_path`, `section_manifest`, `threat_id_duplicate_count`.
|
||||
|
||||
**Threat-ID gate (#4683):** if `threat_id_duplicate_count` is non-zero, read and execute `execute-phase/steps/threat-id-gate.md` BEFORE any dispatch — it is a hard stop (the full duplicate list is in `threat_id_duplicates`).
|
||||
|
||||
@@ -196,8 +196,11 @@ PHASE_NUMBER="{phase_number}"
|
||||
# #4619: {phase_number} may be decimal (01.1) or N-segment (23.1.2) — $((10#...))
|
||||
# is a hard shell syntax error on a non-integer, so zero-strip only the LEADING
|
||||
# integer segment and keep the rest as an escaped-dot string for the ERE below.
|
||||
PHASE_INT=${PHASE_NUMBER%%.*}; PHASE_FRAC=${PHASE_NUMBER#"$PHASE_INT"}
|
||||
PHASE_N="$((10#$PHASE_INT))${PHASE_FRAC//./\\.}"
|
||||
# #4748: it may also carry a letter suffix (03A, 23A.1.2 — the canonical grammar
|
||||
# is digits, optional [A-Z], dotted segments), so split at the first NON-DIGIT,
|
||||
# not the first dot: the letter rides along in the rest, unescaped.
|
||||
PHASE_INT=${PHASE_NUMBER%%[!0-9]*}; PHASE_REST=${PHASE_NUMBER#"$PHASE_INT"}
|
||||
PHASE_N="$((10#$PHASE_INT))${PHASE_REST//./\\.}"
|
||||
PLAN_N=$((10#{plan_padded}))
|
||||
PLAN_SCOPE_RE="^[a-z]+\((0*${PHASE_N})-(0*${PLAN_N})\):"
|
||||
MILESTONE_BASE=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
|
||||
@@ -219,9 +222,10 @@ if [ "$TDD_MODE" = "true" ]; then
|
||||
# #4003: same anchored scope and milestone bound as safe_resume_gate — a padded
|
||||
# literal grep hard-halts on a correct unpadded RED commit.
|
||||
# #4619: PHASE_NUMBER may be decimal/N-segment; zero-strip only the leading
|
||||
# integer segment, escape the rest for the ERE below.
|
||||
PHASE_INT=${PHASE_NUMBER%%.*}; PHASE_FRAC=${PHASE_NUMBER#"$PHASE_INT"}
|
||||
PHASE_N="$((10#$PHASE_INT))${PHASE_FRAC//./\\.}"
|
||||
# integer segment, escape the rest for the ERE below. #4748: it may carry a
|
||||
# letter suffix (03A), so the split is at the first non-digit, not the dot.
|
||||
PHASE_INT=${PHASE_NUMBER%%[!0-9]*}; PHASE_REST=${PHASE_NUMBER#"$PHASE_INT"}
|
||||
PHASE_N="$((10#$PHASE_INT))${PHASE_REST//./\\.}"
|
||||
PLAN_N=$((10#${PLAN_ID}))
|
||||
PLAN_SCOPE_RE="^[a-z]+\((0*${PHASE_N})-(0*${PLAN_N})\):" # TDD gate's own scope check
|
||||
TDD_MILESTONE_BASE=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
|
||||
@@ -1159,7 +1163,9 @@ Skill(skill="gsd-${ref.skill}", args="${PHASE_NUMBER}")
|
||||
|
||||
**Check results using deterministic path (not glob):**
|
||||
```bash
|
||||
PADDED=$(printf "%02d" "${PHASE_NUMBER}")
|
||||
# #4748: bind init's normalized id — `printf "%02d"` cannot pad a letter id
|
||||
# (03A → `03`, exit 1) and reads an already-padded `08` as octal (→ `00`).
|
||||
PADDED="{padded_phase}"
|
||||
REVIEW_FILE="${PHASE_DIR}/${PADDED}-REVIEW.md"
|
||||
REVIEW_STATUS=$(sed -n '/^---$/,/^---$/p' "$REVIEW_FILE" | grep "^status:" | head -1 | cut -d: -f2 | tr -d ' ')
|
||||
```
|
||||
|
||||
@@ -28,9 +28,9 @@ block indefinitely waiting for a signal; verify via filesystem and git state.
|
||||
SUMMARY_EXISTS=$(test -f "{phase_dir}/{plan_number}-{plan_padded}-SUMMARY.md" && echo "true" || echo "false")
|
||||
# #4003: anchored, zero-pad-tolerant scope (see safe_resume_gate); --since stays.
|
||||
SPOT_PHASE_NUMBER="{phase_number}"
|
||||
# #4619: same decimal/N-segment handling as safe_resume_gate.
|
||||
SPOT_PHASE_INT=${SPOT_PHASE_NUMBER%%.*}; SPOT_PHASE_FRAC=${SPOT_PHASE_NUMBER#"$SPOT_PHASE_INT"}
|
||||
SPOT_PHASE_N="$((10#$SPOT_PHASE_INT))${SPOT_PHASE_FRAC//./\\.}"
|
||||
# #4619 / #4748: same decimal/N-segment/letter-suffix handling as safe_resume_gate.
|
||||
SPOT_PHASE_INT=${SPOT_PHASE_NUMBER%%[!0-9]*}; SPOT_PHASE_REST=${SPOT_PHASE_NUMBER#"$SPOT_PHASE_INT"}
|
||||
SPOT_PHASE_N="$((10#$SPOT_PHASE_INT))${SPOT_PHASE_REST//./\\.}"
|
||||
SPOT_PLAN_N=$((10#{plan_padded}))
|
||||
COMMITS_FOUND=$(git log --oneline --all -E --grep="^[a-z]+\((0*${SPOT_PHASE_N})-(0*${SPOT_PLAN_N})\):" --since="1 hour ago" | head -1)
|
||||
COMMITS_SINCE_DISPATCH=$(git log "${EXPECTED_BRANCH}" --since="${DISPATCH_TS}" --oneline | head -1)
|
||||
|
||||
@@ -21,7 +21,9 @@ Read all files referenced by the invoking prompt's execution_context before star
|
||||
Extract from $ARGUMENTS: phase number, reviewer flags (the declared reviewer lane flags, plus `--all`), `--max-cycles N`, `--text`, `--ws`.
|
||||
|
||||
```bash
|
||||
PHASE=$(echo "$ARGUMENTS" | grep -oE '[0-9]+\.?[0-9]*' | head -1)
|
||||
# #4748: canonical phase grammar (digits, optional [A-Z], dotted segments) —
|
||||
# `12A` / `23.1.2` extract whole instead of truncating to `12` / `23.1`.
|
||||
PHASE=$(echo "$ARGUMENTS" | grep -oE '[0-9]+[A-Z]?(\.[0-9]+)*' | head -1)
|
||||
|
||||
# #2315: do NOT default REVIEWER_FLAGS to --codex here. The default is resolved
|
||||
# against review.default_reviewers in step 1.5 (after the config gate) so a bare
|
||||
|
||||
Reference in New Issue
Block a user