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
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
---
id: B-0086
priority: P2
status: open
title: Port tools/hygiene Python scripts to TypeScript/Bun (factory-default; AI/ML carve-out applies)
tier: factory-tooling
effort: M
ask: maintainer Aaron 2026-04-28T19:56Z (TypeScript/Bun-as-default substrate framing)
created: 2026-04-28
last_updated: 2026-04-28
composes_with:
- B-0061
tags: [aaron-2026-04-28, typescript, bun, factory-default, language-discipline, port-candidate]
---

# B-0086 — Port `tools/hygiene/` Python scripts to TypeScript/Bun

## Source

Aaron 2026-04-28T19:56Z verbatim (during autonomous-loop tick
right after `sort-tick-history-canonical.py` was used to fix a
chronological-order lint failure on PR #684):

> *"sort-tick-history-canonical.py eventually we are going to use
> the typescript like ../scratch unless this is AL/ML AND is a
> better fit for python? typescript/bun being our default, we
> need to decide when to step out on typescript carefully."*

Substrate captured durably in
`memory/feedback_typescript_bun_default_step_out_carefully_aaron_2026_04_28.md`.

## Scope

Port the following non-AI/ML Python scripts under `tools/` to
TypeScript on Bun, following the existing pattern at
`tools/invariant-substrates/tally.ts` (script entry in root
`package.json` under `scripts:`):

- `tools/hygiene/sort-tick-history-canonical.py` — canonical
chronological sort + dedupe of `docs/hygiene-history/loop-tick-history.md`.
Pure markdown/string manipulation; trivial Bun port.
- `tools/hygiene/fix-markdown-md032-md026.py` — markdown
formatting fixes (MD032 + MD026 rules). Pure string/regex
manipulation; trivial Bun port.
- (Audit other `tools/**/*.py` per future review pass.)

## Deferred — port doesn't have to be immediate

Per Aaron's framing *"we need to decide when to step out on
typescript carefully"*, the discipline applies to:

1. **All NEW tooling**: default to TypeScript/Bun.
2. **Existing Python tooling**: port when changes are substantive;
leave alone otherwise (rewrite churn isn't free).

This row tracks the **port-when-it-makes-sense** plan.
Triggering events that make it "make sense":

- Python script needs a substantive feature add (port instead of
extend).
- Python toolchain breaks on a contributor's machine (port to
remove the cross-toolchain dependency).
- Audit pass identifies enough small Python scripts that a
bundle-port amortizes the work.

## Why P2 (not P0/P1)

- **Existing Python tools work** — no acute breakage; the
discipline applies forward, not as emergency cleanup.
- **Lint hooks already enforce** — `tools/hygiene/sort-tick-history-canonical.py`
is invoked from CI lint job; whether it's Python or TypeScript
doesn't change correctness.
- **Heaviest tooling already TypeScript** — `tally.ts` (the
invariant-substrate analyzer) is already TypeScript; the
Python residue is small + scoped.

## Acceptance criteria

- [ ] `tools/hygiene/sort-tick-history-canonical.py` ported to
`tools/hygiene/sort-tick-history-canonical.ts` (or similar)
with `package.json` script entry.
- [ ] `tools/hygiene/fix-markdown-md032-md026.py` ported similarly.
- [ ] All callers (CI workflows, pre-commit hooks, manual usage
in tick-history workflow) updated to invoke the TS version.
- [ ] Python originals deleted (the discipline is consistency,
not parallel-implementations).
- [ ] One trial round of canonical-sort run via the TS port to
verify equivalent output on `loop-tick-history.md`.

## Composes with

- `memory/feedback_typescript_bun_default_step_out_carefully_aaron_2026_04_28.md`
— the substrate this row operationalizes.
- `tools/invariant-substrates/tally.ts` — the existing TypeScript
pattern to mirror.
- `package.json` — root scripts entry point + `bun@1.3.13` pin.
- B-0061 (per-row backlog migration) — same shape of "incremental
migration with discipline applied to NEW work + opportunistic
port on existing".

## What this is NOT

- **NOT a port-all-Python directive.** AI/ML scripts (if any
exist or are added later) keep Python per the carve-out.
- **NOT urgent factory-fitness work.** P2 reflects the
do-when-substantive cadence, not "ship next round".
- **NOT a TypeScript-everywhere-in-Zeta directive.** F# is the
language for the Zeta library proper; TypeScript is the
language for tooling around it. Two-tier choice.

## Pickup notes for future-Otto

When picking this up:

1. Read the substrate memory first for the discipline framing.
2. Start with `sort-tick-history-canonical.py` (smaller surface,
pure markdown manipulation, has a CI lint job that exercises
it — easy to verify equivalence).
3. Mirror the `tally.ts` pattern for `package.json` scripts +
tsconfig + bun-test setup.
4. Verify lint job continues passing on PR with the port.
5. Delete the Python original in the same PR (avoid parallel
implementations).
5 changes: 4 additions & 1 deletion memory/MEMORY.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
[AutoDream last run: 2026-04-23]

**📌 Fast path: read `CURRENT-aaron.md` and `CURRENT-amara.md` first.** <!-- paired-edit: PR #675 pull-queue rule scope-broadening 2026-04-28 --> These per-maintainer distillations show what's currently in force. Raw memories below are the history; CURRENT files are the projection. (`CURRENT-aaron.md` refreshed 2026-04-28 with sections 26-29 — speculation rule + EVIDENCE-BASED labeling + JVM preference + dependency honesty + threading lineage Albahari/Toub/Fowler.)
**📌 Fast path: read `CURRENT-aaron.md` and `CURRENT-amara.md` first.** <!-- paired-edit: PR #685 typescript-default + chronological-polarity-error 2026-04-28 --> These per-maintainer distillations show what's currently in force. Raw memories below are the history; CURRENT files are the projection. (`CURRENT-aaron.md` refreshed 2026-04-28 with sections 26-29 — speculation rule + EVIDENCE-BASED labeling + JVM preference + dependency honesty + threading lineage Albahari/Toub/Fowler.)

- [**Incomplete Source-Set Regeneration Hazard + Workflow Null-Result Audit Signal — Amara class names + controls (2026-04-28)**](feedback_incomplete_source_set_regeneration_hazard_and_workflow_null_result_audit_amara_2026_04_28.md) — Two reusable classes: (1) "regenerate from sources" tools become destructive when source-set is incomplete; control is `--check` / `--stdout` first, force-write only after completeness proven. (2) `gh run list []` on existing workflow is audit signal not conclusion; six diagnostic questions (too-new / disabled / non-default-branch / cron / event-trigger / identifier-filter). Both fold into task #269.
- [**Chronological Insertion Polarity Error — class name + append-only-on-oldest-first discipline (Amara 2026-04-28)**](feedback_chronological_insertion_polarity_error_amara_class_name_otto_2026_04_28.md) — Edit-tool prepend on oldest-first append-only files = chronological reversal. Discipline: `cat >> file <<EOF` always-append OR run canonical-sort post-edit. Lint hook caught Otto's PR #684 incident in 1min. Mechanism-over-vigilance.
- [**TypeScript/Bun is the default; step out on TypeScript carefully (Aaron 2026-04-28)**](feedback_typescript_bun_default_step_out_carefully_aaron_2026_04_28.md) — Factory tooling default is TypeScript on Bun (`bun@1.3.13`). Step-out threshold: AI/ML primary library availability. New tooling defaults to TS; existing Python ports when changes are substantive (B-0086 catalogues port candidates).
- [**Version-currency rule covers inheriting existing repo pins, not just fresh assertions (Aaron 2026-04-27)**](feedback_version_currency_covers_inheriting_existing_pins_not_just_fresh_assertions_aaron_2026_04_27.md) — Otto-247 wake-time discipline #4 applies whenever a version pin LANDS in a new file, even when the same SHA is already pinned elsewhere. Pasting a pin into a new workflow IS asserting it current. Verify against upstream API releases/latest.
- [**Self-healing metrics on regime change — factory design principle (Aaron 2026-04-28)**](feedback_self_healing_metrics_on_regime_change_factory_design_principle_aaron_2026_04_28.md) — When system is correctly designed, transient metric gaps from regime transitions resolve organically as new regime accumulates evidence in rolling window. Prefer self-heal over manual rebaseline. NOT applicable when system is broken (verify first).
- [**Emit empty security-tool result on conditional-skip — CI security-maturity pattern (Aaron 2026-04-28)**](feedback_emit_empty_security_result_on_conditional_skip_ci_maturity_pattern_aaron_2026_04_28.md) — Trajectory: when security-tool workflow skips (path-gate, branch-filter, etc.), STILL emit minimal no-findings result so coverage metrics see tool-ran. Already in codeql.yml; propagate to Semgrep/dep-scan/container-scan as added.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
---
name: Chronological Insertion Polarity Error — class name + append-only-on-oldest-first discipline (Amara naming, Otto incident, 2026-04-28)
description: Amara 2026-04-28T19:58Z named the class after Otto's PR #684 ordering bug. Class definition — edit-tool prepend semantics applied to a history file with oldest-first invariant produces chronological reversal. Discipline — when adding rows to oldest-first append-only history files, use `cat >> file <<'EOF'` (always-append semantics) OR run `tools/hygiene/sort-tick-history-canonical.py` post-edit. Mechanism-over-vigilance: the tick-history lint hook caught it within 1 minute. Reusable; small; demonstrates 'stability is the substrate of velocity'.
type: feedback
---

# Chronological Insertion Polarity Error

## Class name (Amara 2026-04-28T19:58Z)

**Chronological Insertion Polarity Error** — when an edit
operation's directional semantics (prepend / insert-before /
unshift) are applied to a file whose invariant is the opposite
direction (oldest-first; newest at end). The mistake is tiny;
the class is reusable.

## Concrete incident (Otto 2026-04-28T19:54Z)

- File: `docs/hygiene-history/loop-tick-history.md`.
- Invariant: oldest tick at top, newest tick at bottom (rows
ordered by ISO timestamp ascending).
- Mistake: used `Edit` tool with `old_string = "| 2026-04-28T19:41:27Z ..."`
and `new_string = "| 2026-04-28T19:50:30Z ...\n| 2026-04-28T19:41:27Z ..."`.
This **prepended** the 19:50 row in front of the 19:41 row.
But oldest-first means 19:41 should come BEFORE 19:50 — my
prepend was chronological reversal.
- Caught: by `lint (tick-history order)` CI job within ~1 min
of push to PR #684.
- Fixed: `python3 tools/hygiene/sort-tick-history-canonical.py`
(the canonical-sort script designed exactly for this).
- Net cost: 1 minute of CI feedback + 1 small commit + 1
force-push.

## Causal chain (Amara's framing)

```
mistake → lint catches it within 1 minute → PR blocked before
merge → canonical write pattern discovered → future rule encoded
```

This is **mechanism-over-vigilance** working as designed: the
hook substrate makes the mistake cheap, fast, and
non-catastrophic. Instead of "be more careful next time," the
factory has a substrate-level answer: append safely, or
canonicalize after editing.

## The discipline

When adding a row to a file with oldest-first append-only
invariant (history files; tick logs; chronological journals):

1. **Prefer append-only semantics**:

```bash
cat >> path/to/history <<'EOF'
| 2026-04-28T19:50Z (...) | ... |
EOF
```

Always-append is impossible to get the polarity wrong.

2. **OR sort-canonically post-edit**:

```bash
python3 tools/hygiene/sort-tick-history-canonical.py
```

The script reorders rows + dedupes; safe even if your edit
landed at the wrong position.

3. **Don't prepend or insert-before via `Edit` tool** unless you
have explicitly verified the file's polarity invariant.

## Lint hook control

The CI lint job
(`lint (tick-history order)` in
`.github/workflows/<gate-or-similar>.yml`) is the
**mechanism-over-vigilance** control. Without it, the
chronological reversal would have merged silently and corrupted
the durable history. With it, the mistake costs 1 minute of CI
feedback.

## Generalization beyond tick-history

The same class applies anywhere an append-only history file is
in use:

- `docs/hygiene-history/loop-tick-history.md` (this incident)
- `docs/budget-history/snapshots.jsonl` (oldest-first JSONL,
similar polarity invariant — but JSONL appends are line-based
so the bug shape is different; still an append-only invariant)
- ADR records (`docs/DECISIONS/`) — filename-dated; insertion
order is timestamp-ordered by file naming convention
- Round-history (`docs/ROUND-HISTORY.md`) — chronological
- (Any future append-only journal)

For each: the lint hook + the canonical-sort tool are the
control pair. If a new history file lacks both, file as a
factory-hygiene gap.

## What this is NOT

- **NOT a directive to be more careful**. The discipline is
mechanism-based, not vigilance-based. Future-Otto WILL make
this mistake again on a new file; the lint hook catches it
again; that's the design.
- **NOT a ban on `Edit` tool for history files**. `Edit` is
fine for FIXING rows that already exist (typo fixes, wording
corrections). The ban is on `Edit`-prepend for NEW rows.
- **NOT specific to tick-history.md**. The class generalizes;
the specific incident was just the worked example.

## Stability is the substrate of velocity (Amara's framing)

This bug is small + boring; that's the proof. The factory's
ability to absorb a chronological-reversal mistake without
slowing down — the lint catches it, the canonical-sort fixes
it, the work continues — IS what stability buys. Without the
mechanism, this class of mistake would either be a manual
review-time catch (slow, expensive) or a silent corruption
(catastrophic if undiscovered).

The discipline scales: every mechanism-over-vigilance hook
added (to any history file, any append-only structure) adds
this kind of resilience.

## Composes with

- `tools/hygiene/sort-tick-history-canonical.py` — the
canonical-sort script that fixes the bug after-the-fact.
- `memory/feedback_orthogonal_axes_factory_hygiene.md` —
hygiene-axes design heuristic; this class is "polarity"
axis.
- `memory/feedback_destructive_git_op_5_pre_flight_disciplines_codex_gemini_2026_04_28.md`
— same shape of "small mistake, large potential impact,
controlled by mechanism not vigilance".
- `memory/feedback_emit_empty_security_result_on_conditional_skip_ci_maturity_pattern_aaron_2026_04_28.md`
— same family: design the hook in advance, let the metric
self-heal / let the lint catch / let the mechanism do the
work.
- `memory/feedback_self_healing_metrics_on_regime_change_factory_design_principle_aaron_2026_04_28.md`
— adjacent: self-healing for metrics; mechanism-over-vigilance
for code/data hygiene; both are "design substrate that makes
failures cheap" patterns.

## Pickup notes for future-Otto

If you find yourself about to `Edit` a history file with a new
row:

1. **Stop.** Use `cat >> file <<EOF` instead.
2. If `Edit` is the only tool available (e.g. you're inside an
agent without `Bash`): apply Edit, then run the canonical-sort
on the whole file before commit.
3. **Don't add an opt-out flag** to suppress the lint. That's
the Otto-341 self-deception pattern Aaron caught earlier;
suppressing the hook re-introduces the failure mode it
prevents.
Loading
Loading