diff --git a/.gitignore b/.gitignore index e458ed5..fc44c28 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .worktrees/ +.tracking/ diff --git a/README.md b/README.md index 5598dbf..046f35e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,326 @@ -# ROS -# ROS +# ROS Program Workspace + +Professional operating manual for the ROS parent repository and its managed multi-repo research program. + +## 1. What This Repository Is + +This parent repository orchestrates the full program workspace, shared workflows, and submodule pointers for domain repositories under `program/`. + +It is the control plane for: +- repository topology +- shared workflows and guidance +- reproducible workspace bootstrap +- integration flow across subrepos + +It is **not** where all implementation code lives; implementation is distributed in submodules. + +## 2. Repository Topology + +### 2.1 Parent Repo Responsibilities +- root-level orchestration +- docs/specs/plans +- shared scripts and operational guidance +- submodule pinning for exact child-repo states + +### 2.2 Submodule Repositories (`program/`) +- `research-program-index`: orchestration roadmap, governance, release schema +- `benchmark-core`: benchmark contracts and baseline verification tests +- `localization-tracking`: localization and tracking domain work +- `uav-mpc-geometric-control`: UAV MPC and geometric control domain work +- `sensorless-estimation-suite`: sensorless estimation workflows +- `swarm-path-planning-bees`: swarm/path-planning workflows +- `ik-uncertainty-learning`: inverse-kinematics uncertainty workflows +- `digital-twin-pipeline`: digital twin integration pipeline +- `docs-multilingual-continuity`: documentation continuity assets +- `repro-packages`: reproducibility packaging artifacts + +## 3. Quick Start (10-Minute Path) + +Run from a clean terminal: + +```bash +git clone --recurse-submodules https://github.com/molhamfetnah/ROS.git +cd ROS +git submodule sync --recursive +git submodule update --init --recursive +``` + +Validation checkpoint: + +```bash +git submodule status -- program/* +``` + +Expected: 10 `program/*` entries listed with commit SHAs. + +## 4. Full Setup (First-Time Environment) + +### 4.1 Ensure parent repo is current + +```bash +cd || exit 1 +git rev-parse --show-toplevel +git rev-parse --is-inside-work-tree >/dev/null +origin_url="$(git remote get-url origin 2>/dev/null || true)" +printf '%s\n' "$origin_url" | grep -Eq '^(git@github\.com:molhamfetnah/ROS(\.git)?|https://github\.com/molhamfetnah/ROS(\.git)?)$' || { echo "Error: origin must be molhamfetnah/ROS (SSH or HTTPS)." >&2; exit 1; } +git checkout main +git pull --ff-only +``` + +Expected: +- `git rev-parse --show-toplevel` prints the absolute path to your clone root. +- Worktree and exact canonical `origin` identity checks pass before branch mutation commands run. +- `Already up to date.` or fast-forward output. + +### 4.2 Initialize submodules + +```bash +git submodule sync --recursive +git submodule update --init --recursive +``` + +Expected: each `program/*` submodule checked out with no errors. + +### 4.3 Verify workspace health + +```bash +git status --short --branch +git submodule status -- program/* +``` + +Expected: +- parent branch shown (normally `## main`) +- no unexpected dirty state +- 10 `program/*` submodule SHAs listed + +### 4.4 GitHub CLI auth (for push/PR operations) + +```bash +gh auth status || gh auth login +gh repo view molhamfetnah/ROS --json defaultBranchRef +``` + +Expected: authenticated account and default branch details returned. + +## 5. Day-2 Operations + +### 5.1 Sync workspace before work + +```bash +cd || exit 1 +git rev-parse --show-toplevel +git rev-parse --is-inside-work-tree >/dev/null +origin_url="$(git remote get-url origin 2>/dev/null || true)" +printf '%s\n' "$origin_url" | grep -Eq '^(git@github\.com:molhamfetnah/ROS(\.git)?|https://github\.com/molhamfetnah/ROS(\.git)?)$' || { echo "Error: origin must be molhamfetnah/ROS (SSH or HTTPS)." >&2; exit 1; } +git checkout main +git pull --ff-only +git submodule sync --recursive +git submodule update --init --recursive +``` + +### 5.2 Worktree-based feature workflow + +```bash +cd || exit 1 +git rev-parse --show-toplevel +git rev-parse --is-inside-work-tree >/dev/null +origin_url="$(git remote get-url origin 2>/dev/null || true)" +printf '%s\n' "$origin_url" | grep -Eq '^(git@github\.com:molhamfetnah/ROS(\.git)?|https://github\.com/molhamfetnah/ROS(\.git)?)$' || { echo "Error: origin must be molhamfetnah/ROS (SSH or HTTPS)." >&2; exit 1; } +git checkout main || exit 1 +git pull --ff-only || exit 1 +git worktree add -b .worktrees/ +cd .worktrees/ +``` + +### 5.3 Tracking-system usage (parent repo only) + +Install the git tracking shim first, then start a new shell (or source your shell rc file) so the shim is active, and run: + +```bash +cd || exit 1 +git rev-parse --show-toplevel +scripts/install-git-tracking-shim.sh +# Start a new shell, or source your shell rc file before continuing. +git status +ls -la .tracking +``` + +Expected: +- `.tracking/latest.json` +- `.tracking/history.ndjson` +- `.tracking/latest.txt` + +### 5.4 Submodule-safe operations + +For submodule-specific work, run commands inside target submodule: + +```bash +cd /program/research-program-index || exit 1 +git rev-parse --show-toplevel +git status +``` + +Parent-tracking artifacts should not be created inside submodules. + +## 6. Troubleshooting Playbooks + +### Issue A: `No commits between ...` when creating PR in parent repo + +Cause: work happened inside submodules, not parent tracked files. + +Fix: +```bash +cd || exit 1 +git rev-parse --show-toplevel +git status --short +git diff --submodule +``` + +If parent has no diff, create PR in the child repo instead. + +### Issue B: SSH push timeout (`github.com:22`) + +Fix: +```bash +gh config set git_protocol https +gh auth setup-git +git remote -v +``` + +Retry push after protocol switch. + +### Issue C: Wrong base branch/default branch mismatch + +Check: +```bash +cd || exit 1 +git rev-parse --show-toplevel +origin_url="$(git remote get-url origin 2>/dev/null || true)" +printf '%s\n' "$origin_url" | grep -Eq '^(git@github\.com:molhamfetnah/ROS(\.git)?|https://github\.com/molhamfetnah/ROS(\.git)?)$' || { echo "Error: origin must be molhamfetnah/ROS (SSH or HTTPS)." >&2; exit 1; } +gh repo view molhamfetnah/ROS --json defaultBranchRef +gh pr view --repo molhamfetnah/ROS --json baseRefName,headRefName,url +``` + +Create PR with explicit base (safe, PR-scoped): +```bash +gh pr create --repo molhamfetnah/ROS --base main +``` + +Retarget an existing PR base (does not change repository default branch): +```bash +gh pr edit --repo molhamfetnah/ROS --base main +``` + +### Issue D: Submodule state drift + +Fix: +```bash +cd || exit 1 +git rev-parse --show-toplevel +git submodule sync --recursive +git submodule update --init --recursive +git submodule status -- program/* +``` + +## 7. Contribution Workflow + +### 7.1 Branching model +- Parent repo: orchestration/docs/submodule-pointer changes. +- Child repo: domain implementation changes. + +### 7.2 PR sequencing +1. Merge child-repo PRs first. +2. Update/pin submodule pointers in parent. +3. Merge parent PR last. + +### 7.3 Pre-PR checks + +Run these checks from the feature worktree that contains your PR changes (not the parent `main` checkout): + +```bash +cd || exit 1 +git rev-parse --show-toplevel +git rev-parse --is-inside-work-tree >/dev/null +origin_url="$(git remote get-url origin 2>/dev/null || true)" +printf '%s\n' "$origin_url" | grep -Eq '^(git@github\.com:molhamfetnah/ROS(\.git)?|https://github\.com/molhamfetnah/ROS(\.git)?)$' || { echo "Error: origin must be molhamfetnah/ROS (SSH or HTTPS)." >&2; exit 1; } +FEATURE_BRANCH= +CURRENT_BRANCH="$(git branch --show-current)" +test "$CURRENT_BRANCH" != "main" +test "$CURRENT_BRANCH" = "$FEATURE_BRANCH" +git status --short --branch +git submodule status -- program/* +bash scripts/tests/test-track-git-status.sh +bash scripts/tests/test-git-tracking-shim.sh +``` + +Perform final parent-repo updates (submodule pointers/docs) in this same feature worktree branch only before opening the parent PR: + +```bash +cd || exit 1 +git rev-parse --show-toplevel +git rev-parse --is-inside-work-tree >/dev/null +origin_url="$(git remote get-url origin 2>/dev/null || true)" +printf '%s\n' "$origin_url" | grep -Eq '^(git@github\.com:molhamfetnah/ROS(\.git)?|https://github\.com/molhamfetnah/ROS(\.git)?)$' || { echo "Error: origin must be molhamfetnah/ROS (SSH or HTTPS)." >&2; exit 1; } +FEATURE_BRANCH= +CURRENT_BRANCH="$(git branch --show-current)" +test "$CURRENT_BRANCH" != "main" +test "$CURRENT_BRANCH" = "$FEATURE_BRANCH" +``` + +Reserve the parent `main` checkout for sync/cleanup commands only (see section 8.1). + +Expected: clean or intentional diff only; test scripts pass. + +## 8. Safety, Recovery, and Cleanup + +### 8.1 Safe cleanup + +Run cleanup/sync commands from the parent `main` checkout, not from an active feature worktree: + +```bash +cd || exit 1 +git rev-parse --show-toplevel +git rev-parse --is-inside-work-tree >/dev/null +origin_url="$(git remote get-url origin 2>/dev/null || true)" +printf '%s\n' "$origin_url" | grep -Eq '^(git@github\.com:molhamfetnah/ROS(\.git)?|https://github\.com/molhamfetnah/ROS(\.git)?)$' || { echo "Error: origin must be molhamfetnah/ROS (SSH or HTTPS)." >&2; exit 1; } +git checkout main +git pull --ff-only +git fetch --prune +``` + +### 8.2 Remove finished worktree + +```bash +cd || exit 1 +git rev-parse --show-toplevel +git rev-parse --is-inside-work-tree >/dev/null +origin_url="$(git remote get-url origin 2>/dev/null || true)" +printf '%s\n' "$origin_url" | grep -Eq '^(git@github\.com:molhamfetnah/ROS(\.git)?|https://github\.com/molhamfetnah/ROS(\.git)?)$' || { echo "Error: origin must be molhamfetnah/ROS (SSH or HTTPS)." >&2; exit 1; } +git worktree list +git worktree remove .worktrees/ +``` + +### 8.3 Submodule recovery + +```bash +cd || exit 1 +git rev-parse --show-toplevel +git rev-parse --is-inside-work-tree >/dev/null +origin_url="$(git remote get-url origin 2>/dev/null || true)" +printf '%s\n' "$origin_url" | grep -Eq '^(git@github\.com:molhamfetnah/ROS(\.git)?|https://github\.com/molhamfetnah/ROS(\.git)?)$' || { echo "Error: origin must be molhamfetnah/ROS (SSH or HTTPS)." >&2; exit 1; } +git submodule sync --recursive +git submodule update --init --recursive +``` + +### 8.4 DO NOT use destructive reset without explicit intent +- Avoid `git reset --hard` unless you explicitly want to discard local work. + +## 9. References + +- Design spec: `docs/superpowers/specs/2026-05-03-master-readme-guidance-design.md` +- Tracking implementation plan: `docs/superpowers/plans/2026-05-02-git-status-tracking-implementation.md` +- Repo architecture spec: `docs/superpowers/specs/2026-04-30-repo-architecture-design.md` +- Master README implementation plan: `docs/superpowers/plans/2026-05-03-master-readme-guidance-implementation.md` +- Core subrepos: + - `program/research-program-index/README.md` + - `program/benchmark-core/README.md` diff --git a/docs/superpowers/plans/2026-05-02-git-status-tracking-implementation.md b/docs/superpowers/plans/2026-05-02-git-status-tracking-implementation.md new file mode 100644 index 0000000..efd69d5 --- /dev/null +++ b/docs/superpowers/plans/2026-05-02-git-status-tracking-implementation.md @@ -0,0 +1,406 @@ +# Git Status Tracking Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add automatic ROS-parent-only tracking for `git status` that writes append-only history and latest snapshots without blocking normal git usage. + +**Architecture:** Implement a small Bash collector script that parses `git status --porcelain=v1` and writes `.tracking/latest.json`, `.tracking/history.ndjson`, and `.tracking/latest.txt`. Add a Bash `git` shim snippet that runs the collector only for `status`/`st` at `/mnt/data/ros`, plus an idempotent installer that sources the shim from `~/.bashrc`. Keep submodules excluded by strict top-level path check. + +**Tech Stack:** Bash, git porcelain output, jq (JSON construction), GNU coreutils. + +--- + +## File Structure Plan + +- Create: `scripts/track-git-status.sh` — collector and writer for tracking artifacts. +- Create: `scripts/install-git-tracking-shim.sh` — idempotent installer into `~/.bashrc`. +- Create: `session/docs/snippets/git-tracking-shim.bash` — shell function shim for `git`. +- Create: `scripts/tests/test-track-git-status.sh` — collector behavior test script. +- Create: `scripts/tests/test-git-tracking-shim.sh` — shim routing test script. +- Modify: `.gitignore` — ignore `.tracking/`. + +--- + +### Task 1: Build collector with TDD for snapshot generation + +**Files:** +- Create: `scripts/tests/test-track-git-status.sh` +- Create: `scripts/track-git-status.sh` +- Modify: `.gitignore` + +- [ ] **Step 1: Write failing test for snapshot/history creation** + +```bash +#!/usr/bin/env bash +set -euo pipefail + +ROOT="/mnt/data/ros" +TMP="$(mktemp -d)" +trap 'rm -rf "$TMP"' EXIT + +cat > "$TMP/porcelain.txt" <<'EOF' +?? new.txt + M changed.md + D removed.log +R old.py -> new.py +EOF + +SCRIPT="$ROOT/scripts/track-git-status.sh" +if [ -x "$SCRIPT" ]; then + "$SCRIPT" \ + --repo-root "$ROOT" \ + --branch "main" \ + --porcelain-file "$TMP/porcelain.txt" \ + --tracking-dir "$TMP/.tracking" +fi + +test -f "$TMP/.tracking/latest.json" +test -f "$TMP/.tracking/history.ndjson" +test -f "$TMP/.tracking/latest.txt" +grep -q '"untracked":\["new.txt"\]' "$TMP/.tracking/latest.json" +grep -q '"modified":\["changed.md"\]' "$TMP/.tracking/latest.json" +grep -q '"deleted":\["removed.log"\]' "$TMP/.tracking/latest.json" +grep -q '"renamed":\["old.py -> new.py"\]' "$TMP/.tracking/latest.json" +``` + +- [ ] **Step 2: Run test to verify failure** + +Run: `bash scripts/tests/test-track-git-status.sh` +Expected: FAIL because `scripts/track-git-status.sh` does not exist yet. + +- [ ] **Step 3: Write minimal collector implementation** + +```bash +#!/usr/bin/env bash +set -euo pipefail + +REPO_ROOT="" +BRANCH="" +PORCELAIN_FILE="" +TRACKING_DIR="" +MAX_BYTES="${TRACKING_MAX_BYTES:-10485760}" + +while [[ $# -gt 0 ]]; do + case "$1" in + --repo-root) REPO_ROOT="$2"; shift 2 ;; + --branch) BRANCH="$2"; shift 2 ;; + --porcelain-file) PORCELAIN_FILE="$2"; shift 2 ;; + --tracking-dir) TRACKING_DIR="$2"; shift 2 ;; + *) echo "Unknown arg: $1" >&2; exit 2 ;; + esac +done + +mkdir -p "$TRACKING_DIR" +HISTORY="$TRACKING_DIR/history.ndjson" +LATEST_JSON="$TRACKING_DIR/latest.json" +LATEST_TXT="$TRACKING_DIR/latest.txt" + +if [[ -f "$HISTORY" ]]; then + size="$(wc -c < "$HISTORY" | tr -d ' ')" + if [[ "$size" -gt "$MAX_BYTES" ]]; then + mv "$HISTORY" "$TRACKING_DIR/history.$(date -u +%Y%m%dT%H%M%SZ).ndjson" + fi +fi + +mapfile -t UNTRACKED < <(awk '/^\?\? /{sub(/^\?\? /,""); print}' "$PORCELAIN_FILE") +mapfile -t MODIFIED < <(awk '/^( M|M |MM)/{sub(/^../,""); sub(/^ /,""); print}' "$PORCELAIN_FILE") +mapfile -t DELETED < <(awk '/^( D|D )/{sub(/^../,""); sub(/^ /,""); print}' "$PORCELAIN_FILE") +mapfile -t RENAMED < <(awk '/^R/{sub(/^../,""); sub(/^ /,""); print}' "$PORCELAIN_FILE") + +json="$(jq -cn \ + --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ + --arg repo "$REPO_ROOT" \ + --arg branch "$BRANCH" \ + --argjson untracked "$(printf '%s\n' "${UNTRACKED[@]:-}" | jq -Rsc 'split("\n")[:-1]')" \ + --argjson modified "$(printf '%s\n' "${MODIFIED[@]:-}" | jq -Rsc 'split("\n")[:-1]')" \ + --argjson deleted "$(printf '%s\n' "${DELETED[@]:-}" | jq -Rsc 'split("\n")[:-1]')" \ + --argjson renamed "$(printf '%s\n' "${RENAMED[@]:-}" | jq -Rsc 'split("\n")[:-1]')" \ + '{timestamp:$ts,repo_root:$repo,branch:$branch,untracked:$untracked,modified:$modified,deleted:$deleted,renamed:$renamed}')" + +printf '%s\n' "$json" > "$LATEST_JSON" +printf '%s\n' "$json" >> "$HISTORY" +{ + echo "timestamp: $(jq -r '.timestamp' "$LATEST_JSON")" + echo "repo: $(jq -r '.repo_root' "$LATEST_JSON")" + echo "branch: $(jq -r '.branch' "$LATEST_JSON")" + echo "untracked: $(jq -r '.untracked | length' "$LATEST_JSON")" + echo "modified: $(jq -r '.modified | length' "$LATEST_JSON")" + echo "deleted: $(jq -r '.deleted | length' "$LATEST_JSON")" + echo "renamed: $(jq -r '.renamed | length' "$LATEST_JSON")" +} > "$LATEST_TXT" +``` + +- [ ] **Step 4: Ignore tracking artifacts in git** + +Append this line to `.gitignore`: + +```gitignore +.tracking/ +``` + +- [ ] **Step 5: Run test to verify pass** + +Run: `bash scripts/tests/test-track-git-status.sh` +Expected: PASS (exit code 0). + +- [ ] **Step 6: Commit** + +```bash +git add .gitignore scripts/track-git-status.sh scripts/tests/test-track-git-status.sh +git commit -m "feat: add git status collector and artifact snapshots" +``` + +--- + +### Task 2: Add collector safeguards (rotation + non-blocking behavior) + +**Files:** +- Modify: `scripts/tests/test-track-git-status.sh` +- Modify: `scripts/track-git-status.sh` + +- [ ] **Step 1: Add failing tests for rotation and malformed input tolerance** + +Add to `scripts/tests/test-track-git-status.sh`: + +```bash +# rotation test +python3 - <<'PY' +from pathlib import Path +p = Path(".tmp-history") +p.mkdir(exist_ok=True) +(p/"history.ndjson").write_text("x"*120) +PY +"$ROOT/scripts/track-git-status.sh" \ + --repo-root "$ROOT" \ + --branch "main" \ + --porcelain-file "$TMP/porcelain.txt" \ + --tracking-dir ".tmp-history" \ + TRACKING_MAX_BYTES=10 +test -f .tmp-history/history.ndjson +ls .tmp-history/history.*.ndjson >/dev/null +rm -rf .tmp-history +``` + +- [ ] **Step 2: Run test to verify failure** + +Run: `bash scripts/tests/test-track-git-status.sh` +Expected: FAIL until environment/rotation handling is corrected. + +- [ ] **Step 3: Implement minimal fix for configurable rotation threshold and robust empty arrays** + +Update `scripts/track-git-status.sh` to: + +```bash +MAX_BYTES="${TRACKING_MAX_BYTES:-10485760}" + +to_json_array() { + if [[ $# -eq 0 ]]; then + printf '[]' + else + printf '%s\n' "$@" | jq -Rsc 'split("\n")[:-1]' + fi +} +``` + +and replace the four `--argjson ...` generators with `to_json_array`. + +- [ ] **Step 4: Re-run test to verify pass** + +Run: `bash scripts/tests/test-track-git-status.sh` +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add scripts/track-git-status.sh scripts/tests/test-track-git-status.sh +git commit -m "fix: harden collector rotation and array handling" +``` + +--- + +### Task 3: Add transparent git shim and installer (ROS parent only) + +**Files:** +- Create: `session/docs/snippets/git-tracking-shim.bash` +- Create: `scripts/install-git-tracking-shim.sh` +- Create: `scripts/tests/test-git-tracking-shim.sh` + +- [ ] **Step 1: Write failing shim-routing test** + +```bash +#!/usr/bin/env bash +set -euo pipefail + +ROOT="/mnt/data/ros" +TMP="$(mktemp -d)" +trap 'rm -rf "$TMP"' EXIT + +cat > "$TMP/fake-git" <<'EOF' +#!/usr/bin/env bash +echo "REAL_GIT $*" >> "$TMP/out.log" +EOF +chmod +x "$TMP/fake-git" + +export REAL_GIT_BIN="$TMP/fake-git" +export TRACKER_SCRIPT="$ROOT/scripts/track-git-status.sh" +source "$ROOT/session/docs/snippets/git-tracking-shim.bash" + +git status +grep -q "REAL_GIT status" "$TMP/out.log" +``` + +- [ ] **Step 2: Run test to verify failure** + +Run: `bash scripts/tests/test-git-tracking-shim.sh` +Expected: FAIL because shim snippet does not exist yet. + +- [ ] **Step 3: Implement shim snippet** + +`session/docs/snippets/git-tracking-shim.bash`: + +```bash +git() { + local real_git="${REAL_GIT_BIN:-/usr/bin/git}" + local tracker="${TRACKER_SCRIPT:-/mnt/data/ros/scripts/track-git-status.sh}" + local cmd="${1:-}" + + if [[ "$cmd" == "status" || "$cmd" == "st" ]]; then + local top + top="$("$real_git" rev-parse --show-toplevel 2>/dev/null || true)" + if [[ "$top" == "/mnt/data/ros" ]]; then + local tmp porcelain branch + tmp="$(mktemp)" + porcelain="$tmp.porcelain" + branch="$("$real_git" -C "$top" branch --show-current 2>/dev/null || echo unknown)" + "$real_git" -C "$top" status --porcelain=v1 > "$porcelain" || true + if ! "$tracker" --repo-root "$top" --branch "$branch" --porcelain-file "$porcelain" --tracking-dir "$top/.tracking"; then + echo "[tracking-warning] failed to write tracking artifacts" >&2 + fi + rm -f "$tmp" "$porcelain" + fi + fi + "$real_git" "$@" +} +``` + +- [ ] **Step 4: Implement installer helper** + +`scripts/install-git-tracking-shim.sh`: + +```bash +#!/usr/bin/env bash +set -euo pipefail + +BASHRC="${HOME}/.bashrc" +MARK_START="# >>> ros git tracking shim >>>" +MARK_END="# <<< ros git tracking shim <<<" +SNIPPET="/mnt/data/ros/session/docs/snippets/git-tracking-shim.bash" + +touch "$BASHRC" +if grep -q "$MARK_START" "$BASHRC"; then + exit 0 +fi + +{ + echo "$MARK_START" + echo "[ -f \"$SNIPPET\" ] && source \"$SNIPPET\"" + echo "$MARK_END" +} >> "$BASHRC" +``` + +- [ ] **Step 5: Run shim test to verify pass** + +Run: `bash scripts/tests/test-git-tracking-shim.sh` +Expected: PASS. + +- [ ] **Step 6: Commit** + +```bash +git add session/docs/snippets/git-tracking-shim.bash scripts/install-git-tracking-shim.sh scripts/tests/test-git-tracking-shim.sh +git commit -m "feat: add transparent git status tracking shim and installer" +``` + +--- + +### Task 4: Install + end-to-end verification in ROS parent repo + +**Files:** +- Modify: none (runtime verification and shell setup) + +- [ ] **Step 1: Install shim idempotently** + +Run: + +```bash +bash /mnt/data/ros/scripts/install-git-tracking-shim.sh +source ~/.bashrc +``` + +Expected: no duplicate shim block entries. + +- [ ] **Step 2: Create representative file-state changes for validation** + +Run: + +```bash +cd /mnt/data/ros +echo "tmp" > .tracking-e2e-untracked.tmp +touch .tracking-e2e-modified.tmp && git add .tracking-e2e-modified.tmp && echo "m" >> .tracking-e2e-modified.tmp +``` + +- [ ] **Step 3: Trigger tracking through git status** + +Run: + +```bash +git status +``` + +Expected: +- `.tracking/latest.json` exists +- `.tracking/history.ndjson` appended +- `.tracking/latest.txt` updated + +- [ ] **Step 4: Validate tracker does not run in submodule** + +Run: + +```bash +cd /mnt/data/ros/program/benchmark-core +git status +test ! -d .tracking +``` + +Expected: PASS (no local `.tracking` folder in submodule). + +- [ ] **Step 5: Cleanup temporary e2e files** + +Run: + +```bash +cd /mnt/data/ros +git restore --staged .tracking-e2e-modified.tmp || true +rm -f .tracking-e2e-untracked.tmp .tracking-e2e-modified.tmp +``` + +- [ ] **Step 6: Commit final operational docs note (if needed)** + +If README operational note is added: + +```bash +git add README.md +git commit -m "docs: add git tracking shim usage note" +``` + +--- + +## Spec Coverage Check (self-review) + +1. **ROS parent only:** Covered via strict top-level check in shim (Task 3). +2. **Track untracked/modified/deleted/renamed:** Covered in collector parsing (Task 1). +3. **Append history + latest snapshot + text summary:** Covered in artifact writes (Task 1). +4. **Non-blocking behavior:** Covered via warning + passthrough (Task 3). +5. **Rotation policy:** Covered in Task 2. +6. **Submodule exclusion:** Covered in Task 4 explicit check. +7. **No placeholders:** All file paths, code, and commands are explicit. + diff --git a/docs/superpowers/plans/2026-05-03-master-readme-guidance-implementation.md b/docs/superpowers/plans/2026-05-03-master-readme-guidance-implementation.md new file mode 100644 index 0000000..318131a --- /dev/null +++ b/docs/superpowers/plans/2026-05-03-master-readme-guidance-implementation.md @@ -0,0 +1,517 @@ +# Master README Guidance Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Rewrite the root README into a comprehensive, command-driven onboarding and day-2 operations guide for the ROS parent repo and its submodule architecture. + +**Architecture:** Keep one authoritative root README with a strict section hierarchy: overview, topology, quickstart, full setup, operations, troubleshooting, contribution flow, and references. Use command-first instructions with explicit path context and verification checkpoints. Avoid duplicating deep technical docs by linking to existing spec/plan artifacts where detail already exists. + +**Tech Stack:** Markdown, Git/GitHub CLI command recipes, shell command verification checkpoints. + +--- + +## File Structure Plan + +- Modify: `/mnt/data/ros/README.md` — replace current minimal content with the full professional guidance manual. +- Verify references only (no content changes expected): + - `/mnt/data/ros/docs/superpowers/specs/2026-05-03-master-readme-guidance-design.md` + - `/mnt/data/ros/program/research-program-index/README.md` + - `/mnt/data/ros/program/benchmark-core/README.md` + - `/mnt/data/ros/.gitmodules` + +--- + +### Task 1: Replace README with approved architecture skeleton + +**Files:** +- Modify: `/mnt/data/ros/README.md` + +- [ ] **Step 1: Write the new top-level README structure** + +Replace `README.md` contents with this exact initial skeleton: + +```markdown +# ROS Program Workspace + +Professional operating manual for the ROS parent repository and its managed multi-repo research program. + +## 1. What This Repository Is + +This parent repository orchestrates the full program workspace, shared workflows, and submodule pointers for domain repositories under `program/`. + +It is the control plane for: +- repository topology +- shared workflows and guidance +- reproducible workspace bootstrap +- integration flow across subrepos + +It is **not** where all implementation code lives; implementation is distributed in submodules. + +## 2. Repository Topology + +### 2.1 Parent Repo Responsibilities +- root-level orchestration +- docs/specs/plans +- shared scripts and operational guidance +- submodule pinning for exact child-repo states + +### 2.2 Submodule Repositories (`program/`) +- `research-program-index`: orchestration roadmap, governance, release schema +- `benchmark-core`: benchmark contracts and baseline verification tests +- `localization-tracking`: localization and tracking domain work +- `uav-mpc-geometric-control`: UAV MPC and geometric control domain work +- `sensorless-estimation-suite`: sensorless estimation workflows +- `swarm-path-planning-bees`: swarm/path-planning workflows +- `ik-uncertainty-learning`: inverse-kinematics uncertainty workflows +- `digital-twin-pipeline`: digital twin integration pipeline +- `docs-multilingual-continuity`: documentation continuity assets +- `repro-packages`: reproducibility packaging artifacts + +## 3. Quick Start (10-Minute Path) + +## 4. Full Setup (First-Time Environment) + +## 5. Day-2 Operations + +## 6. Troubleshooting Playbooks + +## 7. Contribution Workflow + +## 8. Safety, Recovery, and Cleanup + +## 9. References +``` + +- [ ] **Step 2: Run markdown sanity check by visual scan** + +Run: + +```bash +cd /mnt/data/ros +sed -n '1,140p' README.md +``` + +Expected: All section headers `## 1` through `## 9` visible in order; no old duplicate `# ROS` headings remain. + +- [ ] **Step 3: Commit skeleton** + +```bash +cd /mnt/data/ros +git add README.md +git commit -m "docs: scaffold comprehensive root README structure" +``` + +--- + +### Task 2: Add command-driven onboarding and setup sections + +**Files:** +- Modify: `/mnt/data/ros/README.md` + +- [ ] **Step 1: Fill `## 3. Quick Start (10-Minute Path)`** + +Under section 3, add exactly: + +```markdown +## 3. Quick Start (10-Minute Path) + +Run from a clean terminal: + +```bash +git clone --recurse-submodules https://github.com/molhamfetnah/ROS.git +cd ROS +git submodule sync --recursive +git submodule update --init --recursive +``` + +Validation checkpoint: + +```bash +git submodule status +``` + +Expected: 10 `program/*` entries listed with commit SHAs. +``` + +- [ ] **Step 2: Fill `## 4. Full Setup (First-Time Environment)`** + +Under section 4, add: + +```markdown +## 4. Full Setup (First-Time Environment) + +### 4.1 Ensure parent repo is current + +```bash +cd /mnt/data/ros +git checkout main +git pull --ff-only +``` + +Expected: `Already up to date.` or fast-forward output. + +### 4.2 Initialize submodules + +```bash +git submodule sync --recursive +git submodule update --init --recursive +``` + +Expected: each `program/*` submodule checked out with no errors. + +### 4.3 Verify workspace health + +```bash +git status --short --branch +git submodule status +``` + +Expected: +- parent branch shown (normally `## main`) +- no unexpected dirty state +- submodule SHAs listed + +### 4.4 GitHub CLI auth (for push/PR operations) + +```bash +gh auth status || gh auth login +gh repo view molhamfetnah/ROS --json defaultBranchRef +``` + +Expected: authenticated account and default branch details returned. +``` + +- [ ] **Step 3: Validate sections render correctly** + +```bash +cd /mnt/data/ros +sed -n '1,260p' README.md +``` + +Expected: Setup sections include command blocks + expected outcomes after each block. + +- [ ] **Step 4: Commit onboarding/setup** + +```bash +cd /mnt/data/ros +git add README.md +git commit -m "docs: add quickstart and full setup procedures" +``` + +--- + +### Task 3: Add day-2 operations and troubleshooting playbooks + +**Files:** +- Modify: `README.md` (repository root) + +- [ ] **Step 1: Fill `## 5. Day-2 Operations` with operational workflows** + +Add: + +```markdown +## 5. Day-2 Operations + +### 5.1 Sync workspace before work + +```bash +cd || exit 1 +git rev-parse --show-toplevel +git rev-parse --is-inside-work-tree >/dev/null +origin_url="$(git remote get-url origin 2>/dev/null || true)" +printf '%s\n' "$origin_url" | grep -Eq '^(git@github\.com:molhamfetnah/ROS(\.git)?|https://github\.com/molhamfetnah/ROS(\.git)?)$' || { echo "Error: origin must be molhamfetnah/ROS (SSH or HTTPS)." >&2; exit 1; } +git checkout main +git pull --ff-only +git submodule sync --recursive +git submodule update --init --recursive +``` + +### 5.2 Worktree-based feature workflow + +```bash +cd || exit 1 +git rev-parse --show-toplevel +git rev-parse --is-inside-work-tree >/dev/null +origin_url="$(git remote get-url origin 2>/dev/null || true)" +printf '%s\n' "$origin_url" | grep -Eq '^(git@github\.com:molhamfetnah/ROS(\.git)?|https://github\.com/molhamfetnah/ROS(\.git)?)$' || { echo "Error: origin must be molhamfetnah/ROS (SSH or HTTPS)." >&2; exit 1; } +git checkout main || exit 1 +git pull --ff-only || exit 1 +git worktree add -b .worktrees/ +cd .worktrees/ +``` + +### 5.3 Tracking-system usage (parent repo only) + +After shim install, run: + +```bash +cd || exit 1 +git rev-parse --show-toplevel +git status +ls -la .tracking +``` + +Expected: +- `.tracking/latest.json` +- `.tracking/history.ndjson` +- `.tracking/latest.txt` + +### 5.4 Submodule-safe operations + +For submodule-specific work, run commands inside target submodule: + +```bash +cd /program/research-program-index || exit 1 +git rev-parse --show-toplevel +git status +``` + +Parent-tracking artifacts should not be created inside submodules. +``` + +- [ ] **Step 2: Fill `## 6. Troubleshooting Playbooks`** + +Add: + +```markdown +## 6. Troubleshooting Playbooks + +### Issue A: `No commits between ...` when creating PR in parent repo + +Cause: work happened inside submodules, not parent tracked files. + +Fix: +```bash +cd || exit 1 +git rev-parse --show-toplevel +git status --short +git diff --submodule +``` + +If parent has no diff, create PR in the child repo instead. + +### Issue B: SSH push timeout (`github.com:22`) + +Fix: +```bash +gh config set git_protocol https +gh auth setup-git +git remote -v +``` + +Retry push after protocol switch. + +### Issue C: Wrong base branch/default branch mismatch + +Check: +```bash +cd || exit 1 +git rev-parse --show-toplevel +origin_url="$(git remote get-url origin 2>/dev/null || true)" +printf '%s\n' "$origin_url" | grep -Eq '^(git@github\.com:molhamfetnah/ROS(\.git)?|https://github\.com/molhamfetnah/ROS(\.git)?)$' || { echo "Error: origin must be molhamfetnah/ROS (SSH or HTTPS)." >&2; exit 1; } +gh repo view molhamfetnah/ROS --json defaultBranchRef +gh pr view --repo molhamfetnah/ROS --json baseRefName,headRefName,url +``` + +Create PR with explicit base (safe, PR-scoped): +```bash +gh pr create --repo molhamfetnah/ROS --base main +``` + +Retarget an existing PR base (does not change repository default branch): +```bash +gh pr edit --repo molhamfetnah/ROS --base main +``` + +### Issue D: Submodule state drift + +Fix: +```bash +cd || exit 1 +git rev-parse --show-toplevel +git submodule sync --recursive +git submodule update --init --recursive +git submodule status -- program/* +``` +``` + +- [ ] **Step 3: Validate operational/troubleshooting sections** + +```bash +cd || exit 1 +grep -n "^## 5\\|^## 6\\|^### Issue" README.md +grep -n "git submodule status -- program/\\*" README.md +! grep -n "gh repo edit .*--default-branch" README.md +``` + +Expected: section 5/6 headers present, scoped submodule status checks present, and no default-branch mutation command. + +- [ ] **Step 4: Commit day-2 + troubleshooting** + +```bash +cd || exit 1 +git add README.md +git commit -m "docs: add day-2 operations and troubleshooting playbooks" +``` + +--- + +### Task 4: Add contribution workflow, safety, and reference map + +**Files:** +- Modify: `README.md` (repository root) + +- [ ] **Step 1: Fill `## 7. Contribution Workflow`** + +Add: + +```markdown +## 7. Contribution Workflow + +### 7.1 Branching model +- Parent repo: orchestration/docs/submodule-pointer changes. +- Child repo: domain implementation changes. + +### 7.2 PR sequencing +1. Merge child-repo PRs first. +2. Update/pin submodule pointers in parent. +3. Merge parent PR last. + +### 7.3 Pre-PR checks + +```bash +cd || exit 1 +git rev-parse --show-toplevel +git rev-parse --is-inside-work-tree >/dev/null +origin_url="$(git remote get-url origin 2>/dev/null || true)" +printf '%s\n' "$origin_url" | grep -Eq '^(git@github\.com:molhamfetnah/ROS(\.git)?|https://github\.com/molhamfetnah/ROS(\.git)?)$' || { echo "Error: origin must be molhamfetnah/ROS (SSH or HTTPS)." >&2; exit 1; } +FEATURE_BRANCH= +CURRENT_BRANCH="$(git branch --show-current)" +test "$CURRENT_BRANCH" != "main" +test "$CURRENT_BRANCH" = "$FEATURE_BRANCH" +git status --short --branch +git submodule status -- program/* +bash scripts/tests/test-track-git-status.sh +bash scripts/tests/test-git-tracking-shim.sh +``` + +Perform final parent-repo updates (submodule pointers/docs) in this same feature worktree branch only before opening the parent PR: + +```bash +cd || exit 1 +git rev-parse --show-toplevel +git rev-parse --is-inside-work-tree >/dev/null +origin_url="$(git remote get-url origin 2>/dev/null || true)" +printf '%s\n' "$origin_url" | grep -Eq '^(git@github\.com:molhamfetnah/ROS(\.git)?|https://github\.com/molhamfetnah/ROS(\.git)?)$' || { echo "Error: origin must be molhamfetnah/ROS (SSH or HTTPS)." >&2; exit 1; } +FEATURE_BRANCH= +CURRENT_BRANCH="$(git branch --show-current)" +test "$CURRENT_BRANCH" != "main" +test "$CURRENT_BRANCH" = "$FEATURE_BRANCH" +``` + +Reserve the parent `main` checkout for sync/cleanup commands only (see section 8.1). + +Expected: clean or intentional diff only; test scripts pass. +``` + +- [ ] **Step 2: Fill `## 8. Safety, Recovery, and Cleanup`** + +Add: + +```markdown +## 8. Safety, Recovery, and Cleanup + +### 8.1 Safe cleanup + +```bash +cd || exit 1 +git rev-parse --show-toplevel +git rev-parse --is-inside-work-tree >/dev/null +origin_url="$(git remote get-url origin 2>/dev/null || true)" +printf '%s\n' "$origin_url" | grep -Eq '^(git@github\.com:molhamfetnah/ROS(\.git)?|https://github\.com/molhamfetnah/ROS(\.git)?)$' || { echo "Error: origin must be molhamfetnah/ROS (SSH or HTTPS)." >&2; exit 1; } +git checkout main +git pull --ff-only +git fetch --prune +``` + +### 8.2 Remove finished worktree + +```bash +cd || exit 1 +git rev-parse --show-toplevel +git rev-parse --is-inside-work-tree >/dev/null +origin_url="$(git remote get-url origin 2>/dev/null || true)" +printf '%s\n' "$origin_url" | grep -Eq '^(git@github\.com:molhamfetnah/ROS(\.git)?|https://github\.com/molhamfetnah/ROS(\.git)?)$' || { echo "Error: origin must be molhamfetnah/ROS (SSH or HTTPS)." >&2; exit 1; } +git worktree list +git worktree remove .worktrees/ +``` + +### 8.3 Submodule recovery + +```bash +cd || exit 1 +git rev-parse --show-toplevel +git rev-parse --is-inside-work-tree >/dev/null +origin_url="$(git remote get-url origin 2>/dev/null || true)" +printf '%s\n' "$origin_url" | grep -Eq '^(git@github\.com:molhamfetnah/ROS(\.git)?|https://github\.com/molhamfetnah/ROS(\.git)?)$' || { echo "Error: origin must be molhamfetnah/ROS (SSH or HTTPS)." >&2; exit 1; } +git submodule sync --recursive +git submodule update --init --recursive +``` + +### 8.4 DO NOT use destructive reset without explicit intent +- Avoid `git reset --hard` unless you explicitly want to discard local work. +``` + +- [ ] **Step 3: Fill `## 9. References`** + +Add: + +```markdown +## 9. References + +- Design spec: `docs/superpowers/specs/2026-05-03-master-readme-guidance-design.md` +- Tracking implementation plan: `docs/superpowers/plans/2026-05-02-git-status-tracking-implementation.md` +- Repo architecture spec: `docs/superpowers/specs/2026-04-30-repo-architecture-design.md` +- Master README implementation plan: `docs/superpowers/plans/2026-05-03-master-readme-guidance-implementation.md` +- Core subrepos: + - `program/research-program-index/README.md` + - `program/benchmark-core/README.md` +``` + +- [ ] **Step 4: Validate links and section completeness** + +```bash +cd || exit 1 +for p in \ + docs/superpowers/specs/2026-05-03-master-readme-guidance-design.md \ + docs/superpowers/plans/2026-05-02-git-status-tracking-implementation.md \ + docs/superpowers/specs/2026-04-30-repo-architecture-design.md \ + docs/superpowers/plans/2026-05-03-master-readme-guidance-implementation.md \ + program/research-program-index/README.md \ + program/benchmark-core/README.md; do + test -f "$p" || { echo "missing: $p"; exit 1; } +done +echo "reference-check: PASS" +``` + +Expected: `reference-check: PASS`. + +- [ ] **Step 5: Commit final README rewrite** + +```bash +cd || exit 1 +git add README.md +git commit -m "docs: publish comprehensive root README operations guide" +``` + +--- + +## Spec Coverage Check (self-review) + +1. **Single master README approach:** covered (Tasks 1–4 only modify root README). +2. **Onboarding + day-2 operations:** covered (Tasks 2 and 3). +3. **Command-driven with expected checkpoints:** covered throughout each task. +4. **Parent vs submodule boundaries:** covered in topology, operations, troubleshooting. +5. **Professional troubleshooting and contribution flow:** covered in Tasks 3 and 4. +6. **Reference map to deeper docs:** covered in Task 4 references section. +7. **No placeholders/TBDs:** all sections and command blocks explicitly defined. diff --git a/docs/superpowers/specs/2026-05-01-git-status-tracking-design.md b/docs/superpowers/specs/2026-05-01-git-status-tracking-design.md new file mode 100644 index 0000000..480df38 --- /dev/null +++ b/docs/superpowers/specs/2026-05-01-git-status-tracking-design.md @@ -0,0 +1,78 @@ +# Git Status Tracking Design (ROS Parent Repo) + +## Problem +We need persistent tracking of repository file-state changes in the ROS parent repo, with automatic capture whenever `git status` is run, and with append-only history plus a latest snapshot. + +## Scope +- In scope: ROS parent repo only (`/mnt/data/ros` top-level). +- In scope: track untracked, modified, deleted, and renamed paths. +- Out of scope: tracking inside `program/*` submodules. + +## Approach +Implement a shell-level `git` shim (Bash function) that intercepts `git status` and `git st`: +1. Detect current git top-level. +2. If top-level equals ROS parent repo, run tracker collector. +3. Collector writes tracking artifacts. +4. Always forward command to the real git binary. +5. For non-status git commands, pass through unchanged. + +## Tracking Artifacts +Store under repo-local `.tracking/`: + +- `latest.json`: latest full structured snapshot. +- `history.ndjson`: append-only event history (one JSON object per line). +- `latest.txt`: readable summary for quick inspection. + +Each tracking event includes: +- `timestamp` (ISO-8601) +- `repo_root` +- `branch` +- `untracked[]` +- `modified[]` +- `deleted[]` +- `renamed[]` + +## Data Flow +1. User runs `git status` (or `git st`). +2. Bash shim checks repo root. +3. If ROS parent repo, capture `git status --porcelain=v1` and parse by status code. +4. Generate snapshot object and write: + - overwrite `latest.json` + - append to `history.ndjson` + - overwrite `latest.txt` +5. Execute original `git status` output for user. + +## Error Handling +- Tracker failures must not block git usage. +- On parser or write failure, print a warning to stderr and continue to real git command. +- If `.tracking/` is missing, create it automatically. +- If `history.ndjson` exceeds size threshold, rotate to `history..ndjson` and continue with new file. + +## Safety Constraints +- Run collector only when top-level repo path exactly matches `/mnt/data/ros`. +- Never recurse into submodules for collection. +- Never modify tracked source files; only write under `.tracking/`. + +## Implementation Units +1. **Collector script** (`scripts/track-git-status.sh`) + - Read porcelain output + - Parse and build JSON payload + - Write artifacts and rotate history +2. **Shell shim snippet** (`session/docs/snippets/git-tracking-shim.bash`) + - Function override for `git` + - Conditional call to collector for `status`/`st` +3. **Installer helper** (`scripts/install-git-tracking-shim.sh`) + - Idempotently append shim source block to `~/.bashrc` + +## Validation Plan +- Run `git status` with known untracked/modified/deleted/renamed examples. +- Confirm `.tracking/latest.json`, `.tracking/history.ndjson`, `.tracking/latest.txt` are produced. +- Confirm multiple `git status` calls append events. +- Confirm non-status git commands are unaffected. +- Confirm tracker does not run in submodules. + +## Success Criteria +- Every `git status` in ROS parent repo produces/updates tracking artifacts. +- History is append-only and survives shell sessions. +- Tracker is transparent to normal git workflow. +- Submodule repositories are not tracked by this mechanism. diff --git a/docs/superpowers/specs/2026-05-03-master-readme-guidance-design.md b/docs/superpowers/specs/2026-05-03-master-readme-guidance-design.md new file mode 100644 index 0000000..6522de2 --- /dev/null +++ b/docs/superpowers/specs/2026-05-03-master-readme-guidance-design.md @@ -0,0 +1,81 @@ +# Master README Guidance Design + +## Objective +Create a comprehensive, professional, command-driven root README for the ROS parent repository that guides users through onboarding and day-2 operations across the multi-repo structure. + +## Scope +- In scope: + - Rewrite `/mnt/data/ros/README.md`. + - Cover parent repo architecture and role of each `program/*` subrepo. + - Provide step-by-step commands for setup, operations, troubleshooting, and contribution workflow. + - Include verification checkpoints and expected outcomes after key commands. + - Link to deeper references in `docs/superpowers/` and `session/docs/`. +- Out of scope: + - Rewriting READMEs in every subrepo. + - Refactoring code or changing runtime behavior. + +## Audience +- Primary: first-time contributors/operators. +- Secondary: returning maintainers handling operational workflows. +- Language: English only. + +## Information Architecture +The README will follow this exact high-level structure: + +1. **Project Overview** + - Mission, what this repo orchestrates, and what it does not own. +2. **Repository Topology** + - Parent repo responsibilities. + - Submodule map (`program/*`) with one-line purpose per repo. +3. **Quick Start (10-Minute Path)** + - Minimal onboarding path to get a clean working environment. +4. **Full Environment Setup** + - Prerequisites, clone strategy, submodule sync/init, authentication notes. +5. **Operational Workflows (Day-2)** + - Running tracking pipeline, scraper-related flows, operational checks, safe cleanup. +6. **Verification Checkpoints** + - Command-level expected outputs and pass/fail indicators. +7. **Troubleshooting Playbooks** + - Common failures seen in this project (git/submodule, PR base, SSH/HTTPS, shell shim). +8. **Contribution & Change Workflow** + - Branching, test expectations, review sequencing, PR integration order. +9. **Safety, Recovery, and Cleanup** + - Idempotent recovery commands, non-destructive cleanup, rollback steps. +10. **References** + - Direct links to plan/spec files and key operational docs. + +## Content Standards +- Command-first: each actionable step includes copy-paste commands. +- Every major procedure includes: + - **Prerequisites** + - **Execution** + - **Validation** + - **Recovery / rollback (when relevant)** +- Distinguish clearly: + - Parent-repo commands vs submodule commands. + - One-time setup vs routine operations. +- Use concise but professional tone; avoid ambiguity and placeholders. + +## Critical Accuracy Requirements +- Must reflect the current submodule-based architecture in `program/*`. +- Must include explicit guardrails for common confusion points: + - “No commits between branches” in parent repo vs nested repos. + - Default branch/base branch mismatch. + - SSH push failures and HTTPS fallback. + - Worktree + submodule interactions. +- Must preserve legal/operational boundaries already established in project docs. + +## Risks and Mitigations +- Risk: README becomes too monolithic and hard to scan. + - Mitigation: strict sectioning, quick-start first, deep details lower in file. +- Risk: Commands become stale. + - Mitigation: centralized references and clearly labeled command intent. +- Risk: Users run commands in wrong repo. + - Mitigation: add path prompts and “Run from:” notes per section. + +## Acceptance Criteria +- A new user can clone, initialize submodules, and run baseline workflows without external clarification. +- A maintainer can diagnose common operational failures using troubleshooting sections. +- README provides clear map to advanced docs without duplicating all deep content. +- All command blocks are path-scoped and include validation checkpoints. + diff --git a/scripts/install-git-tracking-shim.sh b/scripts/install-git-tracking-shim.sh new file mode 100755 index 0000000..1fc57d9 --- /dev/null +++ b/scripts/install-git-tracking-shim.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -euo pipefail + +BASHRC="${HOME}/.bashrc" +MARK_START="# >>> ros git tracking shim >>>" +MARK_END="# <<< ros git tracking shim <<<" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" +SNIPPET="${REPO_ROOT}/docs/snippets/git-tracking-shim.bash" +SOURCE_LINE="[ -f \"$SNIPPET\" ] && source \"$SNIPPET\"" + +touch "$BASHRC" + +if grep -Fq "$MARK_START" "$BASHRC"; then + exit 0 +fi + +{ + printf '%s\n' "$MARK_START" + printf '%s\n' "$SOURCE_LINE" + printf '%s\n' "$MARK_END" +} >> "$BASHRC" diff --git a/scripts/tests/test-git-tracking-shim.sh b/scripts/tests/test-git-tracking-shim.sh new file mode 100755 index 0000000..99cbf68 --- /dev/null +++ b/scripts/tests/test-git-tracking-shim.sh @@ -0,0 +1,152 @@ +#!/usr/bin/env bash +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +TRACKED_ROOT="/mnt/data/ros" +TMP_BASE="$REPO_ROOT/scripts/tests/.tmp-git-shim-test-$$" +trap 'rm -rf "$TMP_BASE"' EXIT +mkdir -p "$TMP_BASE" + +REAL_GIT="$TMP_BASE/fake-git" +BIN_DIR="$TMP_BASE/bin" +TRACKER="$TMP_BASE/fake-tracker.sh" +REAL_LOG="$TMP_BASE/real.log" +TRACKER_LOG="$TMP_BASE/tracker.log" +WARN_LOG="$TMP_BASE/warn.log" + +mkdir -p "$BIN_DIR" + +cat > "$REAL_GIT" <<'FAKEGIT' +#!/usr/bin/env bash +set -euo pipefail +: "${REAL_LOG:?}" +if [[ "${1:-}" == "-C" && "${3:-}" == "rev-parse" && "${4:-}" == "--show-toplevel" ]]; then + printf '%s\n' "${FAKE_TOPLEVEL:-$2}" + exit 0 +fi +if [[ "${1:-}" == "rev-parse" && "${2:-}" == "--show-toplevel" ]]; then + printf '%s\n' "${FAKE_TOPLEVEL:-$PWD}" + exit 0 +fi +if [[ "${1:-}" == "-C" && "${3:-}" == "branch" && "${4:-}" == "--show-current" ]]; then + printf '%s\n' "${FAKE_BRANCH:-main}" + exit 0 +fi +if [[ "${1:-}" == "-C" && "${3:-}" == "status" && "${4:-}" == "--porcelain=v1" ]]; then + printf '%s\n' "${FAKE_PORCELAIN_LINE:-?? new.txt}" + exit 0 +fi +printf 'REAL_GIT %s\n' "$*" >> "$REAL_LOG" +if [[ "${1:-}" == "-C" && ( "${3:-}" == "status" || "${3:-}" == "st" ) ]]; then + printf 'REAL_STATUS\n' +fi +if [[ "${1:-}" == "status" || "${1:-}" == "st" ]]; then + printf 'REAL_STATUS\n' +fi +FAKEGIT +chmod +x "$REAL_GIT" +cp "$REAL_GIT" "$BIN_DIR/git" + +cat > "$TRACKER" <<'FAKETRACKER' +#!/usr/bin/env bash +set -euo pipefail +: "${TRACKER_LOG:?}" +printf 'TRACKER %s\n' "$*" >> "$TRACKER_LOG" +if [[ "${TRACKER_FAIL:-0}" == "1" ]]; then + exit 7 +fi +FAKETRACKER +chmod +x "$TRACKER" + +export REAL_GIT_BIN="$REAL_GIT" +export TRACKER_SCRIPT="$TRACKER" +export REAL_LOG +export TRACKER_LOG +export FAKE_BRANCH="main" +export PATH="$BIN_DIR:$PATH" + +# RED trigger if shim does not exist yet. +# shellcheck disable=SC1091 +source "$REPO_ROOT/session/docs/snippets/git-tracking-shim.bash" + +: > "$REAL_LOG" +: > "$TRACKER_LOG" +FAKE_TOPLEVEL="$TRACKED_ROOT" git log --oneline >/dev/null + +grep -Fq "REAL_GIT log --oneline" "$REAL_LOG" +if [[ -s "$TRACKER_LOG" ]]; then + echo "tracker should not run for non-status commands" >&2 + exit 1 +fi + +: > "$REAL_LOG" +: > "$TRACKER_LOG" +FAKE_TOPLEVEL="$TRACKED_ROOT" git status >"$TMP_BASE/status.out" +grep -Fq "REAL_STATUS" "$TMP_BASE/status.out" +grep -Fq "REAL_GIT status" "$REAL_LOG" +grep -Fq "TRACKER --repo-root $TRACKED_ROOT --branch main" "$TRACKER_LOG" + +: > "$REAL_LOG" +: > "$TRACKER_LOG" +FAKE_TOPLEVEL="$TRACKED_ROOT" git st >/dev/null +grep -Fq "REAL_GIT st" "$REAL_LOG" +grep -Fq "TRACKER --repo-root $TRACKED_ROOT --branch main" "$TRACKER_LOG" + +: > "$REAL_LOG" +: > "$TRACKER_LOG" +FAKE_TOPLEVEL="$TRACKED_ROOT" git -C "$TRACKED_ROOT" status >"$TMP_BASE/status-with-global.out" +grep -Fq "REAL_STATUS" "$TMP_BASE/status-with-global.out" +grep -Fq "REAL_GIT -C $TRACKED_ROOT status" "$REAL_LOG" +grep -Fq "TRACKER --repo-root $TRACKED_ROOT --branch main" "$TRACKER_LOG" + +: > "$REAL_LOG" +: > "$TRACKER_LOG" +: > "$WARN_LOG" +REAL_GIT_BIN="git" FAKE_TOPLEVEL="$TRACKED_ROOT" git status >"$TMP_BASE/status-real-git-bin-git.out" 2>"$WARN_LOG" +grep -Fq "REAL_STATUS" "$TMP_BASE/status-real-git-bin-git.out" +grep -Fq "REAL_GIT status" "$REAL_LOG" +grep -Fq "unsafe REAL_GIT_BIN value: git" "$WARN_LOG" + +: > "$REAL_LOG" +: > "$TRACKER_LOG" +: > "$WARN_LOG" +REAL_GIT_BIN="fake-git" FAKE_TOPLEVEL="$TRACKED_ROOT" git status >"$TMP_BASE/status-real-git-bin-relative.out" 2>"$WARN_LOG" +grep -Fq "REAL_STATUS" "$TMP_BASE/status-real-git-bin-relative.out" +grep -Fq "REAL_GIT status" "$REAL_LOG" +grep -Fq "unsafe REAL_GIT_BIN value: fake-git" "$WARN_LOG" + +: > "$REAL_LOG" +: > "$TRACKER_LOG" +TRACKER_FAIL=1 FAKE_TOPLEVEL="$TRACKED_ROOT" git status >"$TMP_BASE/fail.out" 2>"$TMP_BASE/fail.err" +grep -Fq "REAL_STATUS" "$TMP_BASE/fail.out" +grep -Fq "[tracking-warning] failed to write tracking artifacts" "$TMP_BASE/fail.err" +grep -Fq "REAL_GIT status" "$REAL_LOG" + +: > "$REAL_LOG" +: > "$TRACKER_LOG" +FAKE_TOPLEVEL="$TMP_BASE/not-ros" git status >/dev/null +grep -Fq "REAL_GIT status" "$REAL_LOG" +if [[ -s "$TRACKER_LOG" ]]; then + echo "tracker should not run outside tracked root" >&2 + exit 1 +fi + +ORIG_HOME="${HOME:-}" +export HOME="$TMP_BASE/home" +mkdir -p "$HOME" +: > "$HOME/.bashrc" + +"$REPO_ROOT/scripts/install-git-tracking-shim.sh" +"$REPO_ROOT/scripts/install-git-tracking-shim.sh" + +start_count="$(grep -Fc '# >>> ros git tracking shim >>>' "$HOME/.bashrc")" +end_count="$(grep -Fc '# <<< ros git tracking shim <<<' "$HOME/.bashrc")" +line_count="$(grep -Fc '[ -f "/mnt/data/ros/session/docs/snippets/git-tracking-shim.bash" ] && source "/mnt/data/ros/session/docs/snippets/git-tracking-shim.bash"' "$HOME/.bashrc")" + +[[ "$start_count" -eq 1 ]] +[[ "$end_count" -eq 1 ]] +[[ "$line_count" -eq 1 ]] + +if [[ -n "$ORIG_HOME" ]]; then + export HOME="$ORIG_HOME" +fi diff --git a/scripts/tests/test-track-git-status.sh b/scripts/tests/test-track-git-status.sh new file mode 100755 index 0000000..f914b9f --- /dev/null +++ b/scripts/tests/test-track-git-status.sh @@ -0,0 +1,153 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +WORK="$ROOT/scripts/tests/.tmp-track-git-status.$$.$RANDOM" +SCRIPT="$ROOT/scripts/track-git-status.sh" + +rm -rf "$WORK" +mkdir -p "$WORK" +trap 'rm -rf "$WORK"' EXIT + +cat > "$WORK/porcelain.txt" <<'PORCELAIN' +?? new.txt + M changed.md + D removed.log +R old.py -> new.py +PORCELAIN + +if [[ -x "$SCRIPT" ]]; then + "$SCRIPT" \ + --repo-root "$ROOT" \ + --branch "main" \ + --porcelain-file "$WORK/porcelain.txt" \ + --tracking-dir "$WORK/.tracking" +fi + +test -f "$WORK/.tracking/latest.json" +test -f "$WORK/.tracking/history.ndjson" +test -f "$WORK/.tracking/latest.txt" + +jq -e '.untracked == ["new.txt"]' "$WORK/.tracking/latest.json" >/dev/null +jq -e '.modified == ["changed.md"]' "$WORK/.tracking/latest.json" >/dev/null +jq -e '.deleted == ["removed.log"]' "$WORK/.tracking/latest.json" >/dev/null +jq -e '.renamed == ["old.py -> new.py"]' "$WORK/.tracking/latest.json" >/dev/null + +jq -e '.untracked == ["new.txt"]' "$WORK/.tracking/history.ndjson" >/dev/null + +grep -q '^untracked: 1$' "$WORK/.tracking/latest.txt" +grep -q '^modified: 1$' "$WORK/.tracking/latest.txt" +grep -q '^deleted: 1$' "$WORK/.tracking/latest.txt" +grep -q '^renamed: 1$' "$WORK/.tracking/latest.txt" + +ROTATION_DIR="$WORK/.rotation" +mkdir -p "$ROTATION_DIR" +python3 - "$ROTATION_DIR/history.ndjson" <<'PY' +from pathlib import Path +import sys + +Path(sys.argv[1]).write_text("x" * 120, encoding="utf-8") +PY + +TRACKING_MAX_BYTES=10 "$SCRIPT" \ + --repo-root "$ROOT" \ + --branch "main" \ + --porcelain-file "$WORK/porcelain.txt" \ + --tracking-dir "$ROTATION_DIR" + +test -f "$ROTATION_DIR/history.ndjson" +ls "$ROTATION_DIR"/history.*.ndjson >/dev/null + +FAKE_BIN="$WORK/fake-bin" +mkdir -p "$FAKE_BIN" +cat > "$FAKE_BIN/date" <<'SH' +#!/usr/bin/env bash +if [[ "${1:-}" == "-u" ]]; then + shift +fi + +case "${1:-}" in + +%Y%m%dT%H%M%SZ) + printf '20000101T000000Z\n' + ;; + +%Y-%m-%dT%H:%M:%SZ) + printf '2000-01-01T00:00:00Z\n' + ;; + *) + /usr/bin/date -u "${1:-}" + ;; +esac +SH +chmod +x "$FAKE_BIN/date" + +COLLISION_DIR="$WORK/.rotation-collision" +mkdir -p "$COLLISION_DIR" +python3 - "$COLLISION_DIR/history.ndjson" <<'PY' +from pathlib import Path +import sys + +Path(sys.argv[1]).write_text("x" * 120, encoding="utf-8") +PY + +PATH="$FAKE_BIN:$PATH" TRACKING_MAX_BYTES=10 "$SCRIPT" \ + --repo-root "$ROOT" \ + --branch "main" \ + --porcelain-file "$WORK/porcelain.txt" \ + --tracking-dir "$COLLISION_DIR" + +PATH="$FAKE_BIN:$PATH" TRACKING_MAX_BYTES=10 "$SCRIPT" \ + --repo-root "$ROOT" \ + --branch "main" \ + --porcelain-file "$WORK/porcelain.txt" \ + --tracking-dir "$COLLISION_DIR" + +mapfile -t ROTATED_FILES < <(find "$COLLISION_DIR" -maxdepth 1 -type f -name 'history.*.ndjson' | sort) +test "${#ROTATED_FILES[@]}" -eq 2 + +CONCURRENT_DIR="$WORK/.concurrent-rotation" +mkdir -p "$CONCURRENT_DIR" +python3 - "$CONCURRENT_DIR/history.ndjson" <<'PY' +from pathlib import Path +import sys + +Path(sys.argv[1]).write_text("x" * 20000, encoding="utf-8") +PY + +RUNS=16 +set +e +for i in $(seq 1 "$RUNS"); do + ( + TRACKING_MAX_BYTES=10000 "$SCRIPT" \ + --repo-root "$ROOT" \ + --branch "main" \ + --porcelain-file "$WORK/porcelain.txt" \ + --tracking-dir "$CONCURRENT_DIR" \ + >/dev/null 2>"$CONCURRENT_DIR/err.$i" + echo $? > "$CONCURRENT_DIR/rc.$i" + ) & +done +wait +set -e + +FAILS=0 +for i in $(seq 1 "$RUNS"); do + rc="$(cat "$CONCURRENT_DIR/rc.$i")" + if [[ "$rc" -ne 0 ]]; then + FAILS=$((FAILS + 1)) + fi +done + +test "$FAILS" -eq 0 +test "$(wc -l < "$CONCURRENT_DIR/history.ndjson")" -eq "$RUNS" + +: > "$WORK/porcelain-empty.txt" +"$SCRIPT" \ + --repo-root "$ROOT" \ + --branch "main" \ + --porcelain-file "$WORK/porcelain-empty.txt" \ + --tracking-dir "$WORK/.tracking-empty" + +jq -e '.untracked == []' "$WORK/.tracking-empty/latest.json" >/dev/null +jq -e '.modified == []' "$WORK/.tracking-empty/latest.json" >/dev/null +jq -e '.deleted == []' "$WORK/.tracking-empty/latest.json" >/dev/null +jq -e '.renamed == []' "$WORK/.tracking-empty/latest.json" >/dev/null diff --git a/scripts/track-git-status.sh b/scripts/track-git-status.sh new file mode 100755 index 0000000..c372c4d --- /dev/null +++ b/scripts/track-git-status.sh @@ -0,0 +1,143 @@ +#!/usr/bin/env bash +set -euo pipefail + +REPO_ROOT="" +BRANCH="" +PORCELAIN_FILE="" +TRACKING_DIR="" +TRACKING_MAX_BYTES_RAW="${TRACKING_MAX_BYTES:-}" +if [[ "$TRACKING_MAX_BYTES_RAW" =~ ^[0-9]+$ ]]; then + MAX_BYTES="$TRACKING_MAX_BYTES_RAW" +else + MAX_BYTES="10485760" +fi + +while [[ $# -gt 0 ]]; do + case "$1" in + --repo-root) + REPO_ROOT="$2" + shift 2 + ;; + --branch) + BRANCH="$2" + shift 2 + ;; + --porcelain-file) + PORCELAIN_FILE="$2" + shift 2 + ;; + --tracking-dir) + TRACKING_DIR="$2" + shift 2 + ;; + *) + echo "Unknown arg: $1" >&2 + exit 2 + ;; + esac +done + +if [[ -z "$REPO_ROOT" || -z "$BRANCH" || -z "$PORCELAIN_FILE" || -z "$TRACKING_DIR" ]]; then + echo "Missing required args" >&2 + exit 2 +fi + +for required_cmd in jq flock; do + if ! command -v "$required_cmd" >/dev/null 2>&1; then + echo "Missing required command: $required_cmd. Please install '$required_cmd' and retry." >&2 + exit 2 + fi +done + +mkdir -p "$TRACKING_DIR" +LATEST_JSON="$TRACKING_DIR/latest.json" +HISTORY="$TRACKING_DIR/history.ndjson" +LATEST_TXT="$TRACKING_DIR/latest.txt" +LOCK_FILE="$TRACKING_DIR/.history.lock" + +UNTRACKED=() +MODIFIED=() +DELETED=() +RENAMED=() + +while IFS= read -r line; do + [[ -z "$line" ]] && continue + + if [[ "$line" == '?? '* ]]; then + UNTRACKED+=("${line:3}") + continue + fi + + status="${line:0:2}" + path="${line:3}" + x="${status:0:1}" + y="${status:1:1}" + + if [[ "$x" == "R" || "$y" == "R" ]]; then + RENAMED+=("$path") + fi + if [[ "$x" == "D" || "$y" == "D" ]]; then + DELETED+=("$path") + fi + if [[ "$x" == "M" || "$y" == "M" ]]; then + MODIFIED+=("$path") + fi +done < "$PORCELAIN_FILE" + +to_json_array() { + if [[ $# -eq 0 ]]; then + printf '[]' + else + printf '%s\n' "$@" | jq -Rsc 'split("\n")[:-1]' + fi +} + +json="$(jq -cn \ + --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ + --arg repo "$REPO_ROOT" \ + --arg branch "$BRANCH" \ + --argjson untracked "$(to_json_array "${UNTRACKED[@]}")" \ + --argjson modified "$(to_json_array "${MODIFIED[@]}")" \ + --argjson deleted "$(to_json_array "${DELETED[@]}")" \ + --argjson renamed "$(to_json_array "${RENAMED[@]}")" \ + '{timestamp:$ts,repo_root:$repo,branch:$branch,untracked:$untracked,modified:$modified,deleted:$deleted,renamed:$renamed}')" + +{ + flock -x 9 + + if [[ -f "$HISTORY" ]]; then + size="$(wc -c < "$HISTORY" | tr -d '[:space:]')" + if [[ "$size" -gt "$MAX_BYTES" ]]; then + rotation_ts="$(date -u +%Y%m%dT%H%M%SZ)" + rotation_ns="$(date -u +%N)" + rotation_base="$TRACKING_DIR/history.${rotation_ts}.${rotation_ns}.$$" + rotation_target="${rotation_base}.ndjson" + rotation_index=0 + while [[ -e "$rotation_target" ]]; do + rotation_index=$((rotation_index + 1)) + rotation_target="${rotation_base}.${rotation_index}.ndjson" + done + + if [[ -f "$HISTORY" ]]; then + if ! mv "$HISTORY" "$rotation_target"; then + if [[ -f "$HISTORY" ]]; then + exit 1 + fi + fi + fi + fi + fi + + printf '%s\n' "$json" > "$LATEST_JSON" + printf '%s\n' "$json" >> "$HISTORY" +} 9>>"$LOCK_FILE" + +{ + echo "timestamp: $(jq -r '.timestamp' "$LATEST_JSON")" + echo "repo: $(jq -r '.repo_root' "$LATEST_JSON")" + echo "branch: $(jq -r '.branch' "$LATEST_JSON")" + echo "untracked: $(jq -r '.untracked | length' "$LATEST_JSON")" + echo "modified: $(jq -r '.modified | length' "$LATEST_JSON")" + echo "deleted: $(jq -r '.deleted | length' "$LATEST_JSON")" + echo "renamed: $(jq -r '.renamed | length' "$LATEST_JSON")" +} > "$LATEST_TXT" diff --git a/session/docs/snippets/git-tracking-shim.bash b/session/docs/snippets/git-tracking-shim.bash new file mode 100644 index 0000000..ef0dc0f --- /dev/null +++ b/session/docs/snippets/git-tracking-shim.bash @@ -0,0 +1,123 @@ +_git_tracking_resolve_real_git_bin() { + local configured resolved + configured="${REAL_GIT_BIN:-}" + + if [[ -n "$configured" ]]; then + if [[ "$configured" != /* ]]; then + printf '[tracking-warning] unsafe REAL_GIT_BIN value: %s\n' "$configured" >&2 + elif [[ ! -x "$configured" ]]; then + printf '[tracking-warning] REAL_GIT_BIN is not executable: %s\n' "$configured" >&2 + else + printf '%s\n' "$configured" + return + fi + fi + + resolved="$(type -P git 2>/dev/null || true)" + if [[ -z "$resolved" || ! -x "$resolved" ]]; then + resolved="/usr/bin/git" + fi + printf '%s\n' "$resolved" +} + +_git_tracking_effective_command() { + local arg + while (($#)); do + arg="$1" + case "$arg" in + --) + shift + printf '%s\n' "${1:-}" + return + ;; + -C|-c|--exec-path|--git-dir|--work-tree|--namespace|--super-prefix|--config-env) + shift 2 + ;; + --exec-path=*|--git-dir=*|--work-tree=*|--namespace=*|--super-prefix=*|--config-env=*|--list-cmds=*|\ + -p|--paginate|--no-pager|--no-replace-objects|--bare|--literal-pathspecs|--no-literal-pathspecs|\ + --glob-pathspecs|--noglob-pathspecs|--icase-pathspecs) + shift + ;; + -*) + shift + ;; + *) + printf '%s\n' "$arg" + return + ;; + esac + done +} + +_git_tracking_global_args() { + local arg + while (($#)); do + arg="$1" + case "$arg" in + --) + return + ;; + -C|-c|--exec-path|--git-dir|--work-tree|--namespace|--super-prefix|--config-env) + printf '%s\n' "$arg" + shift + if (($#)); then + printf '%s\n' "$1" + fi + shift + ;; + --exec-path=*|--git-dir=*|--work-tree=*|--namespace=*|--super-prefix=*|--config-env=*|--list-cmds=*|\ + -p|--paginate|--no-pager|--no-replace-objects|--bare|--literal-pathspecs|--no-literal-pathspecs|\ + --glob-pathspecs|--noglob-pathspecs|--icase-pathspecs) + printf '%s\n' "$arg" + shift + ;; + -*) + printf '%s\n' "$arg" + shift + ;; + *) + return + ;; + esac + done +} + +git() { + local real_git tracker tracked_root cmd + local -a global_args + real_git="$(_git_tracking_resolve_real_git_bin)" + tracker="${TRACKER_SCRIPT:-/mnt/data/ros/scripts/track-git-status.sh}" + tracked_root="/mnt/data/ros" + cmd="$(_git_tracking_effective_command "$@")" + mapfile -t global_args < <(_git_tracking_global_args "$@") + + if [[ "$cmd" == "status" || "$cmd" == "st" ]]; then + local top + top="$("$real_git" "${global_args[@]}" rev-parse --show-toplevel 2>/dev/null || true)" + + if [[ "$top" == "$tracked_root" ]]; then + local tracking_dir branch porcelain_file + tracking_dir="$top/.tracking" + branch="$("$real_git" -C "$top" branch --show-current 2>/dev/null || printf 'unknown')" + porcelain_file="$tracking_dir/.git-status-porcelain.$$.$RANDOM" + + mkdir -p "$tracking_dir" + if ! "$real_git" -C "$top" status --porcelain=v1 >"$porcelain_file" 2>/dev/null; then + : >"$porcelain_file" + fi + + if ! "$tracker" \ + --repo-root "$top" \ + --branch "$branch" \ + --porcelain-file "$porcelain_file" \ + --tracking-dir "$tracking_dir" \ + >/dev/null 2>&1; then + printf '[tracking-warning] failed to write tracking artifacts\n' >&2 + fi + + rm -f "$porcelain_file" + fi + fi + + "$real_git" "$@" +}