Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
8fb6843
ci: install ffmpeg with apt in the provisioned e2e job too
lorem-dev Sep 5, 2026
4c13b08
ci: put the provisioned binaries on PATH, and let Actions open the ba…
lorem-dev Sep 5, 2026
3974e91
docs: note which checks are required, and why not the provisioned one
lorem-dev Sep 5, 2026
6e5bf2e
fix: restore the e2e test timeout, and lint the config files
lorem-dev Sep 5, 2026
b33e268
feat: fold pre-release changelog sections, and test the release scripts
lorem-dev Sep 5, 2026
6893ce8
feat: retire the pre-releases a final release supersedes
lorem-dev Sep 5, 2026
a235377
fix: stop the script tests reading CI's environment, and retry the pn…
lorem-dev Sep 5, 2026
771f58c
fix: give the layer-boundary rule the half of its job it was silently…
lorem-dev Sep 5, 2026
d4484ae
fix: install ffmpeg in the publish job, which runs the same gate as CI
lorem-dev Sep 5, 2026
a8348f9
test: stop the suite printing expected errors and a notice it acts on
lorem-dev Sep 5, 2026
dbe239f
chore: 1.0.0-dev.1
lorem-dev Sep 5, 2026
ae066f3
fix: publish the tarballs by absolute path, not one npm reads as a repo
lorem-dev Sep 5, 2026
41caaf4
test: cover the video containers import accepts
lorem-dev Sep 5, 2026
23d7dc9
fix: resolve the release tag once, instead of letting a step default …
lorem-dev Sep 5, 2026
0dda370
fix: let npm do the OIDC exchange by giving it no credentials to find
lorem-dev Sep 5, 2026
11333c9
feat: allow a token for the bootstrap publish, and prefer OIDC once i…
lorem-dev Sep 5, 2026
d90fe06
docs: state the one case where a pre-release does move npm's latest tag
lorem-dev Sep 5, 2026
cd4a70f
fix: give every published package a README, and check that it has one
lorem-dev Sep 5, 2026
20b0a11
chore: 1.0.0-dev.2
lorem-dev Sep 5, 2026
f208e65
fix: report the installed version, not the literal 0.0.0
lorem-dev Sep 5, 2026
930ff79
feat: refuse a final release that would publish on a stored token
lorem-dev Sep 5, 2026
6dd61d7
docs: note what the next snapshot is for
lorem-dev Sep 5, 2026
6d57a47
chore: 1.0.0-dev.3
lorem-dev Sep 5, 2026
e620380
feat: retire superseded pre-releases automatically, still with no sto…
lorem-dev Sep 5, 2026
2b23eaa
fix: retire pre-releases only as part of a production release
lorem-dev Sep 5, 2026
4e5cd1c
docs: describe the release rules once, and how the credential works
lorem-dev Sep 5, 2026
f84f261
feat: check dependencies for advisories and age before a release
lorem-dev Sep 5, 2026
8d386eb
feat: add Dependabot and CodeQL, both held to the 14-day rule
lorem-dev Sep 5, 2026
4c50586
fix: match provider hosts by hostname, and stop three patterns that b…
lorem-dev Sep 5, 2026
218ce6b
chore: hold TypeScript at 6 until typescript-eslint can load against 7
lorem-dev Sep 5, 2026
20ddf0b
fix: take over a stale provisioning lock without deleting a live one
lorem-dev Sep 5, 2026
4caebed
chore(deps): bump the actions group across 1 directory with 2 updates…
dependabot[bot] Sep 5, 2026
8a5a408
docs: add a version badge to the README
lorem-dev Sep 5, 2026
04e331b
feat: refuse a release that ships an unreviewed high-severity finding
lorem-dev Sep 5, 2026
d7d24fe
docs: cut what 1.0.0's notes should not carry
lorem-dev Sep 5, 2026
f7a62b7
docs: say what the CLI actually has, and stop claiming half of it is …
lorem-dev Sep 5, 2026
8d7b0df
fix: close the provisioning lock race properly, and stop the release …
lorem-dev Sep 5, 2026
d92f61b
feat: gate the docs on a successful publish, create the release, warn…
lorem-dev Sep 5, 2026
26c57b9
ci: exclude the one CodeQL rule that keeps mis-reading the provisioni…
lorem-dev Sep 5, 2026
eca7b80
chore: 1.0.0
lorem-dev Sep 5, 2026
2fc369e
fix: retire snapshots by hand, because the OIDC token cannot deprecate
lorem-dev Sep 5, 2026
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 .agents/skills/bump-version/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
---
name: bump-version
description: >
Set the laud version across every package.json and promote the CHANGES.md
Set the ailoud version across every package.json and promote the CHANGES.md
Development section to a released Version section, then create the release
commit. Does not tag or push.
---

# bump-version

Bump the laud version and prepare the release commit. Tagging and pushing
Bump the ailoud version and prepare the release commit. Tagging and pushing
remain separate, deliberate steps.

## Steps
Expand Down Expand Up @@ -44,7 +44,7 @@ Run `git diff` and confirm the version bump touched exactly these files:
`## Development`).

No other files changed. There is no `Cargo.toml` and no
`tauri.conf.json` in this project -- laud is a pure TypeScript workspace.
`tauri.conf.json` in this project -- ailoud is a pure TypeScript workspace.

### 4. Create the release commit

Expand Down
141 changes: 141 additions & 0 deletions .agents/skills/check-dependencies/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
---
name: check-dependencies
description: >
Audit npm dependencies for known vulnerabilities, report who asks for
funding, and update what is behind -- refusing any version published less
than 14 days ago unless it fixes a critical advisory. Run before every
release and after any dependency change.
---

# check-dependencies

Three questions, in this order: is anything we depend on known to be
vulnerable, who is asking to be paid for it, and what should be updated.
Updating comes last because the first two decide what an update is for.

## Steps

1. **Look for known vulnerabilities.**

```bash
pnpm audit # everything, including the dev toolchain
pnpm audit --prod # only what the three published packages ship
```

Read them separately. A high-severity advisory in a test runner is a
different problem from one in `commander`: the first cannot reach a user,
the second is installed on their machine. Fix the `--prod` findings first
and say which list a finding came from.

2. **Report funding.**

```bash
npm fund
```

`pnpm` has no `fund` command; `npm fund` reads the installed tree and works
in this workspace. The list is long and almost all of it is the dev
toolchain. What is worth surfacing is the intersection with what actually
ships:

```bash
npm fund --json | node -e '
let s = "";
process.stdin.on("data", (d) => (s += d)).on("end", () => {
const names = [];
const rec = (n) => {
for (const [name, child] of Object.entries(n.dependencies ?? {})) {
names.push(name);
rec(child);
}
};
rec(JSON.parse(s));
const runtime = new Set();
for (const p of ["packages/core", "packages/providers", "apps/cli"]) {
Object.keys(require(`./${p}/package.json`).dependencies ?? {}).forEach((k) =>
runtime.add(k),
);
}
console.log(names.filter((n) => runtime.has(n)).join(", ") || "(none)");
})'
```

Report the count and that intersection. Do not open funding pages or
install anything.

3. **See what is behind.**

```bash
pnpm outdated -r
```

Nothing here is urgent by default. A version bump is worth taking when it
fixes something this project hits, closes an advisory from step 1, or keeps
a major version from drifting far enough to become a project of its own.
"It is newer" is not a reason.

4. **Apply the 14-day rule BEFORE updating anything.**

```bash
node scripts/check-dependency-age.mjs
```

A compromised release is discovered by other people, and that takes days --
every npm supply-chain incident in recent memory was caught within a week
or two of publication, after everyone who upgraded immediately had already
installed it. Waiting costs nothing: there is no urgency in a patch that
has been out for two weeks that was not there on day one.

So a candidate version younger than 14 days is not taken. Pin the previous
one and come back to it.

**Unless it fixes a critical or high advisory.** Then waiting is the worse
risk -- a known exploit beats an unaudited release. Record the decision in
`scripts/dependency-age-exceptions.json` with the advisory ID:

```json
{
"some-package@2.0.1": "fixes GHSA-xxxx-yyyy-zzzz (critical), 2026-09-05"
}
```

The exception is a statement about one moment, so the check reports entries
that have aged out and should be deleted. Never add one to silence the
check for convenience; if there is no advisory ID, there is no exception.

5. **Update carefully.**

One concern per commit, so a regression is bisectable:

```bash
pnpm update --latest <package> # or edit the manifest and pnpm install
```

- Keep versions **exact**. This project pins them; a range hands the
decision to whatever resolved last, and the age check cannot judge it.
- Run the `check-licenses` skill if any `package.json` changed -- a new
version can change its licence.
- Run the `run-tests-and-linters` skill. A dependency update that passes
nothing is not an update.
- A user-visible consequence goes in CHANGES.md; a toolchain bump does not.

6. **Verify the result.**

```bash
pnpm audit --prod
node scripts/check-dependency-age.mjs
```

Both must pass. Report what changed, what was deliberately left behind and
why, and any advisory that remains open with the reason it cannot be closed
yet.

## Do not

- Do not run `pnpm audit --fix` or `npm audit fix`. They resolve to whatever
is newest, which is the version the 14-day rule exists to refuse.
- Do not update a major version as part of a release. It is its own change,
with its own testing.
- Do not treat a clean `pnpm audit` as proof of anything beyond "no advisory
has been published". Most of what the age rule protects against has no
advisory yet.
4 changes: 2 additions & 2 deletions .agents/skills/check-docs/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ release, or right after adding or changing a CLI command or option.
- Confirm every `pnpm` command shown in README.md exists as a script in
the root `package.json` (`pnpm lint`, `pnpm typecheck`, `pnpm test:cov`,
`pnpm build`, `pnpm format:check`, `pnpm test:e2e`, etc.).
- Confirm every `laud` CLI command shown (`import`, `transcribe`, `ls`,
- Confirm every `ailoud` CLI command shown (`import`, `transcribe`, `ls`,
`show`, `doctor`) is a command M1 actually ships, per the "Project
Overview" section of AGENTS.md. `laud` has no `search`, `collection`,
Overview" section of AGENTS.md. `ailoud` has no `search`, `collection`,
`tag`, `summarize`, `export`, or `config` command yet; flag any of
those names if they appear in README.md or AGENTS.md.
- Confirm every relative link in README.md and AGENTS.md resolves to a
Expand Down
14 changes: 7 additions & 7 deletions .agents/skills/check-fixtures/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
name: check-fixtures
description: >
Drive the built laud binary against fixtures/ end to end -- import,
Drive the built ailoud binary against fixtures/ end to end -- import,
transcribe, ls, show, and doctor -- in a throwaway HOME, XDG_CONFIG_HOME,
and XDG_DATA_HOME, and confirm the working tree stays clean afterward.
---
Expand All @@ -10,7 +10,7 @@ description: >

The unit tests cover the domain core against fakes (`MemFs`, `FakeClock`,
`FakeIds`, `FakeStt`). This skill covers the layer they cannot: the real
`laud` binary, against real audio, writing to a real filesystem (inside a
`ailoud` binary, against real audio, writing to a real filesystem (inside a
sandbox). It is the only check that would catch a regression living in the
wiring between the CLI, the providers, and the filesystem -- for example a
provider writing to the wrong data directory, or a pipeline that behaves
Expand All @@ -28,14 +28,14 @@ right lens" below.
Three short fixtures, each with a reference transcript: an English clip, a
Russian clip, and a clip that mixes both languages. The suite drives:

- `laud doctor` against a sandbox with no config, and again after
- `ailoud doctor` against a sandbox with no config, and again after
configuring the model.
- `laud import` against a fixture file, including the "already present"
- `ailoud import` against a fixture file, including the "already present"
path on a repeat import.
- `laud transcribe`, checked by word error rate against the reference
- `ailoud transcribe`, checked by word error rate against the reference
transcript rather than exact string equality -- a model or quantization
change shifts wording by a word or two without being a regression.
- `laud show` in both `srt` and `json` formats, plus its error paths (a
- `ailoud show` in both `srt` and `json` formats, plus its error paths (a
missing id, an unsupported `--format`).

## Isolation
Expand All @@ -44,7 +44,7 @@ The suite must never touch the developer's machine state. Every invocation
of the built binary sets all three of:

- `XDG_CONFIG_HOME`, which relocates `config.yaml`.
- `XDG_DATA_HOME`, which relocates `laud.db` and the `media/` tree.
- `XDG_DATA_HOME`, which relocates `ailoud.db` and the `media/` tree.
- `HOME`, so nothing the process resolves relative to the real home
directory (for example a fallback default when an XDG variable is
unset) can reach outside the sandbox.
Expand Down
4 changes: 2 additions & 2 deletions .agents/skills/check-licenses/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ description: >

Verify that every direct npm dependency is license-compatible with
Apache-2.0 and keep the Third-Party Notices section of `LICENSE` up to
date. `laud` is a pure TypeScript pnpm workspace -- there is no cargo
date. `ailoud` is a pure TypeScript pnpm workspace -- there is no cargo
workspace to check, unlike the source repository this project inherits its
conventions from.

Expand Down Expand Up @@ -71,7 +71,7 @@ dependencies` subsection's table -- replace from its `| Package |`
| <name> | npm | <version> | <spdx> | <copyright> |
```

Every row's Ecosystem column is `npm` -- laud has no other dependency
Every row's Ecosystem column is `npm` -- ailoud has no other dependency
ecosystem. List rows alphabetically by package name. Preserve
everything above the table verbatim: the Apache 2.0 license text, the
`## Third-Party Notices` heading, and the intro paragraph above the
Expand Down
96 changes: 96 additions & 0 deletions .agents/skills/dev-tag/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
---
name: dev-tag
description: >
Cut a throwaway development tag (`v<version>-dev.<n>`) to publish a snapshot
to npm under the `dev` dist-tag and exercise the release pipeline. Publishes
no documentation and never moves `latest`.
---

# dev-tag

A development tag publishes a snapshot nobody will get by accident, so a real
install of real code can be tried before a release is promised.

## The three kinds of tag

| Tag | Cut from | npm dist-tag | Docs published |
| -------------- | ---------- | ------------ | -------------- |
| `v1.2.3-dev.1` | any branch | `dev` | no |
| `v1.2.3-rc.1` | `develop` | `next` | no |
| `v1.2.3` | `main` | `latest` | yes |

Only a final tag moves `latest` and publishes documentation. `npm install
ailoud` therefore never picks up a dev tag, and the site never describes a
version nobody can install.

Except once per package: npm sets `latest` on a FIRST publish whatever `--tag`
says, and `latest` cannot be removed afterwards, only moved. If a dev tag is
what introduces a package to the registry -- as v1.0.0-dev.1 was -- then
`latest` points at that snapshot until a final version is published.

## Cutting one

1. The working tree must be clean and the gate green:

```
pnpm build && pnpm format:check && pnpm lint && pnpm typecheck && pnpm test:cov
```

2. Set the version across the manifests. `-dev.<n>` is a pre-release, so it
sorts BELOW the release it precedes:

```
node scripts/bump-version.mjs 1.2.3-dev.1
```

`bump-version` promotes the CHANGES.md `## Development` section, which a dev
tag does not want -- so for a dev tag, set the versions by hand and leave
the changelog alone:

```
node -e "for (const p of ['package.json','packages/core/package.json','packages/providers/package.json','apps/cli/package.json']) { const fs=require('fs'); const d=JSON.parse(fs.readFileSync(p,'utf8')); d.version='1.2.3-dev.1'; fs.writeFileSync(p, JSON.stringify(d,null,2)+'\n'); }"
```

3. Commit, tag and push. The publish workflow checks that every manifest
agrees with the tag, so these cannot drift:

```
git commit -am "chore: 1.2.3-dev.1"
git tag -s v1.2.3-dev.1 -m "v1.2.3-dev.1"
git push origin HEAD --tags
```

4. Try it from the registry, which is the whole point:

```
npm install -g ailoud@dev
ailoud --version
```

## What it does not do

- It does not publish documentation. `docs.yml` starts only on a final tag and
refuses a pre-release twice over.
- It does not move `latest`.
- It is not a release candidate. An `-rc.` tag says "this is what the release
will be"; a `-dev.` tag says "this is a snapshot to try".

## Cleaning up

A dev version cannot be unpublished after 72 hours and its number can never be
reused, so use a fresh `-dev.<n>` each time rather than retrying one.

Once the final release is out, retire every snapshot it supersedes in one step:

```
node scripts/retire-prereleases.mjs 1.2.3 # prints the plan
node scripts/retire-prereleases.mjs 1.2.3 --yes # carries it out
```

It deprecates the versions rather than unpublishing them, drops the `dev`
dist-tag, and deletes the tags -- but only those whose commit is reachable from
`main`. The provenance of a published package names both the commit and the tag
it was built from: delete the tag and the name stops resolving, which costs
convenience; delete a tag holding the only reference to its commit and the
commit itself can be collected, which costs the attestation its subject. Tags
in the second case are reported and left alone.
26 changes: 16 additions & 10 deletions .agents/skills/pre-release-check/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
---
name: pre-release-check
description: >
Gate a release by running check-licenses, run-tests-and-linters,
check-fixtures, check-docs, and check-changes, plus verifying the version
bump and that all commits since the last release follow the
conventional-commits format.
Gate a release by running check-licenses, check-dependencies,
run-tests-and-linters, check-fixtures, check-docs, and check-changes, plus
verifying the version bump and that all commits since the last release
follow the conventional-commits format.
---

# pre-release-check
Expand All @@ -21,8 +21,11 @@ details):

1. **check-licenses** -- must run first because a license failure is the
most fundamental blocker.
2. **run-tests-and-linters** -- lint, typecheck, and coverage at 90%.
3. **check-fixtures** -- drive the built binary against `fixtures/` end to
2. **check-dependencies** -- advisories, funding, and the 14-day rule on any
version taken. Runs early: an update it recommends changes what everything
below is testing, so taking one afterwards invalidates the whole gate.
3. **run-tests-and-linters** -- lint, typecheck, and coverage at 90%.
4. **check-fixtures** -- drive the built binary against `fixtures/` end to
end. This is the only check that exercises the real binary against real
files, so it catches wiring regressions the unit tests (which run
against in-memory fakes) cannot see. Six of the twelve specs need a real
Expand All @@ -31,12 +34,14 @@ details):
not a release blocker on its own -- attribute every failure (missing
whisper-cli, fixture drift, product change, harness defect) before
deciding whether it blocks.
4. **check-docs** -- README.md, cross-references, command accuracy,
5. **check-docs** -- README.md, cross-references, command accuracy,
version references.
5. **check-changes** -- CHANGES.md Development section vs. commit history.
6. **check-changes** -- CHANGES.md Development section vs. commit history.

If check-licenses or run-tests-and-linters fails, report the failure and
stop. If check-fixtures fails for a reason other than the missing
If check-licenses, check-dependencies or run-tests-and-linters fails, report
the failure and stop. A `--prod` advisory or a version younger than 14 days
blocks a release: the first ships a known vulnerability, and the second ships
a version nobody has had time to find one in. If check-fixtures fails for a reason other than the missing
`whisper-cli` binary (fixture drift, a product change, or a harness
defect), treat it the same way -- a release must not ship while tests,
licenses, or the end-to-end run are failing for a reason within this
Expand Down Expand Up @@ -93,6 +98,7 @@ Produce a release-readiness summary:

```
check-licenses: PASS / FAIL
check-dependencies: PASS / FAIL (advisories: prod / dev only; ages: ok / N too young)
run-tests-and-linters: PASS / FAIL
check-fixtures: PASS / FAIL (attribute: missing whisper-cli / fixture drift / product change / harness defect)
check-docs: PASS / FAIL
Expand Down
Loading