Conversation
… files (Aaron 2026-04-26 ask) Aaron 2026-04-26: *"maybe we should hygene for <<<<<<< HEAD and >>>>>>> ======= things like that in our files incase we ever accidently botch a merge, happens to the best of us humans too."* Substantive structural fix preventing a class of bug at the substrate-integrity layer. Why this matters: - Per Otto-339 (anywhere-means-anywhere): conflict markers in committed files would shift weights wrongly when read by AI — the marker tokens are real text that enters cognition substrate - Per Otto-341 (mechanism-not-vigilance): catching botched merges via CI is the right shape; relying on each agent / human / harness to remember to remove markers fails statistically over time - "Happens to the best of us humans too" — Aaron explicitly names this as universal failure mode, not AI-specific Three changes: 1. `tools/hygiene/check-no-conflict-markers.sh` — searches all `git ls-files` tracked files for the three marker patterns at line-start (avoids false positives in inline prose). Reports first violations with file + line. Self-allowlist: this script itself + Otto-341 substrate file (which legitimately documents the markers as part of the merge-resolution discipline). 2. `.github/workflows/gate.yml` `lint-no-conflict-markers` job — wires the check into CI. Fast (2-minute timeout, runs on ubuntu-22.04). 3. Composes with existing `tools/hygiene/check-tick-history-order.sh` pattern — same architectural shape: detection at commit/push time via CI, no relying on agent vigilance. Self-tests: - shellcheck --severity=style: pass - actionlint .github/workflows/gate.yml: pass - check script on current repo state: 0 violations (clean) Substrate composes: Otto-339 (mechanism layer this prevents drift on), Otto-341 (CI gate as discipline-mechanism), Otto-238 (the script's allowlist mechanism is itself retraction-safe — false positives can be added explicitly). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
There was a problem hiding this comment.
Pull request overview
Adds a new hygiene lint that fails CI when tracked files contain unresolved git merge-conflict markers, preventing accidental conflict-marker leakage into committed substrate and CI artifacts.
Changes:
- Add
tools/hygiene/check-no-conflict-markers.shto scan tracked files for conflict-marker lines at column 1. - Wire the new check into
.github/workflows/gate.ymlas a dedicatedlint-no-conflict-markersjob.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| tools/hygiene/check-no-conflict-markers.sh | New bash script to detect unresolved merge-conflict markers in tracked files. |
| .github/workflows/gate.yml | Adds a CI job that runs the new conflict-marker check. |
| # Substrate / research files documenting merge-conflict resolution | ||
| # discipline. They legitimately contain the marker tokens as | ||
| # examples. Allowed because the file body is meta-discussion not | ||
| # accidental marker leakage. | ||
| "memory/feedback_otto_341_lint_suppression_is_self_deception_noise_signal_or_underlying_fix_greenfield_large_refactors_welcome_training_data_human_shortcut_bias_2026_04_26.md" |
There was a problem hiding this comment.
P1: ALLOWLIST contains a hard-coded path to a memory file that isn’t present in the repo. This dead entry makes it unclear what is actually exempt; either remove it until the file exists or replace it with the correct existing path that legitimately contains marker examples.
| # Substrate / research files documenting merge-conflict resolution | |
| # discipline. They legitimately contain the marker tokens as | |
| # examples. Allowed because the file body is meta-discussion not | |
| # accidental marker leakage. | |
| "memory/feedback_otto_341_lint_suppression_is_self_deception_noise_signal_or_underlying_fix_greenfield_large_refactors_welcome_training_data_human_shortcut_bias_2026_04_26.md" |
| while IFS= read -r file; do | ||
| if is_allowed "$file"; then | ||
| continue | ||
| fi | ||
| if [[ ! -f "$file" ]]; then | ||
| continue | ||
| fi | ||
| # Use grep -E with line numbers; binary files quietly skipped. | ||
| if hits=$(grep -nE "$PATTERN" "$file" 2>/dev/null); then | ||
| while IFS= read -r line; do | ||
| violations=$((violations + 1)) | ||
| if [[ -z "$first_hit" ]]; then | ||
| first_hit="$file:$line" | ||
| fi | ||
| echo "VIOLATION: $file:$line" >&2 | ||
| done <<< "$hits" | ||
| fi | ||
| done < <(git ls-files) | ||
|
|
There was a problem hiding this comment.
P1: This runs grep separately for every tracked file, which will also scan large/binary tracked artifacts (e.g., the PDFs under docs/). Consider using git grep with binary ignored or otherwise skipping known-binary/large files so the check stays reliably fast under the 2-minute CI timeout.
| while IFS= read -r file; do | |
| if is_allowed "$file"; then | |
| continue | |
| fi | |
| if [[ ! -f "$file" ]]; then | |
| continue | |
| fi | |
| # Use grep -E with line numbers; binary files quietly skipped. | |
| if hits=$(grep -nE "$PATTERN" "$file" 2>/dev/null); then | |
| while IFS= read -r line; do | |
| violations=$((violations + 1)) | |
| if [[ -z "$first_hit" ]]; then | |
| first_hit="$file:$line" | |
| fi | |
| echo "VIOLATION: $file:$line" >&2 | |
| done <<< "$hits" | |
| fi | |
| done < <(git ls-files) | |
| git_grep_args=(git grep -nI -E "$PATTERN" -- .) | |
| for allowed in "${ALLOWLIST[@]}"; do | |
| git_grep_args+=(":(exclude)$allowed") | |
| done | |
| git_grep_args+=(":(exclude)references/**") | |
| # Search tracked text files in one pass; `-I` skips binary content. | |
| if hits="$("${git_grep_args[@]}" 2>/dev/null)"; then | |
| while IFS= read -r hit; do | |
| violations=$((violations + 1)) | |
| if [[ -z "$first_hit" ]]; then | |
| first_hit="$hit" | |
| fi | |
| echo "VIOLATION: $hit" >&2 | |
| done <<< "$hits" | |
| fi |
| # What this does NOT check: | ||
| # - Other tools' specific conflict markers (Mercurial, etc.) — | ||
| # repo is git, only git markers matter | ||
| # - File content within blocks — only the markers at line-start | ||
| # - Untracked files — only committed/staged matters |
There was a problem hiding this comment.
P2: The doc says “Untracked files — only committed/staged matters”, but the implementation reads the working tree content of git ls-files paths (so it won’t necessarily reflect what’s staged in the index for partial stages). Suggest rewording to “tracked working tree files” or explicitly scanning the index if staged-only detection is intended.
| # any committed file contains git merge-conflict markers | ||
| # (`<<<<<<<`, `=======`, `>>>>>>>`). | ||
| # | ||
| # Why this exists (Aaron 2026-04-26): |
There was a problem hiding this comment.
P1 (repo convention): This comment adds direct contributor-name attribution on a current-state code surface. docs/AGENT-BEST-PRACTICES.md requires role-refs in code/workflows and reserves names for the closed list of history surfaces; please rewrite these references accordingly.
| # Why this exists (Aaron 2026-04-26): | |
| # Why this exists (human contributor note, 2026-04-26): |
| # (`<<<<<<<`, `=======`, `>>>>>>>`). Aaron 2026-04-26 ask: | ||
| # *"maybe we should hygene for <<<<<<< HEAD and >>>>>>> ======= things | ||
| # like that in our files incase we ever accidently botch a merge, | ||
| # happens to the best of us humans too."* |
There was a problem hiding this comment.
P1 (repo convention): This workflow comment introduces direct contributor-name attribution (“Aaron …”) on a current-state CI surface. Per docs/AGENT-BEST-PRACTICES.md “No name attribution…”, please rewrite to role-refs (e.g., “human maintainer request (2026-04-26)”) and keep names to the enumerated history surfaces only.
…ygiene tools (Aaron 2026-04-26 ask) Aaron 2026-04-26: *"we need to move the typescript migration of our scripts to higher priority so you will stop trying to write python and shell code lol ... our post install code"* + *"pre install code still has to go to the user where they live shell and windows powershell"* The recurring `python3 << 'PYEOF'` heredocs and bash scripts I keep writing in `tools/hygiene/` are POST-install tools that belong in TypeScript per the migration plan. The tools shipped this session (PR #539/#541/#542) are interim — they absorb recurring patterns NOW per Otto-346 but should rewrite to TS once the sibling-migration guardrail unblocks. Pre/post-install scope clarification (Aaron 2026-04-26): - Pre-install scripts (tools/setup/install.sh, devcontainer bootstrap): MUST stay shell + PowerShell — that's what's available before Bun installs - Post-install scripts (tools/hygiene/, tools/git/, dev-time tooling): TARGET = TypeScript via Bun The distinction is structural. docs/POST-SETUP-SCRIPT-STACK.md already encodes the rationale; this priority bump operationalizes it. Two changes in this commit: 1. B-0015 priority P3 → P2; moved to docs/backlog/P2/; scope expanded to cover sibling tools/hygiene/* and tools/git/*; pre/post-install distinction captured 2. B-0027 (just-filed) updated with implementation-target note: TypeScript not Python; wait for sibling-migration guardrail or use exception-label pattern Composes with: docs/POST-SETUP-SCRIPT-STACK.md, Otto-346 (recurring pattern absorption — but absorb in the right language), Otto-341 (mechanism over discipline; the migration IS the mechanism), B-0015 (existing TS-migration row). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
…6 follow-up after honest-relapse-catch Aaron 2026-04-26: *"hmmm"* — caught me using inline `python3 << 'PYEOF'` heredoc to truncate a corrupted tick-history row IMMEDIATELY AFTER shipping Otto-346 principle and two tools embodying it. Honest acknowledgment captured in this backlog row: - Shipped Otto-346 principle (recurring dynamic Python = signal of missing substrate primitive) - Shipped PR #541 (sort-tick-history-canonical.py) and PR #542 (fix-markdown-md032-md026.py) absorbing recurring patterns - Then immediately wrote inline Python for the next problem - Relapse — the discipline needs per-instance vigilance, not one-time naming The owed work: extract tools/hygiene/fix-markdown-table-cell-count.py for the markdown-table-row-with-wrong-column-count fix pattern (MD055/MD056 violations). Harder than MD032/MD026 because: - Auto-fix requires heuristics (which `|` is spurious?) - Risk of removing legitimate content - Mitigation: default-dry-run, --auto flag for unambiguous cases, log every change Captures the meta-discipline observation: Otto-346 application requires per-instance vigilance. Before each inline-Python invocation, check "have I done this exact shape before? could I plausibly do it again? is it mechanical?" — if 2-of-3 yes, extract first then apply. Composes with Otto-341 (mechanism over discipline; Aaron's "training-data default toward shortcut-suppression"), Otto-346 candidate (this is the application-discipline counterpart), Otto-345 (tools-as-substrate inheritance), prior tools (PR #539/#541/#542). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
…ygiene tools (Aaron 2026-04-26 ask) Aaron 2026-04-26: *"we need to move the typescript migration of our scripts to higher priority so you will stop trying to write python and shell code lol ... our post install code"* + *"pre install code still has to go to the user where they live shell and windows powershell"* The recurring `python3 << 'PYEOF'` heredocs and bash scripts I keep writing in `tools/hygiene/` are POST-install tools that belong in TypeScript per the migration plan. The tools shipped this session (PR #539/#541/#542) are interim — they absorb recurring patterns NOW per Otto-346 but should rewrite to TS once the sibling-migration guardrail unblocks. Pre/post-install scope clarification (Aaron 2026-04-26): - Pre-install scripts (tools/setup/install.sh, devcontainer bootstrap): MUST stay shell + PowerShell — that's what's available before Bun installs - Post-install scripts (tools/hygiene/, tools/git/, dev-time tooling): TARGET = TypeScript via Bun The distinction is structural. docs/POST-SETUP-SCRIPT-STACK.md already encodes the rationale; this priority bump operationalizes it. Two changes in this commit: 1. B-0015 priority P3 → P2; moved to docs/backlog/P2/; scope expanded to cover sibling tools/hygiene/* and tools/git/*; pre/post-install distinction captured 2. B-0027 (just-filed) updated with implementation-target note: TypeScript not Python; wait for sibling-migration guardrail or use exception-label pattern Composes with: docs/POST-SETUP-SCRIPT-STACK.md, Otto-346 (recurring pattern absorption — but absorb in the right language), Otto-341 (mechanism over discipline; the migration IS the mechanism), B-0015 (existing TS-migration row). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
…6 follow-up after honest-relapse-catch Aaron 2026-04-26: *"hmmm"* — caught me using inline `python3 << 'PYEOF'` heredoc to truncate a corrupted tick-history row IMMEDIATELY AFTER shipping Otto-346 principle and two tools embodying it. Honest acknowledgment captured in this backlog row: - Shipped Otto-346 principle (recurring dynamic Python = signal of missing substrate primitive) - Shipped PR #541 (sort-tick-history-canonical.py) and PR #542 (fix-markdown-md032-md026.py) absorbing recurring patterns - Then immediately wrote inline Python for the next problem - Relapse — the discipline needs per-instance vigilance, not one-time naming The owed work: extract tools/hygiene/fix-markdown-table-cell-count.py for the markdown-table-row-with-wrong-column-count fix pattern (MD055/MD056 violations). Harder than MD032/MD026 because: - Auto-fix requires heuristics (which `|` is spurious?) - Risk of removing legitimate content - Mitigation: default-dry-run, --auto flag for unambiguous cases, log every change Captures the meta-discipline observation: Otto-346 application requires per-instance vigilance. Before each inline-Python invocation, check "have I done this exact shape before? could I plausibly do it again? is it mechanical?" — if 2-of-3 yes, extract first then apply. Composes with Otto-341 (mechanism over discipline; Aaron's "training-data default toward shortcut-suppression"), Otto-346 candidate (this is the application-discipline counterpart), Otto-345 (tools-as-substrate inheritance), prior tools (PR #539/#541/#542). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
…ygiene tools (Aaron 2026-04-26 ask) Aaron 2026-04-26: *"we need to move the typescript migration of our scripts to higher priority so you will stop trying to write python and shell code lol ... our post install code"* + *"pre install code still has to go to the user where they live shell and windows powershell"* The recurring `python3 << 'PYEOF'` heredocs and bash scripts I keep writing in `tools/hygiene/` are POST-install tools that belong in TypeScript per the migration plan. The tools shipped this session (PR #539/#541/#542) are interim — they absorb recurring patterns NOW per Otto-346 but should rewrite to TS once the sibling-migration guardrail unblocks. Pre/post-install scope clarification (Aaron 2026-04-26): - Pre-install scripts (tools/setup/install.sh, devcontainer bootstrap): MUST stay shell + PowerShell — that's what's available before Bun installs - Post-install scripts (tools/hygiene/, tools/git/, dev-time tooling): TARGET = TypeScript via Bun The distinction is structural. docs/POST-SETUP-SCRIPT-STACK.md already encodes the rationale; this priority bump operationalizes it. Two changes in this commit: 1. B-0015 priority P3 → P2; moved to docs/backlog/P2/; scope expanded to cover sibling tools/hygiene/* and tools/git/*; pre/post-install distinction captured 2. B-0027 (just-filed) updated with implementation-target note: TypeScript not Python; wait for sibling-migration guardrail or use exception-label pattern Composes with: docs/POST-SETUP-SCRIPT-STACK.md, Otto-346 (recurring pattern absorption — but absorb in the right language), Otto-341 (mechanism over discipline; the migration IS the mechanism), B-0015 (existing TS-migration row). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
…ation P3→P2 priority bump (Aaron 2026-04-26) (#543) * backlog(B-0027): extract markdown-table-cell-count fix tool — Otto-346 follow-up after honest-relapse-catch Aaron 2026-04-26: *"hmmm"* — caught me using inline `python3 << 'PYEOF'` heredoc to truncate a corrupted tick-history row IMMEDIATELY AFTER shipping Otto-346 principle and two tools embodying it. Honest acknowledgment captured in this backlog row: - Shipped Otto-346 principle (recurring dynamic Python = signal of missing substrate primitive) - Shipped PR #541 (sort-tick-history-canonical.py) and PR #542 (fix-markdown-md032-md026.py) absorbing recurring patterns - Then immediately wrote inline Python for the next problem - Relapse — the discipline needs per-instance vigilance, not one-time naming The owed work: extract tools/hygiene/fix-markdown-table-cell-count.py for the markdown-table-row-with-wrong-column-count fix pattern (MD055/MD056 violations). Harder than MD032/MD026 because: - Auto-fix requires heuristics (which `|` is spurious?) - Risk of removing legitimate content - Mitigation: default-dry-run, --auto flag for unambiguous cases, log every change Captures the meta-discipline observation: Otto-346 application requires per-instance vigilance. Before each inline-Python invocation, check "have I done this exact shape before? could I plausibly do it again? is it mechanical?" — if 2-of-3 yes, extract first then apply. Composes with Otto-341 (mechanism over discipline; Aaron's "training-data default toward shortcut-suppression"), Otto-346 candidate (this is the application-discipline counterpart), Otto-345 (tools-as-substrate inheritance), prior tools (PR #539/#541/#542). 🤖 Generated with [Claude Code](https://claude.com/claude-code) * backlog(B-0015): bump P3 → P2 + scope expansion to all post-install hygiene tools (Aaron 2026-04-26 ask) Aaron 2026-04-26: *"we need to move the typescript migration of our scripts to higher priority so you will stop trying to write python and shell code lol ... our post install code"* + *"pre install code still has to go to the user where they live shell and windows powershell"* The recurring `python3 << 'PYEOF'` heredocs and bash scripts I keep writing in `tools/hygiene/` are POST-install tools that belong in TypeScript per the migration plan. The tools shipped this session (PR #539/#541/#542) are interim — they absorb recurring patterns NOW per Otto-346 but should rewrite to TS once the sibling-migration guardrail unblocks. Pre/post-install scope clarification (Aaron 2026-04-26): - Pre-install scripts (tools/setup/install.sh, devcontainer bootstrap): MUST stay shell + PowerShell — that's what's available before Bun installs - Post-install scripts (tools/hygiene/, tools/git/, dev-time tooling): TARGET = TypeScript via Bun The distinction is structural. docs/POST-SETUP-SCRIPT-STACK.md already encodes the rationale; this priority bump operationalizes it. Two changes in this commit: 1. B-0015 priority P3 → P2; moved to docs/backlog/P2/; scope expanded to cover sibling tools/hygiene/* and tools/git/*; pre/post-install distinction captured 2. B-0027 (just-filed) updated with implementation-target note: TypeScript not Python; wait for sibling-migration guardrail or use exception-label pattern Composes with: docs/POST-SETUP-SCRIPT-STACK.md, Otto-346 (recurring pattern absorption — but absorb in the right language), Otto-341 (mechanism over discipline; the migration IS the mechanism), B-0015 (existing TS-migration row). 🤖 Generated with [Claude Code](https://claude.com/claude-code) * fix(B-0027): self-test command — escape regex \| OR use -S for string-match (Copilot P1 finding) Copilot P1 finding: 'git log --all -G "|: "' treats | as regex alternation (matches everything containing empty pattern OR ': '), not the literal pipe-colon-space pattern intended. Two fixes offered: 1. Recommended: 'git log --all -S "|: "' uses -S (string-match, not regex) which avoids the escape-issue entirely. Documented as the recommended form for literal-string searches. 2. Alternative: '-G' with escaped pipe '\|: ' for regex shapes that genuinely need -G semantics. Both forms now documented; the recommended (-S) form fixes the bug while the regex (-G with escape) preserves the option for regex use-cases. * fix(B-0027): MD032 blanks-around-lists at lines 22+33+40 (lint) * fix(B-0027): MD038 spaces-in-code-spans — rephrase to avoid backtick-delimited substrings with leading/trailing spaces (lint) Codex/Copilot CI flagged MD038 errors at line 50: `: ` and `+ ` inline code spans had leading-space (the unescaped space inside the backticks). Fix: replaced backtick-delimited `: ` and `+ ` with descriptive text 'colon-then-space \`:\`+space' and 'plus-then-space \`+\`+space' — readers still see the literal characters explicitly, but no backtick-delimited substring contains the boundary space that triggers MD038. Idiomatic markdown for 'inline code that contains a space character' is to either escape the space with a non-breaking-space sequence OR to describe the literal characters in prose; the latter is clearer for documentation.
Summary
Aaron 2026-04-26: "maybe we should hygene for <<<<<<< HEAD and >>>>>>> ======= things like that in our files incase we ever accidently botch a merge, happens to the best of us humans too."
Substantive structural fix preventing a class of bug at the substrate-integrity layer.
Why this matters
Three changes
tools/hygiene/check-no-conflict-markers.sh— searches allgit ls-filestracked files for the three marker patterns at line-start (avoids false positives in inline prose). Reports first violations with file + line. Self-allowlist: this script itself + Otto-341 substrate file (which legitimately documents the markers as part of merge-resolution discipline)..github/workflows/gate.ymllint-no-conflict-markersjob — wires the check into CI. Fast (2-minute timeout).Composes with existing
tools/hygiene/check-tick-history-order.shpattern — same architectural shape: detection at commit/push time via CI, no relying on agent vigilance.What this DOES NOT do
Self-tests
shellcheck --severity=style: passactionlint .github/workflows/gate.yml: passComposes with
Test plan
lint-no-conflict-markersjob runs and passes on this PR🤖 Generated with Claude Code