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 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.
**Status**: Active (Lane B slice 10 merged — [#883](https://github.com/Lucent-Financial-Group/Zeta/pull/883), commit `271bc38`)
**Milestone**: 30 ported + 2 in-flight = 32 total (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 from #883 = 30 merged; +2 in-flight in slice-11). Slice-11 opens **skill-catalog cluster + nuget audit** (backfill_dv2_frontmatter + audit-packages). 12 Bucket B files remain.
**Current blocker**: None.
**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.
**Next concrete action**: Pick a coherent next slice from Bucket B (12 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 11 — 2 ports (skill-catalog cluster + nuget audit) (PR pending — `lane-b/ts-bun-slice-11-dv2-frontmatter-backfill-2026-04-30`)

**Slice files**:

- `tools/skill-catalog/backfill_dv2_frontmatter.{sh→ts}` (DV-2.0 frontmatter mechanical backfill)
- `tools/audit-packages.{sh→ts}` (NuGet feed audit per Directory.Packages.props entry)

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

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

- **`backfill_dv2_frontmatter.ts`** (209 → 316 lines): bash awk frontmatter parse → `fieldPresent` + `dashCount` helpers. Bash `compute_record_source` heuristic preserved. Bash `mktemp` + awk inject + mv rename → `readFileSync` + `injectBeforeSecondFence` + `writeFileSync`. Bash `INJECT_BLOB` env-passing pattern → in-memory string array. `--all` find-glob → readdirSync filter.
- **`audit-packages.ts`** (51 → 143 lines): bash grep+sed extraction → `PACKAGE_RE.exec` loop. Bash awk pipe-table parse → `cols.split('|').map(trim)` + col2 match check (preserves "last matching row" semantics). Three statuses preserved: `✓ up-to-date` / `? couldn't query` / `⚠ bump available`.

### Equivalence audit

- **`backfill_dv2_frontmatter`**: byte-equivalent on `--dry-run` mode. Write-side path verified by snapshot-test.
- **`audit-packages`**: network-dependent; offline-mode produces '?' for all packages in both bash + TS (verified locally).

### Outcome

Slice 11 passes audit. Skill-catalog cluster opened + NuGet audit added. Bucket B 14 → 12.

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

**Slice files**:
Expand Down
153 changes: 153 additions & 0 deletions tools/audit-packages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
#!/usr/bin/env bun
// audit-packages.ts — checks every Directory.Packages.props entry
// against the NuGet feed via `dotnet package search`.
//
// TypeScript+Bun port of audit-packages.sh, slice 11 of the TS+Bun
// migration. See docs/best-practices/repo-scripting.md.
//
// Network-dependent: shells out to `dotnet package search <id>
// --exact-match` per package; non-deterministic without a NuGet
// snapshot. Equivalence-test via the no-network failure path
// (each `latest` falls back to `?`).
//
// Usage:
// bun tools/audit-packages.ts
//
// Exit codes:
// 0 all queryable packages on latest
// 1 one or more packages have a bump available

import { readFileSync } from "node:fs";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { spawnSync } from "node:child_process";

type ExitCode = 0 | 1;

const SPAWN_MAX_BUFFER = 64 * 1024 * 1024;

const PACKAGE_RE = /PackageVersion Include="([^"]+)" Version="([^"]+)"/g;

interface PackageEntry {
readonly id: string;
readonly pinned: string;
}

function repoRoot(): string {
// Match bash original: REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)".
// Bash resolves the script's path, walks up one (tools/.. → repo root),
// and cds. The TS port mirrors this via import.meta.url so the script
// works regardless of caller cwd, the same as the bash behavior.
const scriptPath = fileURLToPath(import.meta.url);
return resolve(dirname(scriptPath), "..");
}

function parsePackages(content: string): readonly PackageEntry[] {
const out: PackageEntry[] = [];
PACKAGE_RE.lastIndex = 0;
let m: RegExpExecArray | null = PACKAGE_RE.exec(content);
while (m !== null) {
out.push({ id: m[1] ?? "", pinned: m[2] ?? "" });
m = PACKAGE_RE.exec(content);
}
return out;
}

function queryLatest(pkgId: string): string {
const args = ["package", "search", pkgId, "--exact-match"];
// eslint-disable-next-line sonarjs/no-os-command-from-path
const result = spawnSync("dotnet", args, {
encoding: "utf8",
maxBuffer: SPAWN_MAX_BUFFER,
});
if (result.status !== 0) return "";
Comment thread
AceHack marked this conversation as resolved.
let last = "";
for (const line of result.stdout.split("\n")) {
const cols = line.split("|").map((c) => c.trim());
const col2 = cols[1] ?? "";
if (col2 !== pkgId) continue;
last = cols[2] ?? "";
}
return last;
}

function pad(s: string, width: number): string {
return s.length >= width ? s : s + " ".repeat(width - s.length);
}

interface Report {
readonly id: string;
readonly pinned: string;
readonly latest: string;
readonly marker: string;
}

function classify(
pinned: string,
latest: string,
): { marker: string; failed: boolean } {
if (latest === pinned) return { marker: "✓ up-to-date", failed: false };
if (latest === "?") return { marker: "? couldn't query", failed: false };
return { marker: "⚠ bump available", failed: true };
}

export function main(): ExitCode {
const root = repoRoot();
const propsPath = join(root, "Directory.Packages.props");
let content: string;
try {
content = readFileSync(propsPath, "utf8");
} catch {
process.stderr.write(`error: cannot read ${propsPath}\n`);
return 1;
}
Comment thread
AceHack marked this conversation as resolved.
const packages = parsePackages(content);
Comment thread
AceHack marked this conversation as resolved.

// If parsing yields zero entries on a non-empty Directory.Packages.props,
// the regex has likely drifted from the file format — silent success
// would hide real audit failure (Codex P2). Fail with a clear message.
if (packages.length === 0) {
process.stderr.write(
`error: parsed 0 PackageVersion entries from ${propsPath} — regex may be stale relative to file format\n`,
);
return 1;
}

process.stdout.write("=== Dbsp package audit ===\n");
process.stdout.write(
`${pad("Package", 35)} ${pad("Pinned", 15)} ${pad("Latest", 15)} Status\n`,
);
process.stdout.write(
`${pad("-------", 35)} ${pad("------", 15)} ${pad("------", 15)} ------\n`,
);
Comment thread
AceHack marked this conversation as resolved.

let failed = false;
const reports: Report[] = [];
for (const pkg of packages) {
const latest = queryLatest(pkg.id);
const display = latest === "" ? "?" : latest;
const { marker, failed: thisFailed } = classify(pkg.pinned, display);
if (thisFailed) failed = true;
reports.push({ id: pkg.id, pinned: pkg.pinned, latest: display, marker });
}

for (const r of reports) {
process.stdout.write(
`${pad(r.id, 35)} ${pad(r.pinned, 15)} ${pad(r.latest, 15)} ${r.marker}\n`,
);
}

process.stdout.write("\n");
if (!failed) {
process.stdout.write("✓ All queryable packages on latest.\n");
return 0;
}
process.stdout.write(
"⚠ Bumps available — update Directory.Packages.props and re-run tests.\n",
);
return 1;
}

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