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
30 changes: 26 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,28 @@ All notable changes to **basou** are recorded here. The project follows
`--from-file ./-` for a file named `-`. With `-` refused by all six flags,
a later release can read stdin for it by adding to what they accept,
without changing what any of them means.
- **`docs/spec/compatibility.md` reserves `-` for standard input, guarantees
`basou review record`'s input shape, and says what a signal's exit code
means across platforms.** Within `1.x` a flag may come to accept a value it
refuses, as an addition, only when every invocation accepted before parses
and behaves as before. If an input-file flag, one of today's six or one
added later, comes to accept `-`, it will read standard input. `basou review
record`'s JSON input is now a documented input shape, like `basou decision
capture`'s: the fields, types, required fields and enumerated values of the
`Input format` section of `--help`, while whether a `repos` entry names a
repository root follows `basou review-gaps`. The envelope of `basou session
import` is named among the input shapes, under its own version rule. For a
child of `basou exec` or `basou run` that a signal ended, any signal but the
five with fixed values gives `128` plus its number on the POSIX platform
basou runs on, comparable only there. A signal sent to basou itself counts
only if it is `SIGINT` or `SIGTERM`; any other ends basou before the run is
recorded. A signal Node has no name for is outside the promise, and today's
exit `0` for it is described, not promised (`docs/spec/schemas.md` says the
same of the event). The deprecation policy records refusing `-` as the one
exception, since the policy was adopted in `0.31.0`, to a flag keeping what
it accepts through `0.x`, and the README says so.
`docs/spec/terminal-and-import.md` points `session import`'s refusal of
`--from -` at the reservation.

### Fixed

Expand All @@ -31,10 +53,10 @@ All notable changes to **basou** are recorded here. The project follows
number on the platform basou runs on: `SIGSEGV` gives `139` and `SIGPIPE`
`141` on Linux and macOS, while `SIGUSR1` gives `138` on Linux and `158` on
macOS, so such a value is comparable only on one platform, and the recorded
signal name says which signal it was. Only the five signals' values are
promised. A signal Node has no name for (`SIGEMT` on macOS, the real-time
signals on Linux) still reaches basou as an exit code of `0`, so basou
still exits `0` for it.
signal name says which signal it was. `docs/spec/compatibility.md` now
promises this (see Changed). A signal Node has no name for (`SIGEMT` on
macOS, the real-time signals on Linux) still reaches basou as an exit code
of `0`, so basou still exits `0` for it.

## 0.68.4 — 2026-10-15

Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,10 @@ zero-network design.
What that means for you today:

- **The `basou` CLI surface is frozen for the `0.x` line** — commands and
flags are stable, except the experimental commands that
flags are stable, except that `-` as the file of an input-file flag is
refused, [reserved for standard
input](docs/spec/compatibility.md#a-value-a-flag-refuses), and except the
experimental commands that
[docs/spec/compatibility.md](docs/spec/compatibility.md#what-is-not-guaranteed)
lists, which may also write files beside one of yours that they read, never
under `.basou/` (`basou board record` writes records beside its
Expand Down
74 changes: 64 additions & 10 deletions docs/spec/compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,19 @@ At `1.0`, semantic versioning applies to exactly three surfaces:
1. **The `basou` CLI** — the set of commands and subcommands, their flags,
exit codes, the documented `--json` output shapes of the commands that offer
one, and the documented JSON **input** shapes of the commands that read one
from stdin or `--file` (today: `basou decision capture`). An input shape is a
contract for the same reason an output shape is — something else produces it,
and tightening what is accepted breaks that producer exactly as surely as
dropping a field breaks a consumer. Accepting MORE is additive; accepting
less is not.
from stdin or an input-file flag (today: `basou decision capture`, `basou
review record`, and the envelope of `basou session import`, whose version
follows [its own rule](#the-import-envelope-version-may-move-at-a-minor)).
An input shape is a contract for the same reason an output shape is —
something else produces it, and tightening what is accepted breaks that
producer exactly as surely as dropping a field breaks a consumer. Accepting
MORE is additive; accepting less is not. For `basou decision capture` and
`basou review record`, the documented shape is the `Input format` section of
the command's `--help`, which is contract rather than presentation: the
fields, their JSON types, which are required, and the values a field
enumerates. Whether a `repos` entry of `basou review record` names a
repository root on the machine is judged by the rule `basou review-gaps`
binds a record with, and may change with that rule.
2. **The `@basou/sdk` package** — its exported read-only API for reading a
workspace's provenance.
3. **The `.basou/` on-disk format** — the durable file schemas (manifest,
Expand All @@ -37,6 +45,27 @@ below, and the extension rules of [schemas
for the on-disk format, state the exceptions to that rule and what bounds each
of them.

### A value a flag refuses

Within `1.x` a flag may come to accept a value it refuses today, and that is
additive, as accepting more of an input shape is — but only when every
invocation accepted before is parsed and behaves as before. Making an option's
value optional does not qualify: `--file --json` reads a file named `--json`
today, and would parse `--json` as a flag instead. Accepting a refused value
moves the invocations it accepts from a non-zero exit to `0`, the one way an
invocation crosses zero within `1.x`; a [split of the non-zero
values](#exit-codes-zero-is-success-and-a-failures-value-may-be-refined) never does.

One such value has its meaning fixed now, so that accepting it later cannot
surprise anyone: `-` as the file of an input-file flag, a flag that names a
file basou reads its input from — `--file` on `basou decision capture`, `basou
review record` and `basou note`, `--from-file` on `basou task new` and `basou
session note`, and `--from` on `basou session import`. Each of them refuses
`-` today, and an input-file flag added later either refuses it too or reads
standard input for it from the start. If one comes to accept it, `-` will mean
standard input, never a file named `-`. A file named `-` is read by naming it
`./-`.

### `--version`: the first token is the contract, the rest is build identity

`basou --version` prints the semver version as its **first whitespace-delimited
Expand Down Expand Up @@ -79,11 +108,25 @@ an error, for instance).

- When the child exits with a code, basou exits with that code.
- When the child is ended by a signal, basou exits with `128` plus a signal's
number: the signal basou itself received while the child ran, when one
reached it, and otherwise the signal that ended the child. `basou exec
--timeout` ends the child with `SIGTERM`, then `SIGKILL`. The number is
exact for `SIGHUP`, `SIGINT`, `SIGQUIT`, `SIGKILL` and `SIGTERM`; any other
signal gives a value above `128` that does not identify it.
number: the `SIGINT` or `SIGTERM` basou itself received while the child ran,
when one reached it, and otherwise the signal that ended the child. Any other
signal sent to basou ends basou as it ends any process, and the run's
`command_executed` event is not written. `basou exec --timeout` ends the
child with `SIGTERM`, then `SIGKILL`. The value is the same on every POSIX
platform for `SIGHUP` (`129`), `SIGINT` (`130`), `SIGQUIT` (`131`), `SIGKILL`
(`137`) and `SIGTERM` (`143`). Any other signal gives `128` plus its number
on the platform basou runs on, the value a POSIX shell such as `sh`, `bash`
or `zsh` reports there. Those numbers differ between platforms — `138` is
`SIGUSR1` on Linux and `SIGBUS` on macOS — so such a value is comparable
only on one platform; the signal names recorded on the run's
`command_executed` event (`signal`, `received_signal`) say which signal it
was.
- A signal Node has no name for — `SIGEMT` on macOS, the real-time signals on
Linux — reaches basou as if the child had exited `0`, and is outside the
promise above. Today basou then exits `0`, records an `exit_code` of `0`
with no `signal`, and marks the session completed. None of that is
promised: a later basou, or a later Node, may report such a signal. A
reader cannot rule one out from an `exit_code` of `0`.
- When basou itself fails, it exits `1` — including when it fails after the
child has exited, so a child that exited `0` is then reported as `1`. A
caller cannot tell a basou failure apart from a child that exited `1`, and
Expand Down Expand Up @@ -525,6 +568,17 @@ requirement therefore lands on the input shape. `record` instead names the
vessel it used on its receipt, which needs no new flag on a surface this line
treats as frozen.

Since this policy was adopted (`0.31.0`), a flag has also kept what it
accepts, with one exception: `-` as the file of an input-file flag, refused
before `1.0` so that `1.0` does not freeze it as a file name in the way of
the [reservation](#a-value-a-flag-refuses). Four flags read it as a file named
`-` until the release after `0.68.4`: `--file` on `basou decision capture`,
`basou review record` and `basou note`, and `--from` on `basou session
import`. It was refused outright, with no warning release. A warning release
exists so that a caller does not lose input it cannot easily make again; here
the input is a file, which stays on disk and is read as `./-`, as the refusal
says. No other value is refused this way before `1.0`.

After `1.0`, removing or changing the meaning of any guaranteed-surface element
requires a major bump; additions within a line remain backward-compatible.

Expand Down
11 changes: 11 additions & 0 deletions docs/spec/schemas.md
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,17 @@ Events written by the import paths additionally carry an optional top-level
| Review | `review_recorded` | self-reported record that an adversarial / second-opinion review ran. Emitted by `basou review record`. `reviewer` + `target` are required; `repos` (the repository paths reviewed) is what lets `basou review-gaps` bind the record to a unit of work, since the record itself lands in the planning repo. |
| Adapter | `adapter_output` | adapter output (summary only; raw kept separately) |

### `command_executed`: a signal Node has no name for

`exit_code` is the code the child exited with, and `signal` the signal that
ended it; an `exit_code` of `null` means no code was observed — a signal ended
the child, or the source did not record one. One case falls outside that: a
signal Node has no name for — `SIGEMT` on macOS, the real-time signals on
Linux — reaches basou as if the child had exited `0`, so the event holds an
`exit_code` of `0` and no `signal`. An `exit_code` of `0` therefore does not
rule such a signal out. That is how basou records it today, not a promise (see
[compatibility](compatibility.md#exit-codes-zero-is-success-and-a-failures-value-may-be-refined)).

### `file_changed.change_type`

`change_type` is one of `added`, `modified`, `deleted` and `renamed`. It says
Expand Down
6 changes: 4 additions & 2 deletions docs/spec/terminal-and-import.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,10 @@ basou session import --format json --from <path>
```

- `--format` and `--from` are both required; the JSON is read from the
`--from` file (not from stdin). `--from -` is refused: `-` is reserved for
standard input. A file named `-` is read with `--from ./-`.
`--from` file. This command does not read stdin today, and `--from -` is
refused: `-` is [reserved for standard
input](compatibility.md#a-value-a-flag-refuses). A file named `-` is read
with `--from ./-`.
- It is converted to the basou event schema.
- Events are appended to
`.basou/sessions/<session_id>/events.jsonl`.
Expand Down
Loading