Skip to content

Spec workflow

How to read, edit, add, and verify spec items. docs/spec/index.html is the source of truth for what Josh Foundation v1 ships; this page is the reference for working with it.

Each spec item is a YAML file at docs/spec/data/<id>.yaml. The renderer (bin/build-spec.py) and the in-browser editor both consume the same JSON Schema at docs/spec/data/_schema.json.

Required fields:

FieldPurpose
idkebab-case, must equal the filename stem.
titleSentence-case, no trailing period.
categorysubstrate (cross-cutting machinery), source (per-data-source ingester), surface (REST/MCP/CLI/UI), or launch (OSS launch artifacts).
statusSee lifecycle.
why1-3 sentences. The deliverable + intent. Anthropic's framing: "ambitious deliverables, not steps."
acceptance_criteriaEARS-form sentences (When …, While …, Where …, If … then …, or ubiquitous "The system shall …").
success_determinerTagged union — load-bearing. An agent runs this to decide if the spec is met. Never free prose.

Optional but encouraged: user_stories, priority (p0/p1/p2), dependencies, plan, tasks, out_of_scope, clarifications_needed. changelog is append-only — auto-prepended when the in-browser editor flips status; if you edit YAML directly you must append entries by hand.

Pick the most mechanical kind that fits. manual is a fallback, not a goal.

KindUse whenRequired fields
bashA shell command can prove the spec is met.command, expect
sqlThe proof is data in the substrate DB.db, query, expect
test_fileA test file in the repo encodes the contract.path, runner
expected_outputThe proof is a string in command stdout, file contents, or a URL response.source, expect
manualOnly when no mechanical check is possible. Lists items a human (or computer-use agent) confirms.checklist

draft → planned → in_progress → blocked → verified → shipped

  • draft — idea, not committed.
  • planned — scoped, not started. Acceptance criteria and a real success determiner exist.
  • in_progress — active work.
  • blocked — waiting on a dependency.
  • verified — success determiner ran green.
  • shipped — change is live in production. Always reached through verified, never directly.

verifiedshipped. Verified means the determiner passes locally or in CI; shipped means the change is live and inventory pages reflect it.

Workflow rules — apply before every status flip

Section titled “Workflow rules — apply before every status flip”
  1. Before planned → in_progress: dry-run the determiner with bin/dry-run-spec.py <id> --explain. It must fail because the implementation doesn't exist yet — not because the determiner is malformed. If it fails for typos or missing files, the spec itself is buggy. Fix it before flipping.
  2. Before in_progress → verified: run bin/build-spec.py --lint, run the determiner (must pass), and append a changelog entry. The build script does not auto-prepend; only the in-browser editor does.
  3. Before verified → shipped: confirm the change is live and both inventory pages — josh-data-sources.html and data-status.html — reflect it in the same commit. Bump Last updated on data-status.
  4. After shipping a substrate-level architectural decision: grep all draft/planned/in_progress specs for references to the old pattern. Update or invalidate them in the same change.
  5. When you find yourself adding a special case to a doc ("this source is different because…"), pause and consider factoring the code instead. The third instance of a pattern is the inflection point.

Per-source ingester specs use the source template, which bakes in the 14-task ingestion-built contract:

Terminal window
cp docs/spec/data/_templates/source.yaml docs/spec/data/<your-source>.yaml
# edit the yaml — at minimum: id, title, why, acceptance_criteria, success_determiner
bin/build-spec.py # regenerate spec HTML
bin/sync-nav.py # propagate the canonical sidebar to peers

For non-source specs (substrate, surface, launch), copy any existing seeded example as a starting point. Keep the ID kebab-case and matching the filename stem — the build script will reject the file otherwise.

Two paths:

  • In-browser editor. Open docs/spec/index.html locally (via ./serve.sh) and click any pencil icon. Chrome, Edge, and Arc write directly back to the YAML via the File System Access API. The editor auto-prepends a changelog entry when status changes and auto-bumps updated_at.
  • Direct YAML edit. Edit the file in docs/spec/data/, then run bin/build-spec.py to regenerate HTML. You must append the changelog entry by hand if status changed.
CommandWhat it does
bin/build-spec.pyRegenerates HTML from YAML. Validates every spec against the JSON Schema first; refuses to write on a schema violation.
bin/build-spec.py --lintHeuristic grounding pass. Warns on stub-drift, stale [STUB] placeholders left in non-draft specs, and backtick-quoted identifiers (file paths, function names) that don't exist in the implementation corpus.
bin/build-spec.py --lint --strictSame, but lint findings exit nonzero. Wire this into CI.
bin/dry-run-spec.py <id>Runs the spec's success_determiner against current repo state. Returns pass/fail.
bin/dry-run-spec.py <id> --explainSame, plus prints what the determiner ran and what it expected.
bin/sync-nav.pyPropagates the canonical sidebar from docs/index.html to every peer page. Run after adding or renaming any nav entry.
bin/sync-nav.py --checkCI-friendly variant: nonzero exit if any peer has drifted from the canonical.

If a planned spec turns out not to be needed, prefer deletion over a perpetual draft. A directory of unstarted ideas dilutes the spec system's signal — the spec page should describe what Josh ships, not what someone once wrote down. Archive the rationale separately if it's worth keeping.