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
12 changes: 11 additions & 1 deletion .devia/01_ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ src/cli.mjs argument parsing, command table, context (root, .devia, f
src/commands/*.mjs one file per command, each exporting a default (ctx, name) => exit code
src/lib/*.mjs yaml · markdown · fs · git · rules · ui · vendor · version — no command logic
src/lib/*.mjs yaml · markdown · fs · git · rules · gates · tokens · context ·
sanitize · contribution · ui · vendor · version — no command logic
content rules/ · standard/ · checklists/ · templates/ (read, never imported)
```
Expand Down Expand Up @@ -60,6 +61,15 @@ Content is data. Code reads it; code never encodes what a rule says.
| `check` scans what git carries, not what the disk holds | A P0 failure on an ignored build artefact is a false positive that teaches people to ignore the gate | `src/lib/git.mjs` |
| Design rule IDs carried over unchanged | Consolidation must not invalidate existing citations | `MIGRATION.md` |
| A check that cannot answer returns SKIP | `PASS` must mean verified, never assumed | `src/commands/check.mjs` |
| The gate table is data in `src/lib/gates.mjs`, not structure inside `check` | Two readers need it and only one has a repository to scan: `check` attaches behaviour, `context` asks whether a rule is machine-enforced. One table is what stops the two answers drifting | `src/lib/gates.mjs`, `src/lib/context.mjs` |
| Context is routed and budgeted, never dumped | More context is not better context: the standard is ~19k tokens and a task needs a fraction of it | `src/lib/context.mjs`, `scripts/benchmark-context.mjs` |
| A target is not a floor, and both are printed | Reporting "target 600, selected 1380" made a stated design read as a broken promise. Three numbers now travel together: target, mandatory floor, selected — plus the status that reconciles them | `src/lib/context.mjs` `select`, `src/commands/context.mjs` `report` |
| `advisory` keeps every mandatory item whole; `strict` never exceeds | Two honest promises beat one vague one. Advisory reports `over` and includes the floor anyway; strict compresses mandatory items toward their identifier — never dropping one — and says `impossible` rather than going over | `src/lib/context.mjs` `SMALLEST`, `LADDER` |
| Compression is minimal, and restores upward | The first version degraded everything and then spent the freed tokens on *optional* rules at full text. Every mandatory item now starts at its smallest form and is bought back in relevance order | `src/lib/context.mjs` `select` |
| The impact map is a router, not only a checklist | It is the one routing table the project wrote, in the project's own vocabulary. A declared change type routes the domains of the memory files it names, so `new_consent_record` routes as well as a built-in | `src/lib/context.mjs` `matchedChangeTypes` |
| Every run is bound to its fixture and its devia | `fixed` means devia saw the problem and then saw it gone — same fixture, different devia. Without both digests, editing the fixture until it passes reads exactly like fixing the tool | `src/lib/contribution.mjs` `evidenceChain` |
| A contribution is eligible because devia reproduced it | An agent that can file an issue will invent reasons to. The state is computed from a re-run, bound to a hash of the claim, so editing the claim drops the verdict instead of carrying it forward | `src/lib/contribution.mjs` |
| devia never holds a GitHub token | The contribution path hands a prepared file to `gh` under an identity the project declared. A tool that stores credentials to be helpful is a tool that leaks them | `src/commands/contribute.mjs`, `.devia/04_PERMISSIONS.md` |
| devia is for every agent | No agent is privileged: a surface that serves one must say why the others are not served, and record the gap. Absence of evidence about an agent is reported as SKIP, never as "unsupported" | `.devia/11_GAPS.md` G6, `src/commands/skills.mjs` |
| The npm package is scoped, the command is not | npm refused the bare name `devia` as too similar to `degit`, `dexie` and `dva`; scoped names skip that filter. Docs say `npm i -D @schneiderjoseph/devia`, then `npx devia` | `package.json` |

Expand Down
19 changes: 19 additions & 0 deletions .devia/02_SURFACES.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,34 @@
| `devia doctor` | Adoption, drift, staleness | `src/commands/doctor.mjs` | 1 when there is no `.devia/` |
| `devia rules` | Query the registry | `src/commands/rules.mjs` | 1 when `--id` is unknown |
| `devia read` | Render the memory as one self-contained page | `src/commands/read.mjs` | 1 without `.devia/` |
| `devia context` | The smallest sufficient context for one task | `src/commands/context.mjs` | 1 without `.devia/`, or when a strict target cannot hold the mandatory set |
| `devia sync` | Pin the standard, or refresh a pinned copy | `src/commands/sync.mjs` | 1 without `.devia/` |
| `devia skills` | Install adapters and the skill pack, per repository or `--global` | `src/commands/skills.mjs` | 2 on a bad action |
| `devia gap` / `devia debt` | Registry lines | `src/commands/registry.mjs` | 1 when the id is unknown |
| `devia contribute` | A devia problem observed here, as an issue or a pull request | `src/commands/contribute.mjs` | 1 when a candidate is not eligible, 2 on a bad action |

Global flags: `--root`, `--json`, `--help`, `--version` (prints the CLI **and** standard
versions — an adopter pins one and reports the other).

`init` alone refuses to act on a root it inferred that is not the current directory: `--root` to
say where, or `--yes` to accept it. Nothing is written before that question is settled.

## The one surface that can reach the network

`devia contribute submit --yes` is the only command in devia that can make a network request, and
it makes it by handing a prepared file to `gh`. Everything else — recording, reproducing,
verifying, rendering the payload — is local, and `submit` without `--yes` writes the payload and
prints the command rather than running it.

| Step | Reaches the network | Guard |
|---|---|---|
| `contribute new` · `repro` · `verify` · `show` | No | — |
| `contribute submit` | No | Writes `payload/` and prints the `gh` command |
| `contribute submit --yes` | Yes, through `gh` | Eligible · payload clean · identity declared and not the maintainer · `gh` authenticated as that identity |

devia holds no GitHub token, reads none from the environment, and never commits or pushes in a
checkout. A pull request is opened only against a branch the contributor already pushed.

## Package exports

| Export | Path | For |
Expand All @@ -39,6 +57,7 @@ say where, or `--yes` to accept it. Nothing is written before that question is s
| `init`, `skills install` | `AGENTS.md`, `CLAUDE.md`, `.cursor/rules/devia.mdc`, `.github/copilot-instructions.md`, `.windsurfrules` |
| `skills install --skill` | `.cursor/skills/devia/SKILL.md`, `.claude/skills/devia/SKILL.md` |
| `read` | `.devia/reader.html` — a generated snapshot, gitignored, never the source |
| `contribute` | `.devia/contributions/<id>/` — the record, the fixture, and a generated `payload/` |
| `skills install --global` | Outside the repository, in each agent's own configuration: `~/.claude/skills/devia/`, `~/.codex/skills/devia/`, `~/.cursor/rules/devia.mdc`, `~/.gemini/GEMINI.md` when empty. Copilot and Windsurf report `SKIP` (`12_DEBT.md` D8) |

`files` in `package.json` decides what npm ships. Adding a directory the CLI reads at runtime
Expand Down
22 changes: 22 additions & 0 deletions .devia/03_DATA_MODEL.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
| Registry line | A gap or a debt row | `.devia/11_GAPS.md`, `.devia/12_DEBT.md` | Markdown table row, id `G<n>` / `D<n>` |
| Waiver | Time-boxed exception | `devia.json` `waivers[]` | `schema/waiver.schema.json` |
| Version pin | Standard and schema versions | `VERSION` | YAML scalars |
| Contribution record | Evidence for a devia problem seen here | `.devia/contributions/<id>/record.json` | `schema/contribution.schema.json` |
| Context item | One addressable piece of deliverable context | Derived from rules and `.devia/` | `{ kind, id, domains, tier, tokens, why }` in `src/lib/context.mjs` |

A contribution record is JSON rather than YAML because it is machine data the CLI writes and
reads, like `devia.json` — not content a human authors, which is where the frontmatter shape
belongs.

## Invariants

Expand All @@ -21,6 +27,17 @@
- Every rule has at least one `source` and at least one validation method.
- Registry ids are monotone per registry and never reused, including after closure (`MEM-004`).
- Generated files are derived from the rule files; the rule files are the source of truth.
- A contribution record never stores its own state. The state is computed from the verification,
and the verification is bound to a hash of the claim it was made about — editing the claim
drops the verdict rather than carrying it forward.
- `fixed` requires two observations that are the same experiment: devia saw the problem, then
devia saw it gone, with the same fixture digest and a different devia digest. One run can only
ever be half of that, and two runs over two different fixtures are not a fix at all.
- A mandatory context item is admitted before the target is consulted. In `advisory` it is never
compressed and the target is reported as exceeded; in `strict` it may be compressed toward its
identifier but is never dropped, and the target is never exceeded.
- `target`, `mandatory floor` and `selected` are three separate numbers and are always reported
as three.

## Lifecycles

Expand All @@ -31,6 +48,11 @@ Rule status: `draft → proposed → active → deprecated → superseded → re
Registry line: `open → closed` for a gap, `open → discharged` for debt. Closure records the
change that closed it; partial work reduces the line instead of removing it (`MEM-003`).

Contribution: `incomplete → observed → reproduced → fixed`, with `rejected` reachable from any
verification that did not show the reported behaviour. Only `reproduced` and `fixed` are
eligible to be proposed, and only `fixed` with a named regression test routes to a pull request
(`AGT-012`).

## Migrations

There is no schema migration. The equivalent is versioning: a breaking change to a rule id, the
Expand Down
32 changes: 31 additions & 1 deletion .devia/04_PERMISSIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
|---|---|
| Create and update files under `<root>/.devia/` | Write outside `--root`, except `skills install --global` |
| Write the agent adapters at the repository root | Overwrite a file the user has edited, without `--force` |
| Read files in the target repository to produce evidence | Send anything over the network |
| Read files in the target repository to produce evidence | Send anything over the network, except `contribute submit --yes` |
| Replace `.devia/standard/` wholesale on `sync` | Touch the project's own memory content on `sync` |
| Copy a named file into a contribution fixture, sanitized | Copy a file into a fixture that the user did not name |

`init` keeps every existing memory file unless `--force` is passed, because those files hold
decisions the tool did not make.
Expand All @@ -26,13 +27,42 @@ A file the agent owns and devia adds to (`~/.claude/skills/`, `~/.codex/skills/`
(`~/.gemini/GEMINI.md`) is written only when absent or empty; otherwise `SKIP` says so and the
content stays. `--force` overrides both, and says which paths it took.

## What may leave the machine

`devia contribute` is the only feature that can publish anything, and it is built so that the
answer to "what did it send" is always a file the user read first.

| Never leaves | Leaves only through `submit --yes` |
|---|---|
| The repository's source, unless a file is named with `--include` | A sanitized minimal fixture the contributor built |
| An environment file — refused outright, never sanitized and copied | The devia version, standard version, node and platform |
| Secrets, tokens, email addresses, IP addresses, the home directory, the account name, the repository path | The gate or rule involved, and the expected and actual behaviour |
| Dependency lists, private filenames, git history | The regression test's path, when there is one |

Four gates stand between a candidate and a request:

```text
eligible devia itself reproduced it, and there is a minimal case (AGT-012)
clean the finished payload is re-scanned; a surviving secret shape blocks, not warns
authorised --yes, given per submission, never remembered
attributed an identity declared in devia.json, refused if it is the maintainer's,
and checked against the account gh is actually authenticated as
```

devia stores no token and reads none from the environment. It does not commit, branch or push in
anyone's checkout: a pull request is opened only against a branch the contributor already pushed.
`"contribution": { "enabled": false }` in `devia.json` turns the whole feature off, local
commands included.

## Destructive operations

| Operation | Where | Guard |
|---|---|---|
| `rm -rf .devia/standard` before re-pinning | `init --vendor`, `sync` | Only that one directory, which the tool owns |
| Overwriting memory files | `init --force` | Off by default, warned about in the output |
| Removing a registry line | `gap`/`debt close` | Moves the line to the closed table, never deletes it |
| `rm -rf` a contribution candidate | `contribute rm` | Only that candidate's directory, which the tool owns |
| Rebuilding a contribution fixture | `contribute repro --force` | Off by default; without it the existing fixture is kept |

## Repository permissions

Expand Down
9 changes: 8 additions & 1 deletion .devia/05_FLOWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
| Record | `devia gap add` / `devia debt add` / `debt close` → monotone ids, nothing deleted | "gap and debt lines get monotone ids…" |
| Upgrade | `devia sync` → standard pinned or refreshed, pin updated, memory untouched | "sync pins the standard on demand" |
| Cite | `devia rules --id SEC-001` → the full rule text | "rules can be queried by id and by filter" |
| Brief | `devia context "<task>"` → the blocking set, then what fits the budget | `tests/context.test.mjs`, `scripts/benchmark-context.mjs` |
| Contribute | `contribute new → repro → verify → submit` → nothing sent without `--yes` | `tests/contribute.test.mjs` |

## Failure behaviour

Expand All @@ -24,5 +26,10 @@
| Gate | A check cannot determine an answer | `SKIP` with the reason | Never counts as a pass |
| Upgrade | The pinned version differs from the installed one | `validate` and `doctor` warn and name `devia sync` | Leaves the pin until sync runs |
| Any | An unexpected exception | `devia: <message>` (stack with `DEVIA_DEBUG=1`) | Exits 1 |
| Brief | The budget is too small for the blocking set | `OVERRUN` and the blocking cost | Includes them anyway; a budget never evicts a P0 |
| Contribute | The problem was never reproduced | The blocker and the command that would reproduce it | Refuses to submit; writes the payload for reading |
| Contribute | A secret survives into the payload | `not sanitized` and what survived | Blocks the upload — it is never downgraded to a warning |
| Contribute | No identity, or `gh` authenticated as someone else | The mismatch, named | Sends nothing, and says nothing was sent |

The rule behind the whole table: silence is never a pass.
The rule behind the whole table: silence is never a pass, and nothing leaves the machine without
a sentence saying it did.
20 changes: 20 additions & 0 deletions .devia/10_NEVER_ALWAYS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,24 @@
tool did not make.
- **Never add a directory the CLI reads at runtime without adding it to `files` in
`package.json`.** It works locally and ships broken.
- **Never set `FORCE_COLOR` to an empty string to neutralise it.** Node warns on stderr when it
sees both `NO_COLOR` and `FORCE_COLOR`, and `""` still counts as seeing it. Delete the key from
the inherited environment instead. The tests learned this once; `runObservation` had to learn
it again, because a warning leaking into captured stdout turns a verdict into a parse failure.
- **Never let a routing table match a substring of a word.** `key` inside `monkey` redacted
`monkey: banana`; `table` in the component keywords sent a schema change through
`components → accessibility` and pulled the whole screen corpus into it. Anchor on a whole
segment, and let a benchmark or a test name the word it must not match.
- **Never call a number a budget when it is a target that can be exceeded.** Print the target,
the mandatory floor and what was actually selected, and the status that reconciles them.
"Budget 600, selected 1380" reads as a broken promise even when the design was stated.
- **Never degrade everything to fit, then spend the freed room on something less important.**
Compression starts at the smallest form and buys back upward in relevance order. The first
version shrank every mandatory rule to an identifier and then admitted *optional* rules at
full text.
- **Never record a verdict that outlives the claim it was made about.** A verification is bound
to a hash of the observation; edit the observation and the state drops back to `observed`. A
stale verdict is exactly how an unreproduced problem reaches a maintainer.
- **Never vendor a file without whatever it links to.** A relative link that resolves in this
repository and not in `.devia/standard/` is a broken link shipped to every adopter, invisible
here because `validate-links.mjs` skips `.devia/`. Add the target to `src/lib/vendor.mjs`, and
Expand All @@ -32,6 +50,8 @@
- Always update `.devia/` in the same change as the code (`MEM-009`).
- Always run `npm run validate`, `npm test` and `node bin/devia.mjs check --root .` before
reporting done — all three, because they catch different things.
- Always run `npm run benchmark:context` after touching the router, the tiers or the budget. A
reduction that drops a rule the task needed is not a reduction, and only the benchmark says so.
- Always regenerate the index after touching a rule file.
- Always give a new rule a `source` and a real validation method, or mark honestly that it can
only be reviewed by a human.
Expand Down
Loading
Loading