Skip to content
Open
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
21 changes: 21 additions & 0 deletions .context/LEARNINGS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Learnings

<!-- INDEX:START -->
| Date | Learning |
|----|--------|
| 2026-08-19 | hack/ lint scripts assumed GNU bash 4+ and GNU grep; stock macOS breaks all three ways |
| 2026-07-25 | Using the proprietary sibling repo as design evidence leaks its internals into tracked files |
| 2026-07-25 | Skill and doc examples of a serialized structure must round-trip through the real parser |
| 2026-07-25 | A guard derived from a capability accessor silently lifts when the accessor is extended |
| 2026-07-19 | The disclosure parser is a deliberately dumb line-scanner (skips <!-- --> comments, not code fences) |
| 2026-07-19 | Measurement gates surface a real bug in every disclosure milestone |
<!-- INDEX:END -->

<!--
UPDATE WHEN:
- Discover a gotcha, bug, or unexpected behavior
Expand All @@ -15,6 +26,16 @@ DO NOT UPDATE FOR:
-->


## [2026-08-19-211547] hack/ lint scripts assumed GNU bash 4+ and GNU grep; stock macOS breaks all three ways

**Context**: make audit had never passed on this stock macOS machine: lint-drift.sh died on empty-array expansion under set -u (bash 3.2), lint-docstrings.sh died on an apostrophe in a comment inside $( ) (bash 3.2 substitution parser), then false-positived MISSING_FIELDS on every exported struct because BSD grep silently lacks -P/PCRE.

**Lesson**: Three distinct GNU-isms bite on macOS defaults: empty-array ${arr[@]} under set -u (bash 3.2), apostrophes in comments inside command substitution (bash 3.2), and grep -P (BSD grep). The -P failure is the worst: it fails per-line inside || true pipelines, so counts come back empty and checks false-positive rather than erroring.

**Application**: In hack/ scripts use ${arr[@]+"${arr[@]}"} guards, no apostrophes in comments inside $( ), and POSIX patterns ($'\t' + [[:space:]]) instead of grep -P. Per specs/hack-script-portability.md; long-term fix is the Go rewrite tracked in TASKS.

---

## [2026-07-25-124457] Using the proprietary sibling repo as design evidence leaks its internals into tracked files

**Context**: While deciding the pd-m4 add-path shape, I read the sibling repo's convention file to settle the question, then quoted its guide text and attributed the decision to it in a tracked plan file. An unrelated build warning prompted the sweep that caught it.
Expand Down
8 changes: 5 additions & 3 deletions hack/lint-docstrings.sh
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ find internal/ cmd/ -name '*.go' ! -name '*_test.go' ! -name 'doc.go' | sort | w
continue
fi
returnpart=$(echo "$rest" | sed 's/^func [A-Za-z0-9_]*([^)]*) //')
# Guard: if sed didn't match (returnpart unchanged), skip
# Guard: if sed did not match (returnpart unchanged), skip.
# (No apostrophes in comments inside $(...): bash 3.2 mis-parses them.)
if [ "$returnpart" = "$rest" ]; then
continue
fi
Expand All @@ -105,8 +106,9 @@ find internal/ cmd/ -name '*.go' ! -name '*_test.go' ! -name 'doc.go' | sort | w
if [ -z "$closing" ]; then
continue
fi
# $'…' expands \t to a real tab: BSD grep (macOS) has no -P/PCRE.
fieldcount=$(sed -n "$((lineno+1)),$((closing-1))p" "$file" \
| grep -cP '^\t[A-Z]' || true)
| grep -c $'^\t[A-Z]' || true)
if [ "$fieldcount" -lt 2 ]; then
continue
fi
Expand Down Expand Up @@ -138,7 +140,7 @@ find internal/ cmd/ -name '*.go' ! -name '*_test.go' ! -name 'doc.go' | sort | w
# Accept inline field comments as alternative to Fields: section.
# Count fields with a preceding or same-line comment.
inlinecount=$(sed -n "$((lineno+1)),$((closing-1))p" "$file" \
| grep -cP '^\t// [A-Z]|^\t[A-Z].*//\s' || true)
| grep -cE $'^\t// [A-Z]|^\t[A-Z].*//[[:space:]]' || true)
if [ "$inlinecount" -ge "$fieldcount" ]; then
continue
fi
Expand Down
5 changes: 4 additions & 1 deletion hack/lint-drift.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ drift_grep() {
for ex in "$@"; do
exclude_args+=(--exclude="$ex")
done
grep -rn --include='*.go' --exclude='*_test.go' "${exclude_args[@]}" \
# ${arr[@]+...} guard: bash 3.2 (macOS default) treats an empty
# array expansion as unbound under `set -u`.
grep -rn --include='*.go' --exclude='*_test.go' \
${exclude_args[@]+"${exclude_args[@]}"} \
-E "$pattern" internal/ 2>/dev/null || true
}

Expand Down
47 changes: 47 additions & 0 deletions specs/hack-script-portability.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Spec: hack/ Script Portability — macOS Default Toolchain

## Problem

`make audit` fails before running a single real check on a stock
macOS machine (bash 3.2, BSD grep). The lint scripts in `hack/`
assumed GNU bash ≥ 4 and GNU grep:

1. `hack/lint-drift.sh` — `"${exclude_args[@]}"` on an empty array
aborts under `set -u` on bash 3.2 ("unbound variable"; bash 4.4+
treats it as empty).
2. `hack/lint-docstrings.sh` — an apostrophe in a comment inside a
`$( … )` command substitution breaks bash 3.2's substitution
parser ("unexpected EOF while looking for matching `'`").
3. `hack/lint-docstrings.sh` — `grep -cP` (PCRE) is GNU-only; BSD
grep has no `-P`, so every field count came back empty and every
exported struct false-positived as `MISSING_FIELDS`.

These failures predate any feature work and mask real findings: the
audit gate cannot run at all on contributor machines with the default
macOS toolchain.

## Fix

Minimal, behavior-preserving substitutions that run identically under
GNU and BSD toolchains:

- `${arr[@]+"${arr[@]}"}` guard for empty-array expansion.
- Reword the offending comment (no apostrophes inside `$( … )`).
- Replace `-P` patterns with POSIX equivalents: `$'^\t…'` (real tab
via ANSI-C quoting, supported by bash 3.2) and `[[:space:]]` for
`\s`.

## Non-Goals

- Rewriting the lint scripts in Go (tracked in TASKS.md: "Replace
hack/lint-drift.sh with AST-based Go tests"; "Rewrite lint-style
scripts in Go as ctxctl subcommands"). This spec only unblocks the
gate until that lands.
- A full portability audit of every script under `hack/` (only the
`make audit` chain is in scope).

## Verification

`make audit` completes on macOS (bash 3.2.57, BSD grep) with the same
findings as on a GNU toolchain: `lint-drift: clean`, no docstring
false-positives, all checks green.
Loading