Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/workflows/validate-command-anchors-ratchet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@ on:
paths:
- "pmoves/docs/**/*.md"
- ".claude/skills/**/*.md"
# The always-loaded orientation layer — first contact for any agent
# entering this repo, and therefore the highest-cost place for a dead
# reference. .claude/CLAUDE.md currently names a target that does not exist.
- ".claude/*.md"
- "CLAUDE.md"
- "AGENTS.md"
- ".claude/context/**/*.md"
- ".claude/commands/**/*.md"
- ".claude/agents/**/*.md"
# The damage-control routing table: every "correct path" it offers is a
# promise made to an agent at its least recoverable moment.
- ".claude/hooks/damage-control/patterns.yaml"
- "deploy/runbooks/**/*.md"
- "pmoves/Makefile"
- "pmoves/mk/*.mk"
Expand Down
33 changes: 21 additions & 12 deletions pmoves/configs/command_anchors/_known_gaps.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,24 @@ known_gaps:
- "GHOST_PATH|pmoves/docs/AGENTS/AGNOTE4482PHI.t1.md|pmoves/services/flute-gateway/pipecat/pipelines/voice_agent.py"
- "GHOST_PATH|pmoves/docs/reviews/2026-03-01/tokenism-multi-review.md|.claude/skills/pmoves-integration/tools/nats-monitor.ts"
- "GHOST_PATH|pmoves/docs/reviews/2026-03-01/tokenism-multi-review.md|.github/workflows/ci.yml"
- "GHOST_ROAD|.claude/hooks/damage-control/patterns.yaml|guard offers `make -C pmoves db-migrate` as the correct path; no such target"
- "GHOST_ROAD|.claude/hooks/damage-control/patterns.yaml|guard offers `make -C pmoves targets` as the correct path; no such target"
- "GHOST_TARGET|.claude/BOOTSTRAP.md|health-quick"
- "GHOST_TARGET|.claude/CLAUDE.md|worktree-sitrep-strict"
- "GHOST_TARGET|.claude/PATTERNS.md|worktree-sitrep"
- "GHOST_TARGET|.claude/PATTERNS.md|worktree-sitrep-strict"
- "GHOST_TARGET|.claude/commands/test/smoke.md|test-smoke"
- "GHOST_TARGET|.claude/commands/test/smoke.md|test-smoke-critical"
- "GHOST_TARGET|.claude/commands/test/smoke.md|test-smoke-health"
- "GHOST_TARGET|.claude/commands/test/smoke.md|test-smoke-quick"
- "GHOST_TARGET|.claude/context/testing-strategy.md|discord-smoke"
- "GHOST_TARGET|.claude/context/testing-strategy.md|smoke-creator-pipeline"
- "GHOST_TARGET|.claude/context/testing-strategy.md|smoke-prerequisites"
- "GHOST_TARGET|.claude/context/testing-strategy.md|smoke-tensorzero-observability"
- "GHOST_TARGET|.claude/context/tier-architecture.md|up-nats"
- "GHOST_TARGET|.claude/skills/agentgym-run/SKILL.md|agentgym-results"
- "GHOST_TARGET|.claude/skills/agentgym-run/SKILL.md|agentgym-run"
- "GHOST_TARGET|.claude/skills/agentgym-run/SKILL.md|agentgym-run-lightweight"
- "GHOST_TARGET|.claude/skills/google-workspace/SKILL.md|secrets-"
- "GHOST_TARGET|pmoves/docs/AGENTS/AGNOTE4482DnB.PHI.Orchestra.md|up-"
- "GHOST_TARGET|pmoves/docs/AGENTS/AGNOTE4482PHI.t1.md|agentgym-up"
- "GHOST_TARGET|pmoves/docs/AGENTS/AGNOTE4482PHI.t1.md|submodules"
- "GHOST_TARGET|pmoves/docs/AGENTS/AGNOTE4482PHI.t1.md|tac-check"
Expand All @@ -37,7 +50,6 @@ known_gaps:
- "GHOST_TARGET|pmoves/docs/NEXT_STEPS.md|channel-monitor-discord-drop-smoke"
- "GHOST_TARGET|pmoves/docs/NEXT_STEPS.md|channel-monitor-discord-gate-smoke"
- "GHOST_TARGET|pmoves/docs/NEXT_STEPS.md|demo-content-published"
- "GHOST_TARGET|pmoves/docs/NEXT_STEPS.md|integrations-"
- "GHOST_TARGET|pmoves/docs/NEXT_STEPS.md|jellyfin-verify-single"
- "GHOST_TARGET|pmoves/docs/NEXT_STEPS.md|release"
- "GHOST_TARGET|pmoves/docs/NEXT_STEPS.md|seed-repo-docs"
Expand Down Expand Up @@ -175,7 +187,6 @@ known_gaps:
- "GHOST_TARGET|pmoves/docs/PMOVESCHIT/CHIT_USER_GUIDE.md|secrets-chit-encode"
- "GHOST_TARGET|pmoves/docs/PMOVESCHIT/PMOVES-CONCHexecution_guide.md|smoke-geometry-db"
- "GHOST_TARGET|pmoves/docs/PMOVESCHIT/PMOVES-CONCHexecution_guide.md|smoke-hirag-v1"
- "GHOST_TARGET|pmoves/docs/SECRETS_PIPELINE_REFERENCE.md|up-"
- "GHOST_TARGET|pmoves/docs/TAC/TAC_E2B_SANDBOX.md|build-e2b"
- "GHOST_TARGET|pmoves/docs/TAC/TAC_E2B_SANDBOX.md|e2b-down"
- "GHOST_TARGET|pmoves/docs/TAC/TAC_E2B_SANDBOX.md|e2b-health"
Expand Down Expand Up @@ -214,8 +225,6 @@ known_gaps:
- "GHOST_TARGET|pmoves/docs/handoffs/DARKXSIDE_E2B_DESKTOP_FANOUT_2026-08-06.md|local-build-base-template"
- "GHOST_TARGET|pmoves/docs/handoffs/DARKXSIDE_E2B_DESKTOP_FANOUT_2026-08-06.md|local-infra"
- "GHOST_TARGET|pmoves/docs/handoffs/DARKXSIDE_E2B_DESKTOP_FANOUT_2026-08-06.md|up-e2b-mcp"
- "GHOST_TARGET|pmoves/docs/handoffs/SECRET_ROTATION_RUNBOOK.md|up-"
- "GHOST_TARGET|pmoves/docs/handoffs/compose-overlay-defensive-networks-2026-05-18.md|overlay-up-"
- "GHOST_TARGET|pmoves/docs/handoffs/creator-comfyui-selfhost-config-2026-06-24.md|comfyui-up"
- "GHOST_TARGET|pmoves/docs/handoffs/juicefs-cross-node-storage-blocker-2026-08-04.md|juicefs-mount-pg"
- "GHOST_TARGET|pmoves/docs/integrations/EXTERNAL_IMAGES_BUILD.md|docker-login-ghcr"
Expand Down Expand Up @@ -244,15 +253,13 @@ known_gaps:
- "GHOST_TARGET|pmoves/docs/operations/COMPLETE_BRING_UP_RUNBOOK.md|restore-data"
- "GHOST_TARGET|pmoves/docs/operations/COMPLETE_BRING_UP_RUNBOOK.md|supabase-generate-keys"
- "GHOST_TARGET|pmoves/docs/operations/COMPLETE_BRING_UP_RUNBOOK.md|test-smoke"
- "GHOST_TARGET|pmoves/docs/operations/COMPOSE_LAYERING_RUNBOOK.md|overlay-up-"
- "GHOST_TARGET|pmoves/docs/operations/ENVIRONMENT_POLICY.md|jellyfin-hosts-generate"
- "GHOST_TARGET|pmoves/docs/operations/ENVIRONMENT_POLICY.md|up-jellyfin-single"
- "GHOST_TARGET|pmoves/docs/operations/FORDHAM_ROOM_LAUNCH_PLAN.md|fleet-deploy"
- "GHOST_TARGET|pmoves/docs/operations/FORDHAM_ROOM_LAUNCH_PLAN.md|room-deploy"
- "GHOST_TARGET|pmoves/docs/operations/FORDHAM_ROOM_LAUNCH_PLAN.md|smoke-test-room"
- "GHOST_TARGET|pmoves/docs/operations/FORDHAM_ROOM_LAUNCH_PLAN.md|validate-room-manifest"
- "GHOST_TARGET|pmoves/docs/operations/JUICEFS_PHASE3_CUTOVER.md|juicefs-mirror"
- "GHOST_TARGET|pmoves/docs/operations/JUICEFS_PHASE3_CUTOVER.md|up-"
- "GHOST_TARGET|pmoves/docs/operations/LOCAL_DEV.md|bootstrap"
- "GHOST_TARGET|pmoves/docs/operations/LOCAL_DEV.md|demo-content-published"
- "GHOST_TARGET|pmoves/docs/operations/LOCAL_DEV.md|discord-ping"
Expand Down Expand Up @@ -342,7 +349,6 @@ known_gaps:
- "GHOST_TARGET|pmoves/docs/operations/SMOKETESTS.md|yt-playlist-smoke"
- "GHOST_TARGET|pmoves/docs/operations/SUBMODULE_BUILD_AND_MOUNT_GAP.md|submodules"
- "GHOST_TARGET|pmoves/docs/operations/UPSTREAM_UPDATE_RUNBOOK.md|up-agent-zero"
- "GHOST_TARGET|pmoves/docs/operations/rto-rpo-targets.md|up-"
- "GHOST_TARGET|pmoves/docs/pmoves-model-management-starter/README.md|health-agent-zero"
- "GHOST_TARGET|pmoves/docs/pmoves-model-management-starter/README.md|smoke-archon"
- "GHOST_TARGET|pmoves/docs/pmoves-model-management-starter/README.md|yt-emit-smoke"
Expand Down Expand Up @@ -385,7 +391,8 @@ known_gaps:
- "GHOST_TARGET|pmoves/docs/specs/p7-service-spec-2026-07-20.md|rooms-reload"
- "GHOST_TARGET|pmoves/docs/specs/tts-pterm-gepeto-review-readmes-2026-08-01.md|tac-check"
- "GHOST_TARGET|pmoves/docs/specs/tts-pterm-gepeto-review-readmes-2026-08-01.md|tts-test-all"
- "GHOST_TARGET|pmoves/docs/voice/VOICE_FABRIC_DEPLOYMENT.md|kokoro-"
- "UNKNOWN_HOST|.claude/PATTERNS.md|pmoves-kvm4-2"
- "UNKNOWN_HOST|.claude/commands/fleet/fix-relay.md|pmoves-kvm2"
- "UNKNOWN_HOST|.claude/skills/ci-expedition/SKILL.md|pmoves-kvm4-1"
- "UNKNOWN_HOST|deploy/runbooks/amd-r9700-install-day.md|pmoves-9850x3d-r9700"
- "UNKNOWN_HOST|deploy/runbooks/fresh-install-fleet.md|pmoves-9850x3d-r9700"
Expand All @@ -401,12 +408,14 @@ known_gaps:
- "UNKNOWN_HOST|pmoves/docs/operations/TAILSCALE_EXIT_NODE_RUNBOOK.md|pmoves-kvm4-1"
- "UNKNOWN_HOST|pmoves/docs/pilots/fordham-hill/06-pilot-observation.md|pmoves-kvm4-1"
- "UNKNOWN_HOST|pmoves/docs/research/comprehensive-analysis/05_network_architecture.md|raw IP 192.168.8.1"
- "UNRUNNABLE_DOC|.claude/skills/ci-expedition/SKILL.md|'docker volume rm' in: docker volume rm"
- "UNRUNNABLE_DOC|.claude/PATTERNS.md|'rm -rf /' in: RUN apt-get update && apt-get upgrade -y && rm -rf /var/lib/apt/lists/*"
- "UNRUNNABLE_DOC|.claude/README.md|'DROP DATABASE' in: DROP DATABASE"
- "UNRUNNABLE_DOC|.claude/README.md|'rm -rf /' in: rm -rf /"
- "UNRUNNABLE_DOC|.claude/commands/db/migrate.md|'DROP TABLE' in: DROP TABLE"
- "UNRUNNABLE_DOC|pmoves/docs/NEO4J_INTEGRATION_GUIDE.md|'docker volume rm' in: docker volume rm pmoves_neo4jdata"
- "UNRUNNABLE_DOC|pmoves/docs/NEO4J_SUBMODULE_INTEGRATION_COMPLETE.md|'docker volume rm' in: docker volume rm pmoves_neo4jdata"
- "UNRUNNABLE_DOC|pmoves/docs/NEO4J_SUBMODULE_PROMOTION.md|'docker volume rm' in: docker volume rm pmoves_neo4jdata"
- "UNRUNNABLE_DOC|pmoves/docs/handoffs/DOCKER_FLEET_PROD_AUDIT.md|'docker system prune -a' in: docker system prune -af --volumes"
- "UNRUNNABLE_DOC|pmoves/docs/handoffs/yt-oauth-legacy-stub-drop-2026-08-01.md|'DROP TABLE' in: DROP TABLE"
- "UNRUNNABLE_DOC|pmoves/docs/handoffs/yt-oauth-legacy-stub-drop-2026-08-01.md|'DROP TABLE' in: DROP TABLE IF EXISTS public.yt_oauth_cookies;"
- "UNRUNNABLE_DOC|pmoves/docs/operations/BRING_UP_WSL2.md|'docker system prune -a' in: docker system prune -a --volumes # Clean Docker (careful!)"
- "UNRUNNABLE_DOC|pmoves/docs/operations/EDGE_TRAEFIK_SSO_RUNBOOK.md|'docker volume rm' in: docker volume rm <project>_traefik-acme"
Expand Down
54 changes: 54 additions & 0 deletions pmoves/tools/tests/test_validate_command_anchors.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,57 @@ def test_inline_span_regex_finds_prose_commands():

def test_inline_span_does_not_span_newlines():
assert vca.INLINE_SPAN_RE.findall("`a\nb`") == []


# ── orientation coverage + guard self-check (#2494) ─────────────────


def test_always_loaded_orientation_files_are_scanned():
"""First contact must be verified. .claude/CLAUDE.md tells every agent that
`worktree-sitrep-strict` is authoritative; no such target exists."""
docs = {d.as_posix() for d in vca.live_docs()}
for must in ("CLAUDE.md", "BOOTSTRAP.md", "PATTERNS.md", "AGENTS.md"):
assert any(d.endswith(must) for d in docs), f"{must} not scanned"


def test_learnings_are_excluded():
"""A learnings file records what was true in a past session. Log, not promise."""
assert "learnings" in vca.DOC_EXCLUDE_PARTS


def test_guard_routing_table_is_checked():
"""The ratchet aimed one layer inward: where does a blocked agent get sent?"""
targets = vca.discover_targets()
findings = vca.scan_guard_roads(targets)
assert isinstance(findings, list)
for f in findings:
assert f["kind"] == "GHOST_ROAD"
assert f["scope"] == "guard"


def test_guard_road_placeholders_are_not_flagged():
"""`up-<service>` is a placeholder; the trailing hyphen is the tell."""
assert "up-" in vca.GUARD_ROAD_SKIP


def test_naming_a_road_on_the_line_exempts_it():
"""`.claude/PATTERNS.md` and AGENTS.md carry a blocked-command -> Known Road
table. Those are the cure; flagging them would punish the docs doing it right."""
row = "| `docker volume " + "rm` | `make -C pmoves volume-reset SERVICE=...` | `/deploy:services` |"
assert vca.ROAD_IN_LINE_RE.search(row)


def test_describing_a_block_exempts_it():
for line in ("# Blocks: dangerous ops, etc.",
"| Raw command (blocked) | Known Road |",
"- NEVER do this anywhere"):
assert vca.DESCRIBES_BLOCK_RE.search(line), line


def test_discriminators_carry_no_control_characters():
"""A literal backslash-b in a heredoc escape-interprets to 0x08 and silently
turns the word boundary into a backspace. That happened here once; the regex
then matched nothing and the exemption looked broken rather than absent."""
for rx in (vca.DESCRIBES_BLOCK_RE, vca.ROAD_IN_LINE_RE, vca.GUARD_ROAD_RE):
assert chr(8) not in rx.pattern
assert chr(12) not in rx.pattern
117 changes: 97 additions & 20 deletions pmoves/tools/validate_command_anchors.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
UNKNOWN_HOST a documented `ssh <host>` names a host absent from the fleet
topology (this also catches raw IPs, which must never appear
in committed docs)
GHOST_ROAD the damage-control guard offers a `make` target as the
"correct path" and no such target exists — a blocked agent is
routed into a wall at the moment it is least able to recover
STALE_BASELINE a baselined key that no longer occurs — i.e. it was FIXED.
Also a failure: leaving it in the file re-accepts the same
defect if it returns, which is not "count only goes down".
Expand Down Expand Up @@ -80,9 +83,36 @@
DOC_ROOTS = [
PMOVES / "docs",
REPO_ROOT / ".claude" / "skills",
REPO_ROOT / ".claude" / "context",
REPO_ROOT / ".claude" / "commands",
REPO_ROOT / ".claude" / "agents",
REPO_ROOT / "deploy" / "runbooks",
]
DOC_EXCLUDE_PARTS = {"archive", "_archive", "node_modules", "pmoves_all_in_one_v10"}
# The ALWAYS-LOADED orientation files. An agent entering this repo reads these
# before it reads anything else, so a dead reference here is the highest-cost
# kind there is: first contact is misdirection, the agent improvises, and the
# breakage gets blamed on the model. `.claude/CLAUDE.md` currently tells every
# agent that `make -C pmoves worktree-sitrep-strict` is "authoritative — prefer
# this"; no such target exists.
DOC_FILES = [
REPO_ROOT / ".claude" / "CLAUDE.md",
REPO_ROOT / ".claude" / "BOOTSTRAP.md",
REPO_ROOT / ".claude" / "PATTERNS.md",
REPO_ROOT / ".claude" / "CATALOG.md",
REPO_ROOT / ".claude" / "PINOKIO_LAUNCHER_GUIDE.md",
REPO_ROOT / ".claude" / "README.md",
REPO_ROOT / "CLAUDE.md",
REPO_ROOT / "AGENTS.md",
]
# `.claude/learnings/` is deliberately excluded alongside archive/: a learnings
# file records what was true during a past session. It is a log, not a promise.
DOC_EXCLUDE_PARTS = {"archive", "_archive", "node_modules", "pmoves_all_in_one_v10", "learnings"}

# The damage-control guard's routing table. Every `make -C pmoves <t>` it offers
# as a "correct path" is a promise made at the exact moment an agent is blocked
# and least able to recover — so a dead road here routes a well-behaved agent
# into a wall and then blames it for improvising.
GUARD_PATTERNS = REPO_ROOT / ".claude" / "hooks" / "damage-control" / "patterns.yaml"

MAKEFILES = [PMOVES / "Makefile"]

Expand Down Expand Up @@ -205,7 +235,7 @@ def blocked_patterns() -> List[str]:


def live_docs() -> List[Path]:
out: List[Path] = []
out: List[Path] = [p for p in DOC_FILES if p.is_file()]
for root in DOC_ROOTS:
if not root.is_dir():
continue
Expand Down Expand Up @@ -246,7 +276,9 @@ def scan(targets: Dict[str, Path], scopes: Dict[str, str]) -> List[dict]:
for block in FENCE_RE.findall(text):
cited |= {m.group(1) for m in MAKE_FENCED_RE.finditer(block)}
for t in sorted(cited):
if t in targets:
# `up-<service>` / `overlay-up-<tier>` are placeholders, not targets.
# The trailing hyphen is the tell.
if t in targets or t.endswith("-"):
continue
findings.append({
"kind": "GHOST_TARGET",
Expand All @@ -266,22 +298,30 @@ def scan(targets: Dict[str, Path], scopes: Dict[str, str]) -> List[dict]:
# tool or the hook itself — both must name the patterns to work.
if self_name in rel or "hooks/" in rel:
continue
# Command-shaped lines (fenced blocks, $-prefixed, bare invocations)
# AND inline code spans in prose. A reader copies both.
candidates: List[str] = []
for line in text.splitlines():
stripped = line.strip()
if stripped.startswith(("$", "ssh ", "docker ", "make ", "sudo ")):
candidates.append(stripped)
candidates.extend(m.group(1) for m in INLINE_SPAN_RE.finditer(text))
for cand in candidates:
if pat.lower() in cand.lower():
findings.append({
"kind": "UNRUNNABLE_DOC",
"doc": rel,
"detail": f"{pat!r} in: {cand[:80]}",
"scope": "docker",
})
# Walk LINES, not spans. A span carries only the command; the
# discriminator lives on the line around it. Testing the span alone
# flagged `.claude/PATTERNS.md` and `AGENTS.md`, both of which carry
# a "| Raw command (blocked) | Known Road |" table — the two docs
# doing this exactly right.
for raw in text.splitlines():
line = raw.strip()
if not line:
continue
# Documentation ABOUT the guard, not instruction THROUGH it:
# either it names the Known Road on the same line, or it frames
# the command as blocked/dangerous.
if ROAD_IN_LINE_RE.search(line) or DESCRIBES_BLOCK_RE.search(line):
continue
Comment on lines +313 to +314

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Evaluate exemptions per command candidate

This skips the entire line before extracting command candidates, so a live instruction such as “If deployment is blocked, run docker system prune -a” is ignored merely because it contains the word blocked; likewise, an unrelated make -C pmoves ... anywhere on the line suppresses every blocked command on that line. A newly documented destructive command can therefore pass the ratchet silently. Apply the exemption to the relevant candidate or require explicit anti-pattern/Known-Road framing rather than broad substring matches on the whole line.

Useful? React with 👍 / 👎.

forms = [line] if line.startswith(("$", "ssh ", "docker ", "make ", "sudo ")) else []
forms.extend(m.group(1) for m in INLINE_SPAN_RE.finditer(line))
for cand in forms:
if pat.lower() in cand.lower():
findings.append({
"kind": "UNRUNNABLE_DOC",
"doc": rel,
"detail": f"{pat!r} in: {cand[:80]}",
"scope": "docker",
})

for m in SSH_CITE_RE.finditer(text):
host = m.group(1)
Expand All @@ -303,6 +343,43 @@ def scan(targets: Dict[str, Path], scopes: Dict[str, str]) -> List[dict]:
return findings


# ── The guard's own routing table ───────────────────────────────────


GUARD_ROAD_RE = re.compile(r"make\s+-C\s+pmoves\s+([a-z][a-z0-9-]{2,})")
# Placeholders, not targets: the guard writes `up-<service>` to mean "the up-
# target for whatever service you meant". Flagging those would be noise.
GUARD_ROAD_SKIP = {"up-", "up-service"}
# Used to tell "here is the correct path" apart from "run this".
ROAD_IN_LINE_RE = re.compile(r"make\s+-C\s+pmoves\s+[a-z][a-z0-9-]{2,}|/deploy:|Known Road")
# "Blocks: X, Y" / "Raw command (blocked)" / an anti-pattern bullet is a
# description of the wall, not an instruction to walk into it.
DESCRIBES_BLOCK_RE = re.compile(r"\bBlocks?:|\bblocked\b|\bDangerous Operation\b|\bNEVER\b|\banti-pattern\b|\bmass deletion\b|Raw command|❌", re.I)


def scan_guard_roads(targets: Dict[str, Path]) -> List[dict]:
"""A blocked agent is routed by patterns.yaml. Check where it gets sent.

This is the ratchet aimed one layer inward: the same GHOST_TARGET question,
asked of the thing that answers the question for everyone else.
"""
if not GUARD_PATTERNS.is_file():
return []
rel = GUARD_PATTERNS.relative_to(REPO_ROOT).as_posix()
text = GUARD_PATTERNS.read_text(encoding="utf-8", errors="replace")
findings: List[dict] = []
for t in sorted({m.group(1) for m in GUARD_ROAD_RE.finditer(text)}):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Exclude YAML comments from guard-road discovery

The regex scans the complete YAML text, including comments and pattern values, although only corrective routing messages actually offer roads. For example, the current file already contains a historical comment mentioning make up-z890; if a similar comment mentions a removed make -C pmoves old-target, this workflow reports a new GHOST_ROAD and blocks the PR even though the guard never presents that target to an agent. Restrict discovery to non-comment routing fields such as reason.

Useful? React with 👍 / 👎.

if t in targets or t in GUARD_ROAD_SKIP or t.endswith("-"):
continue
findings.append({
"kind": "GHOST_ROAD",
"doc": rel,
"detail": f"guard offers `make -C pmoves {t}` as the correct path; no such target",
"scope": "guard",
})
return findings


# ── Baseline ────────────────────────────────────────────────────────


Expand Down Expand Up @@ -357,7 +434,7 @@ def main() -> int:

bodies = target_bodies()
scopes = {t: classify_scope(bodies.get(t, [])) for t in targets}
findings = scan(targets, scopes)
findings = scan(targets, scopes) + scan_guard_roads(targets)

if args.write_baseline:
write_baseline(findings)
Expand Down
Loading