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
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ check-fmt:
$(MDTABLEFIX) --check $(MDTABLEFIX_SELECT) $(MDTABLEFIX_RULES)

lint: markdownlint workflow-parse
node_modules/.bin/oxlint --config oxlint.config.mjs src

# The src tree is TypeScript restricted to erasable syntax; tsc enforces the
# restriction (erasableSyntaxOnly, verbatimModuleSyntax in tsconfig.json).
Expand Down
117 changes: 117 additions & 0 deletions bun.lock

Large diffs are not rendered by default.

89 changes: 89 additions & 0 deletions docs/adr-004-linting-strategy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Architectural decision record (ADR) 004: Oxlint rules for ODW source

## Status

Accepted. The source lint gate applies the Unicorn and deslop recommended
profiles to the authored workflow module tree.

## Date

2026-10-27.

## Context and problem statement

The decomposed ODW workflow is authored as TypeScript under
`src/workflows/df12-build-odw/` and bundled into a JavaScript workflow
artefact. The compiler and documentation gates already constrain its types and
module structure. The repository also needs consistent source-level rules for
correctness and maintainability without linting the generated workflow
artefacts as if they were authored source.

## Decision drivers

- Pin the linter, its type-aware backend, and the deslop plug-in for repeatable
local and CI results.
- Apply the supported Unicorn recommended rules and the deslop recommended
configuration to `src/`.
- Exclude the object code under `workflows/`; check those artefacts with the
existing workflow parse and freshness gates.
- Keep the ODW runtime contract intact: lint dependencies are build tooling and
do not become runtime imports in the generated workflow.
- Resolve only documented conflicts and correct source findings instead of
suppressing them.

## Decision outcome

`make lint` runs Oxlint against `src/` using
[`oxlint.config.mjs`](../oxlint.config.mjs). The configuration enables the
Unicorn plug-in rules supported by the pinned Oxlint release, imports the
recommended `ai-deslop` rules, enables type-aware linting, and turns the other
Oxlint categories off. It excludes `workflows/**`. Tests remain outside this
source lint target and are exercised by `make test`.

The exact development dependency pins are:

| Package | Version | Purpose |
| ----------------- | ---------- | --------------------------------- |
| `oxlint` | `1.82.0` | Linter and built-in Unicorn rules |
| `ai-deslop` | `0.4.9` | Recommended `deslop/*` rules |
| `oxlint-tsgolint` | `7.0.2004` | Type-aware rule backend |

Table 1: pinned Oxlint dependencies.

Unicorn's `prefer-reflect-apply` recommends `Reflect.apply`, while deslop's
`no-reflect-apply` rejects it. Keep Unicorn's recommendation and disable
`deslop/no-reflect-apply`.

The ODW module tree has a scoped override for five rules that conflict with its
runtime and input-boundary contracts:

- `unicorn/no-null` is disabled because ODW uses `null` for failed
`parallel` and `pipeline` slots, and JSON contracts can require explicit
`null` values. Replacing them with `undefined` changes those contracts.
- `deslop/no-unknown-parameters`, `deslop/no-unknown-returns`, and
`deslop/no-unsafe-dictionary-type` are disabled because workflow arguments,
injected primitive results, and filesystem data can be untrusted before
validation. `unknown` and dictionaries of `unknown` preserve that boundary in
the type system.
- `deslop/no-runtime-typeof` is disabled because these boundaries use native
type guards, and the ODW dialect forbids runtime validator-library imports.
Agent outputs remain contracted with JSON Schema at the ODW boundary.

These exceptions apply only to `src/workflows/df12-build-odw/**/*.ts`. The
remaining selected rules stay enabled, including the requirement to justify
type assertions.

## Consequences

- New authored modules under `src/` are included in the Oxlint gate.
- Workflow object code under `workflows/` is checked by `workflow-parse`; the
generated ODW artefact is also checked by `workflow-freshness`.
- Test behaviour continues to be checked by `make test`.
- A lint dependency upgrade requires reviewing the supported Unicorn rule
list, deslop's recommended configuration, and the documented exceptions.

## References

- [Oxlint built-in plug-ins](https://oxc.rs/docs/guide/usage/linter/plugins.html)
- [Deslop recommended configuration](https://github.com/maharshi365/deslop#usage)
- [Simulacat Core plug-in adoption](https://github.com/leynos/simulacat-core/pull/121)
12 changes: 12 additions & 0 deletions docs/coderabbit-wire-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,18 @@ fallback over the error text for older CLI builds), and treats any other
`error` event as `error` unless the text matches the auth patterns in
`faults.ts`, which classify as `auth`.

## Typed event validation

The adapter validates fields consumed from `finding`, `error`, and `complete`
events before using them. Optional finding fields (`severity`, `fileName`,
`comment`, and `codegenInstructions`) and error fields (`errorType` and
`message`) must be strings when present; `suggestions` must be an array when
present. A completion requires a string `status`; its optional `findings` count
must be a finite number. A malformed known event produces an `invalid_output`
error outcome, so a later clean completion cannot mask it. A valid error event
remains authoritative, including a rate-limit error. Non-object JSON values and
non-JSON lines remain diagnostic raw lines.

## Maintenance

When a CLI upgrade changes this stream, capture a fresh session
Expand Down
3 changes: 3 additions & 0 deletions docs/contents.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ workflow assets, or understanding why the system is shaped the way it is.
- [ADR 003: Correlation-first OpenTelemetry observability](adr-003-opentelemetry-observability.md):
records the accepted decision to correlate workflow nodes and agent telemetry
through dispatch-time identity, a local OTLP collector, and a SQLite store.
- [ADR 004: Oxlint source rules](adr-004-linting-strategy.md): records the
pinned Unicorn and deslop profiles for authored ODW source and the workflow
artefact exclusion.

## Plans

Expand Down
84 changes: 75 additions & 9 deletions docs/developers-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ The repository contains workflow scripts, skill documentation, docs, operator
scripts, focused test suites, a project roadmap with ExecPlans, and a small
validation `Makefile`. Development dependencies (`esbuild`, `fast-check`,
`@aboviq/bun-test-cucumber`, `ajv`, `lemmascript`, `typescript`, `@types/bun`,
`markdownlint-cli2`, `typedoc`, and `yaml`) are managed with `bun` via
`package.json`. `yaml` is at `^2.9.0` and parses the CI workflow in
`tests/modules/ci-workflow-gate.test.ts`, which runs under the module test
gates; that contract must parse the workflow rather than search its text, so
the parser is a direct dependency rather than one reached through another
package's tree.
`markdownlint-cli2`, `oxlint`, `oxlint-tsgolint`, `ai-deslop`, `typedoc`, and
`yaml`) are managed with `bun` via `package.json`. `yaml` is at `^2.9.0` and
parses the CI workflow in `tests/modules/ci-workflow-gate.test.ts`, which runs
under the module test gates; that contract must parse the workflow rather than
search its text, so the parser is a direct dependency rather than one reached
through another package's tree.

Relevant paths:

Expand All @@ -58,9 +58,10 @@ Relevant paths:
and CodeScene), `host-review-contracts.ts` (neutral review contracts), and
`host-review.ts` (neutral composition facade), `shell-command.ts` (a
non-evaluating parser for configured shell-command words and assignment
spans), and `run-task.ts`, with the injected ODW primitives declared in
`odw-globals.d.ts`. TypeScript is restricted to erasable syntax by compiler
flags (`erasableSyntaxOnly`, `verbatimModuleSyntax`).
spans), `run-task.ts`, and `task-outcome.ts` (validated task result fields),
with the injected ODW primitives declared in `odw-globals.d.ts`. TypeScript
is restricted to erasable syntax by compiler flags (`erasableSyntaxOnly`,
`verbatimModuleSyntax`).
- `workflows/df12-build-odw.js`: GENERATED ODW/Codex workflow artefact, built
by `make workflow-build` (see `scripts/build-workflow.mjs`); this is the
single file the sidecar copies and ODW loads.
Expand Down Expand Up @@ -116,6 +117,10 @@ loader would reject. Within the src tree, keep the ODW script contract intact:
- Do not import ODW primitives. `args`, `agent`, `parallel`, `phase`, `log`,
and `budget` are injected by ODW.
- Use schemas where JavaScript consumes agent output as structured data.
- When routing options for assessment, write-preflight, or task-dispatch agent
calls, retain the call's required schema even if the router replaces the
options or omits `schema`; ODW uses it to validate structured replies at the
agent boundary.
- Keep phase and label names explicit enough to make the dashboard useful.
- Keep target-project mutation inside agent prompts and real git worktrees;
ODW copy isolation is not a persistent handoff mechanism for this workflow.
Expand Down Expand Up @@ -179,6 +184,34 @@ loader would reject. Within the src tree, keep the ODW script contract intact:
Changes to workflow behaviour must update all relevant prompts, schemas, docs,
and validation notes in the same branch.

### Runtime input and structured-output boundaries

`makeConfig` accepts a record of workflow arguments, or `null`/`undefined` to
use defaults. It rejects arrays, primitives, and wrong types for documented
fields with `Invalid workflow argument type`; `writeProbeModelByAdapter` must
be a record of string values. Unknown keys remain accepted for forward
compatibility. The legacy `resumePartialBranches` key is deliberately untyped,
but only literal `true` enables recovery. The user guide lists the documented
field types and the fields that accept numbers or strings.

Assessment and write-preflight agent calls retain their required JSON Schemas
after adapter option routing. For an in-run assessment reply that is absent or
malformed, `attachAssessment` leaves the task result intact and adds
`assessmentError` with `assessmentEvidence`. At the assessment-module boundary,
`assessRecoveryCandidate` returns `evidence`, `assessment: null`, and
`assessmentError` when no classification is produced. `runRecovery` records
`action: assessment-error`, the error, and the skipped candidate in
`recovery.results`; it does not copy the evidence into that summary entry.
Authentication or provider failures also produce a fatal run result. Assessment
remains report-only.

The write-preflight schema requires a boolean `ok` and permits an optional
string `detail`. The host checks the probe file itself for the exact token in a
regular, non-symlink file; the agent's `ok` claim is not proof of write access.
A malformed reply is treated as an agent error, and when host verification also
fails its detail appears in the probe failure. A failed host-verified probe
fails the task at `stage: worktree-write`.

## Submodule architecture and composition

`workflows/df12-build-odw.js` is compiled from the module tree under
Expand Down Expand Up @@ -299,6 +332,17 @@ writes. Never wrap the integration agent in `withInfraRetry` — its push to
`origin/<base>` is not idempotent, and a source-invariant test pins both call
sites as unwrapped.

`checkedTaskOutcome` in `task-outcome.ts` validates the optional result fields
read by recovery and the worker pool before those consumers use them. The
validator accepts `integration` as `null` or a record, and checks that
`integration.pushed` is boolean when present; `kind` and `assessmentError` are
strings; `proposals` is an array of records; and `assessment` is a record. A
`salvage` value must contain string `classification`, `sha`, and `detail`
fields, `committed` as an array of strings, and `skipped` records with string
`path` and `reason` fields. Unrelated result data is preserved. Invalid shapes
throw `Invalid task outcome`, which the normal and recovery paths turn into a
task failure instead of consuming malformed data.

Recovery assessment carries two distinct evidence channels, typed by
`RecoveryAssessmentFields` in `recovery-decision.ts`: blocking
`missingEvidence` and advisory `residualRisk` (read via the
Expand Down Expand Up @@ -426,6 +470,15 @@ burn review quota; the pipeline seam is covered by a fake NDJSON-emitting
`coderabbit` on `PATH`, and the between-item gate by an injected
`runCoderabbitHostReview`.

The adapter validates consumed fields in known finding, error, and completion
events. It synthesizes an `errorType: invalid_output` event only when a known
event is malformed and no valid error event was parsed; a valid error event is
retained instead of being overwritten by the synthetic error. Rate-limit
classification keeps priority, and recognizable authentication text from a
malformed error event is checked separately. Valid finding events remain
available to the pipeline, which handles blocking findings before an
invalid-output deferral or halt.

Every fix round — gate fix, dual-review fix, and between-item fix — is followed
by `verifyWorktreeCommitted`; a fix that leaves the worktree dirty fails the
task closed (`FIX DURABILITY`) because uncommitted fix output is unreviewable
Expand Down Expand Up @@ -732,6 +785,19 @@ changing, so the assertion is that the `if` key is absent rather than that it
holds any particular value. The same suite pins the strict Dafny step, which is
what stops `make all`'s lenient verification quietly skipping in CI.

`make lint` runs the pinned Oxlint rules over the authored module tree in
`src/`, after Markdown linting and workflow parsing. The configuration in
`oxlint.config.mjs` applies the supported Unicorn recommended rules and the
recommended `ai-deslop` rules with type-aware checks. Its exact dependency
pins, the Unicorn/deslop rule conflict, and scoped ODW rule exceptions are
recorded in `docs/adr-004-linting-strategy.md`.

The workflow artefacts in `workflows/` are excluded from Oxlint. They are
object code: `make workflow-parse` checks their workflow syntax and
`make workflow-freshness` checks that the generated ODW artefact matches its
TypeScript source. Tests are also outside the application source lint target;
`make test` remains their behavioural gate.

`make markdownlint` is the separate Markdown gate. It runs the pinned
`markdownlint-cli2` configuration over maintained Markdown and then runs the
en-GB Oxford spelling gate, which regenerates the configuration from the live
Expand Down
52 changes: 51 additions & 1 deletion docs/users-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,18 @@ Set project-specific behaviour in `args.json`. The workflow also has matching
top-of-file defaults, but the sidecar `args.json` is the normal retuning point
for ODW launches.

Arguments must be an object. Missing or `null` arguments use the defaults;
arrays, primitives, and values of the wrong type for a documented field fail at
launch with `Invalid workflow argument type`. String fields require strings,
numeric fields require numbers, and boolean fields require booleans. These
number-or-string fields also accept strings: `stageAttempts`,
`maxWorkItemRounds`, `resumeTaskId`, `resumeMaxCandidates`,
`reviewTimeoutSeconds`, `dakarTimeoutSeconds`, `dakarBudgetGbp`,
`coderabbitAttempts`, `hostReviewAttempts`, and `commitGateTimeoutSeconds`.
Unknown object keys are ignored by this version and remain available for future
options. The legacy `resumePartialBranches` key accepts any value, but only
literal `true` enables recovery.

Common arguments:

- `base`: integration branch. Defaults to `main`.
Expand Down Expand Up @@ -609,7 +621,8 @@ Common arguments:
- `worktreeWritePreflight`: when `false`, skip the once-per-run probe that
proves the planning and build adapters can write into sibling task worktrees.
Defaults to enabled; a failed probe fails the task at stage `worktree-write`
as an environment fault.
as an environment fault. The host verifies the exact token in the probe file;
it does not rely on the agent's reported `ok` value.
- `buildAdapter` and `buildModel`: adapter and model for worktree creation,
implementation, integration, and remediation agents.
- `planAdapter` and `planModel`: adapter and model for planning agents.
Expand Down Expand Up @@ -676,6 +689,17 @@ CodeScene, then Dakar, and only then dispatches the reviewer agents. Set
`reviewTool` to `coderabbit` to select the retained CodeRabbit adapter; its
CLI-specific behaviour is documented next.

Dakar output is accepted only when its terminal JSON document matches a known
review shape. A non-skipped `changes-requested` verdict needs at least one
finding with a supported severity and string `path`, `title`, `detail`, and
`evidence` fields; an optional `line` must be a positive integer. With
`ok: true`, either `skipped: true` or `verdict: "pass"` is treated as a clean
result when findings are absent or empty. This means `skipped: true` takes
precedence even if the document also says `verdict: "changes-requested"`; any
non-empty findings on that path are rejected. Unparsable, malformed, or
otherwise unrecognized output becomes a host-review `error` with error category
`invalid-output`, not a clean review.

## Host-run CodeRabbit review

> **Applies to `reviewTool: 'coderabbit'` only.** The host review tool defaults
Expand All @@ -692,6 +716,14 @@ the CLI's structured findings; `critical` and `major` severities join the
reviewers' blocking items and drive the ordinary fix rounds, while lower
severities are captured without gating integration.

The adapter validates the consumed fields in known `finding`, `error`, and
`complete` events. Wrong field types make the review an `invalid_output` error
instead of allowing a malformed event stream to count as clean; non-object JSON
values and non-JSON lines remain diagnostic text. Valid error events remain
authoritative, including rate-limit handling, and recognizable authentication
errors still halt the task as `fatal-auth`. Valid blocking findings are
retained and handled even when the same stream also contains malformed output.

Rate limits are absorbed by the host: a rate-limited review waits a
deterministic 45–90 minutes (`hostReviewBackoffMinutes`) and retries, up to
`hostReviewAttempts` total attempts, costing wall-clock but zero agent tokens.
Expand Down Expand Up @@ -799,6 +831,24 @@ Assessment is report-only. It never marks roadmap checkboxes, pushes, merges,
or cherry-picks. Use it to decide whether to preserve, manually finish, park,
or discard the branch before relaunching from `origin/<base>`.

If an in-run assessment agent returns no structured result or a malformed one,
the workflow leaves the task's existing outcome intact and reports
`assessmentError` with host-collected `assessmentEvidence`. For a branch found
during fresh-run recovery, the recovery result records
`action: assessment-error` and the error, and the candidate is marked skipped.
It remains unresolved and blocks the roadmap frontier until an operator handles
it; authentication or provider failures halt the run as fatal errors. No failed
assessment supplies a classification for adoption.

The worker pool validates optional task-result fields before consuming them:
`integration.pushed` must be boolean when present; `kind` and `assessmentError`
must be strings; `proposals` must be an array of records; `assessment` a
record; and `salvage` must contain string `classification`, `sha`, and `detail`
fields, string `committed` entries, and `skipped` records with string `path` and
`reason`. `integration` may be `null`. A malformed result becomes a failed
task with `stage: error` and detail `Invalid task outcome`; inspect the task
entry in `result.json` and the run's `halted` summary for the detail.

After an `adopt-partial` or `continue-manual` verdict — and also after an
infrastructure fault such as schema-retry exhaustion — the workflow commits any
dirty `docs/execplans/*.md` artefacts onto the task branch so they survive
Expand Down
Loading
Loading