Skip to content
Merged
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
6 changes: 3 additions & 3 deletions docs/trajectories/typescript-bun-migration/RESUME.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Trajectory — TypeScript / Bun migration

**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.
**Current blocker**: None.
**Next concrete action**: Pick a coherent next slice from Bucket B (16 files remaining). Per Gate B: read-only scope first, then re-verify the layered baseline currency before first mutating action.
**Next concrete action**: Pick a coherent next slice from Bucket B (14 files remaining). Per Gate B: read-only scope first, then re-verify the layered baseline currency before first mutating action.
**Last updated**: 2026-04-30

## Why this trajectory exists
Expand Down
23 changes: 23 additions & 0 deletions docs/trajectories/typescript-bun-migration/slice-audits.md
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,29 @@ Per-port pattern checklist:

Slice 6 passes audit. No new patterns recorded — all reused from prior slices.

## Slice 10 — 2 ports (counterweight-cluster + first write-side) (PR pending — `lane-b/ts-bun-slice-10-counterweight-audit-2026-04-30`)

**Slice files**:

- `tools/hygiene/counterweight-audit.{sh→ts}` (Otto-278 cadenced re-read)
- `tools/hygiene/append-tick-history-row.{sh→ts}` (chronological-tail-append validator)

**Comparison points**: identical to slice 6/7/8/9. Within Gate B 30-day window.

### Code-pattern audit (per-port)

- **`counterweight-audit.ts`** (253 → 326 lines): BSD/GNU stat-flavor probe replaced with `statSync().mtimeMs`. YAML frontmatter awk parser → manual fence-aware char walk. Arg parser refactored into `classifyArg` helper + ArgStep tagged union. `mktemp` + sort-rn pipeline replaced with in-memory array sort.
- **`append-tick-history-row.ts`** (81 → 106 lines): bash `[[ =~ ]]` regex preserved as `TS_PREFIX_RE.exec`. `grep -oE | sort | tail -1` replaced with `findLatestTimestamp` helper. ISO-8601 sort uses `localeCompare`.

### Equivalence audit

- **`counterweight-audit`**: byte-equivalent on default cadence + all explicit cadence levels.
- **`append-tick-history-row`**: byte-equivalent on usage error + malformed-row + out-of-order-timestamp paths modulo script self-reference (.sh vs .ts).

### Outcome

Slice 10 passes audit. **Counterweight-cluster opened** (Otto-278 cadenced inspect). **First write-side script ported** (append-tick-history-row) — confirms write-side equivalence-test pattern works. Bucket B 16 → 14.

## Slice 9 — 3 ports (agency-signature-pair cluster + snapshot-pinning) (PR pending — `lane-b/ts-bun-slice-9-agencysignature-pair-2026-04-30`)

**Slice files**:
Expand Down
106 changes: 106 additions & 0 deletions tools/hygiene/append-tick-history-row.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#!/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).
//
// TypeScript+Bun port of append-tick-history-row.sh, slice 10 of the
// TS+Bun migration. See docs/best-practices/repo-scripting.md.
//
// Usage:
// bun tools/hygiene/append-tick-history-row.ts "FULL_ROW_TEXT"
//
// The argument is the entire row including leading `| ` and trailing
// `|`. Caller is responsible for row content (signal-in-signal-out);
// this script is a dumb pipe + validator.
//
// Exit codes:
// 0 appended successfully
// 1 row malformed OR timestamp out of order
// 2 wrong number of arguments

Comment on lines +16 to +20
import { appendFileSync, readFileSync } from "node:fs";
import { spawnSync } from "node:child_process";

type ExitCode = 0 | 1 | 2;

const SPAWN_MAX_BUFFER = 64 * 1024 * 1024;

const TS_PREFIX_RE = /^\| (\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z) /;
const ROW_TS_RE = /^\| (\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z)/;

function repoRoot(): string {
// eslint-disable-next-line sonarjs/no-os-command-from-path
const result = spawnSync("git", ["rev-parse", "--show-toplevel"], {
encoding: "utf8",
maxBuffer: SPAWN_MAX_BUFFER,
});
if (result.status !== 0) return process.cwd();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

return result.stdout.trim();
}

function findLatestTimestamp(content: string): string {
const timestamps: string[] = [];
for (const line of content.split("\n")) {
const m = ROW_TS_RE.exec(line);
if (m !== null) timestamps.push(m[1] ?? "");
}
// ISO-8601 is lex-sortable; sort + take last for "latest".
timestamps.sort((a, b) => a.localeCompare(b));
return timestamps.length === 0 ? "" : (timestamps.at(-1) ?? "");
}

export function main(argv: readonly string[]): ExitCode {
if (argv.length !== 1) {
process.stderr.write(
"usage: append-tick-history-row.ts \"<full row text including leading | and trailing |>\"\n",
);
return 2;
}
const row = argv[0];
if (row === undefined) return 2;

const m = TS_PREFIX_RE.exec(row);
if (m === null) {
process.stderr.write("ERROR: row must start with '| YYYY-MM-DDTHH:MM:SSZ '\n");
process.stderr.write(`got: ${row.slice(0, 80)}...\n`);
return 1;
}
const newTs = m[1] ?? "";

const root = repoRoot();
const tickFile = `${root}/docs/hygiene-history/loop-tick-history.md`;

let existing: string;
try {
existing = readFileSync(tickFile, "utf8");
} catch {
process.stderr.write(`ERROR: cannot read tick-history at ${tickFile}\n`);
return 1;
}

const latestTs = findLatestTimestamp(existing);
if (latestTs !== "" && newTs < latestTs) {
process.stderr.write(
`ERROR: new row timestamp ${newTs} is BEFORE latest existing ${latestTs}\n`,
);
process.stderr.write("\n");
process.stderr.write(
"Tick-history is append-only with non-decreasing timestamps.\n",
);
process.stderr.write(
"If your row is for a past tick, you have to either:\n",
);
process.stderr.write(" (a) update the timestamp to current UTC (preferred),\n");
process.stderr.write(" (b) file an ADR explaining the back-dated correction\n");
process.stderr.write(" and use a correction-row pattern per Otto-229.\n");
return 1;
}

appendFileSync(tickFile, `${row}\n`);
process.stdout.write(`OK: appended row at ${newTs}\n`);
return 0;
}

if (import.meta.main) {
process.exit(main(process.argv.slice(2)));
}
Loading
Loading