Repository navigation
Releases: modootoday/specfold
Release list
v0.4.0
0.4.0 makes every write command preview by default: spec new and spec set now write only with --write, like the rest, so add --write where scripts and CI create or change documents. It also adds coverage audits, decision-maker import, [check] link_ignore and safer --write undo for files with uncommitted edits, and fixes about 100 issues across checks, hints and refusals.
Behavior changes
What an existing command does differs from 0.3.0; check scripts and CI that call these.
spec new and spec set now only preview without --write: add --write to every script, CI job and agent instruction that creates or changes documents with them. Like every other write command, a run without --write (or with --dry-run, which does the same) writes nothing, shows the change with its diff, and exits 0 with next: naming the same command with --write; --json reports written: false. Their hints, help, docs and agent skills now give the --write form. The 0.3 stderr warning is gone.
spec check --write-baseline with --rule or --skip is refused with exit 2: a baseline is recorded from a full run only (C181).
Two or more [[guards]] with the same baseline file are refused when the config loads (exit 2, naming both guards): each guard's --write-baseline used to overwrite the other's entries (C184).
A --write (fix, edit, set and the rest) over a tracked file that already has uncommitted edits first copies it to .specfold/trash/<timestamp>/, and its undo: copies that back instead of git stash, which also took the user's own edits; the preview names such files. Files without uncommitted edits keep the git stash undo (C183).
-C <dir> now resolves relative path arguments against <dir>, and next commands include -C <dir>.
Write previews show an update's changed lines as unified-diff hunks (@@ -old,n +new,m @@): changes with an unchanged line between them, such as the refreshed updated: line and an edited section, are separate hunks instead of one block. In --json, update items carry hunks (each with line, old_line, before, after) in place of the top-level before and after; line is still the first changed line.
spec sync --prune previews name the trash folder .specfold/trash/<timestamp>/ instead of the current second, so a preview and the refusal of the same write agree; the write itself uses the second it runs in and prints the real path. The pruned-files note names .specfold/trash/.
spec check --rule/--skip refuse (rc 2) a rule id that does not exist, as --fail-on does; a lower-case id is pointed at its upper-case form (C132).
spec fix --pick refuses (rc 2) an id that is not one of the finding's choices, listing them (C146).
spec log --since must be a real YYYY-MM-DD date, and spec log --type, spec find --type and spec find --status must name a known type or status; others are refused with rc 2 (C148, C153).
--limit 0 is refused (rc 2) by every command that takes --limit; it showed nothing and its --offset hint pointed at itself (C152).
spec new --governs and spec context refuse (rc 2) an empty path or one outside the repository, instead of recording it or suggesting spec new --governs for it (C130).
A guard whose range is not staged, push, all or a git range (a..b) is refused by every command that loads spec.toml, naming the guard; spec doctor reports it as a config finding (C141).
- An id held by two files (I1, e.g. a
.spec/and a.spec.local/copy) is refused (exit 2) by every command that takes a document, listing both paths; name one by its path.spec doctor's fix names the path (C139). spec edit --section/--new-sectionrefuses (exit 2) text holding a heading at the section's level or above, which would start or duplicate a section; deeper headings and fenced code are fine (C125).spec import --apply --writerefuses (exit 4) while rows marked!still need a decision, lists them and suggests--force; the new--forceimports anyway. Previews are unchanged (C155).spec attach --intorefuses (exit 2) an absolute folder or one with..; it used to write outside the bundle (C142).spec new skill|rule|agentrefuses (exit 2) a title with no ASCII words unless--slugis given, and a--slugthat is not kebab-case (C147).spec initrefuses (exit 2) an--owner-kindwith an empty pattern or template, a{n}beyond the pattern's*s, or a suffix other than:manifest, and a--rootthat is not an owner id (C140).spec set <id> title=refuses (exit 2) emptying the required title (C128).spec export … --dry-runpreviews and writes nothing (it exported before): it listsfilesand, over an earlier export,replaceswith exit 0; a plugin exporter is not called.spec check -qnow shows config warnings too (C114, C115, C116).spec set <plan> status=archived --writerefuses with exit 4 while the plan has open tasks and lists them;--forcearchives anyway, and--dry-runreports them with exit 0.- Exit code
4(CLI-17): a write flag was given but a safeguard refused it, so nothing was
written (--writeover[write] max_files,sync --prunewithout--yes, a hand-edited
copy without--force,exportover an earlier export orattachto the store without
--yes). These exited0before. spec set <id> key=[a, b]stores a list, not the string"[a, b]".spec sync --prune --write --yesno longer deletes: each pruned file moves to.specfold/trash/<stamp>/(ignored by git), and the undo line copies it back (cp -Rp .specfold/trash/<stamp>/. .), so a file never committed can be restored. The undo of a sync or copy lists only the files written, not their sources; a successful sync ends withnext: git status --short(C100, C101, C106).spec check --write-baselinewithout[check] baselinesays what to add to spec.toml before previewing, withnext: spec explain config(C102).--dry-runis accepted on every command guarded by--yesor--writeand writes nothing: check and guard ignore it, import previews even with--apply(C120), and export previews instead (C103, C114). An export whose--outis outside the repository suggestsls <out>instead ofgit status(C104).- An invalid spec.toml points to
spec explain config(C106). - Overwrites need ownership or
--force(CLI-4):spec sync assets --writeoverwrites a copy file only when its content is a version its source has had (git history or now); a hand-edited copy needs--force.spec fetchleaves a different existing file alone and fetches nothing unless--force.initupgrades andexportare unchanged. - Bulk writes are bounded (CLI-7):
[write] max_files = 50(0 = no bound). Whenspec mv,fix,syncorimportwould change more files, the preview says how many and--writealone writes nothing; narrow the scope or add--yes(new on mv, fix and import). - One meaning per shared flag (CLI-11):
spec guard --tree(was--all),spec find --body(was--text),spec init --owner-kind(was--owner). The old names keep working as hidden aliases with a stderr warning; help JSON lists them under each flag'sdeprecated_aliases. spec --helpandspec helplist the 12 core commands (context, find, show, graph, new, set, edit, check, guard, fix, explain, init);spec --help --all/spec help --alllist every command.spec help --json,--markdown,--llmsand MCP still describe every command; JSON addscore.spec guard --write-baselinewithout--yespreviews the hit counts it would record.- CLI conventions CLI-1–CLI-16 (AGENTS.md,
tests/cli_contract.rs). Actions that delete,
overwrite or send files out now need--yesbesides their write flag; without it they write
nothing andnextshows the command with--yes:spec sync assets --prune --write,
spec attach … --writewhen a file goes to the store,spec export markdownreplacing an
earlier export in--out, andspec check/spec guard --write-baseline.
Added
spec audit --kind coverage: with[audit] coverage_types = ["sot"], lists each{#ANCHOR}heading of a current document of those types that no other document cites as[[<id>#ANCHOR]]. Off by default.- Import keeps who decided: MADR
decision-makers/deciders,consulted,informedand MADR 2/Log4brains* Deciders:body lines becomedecision_makers,consultedandinformedlists, now declaredadrfields (no S1 warning). [check] link_ignore: globs of link targets rule L8 skips.- A config warning when
require_whennames a status the type does not have. - A config warning when
[audit] coverage_typesnames a type that does not exist. - Rules L8 and L9 (warn). L8 checks plain Markdown links and images offline: a relative path
must exist and a#fragmentmust match a heading's GitHub slug (repeats get-1,-2) or a
{#anchor}in the target file; URLs and ids are skipped. L9 flags an image without alt text. - New rule S8 (warning): a field that
require_whenrequires for the current status must hold a usable value — not a placeholder ([types.<t>] placeholders, defaultTBD,Pending,TODO,?), of the kind declared infields = { approvers = "people", resolution = "url", review_by = "date", decision = "link" }, and not shared within adistinct = [["authors", "approvers"]]group.fieldsas a list works as before. spec help --jsonmarks each command'sdeprecated_aliases(name,use,since). A deprecated name keeps running the new command and warns on stderr (none yet).spec set ID key=with an empty value deletes the key (an emptiedrelis removed too).- D1 also counts staged deletes and the old paths of renames (
p (removed)). Its fix is a runnablespec new change ... --depends-on ID --write;spec explain D1points tospec check --staged --rule D1. A staged/changed run without documents says so in its summary.
...
v0.3.0
- chore: release specfold 0.3.0
- docs: editor setup for spec lsp (C32)
- translation: refuse remote or translated --from, lower-case slug tag (C67-C69)
- set: --unpin, named pin errors, review_by date and self-exception checks; audit lists overdue exceptions
- evals: check consumers before changing a shared decision
- graph: consumers lists documents here and in remotes that name a shared document
- check: review_by and pins are common keys; document T5 as error
- pins: pin remote documents by body revision, rule L7, spec set --pin (X2)
- exceptions: rel.exception_of with review_by, rule T5 (X4)
- remotes: L6 cites, R1 for remote sources, set --source-rev, doctor lists remotes; translation evals
- translations: validate lang and source_rev, reject translation chains, warn on duplicate languages, schema keys, census sections (C57-C61)
- Name documents in other repositories as : through [remotes]
- Pair translations by place: [translations] layout, rule R2, spec new --translation, spec fix --translations
- Quiet config warnings outside check/doctor, open a document by its full timestamp, write imported relations as ids, name changed sections of translations, census --fail-on
- translations: rel.translation_of, source_rev, rule R1, census --translations
- check: A4 skips the CI runner home, A3 skips git refs
- import: presets read both spellings, link superseding records, rewrite links, name the next fix
- skills: cover doctor, lsp/editor/CI, init --upgrade, edit sections, tier, H1/A4/S7; add evals
- tests: store the MADR 4 fixture's decisions directory under a .fixture suffix
- S7 names the --new-section command; spec new writes required sections; typo'd [types.*] tables warn
- import: --preset prints starter maps for adr-tools, MADR and Log4brains
- import: status patterns for compound values such as 'superseded by ADR-0123'
- import: read status, date and links from the body ([body] in the import map)
- help: list find's aliases list and ls
- A4: check well-known agent files too, sharing H1's file list
- H1: scan well-known agent files, escape hidden characters in output, one-character LSP range, before/after fix preview
- check: S7 required sections per type ([types.] sections)
- check: A4 machine-specific home paths in agent assets
- cli: check resolves names like resolve, list/ls find, near-miss status and path candidates, fix and show next lines; docs: H1 in rules reference
- check: H1 reports invisible characters in documents and agent assets, with a removal fix
- lsp help: spec.run acts on disk; every change re-runs the full check
- lsp: one quick fix per candidate; offered --write fix commands run through workspace/executeCommand
- check: name the naming mismatch for unsaved files, mention --stdin-path only when used, explain '-'; lsp: -32700 for bad JSON and a reason on stderr for broken frames; binstall metadata
- check: accept document ids; clearer error for unknown names
- lsp: keep the last full check and reuse it until disk or buffers change
- lsp: offer replacement fixes as quick fix code actions
- doctor: no spec.toml is a finding; check: next for errors left out by --fail-on; new: full rerun with --slug
- lsp: diagnostics over stdio; --stdin-path accepts a new file in a spec directory
- check: --stdin-path for unsaved buffers; clear errors for files that are not documents
- check: report one file's findings and an editor format; mv --cited-paths reads tracked files
- Cycle 23: eval fixes (warnings next, sync without agent files, mv --cited-paths, repeated-title warning, word-boundary slugs, graph json, id prefixes), spec init --upgrade
- Cycle 22: deterministic doctor/guard tests, generated-file guidance, large-corpus regression test
- Cycle 21: README standardization, llms.txt, doctor phase 2, show splitting, mv --tier, S3/guard hints
- feat: --max-bytes list budget (MCP default 40000), graph --assets, spec doctor
- graph impact lists the agent assets that name the document or what reaches it
- find --open-tasks: documents with unchecked task items, with the count
- Revert "Merge feat/cycle18"
- Check published count markers: count_paths for README and docs, with a test
- Cycle 17: positional title, fail-on none and excluded-error summary, real ids in next, Action version from its own tag, slug next
- docs: byte-identical twins (md-kit v1)
- docs: v5 information architecture, md-kit components and touch targets
- Number repeated fingerprints; action gains fail-on and checks a pull request from its merge base
- Add a composite GitHub Action that installs a verified release and runs spec check
- Add check --format github, sarif, gitlab and rdjson; pre-commit hooks use language unsupported
- Errors carry one runnable next command; missing args and did-you-mean in usage errors; set refuses superseded without a successor and warns unknown keys; non-ASCII titles slug from governs; first --local excludes the local dir from git; graph impact on a path suggests governs; real rule ids in next
- help --markdown prints a reference page and suggests the nearest name; mv and import refuse [forbid] paths; set no longer guesses a finishing status; init shows a commented count_paths and names spec explain
- new refuses [forbid] paths and suggests --local; set names reachable statuses and a next command; N1 reads [check] count_paths; help --markdown keeps a blank line before the table
- docs: fix site layout overflow and the commands table; list the install channels
- check: N1 compares numbers in spec:count markers with find filters or table rows; spec fix rewrites them
- graph: --format box draws layered boxes sized to COLUMNS, ASCII on request or non-UTF-8 locale, tree fallback; tests pin the locale
- graph: tree with relation and distance, impact relations, chain both ways, governs in json, mermaid/dot, --ascii/--depth
- Status moves name a real way out: a draft is activated before it is superseded; S4 names the status meant for accepted, proposed, deprecated
- Built-in change type: one user-facing change per note, active until a release archives it with release set (N8)
- docs: build the site with Jekyll on GitHub Pages
- spec sync assets: copy each [[assets]] copy_of source over its root; preview until --write, --prune to delete, refuses paths outside the root and symbolic links (A8)
- [check] extra_keys: frontmatter keys every type may carry, prefix* allowed (S1)
- docs: set the Pages custom domain (CNAME)
- verified_at pin: spec set resolves HEAD, audit stale reports commits to governed code since the pin
- assets name documents: docs list in spec assets, assets field in spec show
- docs: short README with generated command table, docs/ guides, and drift tests
- spec agents: name edit, check summary, assets and audit kinds; commit hash in --version
- feat(edit): --row with --cell COLUMN=VALUE or a whole row; --append adds a missing row
- feat(new): spec new skill/rule/agent writes id-less templates
- feat(audit): drift kind for skill copies that differ from .agents/ and CLAUDE.md without @AGENTS.md
- feat(check): A3 paths and spec commands named in agent assets exist
- feat(check): A1 asset frontmatter and A2 unique asset names
- feat(assets): declare agent asset roots in spec.toml and list them with spec assets
- feat(edit): add --new-section with --after placement
- feat(audit): report plans whose tasks and status disagree (kind finished)
- feat(edit): read piped or redirected stdin when neither --text nor --file is given; add --replace
- feat(check): S1 unknown key names a near key or the spec.toml fields line that declares it
- fix(edit): rerun hint uses --text= so text starting with '-' still parses
- feat(dates): write created/updated as RFC 3339 UTC, read plain dates, offsets and UTC/GMT/KST/JST
- docs: say that timestamp keys and dates are UTC
- feat(edit): rerun hint quotes the input so it can be pasted
- feat(fix): resolve a shared alias (I2) by keeping it on the picked document
- feat(check): --summary counts per rule, -q lists errors only, clearer paging and baseline lines
- feat(check): --new lists documents not in the last commit; say audit is optional
- feat(links): match stale path cites by file name and rewrite them on mv
- feat(edit): replace or append to a section or the whole body
- feat(show): count task items and list them with --tasks
v0.2.6
- chore: release specfold 0.2.6
- feat(owners): a pending owner's old layout raises no findings until import
- feat(check): a moved gitlink brings the files changed inside it into the run
v0.2.5
- chore: release specfold 0.2.5
- fix(attach): re-attaching a file already in the bundle keeps its content
v0.2.4
- chore: release specfold 0.2.4
- fix(guards): a whole-tree guard reads every tracked file, ignored or not
v0.2.3
- chore: release specfold 0.2.3
- fix(check): a baseline is keyed by rule and path, not by the commit it was measured on
v0.2.2
- chore: release specfold 0.2.2
- fix(verify): read subshells and escapes; a configured shell; L6 reads only id-shaped links
v0.2.1
- chore: release specfold 0.2.1
- fix: redirections are not commands; the first alias table and the baseline pass
v0.2.0
- fix(skills): eval example cites a source of truth, not a dated plan path
- chore: release specfold 0.2.0
- chore: plain gitignore for build output and local files
- feat(skills): usage skills shipped with the package; root allowlist gitignore
- docs: install instructions written for people
- feat(npm): ship the CLI on npm as @modootoday/specfold
- fix(files): command stores run from the repository root
- feat(import): wrap raw files into generated documents; drop old-layout files
- fix(import): chapters and attachments move, not copy
- fix(import): nested bundles stay with their first rule; stem aliases; path targets keep .md
- fix(import, links): chapter aliases, clean relation targets, relative paths, external notes
- perf: guards and checks read only what changed, once
- fix(guards): an empty published path is a hit, not a clean pass
- test: neutral fixtures in guard tests (no Korean text, no home paths)
- feat(guards): published-path scope, path patterns, lookaround, script and code exemptions
- refactor: one responsibility per file, context directories, no flat roots
- feat(guards): self-tests, table patterns, allow lists, gzip, empty-scan and baseline
- feat(check): V1 finds published paths, not only configured ones
- feat(check): L6, citations outside documents still resolve
- feat(check): T4, an edited body moves its updated date
- feat(check): baseline of known findings, strict paths, per-rule severity, --fail-on
- feat: configurable transitions, status-required fields, supersede cycles, verify runner
- fix(import): stub-only redirects, home owner fallback, nested history, gitignore globs
- docs: use neutral example paths (services/, research) in docs and tests
- ci: name the release approval environment after its registry (crates)
- feat: first version of specfold