Conversation
First script of slice 10. Cadenced counterweight-memory audit
(Otto-278) — emits per-counterweight audit questions for
`memory/*otto_*.md` files in mtime-newest-first order.
Byte-equivalent against bash original under default cadence
(quick, count=3). Diff against current repo state shows zero
divergence.
Mechanical changes:
- bash glob `memory/*otto_*.md` → readdirSync filter on
`includes('otto_') && endsWith('.md')`
- bash BSD/GNU stat probe (stat -f %m vs stat -c %Y) replaced
with statSync().mtimeMs (single platform-agnostic Node API)
- bash YAML frontmatter awk parser → manual fence-aware char
walk in extractNameField (avoids sonarjs slow-regex flag on
the trailing `(.*)$` capture)
- bash arg parser with case statement → classifyArg helper +
ArgStep tagged union to keep parseArgs cognitive-complexity
under sonarjs threshold (15)
- bash `mktemp` + sort-rn pipeline replaced with in-memory
array sort (faster, no temp file)
- All output strings preserved verbatim including 'newest first'
language, em-dashes, audit-question wording
Lint-clean (eslint + sonarjs + tsc).
There was a problem hiding this comment.
Pull request overview
Ports two repo hygiene scripts from bash to TypeScript/Bun as part of the ongoing TS/Bun migration, and records the slice-10 audit/trajectory status in docs.
Changes:
- Add
tools/hygiene/counterweight-audit.ts(cadenced counterweight-memory audit output generator). - Add
tools/hygiene/append-tick-history-row.ts(tick-history row validator + append helper). - Update TS/Bun migration trajectory docs for slice-10 status/audit notes.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| tools/hygiene/counterweight-audit.ts | New TS/Bun implementation of the counterweight audit report generator. |
| tools/hygiene/append-tick-history-row.ts | New TS/Bun implementation of the tick-history append validator/appender. |
| docs/trajectories/typescript-bun-migration/slice-audits.md | Appends slice-10 audit notes and equivalence summary. |
| docs/trajectories/typescript-bun-migration/RESUME.md | Updates milestone counts/status and slice-10 next action notes. |
| #!/usr/bin/env bun | ||
| // append-tick-history-row.ts — appends a row to the loop-tick-history | ||
| // using simple append (avoids the Edit-tool's reverse-chronological | ||
| // bug shape Aaron flagged 2026-04-26). |
| // Exit codes: | ||
| // 0 appended successfully | ||
| // 1 row malformed OR timestamp out of order | ||
| // 2 wrong number of arguments | ||
|
|
| process.stdout.write("the file and read the rule body + maintainer quote, then\n"); | ||
| process.stdout.write("answer the per-counterweight audit questions below.\n"); | ||
| process.stdout.write("\n"); | ||
| process.stdout.write("_Tool: `tools/hygiene/counterweight-audit.sh` (Otto-278\n"); |
| // Exit codes: | ||
| // 0 normal completion | ||
| // 2 usage error (unknown flag, missing value, invalid cadence, etc.) | ||
|
|
||
| import { readdirSync, readFileSync, statSync } from "node:fs"; | ||
| import { join } from "node:path"; | ||
| import { spawnSync } from "node:child_process"; | ||
|
|
||
| type ExitCode = 0 | 2; |
| **Status**: Active (Lane B slice 8 merged — [#880](https://github.com/Lucent-Financial-Group/Zeta/pull/880), commit `988de70`) | ||
| **Milestone**: 28 hygiene/lint/audit scripts ported (2 from #849 + 3 from #866 + 3 from #868 + 3 from #870 + 2 from #872 + 3 from #874 + 3 from #876 + 3 from #878 + 3 from #880 + 3 in-flight in slice-9). **Cluster H complete (5/5)** in #878 + #880; slice-9 opens **agency-signature-pair cluster** (validate-agencysignature-pr-body + audit-agencysignature-main-tip — paired ferry-7 enforcement-instrument set per Amara) + capture-tick-snapshot (snapshot-pinning per Amara 4th-ferry). 16 Bucket B files remain. | ||
| **Status**: Active (Lane B slice 9 merged — [#882](https://github.com/Lucent-Financial-Group/Zeta/pull/882), commit `02266a7`) | ||
| **Milestone**: 31 hygiene/lint/audit scripts ported (2 from #849 + 3 from #866 + 3 from #868 + 3 from #870 + 2 from #872 + 3 from #874 + 3 from #876 + 3 from #878 + 3 from #880 + 3 from #882 + 2 in-flight in slice-10). **Cluster H complete** + agency-signature-pair complete; slice-10 opens **counterweight-cluster + write-side-tools** (counterweight-audit + append-tick-history-row). 14 Bucket B files remain. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 460ee5d743
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| encoding: "utf8", | ||
| maxBuffer: SPAWN_MAX_BUFFER, | ||
| }); | ||
| if (result.status !== 0) return process.cwd(); |
There was a problem hiding this comment.
Preserve git-root failure behavior before appending history
The TypeScript port falls back to process.cwd() when git rev-parse --show-toplevel fails, but the shell script exited immediately on that failure (set -e with REPO_ROOT="$(git ...)"). This changes a hard-fail into a potentially mutating path: if the command is run outside a repo (or without git) in a directory that happens to contain docs/hygiene-history/loop-tick-history.md, it can append to the wrong file tree instead of stopping.
Useful? React with 👍 / 👎.
…884) * ts(slice-11, wip 1/N): port backfill_dv2_frontmatter (.sh→.ts) First script of slice 11. Mechanical DV-2.0 frontmatter backfill for SKILL.md files. Computes 5 missing fields from git history (record_source / load_datetime / last_updated / status / bp_rules_cited) and injects before closing frontmatter fence. Byte-equivalent against bash original on --dry-run mode. Mechanical changes: - bash awk frontmatter parse → fieldPresent + dashCount helpers - bash compute_record_source heuristic (round-N regex + author- date fallback) preserved as computeRecordSource - bash mktemp + awk inject + mv rename → readFileSync + injectBeforeSecondFence + writeFileSync - bash 'INJECT_BLOB env-passing' awk pattern → in-memory string array - bash --all glob (find -maxdepth 2) → readdirSync filter - All output strings preserved verbatim Lint-clean (eslint + sonarjs + tsc). * ts(slice-11, wip 2/N): port audit-packages (.sh→.ts) Second script of slice 11. Network-dependent NuGet feed audit: checks every Directory.Packages.props PackageVersion entry against `dotnet package search --exact-match`. Mechanical changes: - bash grep+sed extraction → PACKAGE_RE.exec loop yielding PackageEntry tuples - bash awk pipe-table parse → cols.split('|').map(trim) + col2 match check; preserves 'last matching row' semantics - bash printf '%-35s %-15s ...' → pad helper - 3 statuses preserved: ✓ up-to-date / ? couldn't query / ⚠ bump available - Exit-code semantics preserved: 0 = all queryable on latest, 1 = one or more bumps available Network-dependent so byte-equivalence requires offline-mode testing (both bash + TS produce '?' for all packages when `dotnet package search` fails — verified locally). Lint-clean (eslint + sonarjs + tsc). * trajectory(ts-bun): slice 11 audit substrate + RESUME tracker - slice-audits.md: append slice-11 audit (2 ports — backfill_dv2_frontmatter + audit-packages). - RESUME.md: bump slice-9 → slice-10-merged (#883, commit 271bc38). Milestone 31 → 33. Bucket B 14 → 12. * review(slice-11): address PR #884 CodeQL TOCTOU findings CodeQL flagged 2 file-system-race-condition findings on backfill_dv2_frontmatter.ts where stat-then-read patterns (statSync(path).isFile() followed by later readFileSync) could race. Refactored to: 1. processOne: removed initial statSync gate; readFileSync directly with try/catch + ENOENT/EISDIR detection on the error.code property to map to warn/error outcomes. Preserves the original bash 'skip non-file' UX without the race window. 2. findAllSkillFiles: removed statSync probe of candidate paths; readdirSync only filters by directory entries; processOne handles missing/non-file paths gracefully via its try/catch. Equivalence preserved: --dry-run output unchanged on a fresh SKILL.md fixture. Lint-clean (eslint + sonarjs + tsc). * review(slice-11): restore atomic rewrite per Codex P2 Codex flagged backfill_dv2_frontmatter.ts losing the bash original's atomic-rewrite invariant: bash used `mktemp` + `mv` so a crash/kill/disk-full mid-write leaves the source file intact. The TS port's writeFileSync truncates in place — a real regression vs bash. Restored: write to sibling tmp file (`<path>.tmp.<pid>.<ts>`) then renameSync to target. Same-filesystem rename is atomic on POSIX. Cleanup on failure: unlink the tmp + return structured error outcome (exitCode 2). * review(slice-11): address PR #884 round-3 threads Three real fixes: - backfill_dv2_frontmatter: chdir to repoRoot() before --all scan (was cwd-dependent; both bash + TS had the bug, TS fixes at the entry point) + restored full canonical script path in usage message to match emitHelp() - slice-audits.md: corrected audit-packages.ts line count '51 → 154' to '51 → 143' (actual wc -l) Three pushed back as intentional bash-equivalence: - audit-packages stdout vs stderr — bash uses echo to stdout - audit-packages exit-code 64 — bash uses 1 - backfill gitOutput silently returns empty — bash awk on empty input behaves identically (both produce empty injected values) Atomic-rewrite duplicate finding (already addressed in 5baf773) resolved as duplicate. * review(slice-11): audit-packages repoRoot via import.meta.url per Codex P2 Codex flagged that the audit-packages.ts repoRoot using `git rev-parse --show-toplevel` with cwd fallback regresses invocation semantics from the bash original (`cd "$(dirname "$0")/.." && pwd`). Bash resolves the script location and walks up; TS+git-rev-parse fails outside a git checkout (falls back to wrong cwd). Fixed: switch to `fileURLToPath(import.meta.url)` + `resolve` to match bash's script-relative resolution. Now works from any caller directory and outside git checkouts. * review(slice-11): defensive rename fallback per Codex P0 Codex flagged renameSync(tmpPath, file) as a potential Windows data-loss issue (older Node fs.rename refused to overwrite on Windows). Modern Node/Bun renameSync DOES overwrite atomically on all platforms, but edge cases (Windows file locks from open editors, permission-restricted network shares) can still fail. Added defense-in-depth fallback: catch the renameSync failure, unlink the destination, then retry rename. Loses atomicity for the failure window but recovers correctness. Bash 'mv' has the same fragility — same UX as bash, just more robust. * review(slice-11): preserve-original-on-failure + fail-on-empty-parse per Codex P1+P2 P1 backfill_dv2_frontmatter (preserve-original-on-failure): Removed the unlink-before-retry fallback that risked deleting the original SKILL.md without successfully writing the new content. On rename failure, return error with tmp-preserved message; do NOT touch the original. Modern Node/Bun renameSync handles the common case atomically; rare failures (Windows file lock, perms, disk-full) leave the original intact + tmp recoverable. P2 audit-packages (fail-on-empty-parse): If PACKAGE_RE.exec yields 0 entries on a non-empty Directory.Packages.props, the regex has likely drifted from the file format. Silent success hides this. Now returns exit 1 with clear error pointing at the regex-drift suspicion. * review(slice-11): fix RESUME milestone math per Copilot Headline said '33 hygiene/lint/audit scripts ported' but the parenthesized addends summed to 32 (including the 2 in-flight). Two issues conflated: 1. 'ported' implies merged but the count included in-flight. 2. Off-by-one — 11 merged-PR contributions sum to 30, not 31. Fixed: '30 ported + 2 in-flight = 32 total' with explicit delineation between merged and in-flight.
Summary
Files
Plus:
Equivalence
Lint + types
Both files pass:
Test plan
🤖 Generated with Claude Code