diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e71670d..2fa58f6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -246,3 +246,10 @@ jobs: - uses: actions/checkout@v7 - name: Check version consistency run: ./scripts/check-version.sh + planning-hygiene: + name: Planning Hygiene + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - name: Check no tracked planning-plane references + run: bash scripts/check-planning-hygiene.sh diff --git a/.gitignore b/.gitignore index 2f2d7eb..7f6d7b1 100644 --- a/.gitignore +++ b/.gitignore @@ -14,10 +14,11 @@ target/ # Cargo.lock # ----------------------------------------------------------------------------- -# Planning and Local Configuration (NEVER COMMIT) +# Planning and Local Configuration (kept out of the tree) # ----------------------------------------------------------------------------- -# Session planning documents - always local, never committed -.plans/ +# Session planning documents are kept out of the working tree on purpose; this +# entry is a convenience filter, not a security boundary. +/.plans/ # Local agent instructions - per-session tactical guidance AGENTS.local.md diff --git a/.goneat/assess.yaml b/.goneat/assess.yaml index 67ea705..a0e2d96 100644 --- a/.goneat/assess.yaml +++ b/.goneat/assess.yaml @@ -56,7 +56,6 @@ lint: ignore: - "**/target/**" - "**/.git/**" - - ".plans/**" # GitHub Actions linting github_actions: actionlint: diff --git a/AGENTS.md b/AGENTS.md index 7bab00e..4cc30c8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,7 +2,7 @@ ## Read First -1. Check `AGENTS.local.md` if it exists (gitignored, tactical session guidance) +1. **Check for local guidance** if present (e.g. `AGENTS.local.md`) — machine-specific instructions and tactical session overrides. Local context is kept out of the working tree on purpose; a `.gitignore` entry is a convenience filter, not a security boundary. 2. Read `MAINTAINERS.md` for contacts and governance 3. Review this document for operational protocols 4. Understand: this is a **Rust library with cross-language bindings** — correctness is paramount @@ -156,8 +156,8 @@ FFI boundary changes require extra scrutiny: - Change FFI contracts without review - Use `unsafe` without clear justification and review - Assume platform behavior without testing -- **EVER commit anything from `.plans/`** - this directory is gitignored and MUST stay local -- Commit `AGENTS.local.md` (gitignored - session-specific guidance) +- **Add local planning paths, board names, or planning IDs to tracked content** — planning artifacts live in a private, maintainer-managed system kept outside the repository tree; a `.gitignore` entry is a convenience filter, not a security boundary +- Commit `AGENTS.local.md` or other local guidance — it is kept out of the tree as defense-in-depth, not because a `.gitignore` entry makes it safe ## Critical Rules @@ -281,6 +281,15 @@ FFI Architect — Bindings design and cross-language integration. - **FulmenHQ patterns**: https://github.com/fulmenhq/crucible - **Local decisions**: `docs/decisions/` (ADR, DDR, SDR) +## Planning Artifacts + +- Feature briefs, task boards, and operational notes live in a private, + maintainer-managed planning system kept **outside** this repository tree — + not in-tree behind `.gitignore`, which is a convenience filter, not a security + boundary. This conforms to the [3 Leaps OSS Sensitive Local Data Policy](https://github.com/3leaps/oss-policies/blob/main/SENSITIVE-LOCAL-DATA.md). +- Do not add local planning paths, board names, or planning IDs to tracked repo + content (code, docs, comments, commit messages, PR text). + ## Contact - **Lead maintainer**: See MAINTAINERS.md diff --git a/scripts/check-planning-hygiene.sh b/scripts/check-planning-hygiene.sh new file mode 100755 index 0000000..06a50c7 --- /dev/null +++ b/scripts/check-planning-hygiene.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# +# Planning-reference hygiene guard. +# +# Planning artifacts (briefs, task boards, IDs, memos) live in a private, +# maintainer-managed system OUTSIDE this repository tree — see AGENTS.md → +# "Planning Artifacts". A `.gitignore` entry is a convenience filter, not a +# security boundary, so this guard asserts that no *tracked* file reintroduces +# a reference to the private planning plane. +# +# This file stays self-sterile: the guarded tokens are spelled with character +# classes ([.], [/], [-]) so the script never reintroduces the very substrings +# it guards. The single permitted tracked reference is the anchored ignore +# entry (an exact allowlist below), which names the retired directory as +# defense-in-depth; every other guarded hit must fail. +set -euo pipefail + +pattern='[.]plans|planning[/]|brief[-]ipcp|IPCP[-]TASK|IPCP[-][0-9]' +permitted='^\.gitignore:[0-9]+:/[.]plans/$' + +# Content: allow only the single anchored ignore entry; reject every other +# guarded hit in any tracked file. +hits="$(git grep -nE "$pattern" || true)" +if [ -n "$hits" ]; then + bad="$(printf '%s\n' "$hits" | grep -vE "$permitted" || true)" + if [ -n "$bad" ]; then + printf '%s\n' "$bad" + echo "::error::A tracked file references the private planning plane. Keep" \ + "planning artifacts out of the repository tree (see AGENTS.md →" \ + "Planning Artifacts)." + exit 1 + fi +fi +perm_count="$(printf '%s\n' "$hits" | grep -cE "$permitted" || true)" +if [ "$perm_count" -ne 1 ]; then + echo "::error::Expected exactly one permitted ignore entry, found $perm_count." + exit 1 +fi + +# Paths: no tracked path may reintroduce a planning-plane directory segment +# (a content grep alone cannot see a sterile file added under a leaky path). +tracked_paths="$(git ls-files | grep -E "$pattern" || true)" +if [ -n "$tracked_paths" ]; then + printf '%s\n' "$tracked_paths" + echo "::error::A tracked file path references the private planning plane." + exit 1 +fi + +echo "OK: no private planning-plane references in tracked files."