From bb183f162e32b65d89e720daa3c6ab90bc3a5f83 Mon Sep 17 00:00:00 2001 From: screenleon Date: Tue, 14 Jul 2026 11:23:04 +0900 Subject: [PATCH 1/3] fix: select registered frontmatter suite --- scripts/run-tests.sh | 6 +++--- scripts/test-run-tests.sh | 41 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/scripts/run-tests.sh b/scripts/run-tests.sh index dc1e95bd..e2e40eca 100755 --- a/scripts/run-tests.sh +++ b/scripts/run-tests.sh @@ -194,11 +194,11 @@ map_path() { for suite in "${!REGISTERED[@]}"; do [[ "$suite" == test-pmctl-* ]] && add_suite "$suite"; done behavioral=1 ;; agents/*.md) - add_suite lint-agents; add_suite lint-frontmatter; behavioral=1 ;; + add_suite lint-agents; add_suite test-lint-frontmatter; behavioral=1 ;; commands/*.md) - add_suite lint-frontmatter; add_suite test-commands; behavioral=1 ;; + add_suite test-lint-frontmatter; add_suite test-commands; behavioral=1 ;; skills/*) - add_suite lint-frontmatter; add_suite test-commands; behavioral=1 ;; + add_suite test-lint-frontmatter; add_suite test-commands; behavioral=1 ;; adapters/claude/*) add_suite test-claude-dispatch; add_suite test-executor-router; add_suite test-runner-kind; behavioral=1 ;; adapters/codex/*) diff --git a/scripts/test-run-tests.sh b/scripts/test-run-tests.sh index 5b0f863a..8a0aed85 100755 --- a/scripts/test-run-tests.sh +++ b/scripts/test-run-tests.sh @@ -27,7 +27,7 @@ make_fixture() { cat > "$repo/scripts/lib/test-suite-runner.sh" <<'RUNNER' #!/usr/bin/env bash set -euo pipefail -suites=(lint-agents lint-scripts lint-frontmatter test-commands test-check-docs-freshness test-guards test-pmctl-context test-pr-gate test-host-manifest test-host-write-codex test-host-write-parity test-core-schemas test-layer-boundaries test-pm-scripts test-run-tests) +suites=(lint-agents lint-scripts test-lint-frontmatter test-commands test-check-docs-freshness test-guards test-pmctl-context test-pr-gate test-host-manifest test-host-write-codex test-host-write-parity test-core-schemas test-layer-boundaries test-pm-scripts test-run-tests) for arg in "$@"; do if [[ "$arg" == --list ]]; then printf '%s\n' "${suites[@]}"; exit 0; fi done @@ -68,6 +68,42 @@ case_docs_mapping_list_only() { fi } +case_agent_mapping_uses_registered_frontmatter_suite() { + local name=agent-mapping-uses-registered-frontmatter-suite repo out status=0 args + args="$TMP_ROOT/$name.args" + repo="$(make_fixture "$name")" + out=$(RUN_TESTS_ARGS_LOG="$args" "$repo/scripts/run-tests.sh" --path agents/project-pm.md --list 2>&1) || status=$? + if [[ "$status" -eq 0 && "$out" == *"lint-agents"* && "$out" == *"test-lint-frontmatter"* && ! -e "$args" ]]; then + pass "$name" + else + fail "$name" "status=$status out=$out executed=$([[ -e "$args" ]] && echo yes || echo no)" + fi +} + +case_command_mapping_uses_registered_frontmatter_suite() { + local name=command-mapping-uses-registered-frontmatter-suite repo out status=0 args + args="$TMP_ROOT/$name.args" + repo="$(make_fixture "$name")" + out=$(RUN_TESTS_ARGS_LOG="$args" "$repo/scripts/run-tests.sh" --path commands/ship.md --list 2>&1) || status=$? + if [[ "$status" -eq 0 && "$out" == *"test-lint-frontmatter"* && "$out" == *"test-commands"* && ! -e "$args" ]]; then + pass "$name" + else + fail "$name" "status=$status out=$out executed=$([[ -e "$args" ]] && echo yes || echo no)" + fi +} + +case_skill_mapping_uses_registered_frontmatter_suite() { + local name=skill-mapping-uses-registered-frontmatter-suite repo out status=0 args + args="$TMP_ROOT/$name.args" + repo="$(make_fixture "$name")" + out=$(RUN_TESTS_ARGS_LOG="$args" "$repo/scripts/run-tests.sh" --path skills/example/SKILL.md --list 2>&1) || status=$? + if [[ "$status" -eq 0 && "$out" == *"test-lint-frontmatter"* && "$out" == *"test-commands"* && ! -e "$args" ]]; then + pass "$name" + else + fail "$name" "status=$status out=$out executed=$([[ -e "$args" ]] && echo yes || echo no)" + fi +} + case_guard_family_maps_to_guard_suite() { local name=guard-family-maps-to-guard-suite repo out status=0 args args="$TMP_ROOT/$name.args" @@ -213,6 +249,9 @@ case_iteration_result_cannot_verify_as_full() { case_direct_library_mapping case_docs_mapping_list_only +case_agent_mapping_uses_registered_frontmatter_suite +case_command_mapping_uses_registered_frontmatter_suite +case_skill_mapping_uses_registered_frontmatter_suite case_guard_family_maps_to_guard_suite case_evidence_contract_maps_to_runner_regression case_high_fanout_escalates_full From a597e7c5b75994f1cfec3b602ae88e4367cc2ab1 Mon Sep 17 00:00:00 2001 From: screenleon Date: Tue, 14 Jul 2026 11:50:29 +0900 Subject: [PATCH 2/3] docs: order maintainability review before pr gate --- CONTRIBUTING.md | 25 ++++++++-- agents/project-pm.md | 1 + commands/pr-gate.md | 31 ++++++++++-- commands/ship.md | 75 ++++++++++++++++++++++++++-- docs/review-model.md | 40 ++++++++++++++- scripts/test-commands.sh | 102 +++++++++++++++++++++++++++++++++++++-- 6 files changed, 257 insertions(+), 17 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bd8e8466..783a0e1f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,11 +41,19 @@ chore(cc-###): short summary Before opening a PR: -1. Run `/pr-gate` in the main thread. -2. Address any required review blocks. -3. Confirm the final checklist in your PR description. +The authoritative maintainer sequence and failure loop are defined in +[`/ship` Steps 2.5–3.5](commands/ship.md#step-25--refactorreuse-audit). + +1. Use affected tests during implementation, then complete `/ship` Step 2.5's + refactor/reuse audit. +2. Run `/pr-gate` in the main thread. +3. Address findings and apply `/ship` Step 3's conditional recheck rule. +4. After GO, complete `/ship` Step 3.5's authoritative full-suite check. +5. Confirm the final checklist in your PR description. [`/pr-gate` skill](commands/pr-gate.md) is the hard-gate review entry point and is documented alongside the same execution patterns in [`docs/dispatch-brief.md`](docs/dispatch-brief.md). +The refactor/reuse checkpoint is this repository's maintainer policy; it does +not make `pmctl gate` or its component tools mandatory for downstream users. ## PM brief schema and contracts @@ -59,12 +67,21 @@ If this repository adds changes to the dispatch pipeline, update this schema fir ## Testing and validation -For every file-writing or schema-affecting PR, run the full test suite: +During implementation and gate-fix iteration, run only the affected suites: + +```bash +bash scripts/run-tests.sh --base +``` + +After PR-gate returns GO, run the full test suite once against the final tree: ```bash bash scripts/run-all-tests.sh ``` +For full-suite failures and re-gating, follow the authoritative loop in +[`/ship` Step 3.5](commands/ship.md#step-35--authoritative-full-suite). + This runs all suites (hooks, install, portable, pr-gate, usage, pm-scripts, etc.) and prints a pass/fail/skip summary. For a focused run on one suite, invoke it directly, for example `bash scripts/test-guards.sh` or `bash scripts/test-install.sh`. Use diff --git a/agents/project-pm.md b/agents/project-pm.md index 2c242d57..a1a36652 100644 --- a/agents/project-pm.md +++ b/agents/project-pm.md @@ -173,6 +173,7 @@ Record the split decision in project memory and surface it to the user. Never le 3. **`--targeted` re-run**: Once the fix is committed, re-run gate with `--targeted ` (maps to `--reviewers` at the script level). Full tier is for first round and scope-unclear situations only. 4. **Minimum-list principle**: Gate round N's findings are the minimum set to fix, not a complete enumeration. In the fix brief, instruct Codex to "grep for all similar patterns in the same scope and fix them proactively" — this prevents the next round from finding the same class of issue in an adjacent location. 5. **Next-layer sweep**: After each fix, ask "does this fix reveal a deeper layer of the same class of issue?" Common triggers: fixed a missing test → are there adjacent untested behaviors in the same feature? Fixed a doc mismatch → are there other mismatches in the same file? Fixed a contract violation → does the contract have other clauses not yet tested? This check costs one minute and prevents one gate round. +6. **Refactor/reuse recheck threshold**: Apply `commands/ship.md` Step 3's structural-versus-localized threshold. Emit `refactor_reuse_recheck: required` for structural or scope-unclear remediation; emit `refactor_reuse_recheck: skip` plus a one-line reason for localized fixes that preserve the architecture. A required recheck runs before the targeted gate; if it changes the diff, rerun affected focused tests first. ## Executor selection diff --git a/commands/pr-gate.md b/commands/pr-gate.md index e87a649f..d4ab9153 100644 --- a/commands/pr-gate.md +++ b/commands/pr-gate.md @@ -59,14 +59,20 @@ call is still available. ### Executor routing is passed by flag only +Choose the gate executor/model using the authoritative +[gate model diversity policy](../docs/review-model.md#gate-model-diversity). +Resolve actual model identities rather than inferring them from host or adapter +names. Replace `` below with the selected literal value and pass +`--model ` when the route default is not the selected model. + This command should pass exactly one of these explicit modes when known: - `--executor codex` for codex route (or when `command -v codex` is true and user explicitly requested) - `--executor claude` for claude-only path - `--executor auto` for default behavior (`command -v codex` decides) -`--executor auto` is the default; keep that shape here for parity with existing -`/pm` profile defaults and `scripts/install-guards.sh` auto-detect. +`--executor auto` remains the compatibility fallback only when the +implementation model or alternate executor availability cannot be determined. ```bash RAW_ARGS="${ARGUMENTS:-}" @@ -74,6 +80,8 @@ TIER_OVERRIDE="" TARGETED_REVIEWERS="" SCOPE_TOKENS=() PARALLEL=false +GATE_EXECUTOR="" +GATE_MODEL="" if [[ -n "$RAW_ARGS" ]]; then read -r -a TOKENS <<< "$RAW_ARGS" @@ -105,6 +113,15 @@ while [[ "$idx" -lt "${#TOKENS[@]}" ]]; do --parallel) PARALLEL=true ;; + --executor) + idx=$((idx + 1)) + GATE_EXECUTOR="${TOKENS[$idx]:-}" + ;; + --model) + idx=$((idx + 1)) + GATE_MODEL="${TOKENS[$idx]:-}" + [[ -n "$GATE_MODEL" ]] || { echo "error: --model requires a value" >&2; exit 2; } + ;; *) SCOPE_TOKENS+=("$tok") ;; @@ -112,6 +129,13 @@ while [[ "$idx" -lt "${#TOKENS[@]}" ]]; do idx=$((idx + 1)) done +# Validate after parsing so this also rejects a missing substitution of the +# default, not only an invalid explicit --executor value. +case "$GATE_EXECUTOR" in + codex|claude|auto) ;; + *) echo "error: gate executor must resolve to codex, claude, or auto" >&2; exit 2 ;; +esac + SCOPE="${SCOPE_TOKENS[*]:-}" # --cd's value below, "", is a placeholder to replace with the # actual working directory (already known from context) as a literal quoted @@ -121,7 +145,8 @@ SCOPE="${SCOPE_TOKENS[*]:-}" # `pmctl:*` prefix match. The quotes around the placeholder keep this block # valid, executable Bash even before that substitution (bare, unquoted # `` would be parsed as I/O redirection and fail to parse). -GATE_ARGS=(--cd "" --executor auto) +GATE_ARGS=(--cd "" --executor "$GATE_EXECUTOR") +[[ -n "$GATE_MODEL" ]] && GATE_ARGS+=(--model "$GATE_MODEL") [[ -n "$TIER_OVERRIDE" ]] && GATE_ARGS+=(--tier "$TIER_OVERRIDE") [[ -n "$TARGETED_REVIEWERS" ]] && GATE_ARGS+=(--reviewers "$TARGETED_REVIEWERS") [[ -n "$SCOPE" ]] && GATE_ARGS+=(--scope "$SCOPE") diff --git a/commands/ship.md b/commands/ship.md index b7cdacf6..eaf9ae92 100644 --- a/commands/ship.md +++ b/commands/ship.md @@ -3,8 +3,9 @@ description: Take one explicit backlog ticket from implementation through pr-gat argument-hint: "" --- -Run a single, explicitly named ticket end-to-end: implement → gate → fix → gate -→ open PR. This is the main-thread's own default operating discipline made +Run a single, explicitly named ticket end-to-end: implement → affected tests → +refactor/reuse audit → gate → fix → conditional re-audit → gate → full suite → +open PR. This is the main-thread's own default operating discipline made runnable as one command, not a new background/unattended supervisor — the session stays open and you stay reachable the whole time. @@ -97,9 +98,44 @@ implementation to codex/claude/opencode. `pmctl dispatch run` is not part of this flow; the only executor dispatch in `/ship` is the gate's own reviewer dispatch in Step 3. +Use `bash scripts/run-tests.sh --base ` for implementation feedback. +Pass explicit `--path` or suite inputs when narrowing an iteration further. +Do not run `run-all-tests.sh` before the first PR gate: the affected-test +planner is the fast feedback path, while the authoritative full suite belongs +after a GO verdict. + +## Step 2.5 — Refactor/reuse audit + +After the primary implementation and its affected verification are complete, +run one maintainability audit on the actual diff **before the first PR gate**. +This is a pm-dispatch maintainer policy, not a prerequisite built into the +generic `pmctl gate` command. + +1. **Simplify**: inspect changed production code for duplicated branches, + wrappers, parsing, locking, path handling, or indirection that can be made + smaller without changing behavior. +2. **Reuse**: use `rg` to inspect existing helpers, peer implementations, and + all call sites in the affected scope. Prefer a proven shared helper over a + second local implementation, but do not introduce speculative abstractions. +3. **Verify**: apply warranted behavior-preserving cleanup, rerun the affected + focused tests, and record either `CHANGED: ` or + `PASS: no warranted refactor/reuse change` for the PR handoff. + +This initial audit runs exactly once even when it finds no cleanup. It is not +delegated to PR-gate reviewers: doing the cheap maintainability pass first +keeps avoidable duplication and incidental complexity out of the expensive +cross-model review loop. + ## Step 3 — Gate loop -Run `pmctl gate run --executor codex --cd "" --lifecycle foreground` +Choose `` and, when needed, `` before the first gate +using the authoritative [gate model diversity policy](../docs/review-model.md#gate-model-diversity). +Base the choice on actual model identities, record both identities in the +handoff, and keep the same resolved pair for targeted re-runs. Add +`--model ""` below only when the executor default does not already +resolve to the selected gate model. + +Run `pmctl gate run --executor --cd "" --lifecycle foreground` (substitute `` with the literal absolute working directory, not `"$PWD"` — a shell-variable expansion makes the command unanalyzable statically and forces a manual approval every time even though a bare @@ -117,13 +153,26 @@ uses to keep the main thread free for other work; run `foreground` and read the resulting `Final: GO|NO-GO` verdict directly from the gate result file once the call returns. -- **GO** → go to Step 4. +- **GO** → go to Step 3.5. - **NO-GO** → invoke the `project-pm` agent to synthesize the gate result against the verdict table and Rules A/B in `agents/project-pm.md` (source-first read of every cited diff file, discovery sweep of all call sites of a flagged helper, minimum-list is a floor not a ceiling). Fix **every** finding it returns — high, medium, and low, hard gate and advisory alike, not only - the blocking ones. Re-run `pmctl gate run --executor codex --cd "" + the blocking ones. + + Before re-running the gate, classify the remediation: + + - **Re-run the refactor/reuse audit** when the fix changes a shared helper or + public interface, schema, ownership, data flow, layer boundary, introduces + or removes an abstraction, moves logic across files, performs cross-file + deduplication, raises `architecture_impact`, or leaves the structural scope + unclear. Rerun affected focused tests if the audit changes the diff. + - **Skip the re-audit with a recorded reason** for localized corrections that + preserve the established structure, such as wording/comments, a narrow + assertion or fixture adjustment, or a small guard/error-handling fix. + + Then re-run `pmctl gate run --executor --cd "" --lifecycle foreground --reviewers ` (same literal-path substitution as Step 3's first call — never `"$PWD"`; the `/pr-gate` `--targeted` flag maps to this same `--reviewers` option) for the reviewers @@ -143,6 +192,18 @@ once the call returns. Any other NO-GO, at any round count, gets fixed and re-gated without asking. +## Step 3.5 — Authoritative full suite + +Only after PR-gate returns GO, run `bash scripts/run-all-tests.sh` once and +verify its authoritative result artifact. This is the final repo-wide +regression check, not an iteration tool. + +If the full suite finds a diff-caused failure, fix it, rerun affected tests, +apply the same refactor/reuse recheck threshold, and return to the targeted +gate path for every reviewer territory the fix touched. After GO, run the full +suite again against the new tree. Keep pre-existing or infrastructure failures +separate; do not silently attach unrelated repairs to the ticket. + ## Step 4 — Open the PR ```bash @@ -152,9 +213,13 @@ gh pr create --title "(): " --body "$(cat <<'EOF - ## Gate +- Refactor/reuse audit: +- Implementation model: +- Review executor/model: - Rounds: - Final verdict: GO - Result file: +- Full suite: Ticket: EOF diff --git a/docs/review-model.md b/docs/review-model.md index 3017be52..333d8c40 100644 --- a/docs/review-model.md +++ b/docs/review-model.md @@ -84,12 +84,48 @@ If machine verification passes, the task is done. If it fails, it re-opens. This ## How the layers interact ``` -/pre-impl → dispatch brief → executor → /pr-gate → merged - L1 L1 L4 L2+L3+L4 +/pre-impl → dispatch brief → executor → affected tests → refactor/reuse audit + L1 L1 L4 fast feedback maintainer policy + → /pr-gate → full suite → PR + L2+L3+L4 final verify ``` A typical task flows left to right. Each arrow is a checkpoint; a failure at any checkpoint stops forward motion rather than producing a bad merge. +The refactor/reuse audit is a maintainer checkpoint after the main +implementation, not another generic gate layer. It inspects the actual diff and +nearby helpers/call sites for behavior-preserving simplification, consolidation, +and reuse before cross-context review. Run it once before the first PR gate. + +After gate remediation, structural or scope-unclear fixes repeat the audit; +localized fixes that preserve the architecture may skip it with a recorded +reason. [`/ship` Step 3](../commands/ship.md#step-3--gate-loop) is the +authoritative operational threshold. This keeps the cheap maintenance pass +ahead of expensive review without duplicating the full rule across documents. + +The affected-test planner (`scripts/run-tests.sh`) is the iteration path before +and between gate rounds. The authoritative `scripts/run-all-tests.sh` runs only +after a GO verdict against the final tree. A diff-caused full-suite fix returns +through affected tests and targeted review before the full suite is repeated. + +### Gate model diversity + +Select the PR-gate model by comparing it with the **actual primary +implementation model family**, not with a host or adapter label. OpenCode can +run Claude, OpenAI, Gemini, and other model families, and executor routes may +accept an explicit `--model`; therefore host names are not reliable model +identifiers. + +Prefer an available gate model from a different family than the model that +produced the primary diff. For mixed-model implementation, prefer a family not +responsible for the primary change. An explicit user selection wins. Use the +same resolved executor/model pair for targeted re-runs. + +If no alternate family is available, a same-family gate is allowed only when +the handoff records the implementation model, resolved gate model, and fallback +reason. The PR description records both model identities so this best-effort +selection remains reviewable. + For small tasks (`behavioral_units < 3` and `architecture_impact: none`), Layer 1 may be skipped and Layer 3 is lightweight. For tasks with architecture impact, all four layers are active and Layer 3 gets a full conceptual map as input. ### Evidence chain diff --git a/scripts/test-commands.sh b/scripts/test-commands.sh index d746213a..b2a52d39 100755 --- a/scripts/test-commands.sh +++ b/scripts/test-commands.sh @@ -402,6 +402,80 @@ should_run "using-git-worktrees: no CC ticket references" && assert_not_contains # ── ship.md contract ───────────────────────────────────────────────────────── SHIP="$COMMANDS_DIR/ship.md" +PR_GATE="$COMMANDS_DIR/pr-gate.md" + +extract_pr_gate_bash_fence() { + local output="$1" + awk ' + /^```bash$/ && !inside { inside=1; block=""; next } + inside && /^```$/ { + if (block ~ /RAW_ARGS=/) { printf "%s", block; exit } + inside=0 + next + } + inside { block=block $0 ORS } + ' "$PR_GATE" > "$output" +} + +pr_gate_fence_syntax() { + local name="pr-gate: argument parser bash fence parses" + should_run "$name" || return 0 + local snippet="$tmp_root/pr-gate-fence-syntax.sh" + extract_pr_gate_bash_fence "$snippet" + if [[ -s "$snippet" ]] && bash -n "$snippet"; then + pass "$name" + else + fail "$name" "first bash fence is empty or has invalid syntax" + fi +} +pr_gate_fence_syntax + +pr_gate_fence_executor_model_behavior() { + local name="pr-gate: executor and model parser behavior" + should_run "$name" || return 0 + local case_dir="$tmp_root/pr-gate-fence-behavior" + local snippet="$case_dir/snippet.sh" bin_dir="$case_dir/bin" + local args_log="$case_dir/args.log" out="$case_dir/out" err="$case_dir/err" + local valid_status invalid_status missing_executor_status missing_model_status default_status + mkdir -p "$bin_dir" + extract_pr_gate_bash_fence "$snippet" + cat > "$bin_dir/pmctl" <<'STUB' +#!/usr/bin/env bash +printf '%s\n' "$@" > "${PR_GATE_ARGS_LOG:?}" +STUB + chmod +x "$bin_dir/pmctl" + + ARGUMENTS="--executor claude --model claude-sonnet-5" \ + PR_GATE_ARGS_LOG="$args_log" PATH="$bin_dir:$PATH" bash "$snippet" > "$out" 2> "$err" + valid_status=$? + rm -f "$args_log" + + ARGUMENTS="--executor invalid" PR_GATE_ARGS_LOG="$args_log" PATH="$bin_dir:$PATH" \ + bash "$snippet" > "$out" 2> "$err" + invalid_status=$? + ARGUMENTS="--executor" PR_GATE_ARGS_LOG="$args_log" PATH="$bin_dir:$PATH" \ + bash "$snippet" > "$out" 2> "$err" + missing_executor_status=$? + ARGUMENTS="--executor claude --model" PR_GATE_ARGS_LOG="$args_log" PATH="$bin_dir:$PATH" \ + bash "$snippet" > "$out" 2> "$err" + missing_model_status=$? + ARGUMENTS="" PR_GATE_ARGS_LOG="$args_log" PATH="$bin_dir:$PATH" \ + bash "$snippet" > "$out" 2> "$err" + default_status=$? + + ARGUMENTS="--executor claude --model claude-sonnet-5" \ + PR_GATE_ARGS_LOG="$args_log" PATH="$bin_dir:$PATH" bash "$snippet" > "$out" 2> "$err" + if [[ "$valid_status" -eq 0 && "$invalid_status" -eq 2 && + "$missing_executor_status" -eq 2 && "$missing_model_status" -eq 2 && + "$default_status" -eq 2 ]] && + grep -qx -- '--executor' "$args_log" && grep -qx 'claude' "$args_log" && + grep -qx -- '--model' "$args_log" && grep -qx 'claude-sonnet-5' "$args_log"; then + pass "$name" + else + fail "$name" "statuses valid=$valid_status invalid=$invalid_status missing_executor=$missing_executor_status missing_model=$missing_model_status default=$default_status" + fi +} +pr_gate_fence_executor_model_behavior assert_frontmatter "ship: frontmatter valid" "$SHIP" should_run "ship: scoped to a single named ticket per invocation" && assert_file_contains "ship: scoped to a single named ticket per invocation" "$SHIP" "one ticket per invocation" && pass "ship: scoped to a single named ticket per invocation" @@ -425,13 +499,34 @@ should_run "ship: dirty tree aborts fail-fast, does not auto-mutate" && assert_f should_run "ship: dirty-tree abort is not the negotiated stop" && assert_file_contains "ship: dirty-tree abort is not the negotiated stop" "$SHIP" "not the negotiated stop this command reserves" && pass "ship: dirty-tree abort is not the negotiated stop" # implementation stays main-thread, not dispatched should_run "ship: implementation is not dispatched to an executor" && assert_file_contains "ship: implementation is not dispatched to an executor" "$SHIP" "to codex/claude/opencode" && pass "ship: implementation is not dispatched to an executor" +# pre-gate maintainability checkpoint +should_run "ship: has refactor/reuse audit before first gate" && assert_file_contains "ship: has refactor/reuse audit before first gate" "$SHIP" "Step 2.5 — Refactor/reuse audit" && assert_file_contains "ship: has refactor/reuse audit before first gate" "$SHIP" "before the first PR gate" && pass "ship: has refactor/reuse audit before first gate" +if should_run "ship: refactor/reuse audit section precedes gate section"; then + ship_audit_line=$(grep -n '^## Step 2\.5 — Refactor/reuse audit$' "$SHIP" | cut -d: -f1) + ship_gate_line=$(grep -n '^## Step 3 — Gate loop$' "$SHIP" | cut -d: -f1) + if [[ -n "$ship_audit_line" && -n "$ship_gate_line" && "$ship_audit_line" -lt "$ship_gate_line" ]]; then + pass "ship: refactor/reuse audit section precedes gate section" + else + fail "ship: refactor/reuse audit section precedes gate section" "audit_line=$ship_audit_line gate_line=$ship_gate_line" + fi +fi +should_run "ship: audit inspects actual diff and existing helpers" && assert_file_contains "ship: audit inspects actual diff and existing helpers" "$SHIP" "actual diff" && assert_file_contains "ship: audit inspects actual diff and existing helpers" "$SHIP" "existing helpers" && pass "ship: audit inspects actual diff and existing helpers" +should_run "ship: audit rejects speculative abstraction" && assert_file_contains "ship: audit rejects speculative abstraction" "$SHIP" "do not introduce speculative abstractions" && pass "ship: audit rejects speculative abstraction" +should_run "ship: initial audit records changed or no-change result" && assert_file_contains "ship: initial audit records changed or no-change result" "$SHIP" "CHANGED:" && assert_file_contains "ship: initial audit records changed or no-change result" "$SHIP" "PASS: no warranted refactor/reuse change" && pass "ship: initial audit records changed or no-change result" +should_run "ship: structural gate fixes require re-audit" && assert_file_contains "ship: structural gate fixes require re-audit" "$SHIP" "Re-run the refactor/reuse audit" && assert_file_contains "ship: structural gate fixes require re-audit" "$SHIP" "layer boundary" && pass "ship: structural gate fixes require re-audit" +should_run "ship: localized gate fixes may skip re-audit with reason" && assert_file_contains "ship: localized gate fixes may skip re-audit with reason" "$SHIP" "Skip the re-audit with a recorded reason" && assert_file_contains "ship: localized gate fixes may skip re-audit with reason" "$SHIP" "localized corrections" && pass "ship: localized gate fixes may skip re-audit with reason" +should_run "ship: uses affected tests before first gate" && assert_file_contains "ship: uses affected tests before first gate" "$SHIP" "scripts/run-tests.sh --base " && assert_file_contains "ship: uses affected tests before first gate" "$SHIP" 'Do not run `run-all-tests.sh` before the first PR gate' && pass "ship: uses affected tests before first gate" +should_run "ship: runs authoritative full suite only after GO" && assert_file_contains "ship: runs authoritative full suite only after GO" "$SHIP" "Only after PR-gate returns GO" && assert_file_contains "ship: runs authoritative full suite only after GO" "$SHIP" "bash scripts/run-all-tests.sh" && pass "ship: runs authoritative full suite only after GO" +should_run "ship: diff-caused full failure returns through targeted review" && assert_file_contains "ship: diff-caused full failure returns through targeted review" "$SHIP" "full suite finds a diff-caused failure" && assert_file_contains "ship: diff-caused full failure returns through targeted review" "$SHIP" "return to the targeted" && pass "ship: diff-caused full failure returns through targeted review" # gate loop contract -should_run "ship: invokes pmctl gate run --executor codex for review" && assert_file_contains "ship: invokes pmctl gate run --executor codex for review" "$SHIP" "pmctl gate run --executor codex" && pass "ship: invokes pmctl gate run --executor codex for review" +should_run "ship: delegates model selection policy" && assert_file_contains "ship: delegates model selection policy" "$SHIP" "gate model diversity policy" && assert_file_contains "ship: delegates model selection policy" "$SHIP" "actual model identities" && pass "ship: delegates model selection policy" +should_run "pr-gate: delegates model selection policy" && assert_file_contains "pr-gate: delegates model selection policy" "$PR_GATE" "gate model diversity policy" && assert_file_contains "pr-gate: delegates model selection policy" "$PR_GATE" "actual model identities" && pass "pr-gate: delegates model selection policy" +should_run "review-model: selects by model not host or adapter" && assert_file_contains "review-model: selects by model not host or adapter" "$REPO_ROOT/docs/review-model.md" "actual primary" && assert_file_contains "review-model: selects by model not host or adapter" "$REPO_ROOT/docs/review-model.md" "host names are not reliable model" && assert_file_contains "review-model: selects by model not host or adapter" "$REPO_ROOT/docs/review-model.md" "OpenCode can" && pass "review-model: selects by model not host or adapter" should_run "ship: never invokes pr-gate.sh directly" && assert_file_contains "ship: never invokes pr-gate.sh directly" "$SHIP" "never \`bash scripts/pr-gate.sh\` directly" && pass "ship: never invokes pr-gate.sh directly" if should_run "ship: every gate invocation uses --lifecycle foreground"; then ship_flat=$(tr '\n' ' ' < "$SHIP" | tr -s ' ') - ship_gate_calls=$(grep -oE 'pmctl gate run --executor codex' <<< "$ship_flat" | wc -l) - ship_foreground_calls=$(grep -oE 'pmctl gate run --executor codex[^`]*--lifecycle foreground' <<< "$ship_flat" | wc -l) + ship_gate_calls=$(grep -oE 'pmctl gate run --executor ' <<< "$ship_flat" | wc -l) + ship_foreground_calls=$(grep -oE 'pmctl gate run --executor [^`]*--lifecycle foreground' <<< "$ship_flat" | wc -l) if [[ "$ship_gate_calls" -gt 0 && "$ship_gate_calls" -eq "$ship_foreground_calls" ]]; then pass "ship: every gate invocation uses --lifecycle foreground" else @@ -443,6 +538,7 @@ should_run "ship: reads Final GO/NO-GO verdict" && assert_file_contains "ship: r should_run "ship: NO-GO fixes every finding not only blocking ones" && assert_file_contains "ship: NO-GO fixes every finding not only blocking ones" "$SHIP" "the blocking ones" && pass "ship: NO-GO fixes every finding not only blocking ones" should_run "ship: re-runs gate with --reviewers targeting" && assert_file_contains "ship: re-runs gate with --reviewers targeting" "$SHIP" "--reviewers " && pass "ship: re-runs gate with --reviewers targeting" should_run "ship: references project-pm Rules A/B synthesis" && assert_file_contains "ship: references project-pm Rules A/B synthesis" "$SHIP" "Rules A/B" && pass "ship: references project-pm Rules A/B synthesis" +should_run "project-pm: classifies refactor/reuse recheck after gate fixes" && assert_file_contains "project-pm: classifies refactor/reuse recheck after gate fixes" "$PROJECT_PM" "refactor_reuse_recheck: required" && assert_file_contains "project-pm: classifies refactor/reuse recheck after gate fixes" "$PROJECT_PM" "refactor_reuse_recheck: skip" && pass "project-pm: classifies refactor/reuse recheck after gate fixes" # exactly two stop conditions, no more should_run "ship: stop condition heading enumerates the loop's halt cases" && assert_file_contains "ship: stop condition heading enumerates the loop's halt cases" "$SHIP" "Stop the loop only when" && pass "ship: stop condition heading enumerates the loop's halt cases" if should_run "ship: exactly one genuine wait-for-user-direction path"; then From b2b5855ad2322a420b266526450ec36eac93becb Mon Sep 17 00:00:00 2001 From: screenleon Date: Tue, 14 Jul 2026 11:50:33 +0900 Subject: [PATCH 3/3] chore: track test-guards CI hang --- BACKLOG.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/BACKLOG.md b/BACKLOG.md index 57b75635..af9805b2 100644 --- a/BACKLOG.md +++ b/BACKLOG.md @@ -32,6 +32,7 @@ CC-001/CC-002 were consumed by PR #24 fix bundle inline, with no standalone entr | CC-484 | ✅ done | JapanJob 與 qa-testing-rules 的 `pmctl context` refresh 未生效:重現 session/index/update/pack 實際路徑、repo-root/project-key/DB freshness,補跨 repo live E2E 與 actionable diagnostics | ops/memory | 2026-07-13 | feedback:2026-07-13 | P1 | retrieval | | CC-485 | ✅ done | 工具能力與維護者政策分離:通用 gate/test/PM 不規定使用流程;affected feedback 僅屬開發/PR,pm-dispatch release 固定由 `release-verify.sh --e2e`(內含 fresh full suite)+ checklist 驗收 | arch/process | 2026-07-13 | pr:#398 | P3 | design | | CC-486 | ⏸ deferred | direct-impact test planner mapping 提前退出:changed path 含 `agents/*.md`/`commands/*.md` 時 `map_path` 呼叫未註冊的 `lint-frontmatter`,`add_suite` 回傳 1 並在 `set -e` 下無輸出終止,導致 `run-tests.sh --base ... --list` exit 1 | ops/test | 2026-07-13 | feedback:2026-07-13 | P2 | hygiene | +| CC-487 | 🔵 active | GitHub Actions `test-guards` 非確定性掛起:逐 case breadcrumb + bounded background writer/wait + orphan cleanup,避免 main CI 無輸出佔用 runner 近一小時 | ops/test | 2026-07-14 | feedback:2026-07-14 | P1 | hygiene | | CC-465 | 🔵 active | memory/context 關鍵詞管線 CJK 支援:抽出共用零依賴斷詞 lib,取代三處各自 ASCII-only 抽詞;工作序列起點(465→467→468→466)(2026-07-07 記憶系統深入分析) | memory | 2026-07-07 | feedback:2026-07-07 | P2 | retrieval | | CC-466 | 🔵 active | 記憶卡片生命週期閉環:expires_at 執行 + 關窗式 supersede + usage sidecar 休眠偵測 + doctor→distill 接線;排在 CC-467 之後(需其遙測為前置)(2026-07-07 記憶系統分析 + 外部研究 Graphiti/mcp-memory-service) | memory | 2026-07-07 | feedback:2026-07-07 | P2 | retrieval | | CC-467 | 🔵 active | `pmctl memory stats`:注入效益可視化(唯讀聚合器)——注入 bytes/卡片命中分佈/從未命中卡/episode 填寫率,回答「記憶有跟沒有差在哪」;排在 CC-466 之前(2026-07-07;業界僅離線 recall 評測,無 per-injection 遙測) | DX/memory | 2026-07-07 | — | P2 | retrieval | @@ -654,6 +655,26 @@ _Terminal_ (CC-378: swept OUT to `BACKLOG-ARCHIVE.md` by `scripts/archive-closed --- +## CC-487 — GitHub Actions `test-guards` 非確定性掛起與 bounded diagnostics 🔵 active + +**Problem**: main SHA `0b66f1f` 的 GitHub Actions run `29298816362` 中,`test-guards` job 從 2026-07-14 01:32:51Z 執行至 02:30:38Z,停留在 `== guard-inject-memory ==` 後沒有更多輸出,最後由使用者手動取消。runner cleanup 記錄兩個殘留 `bash` process。該區段不只包含 inject hook,亦包含 `memory-usage/concurrent-no-lost-updates` 與 `memory-usage/contention-matrix-flock-and-mkdir-fallback` 等背景 writer/`wait` 測試;CI 未啟用逐 case breadcrumb,現有 artifact 無法確定是哪一個 case 或 process 未收斂。 + +**Boundary**: 目前不能把這次取消判定為 canonical-memory 產品 regression。相同程式碼在乾淨 HOME 的本機完整 `scripts/test-guards.sh` 為 296/296,inject/context 範圍 50/50;contention matrix 連跑 12 次、concurrent update 連跑 20 次均通過。這是一張獨立 repo-wide CI hardening 票,不混入 CC-483、CC-486 或 maintainer workflow 內容。 + +**Requirement**: +1. GitHub Actions 的直接 `test-guards` job 必須輸出 `RUNNING test-guards/`,取消或逾時時能從 log 唯一定位 active case。 +2. 對含背景 writer/FIFO/`wait` 的 guard concurrency cases 加 case-level deadline、TERM→KILL 與 `EXIT` cleanup;任一 writer 失敗或未退出時輸出 backend、round、writer id、PID、lock state 與已完成計數,不得無界等待。 +3. 對直接 CI job 加整體上限,且上限必須大於正常完整 suite 的合理波動、遠小於 57 分鐘;timeout 必須保留最後 case breadcrumb 與診斷,而非只留下 group heading。 +4. 使用接近 GitHub `ubuntu-24.04` runner 的乾淨 HOME/PATH 重跑完整 suite 與 contention stress;不得靠放寬正確性斷言、降低 writer 數或跳過 mkdir fallback 來讓測試變綠。 + +**Acceptance**: (a) 正常 CI `test-guards` 完整通過;(b) 人工注入一個不退出 writer 時,在 bounded deadline 內非零結束、指出精確 case/writer 並清除所有 child process;(c) 不再可能讓單一 guard case 無輸出佔用 runner 近一小時;(d) focused contention stress、完整 `scripts/test-guards.sh`、`scripts/test-run-all-tests.sh` 與 `git diff --check` 全綠。 + +**Evidence**: GitHub Actions run `29298816362`, job `86978053691`;本機乾淨環境完整 296/296 通過,故根因仍需以新增 breadcrumb/bounded diagnostics 捕捉,不能從單次取消紀錄過度推論。 + +**Dependencies**: 承接 [[CC-477]] 已完成的 lock protocol 與 runner breadcrumbs;本票只處理仍存在的 CI hang 可診斷性與 bounded lifecycle。 + +--- + ## CC-484 — JapanJob/qa-testing-rules pmctl context refresh 失效 ✅ 2026-07-13 **Problem**: JapanJob 的 session 與 qa-testing-rules 實際工作流沒有順利用 `pmctl context` 刷新內容。[[CC-455]] 曾修正 context plane 預設 repo root 跟隨 caller CWD,但目前 live 行為仍可能在 repo-root resolution、worktree/project key、context.db 位置、mtime skip、session hook/prepare wiring 或 update/query 順序其中一層失效;只看 unit test 不能確認是哪一層。