Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- tags field in all five artifact schemas (rfc, clause, adr, work, guard) (WI-2026-04-09-001)
- govctl tag new/delete/list commands with usage counts (WI-2026-04-09-001)
- govctl check validates tags against config allowed list (WI-2026-04-09-001)
- --tag filter on rfc/clause/adr/work/guard list commands (WI-2026-04-09-001)

### Fixed

- clause edit <ID> text --stdin works without explicit --set (WI-2026-04-10-001)

## [0.8.1] - 2026-04-08

### Added
Expand Down
55 changes: 41 additions & 14 deletions docs/rfc/RFC-0002.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<!-- GENERATED: do not edit. Source: RFC-0002 -->
<!-- SIGNATURE: sha256:cc151bfa5029e201e922222957ebad390015a041fed9f8bbfa287bafc4c62af2 -->
<!-- SIGNATURE: sha256:8b40e4a8ba1cb98f5e073ebd1735e4906ade1a949a276d3003a25f5390214568 -->

# RFC-0002: CLI Resource Model and Command Architecture

> **Version:** 0.6.1 | **Status:** normative | **Phase:** test
> **Version:** 0.7.0 | **Status:** normative | **Phase:** test

---

Expand Down Expand Up @@ -72,29 +72,29 @@ The following resource types MUST be supported as top-level command namespaces:
Manages RFC specifications (normative documents defining system behavior).

- ID Format: `RFC-NNNN` (e.g., RFC-0001)
- Lifecycle: draft → normative → deprecated (per [RFC-0001:C-RFC-STATUS](../rfc/RFC-0001.md#rfc-0001c-rfc-status))
- Phase: spec → impl → test → stable (per [RFC-0001:C-RFC-PHASE](../rfc/RFC-0001.md#rfc-0001c-rfc-phase))
- Lifecycle: draft → normative → deprecated (per RFC-0001:C-RFC-STATUS)
- Phase: spec → impl → test → stable (per RFC-0001:C-RFC-PHASE)

**2. `adr` - Architecture Decision Record**

Manages ADRs (records of architectural decisions).

- ID Format: `ADR-NNNN` (e.g., ADR-0001)
- Lifecycle: proposed → accepted → superseded, or proposed → rejected (per [RFC-0001:C-ADR-STATUS](../rfc/RFC-0001.md#rfc-0001c-adr-status))
- Lifecycle: proposed → accepted → superseded, or proposed → rejected (per RFC-0001:C-ADR-STATUS)

**3. `work` - Work Item**

Manages work items (tasks, features, bugs).

- ID Format: `WI-YYYY-MM-DD-NNN` (e.g., WI-2026-01-19-001)
- Lifecycle: queue → active → done, with cancellation at any stage (per [RFC-0001:C-WORK-STATUS](../rfc/RFC-0001.md#rfc-0001c-work-status))
- Lifecycle: queue → active → done, with cancellation at any stage (per RFC-0001:C-WORK-STATUS)

**4. `clause` - RFC Clause**

Manages individual clauses within RFCs.

- ID Format: `RFC-NNNN:C-NAME` (e.g., RFC-0001:C-SUMMARY)
- Lifecycle: active → deprecated → superseded (per [RFC-0001:C-CLAUSE-STATUS](../rfc/RFC-0001.md#rfc-0001c-clause-status))
- Lifecycle: active → deprecated → superseded (per RFC-0001:C-CLAUSE-STATUS)

**Clause Namespace vs Storage:**

Expand All @@ -104,7 +104,7 @@ The CLI namespace is independent of filesystem layout. Implementations MAY store

**5. `guard` - Verification Guard**

Manages reusable executable completion checks defined by [RFC-0000:C-GUARD-DEF](../rfc/RFC-0000.md#rfc-0000c-guard-def).
Manages reusable executable completion checks defined by RFC-0000:C-GUARD-DEF.

- ID Format: `GUARD-NAME` (e.g., GUARD-CARGO-TEST)
- Storage: `gov/guard/` as individual TOML files
Expand All @@ -115,7 +115,7 @@ Manages reusable executable completion checks defined by [RFC-0000:C-GUARD-DEF](
Manages published versions and their included work item references.

- ID Format: Semantic version (e.g., 1.0.0)
- Storage: `gov/releases.toml` as defined by [RFC-0000:C-RELEASE-DEF](../rfc/RFC-0000.md#rfc-0000c-release-def)
- Storage: `gov/releases.toml` as defined by RFC-0000:C-RELEASE-DEF
- No lifecycle (immutable once created)

**Resource Identification:**
Expand All @@ -131,9 +131,13 @@ Each resource type MUST have a unique, predictable ID format that:

All date-valued resource metadata fields (such as `created`, `updated`, `started`, `completed`, and `date`) MUST use ISO 8601 calendar date format `YYYY-MM-DD`.

**Tags:**

RFCs, clauses, ADRs, work items, and guards MAY include an optional `tags` array in the `[govctl]` section. Each tag MUST be a string from the project's controlled vocabulary defined in `gov/config.toml` under `[tags] allowed`. Tags MUST match the pattern `[a-z][a-z0-9-]*` (lowercase kebab-case). Releases do not carry tags. `govctl check` MUST reject any artifact that references a tag not present in the allowed set.

**Future Extensions:**

Additional resource types MAY be added via RFC amendment. New resource types MUST follow the same structural patterns defined in [RFC-0002:C-CRUD-VERBS](../rfc/RFC-0002.md#rfc-0002c-crud-verbs).
Additional resource types MAY be added via RFC amendment. New resource types MUST follow the same structural patterns defined in RFC-0002:C-CRUD-VERBS.

*Since: v0.1.0*

Expand All @@ -158,7 +162,7 @@ Guard-specific: `govctl guard new "<title>"` scaffolds a new guard TOML file und

**2. `list` - List Resources**

Syntax: `govctl <resource> list [filter]`
Syntax: `govctl <resource> list [filter] [--tag <tag>[,<tag>...]]`

Lists all instances of the resource type. Optional filter narrows results.

Expand All @@ -168,6 +172,7 @@ Behavior:
- Supports filtering (exact match or substring)
- Sorted by ID (lexicographic order)
- MUST respect [RFC-0002:C-OUTPUT-FORMAT](../rfc/RFC-0002.md#rfc-0002c-output-format) flags
- With `--tag`: filters results to artifacts that carry ALL specified tags. MUST support comma-separated tag values. Applies to rfc, clause, adr, work, and guard (releases do not carry tags).

**3. `get` - Read Resource**

Expand Down Expand Up @@ -244,7 +249,7 @@ Permanent deletion breaks referential integrity. These constraints ensure deleti

- MUST NOT use different verb names for the same operation (e.g., "show" vs "get")
- MUST NOT reorder arguments between resource types (ID always comes before field)
- MUST NOT have resource-specific flags for universal operations
- MUST NOT have resource-specific flags for universal operations. Flags that filter by a cross-resource metadata field (e.g., `--tag`) are permitted on resources that carry that field and silently ignored or unavailable on resources that do not.

*Since: v0.1.0*

Expand Down Expand Up @@ -535,6 +540,24 @@ Behavior:
- Reports created/updated/skipped counts
- This command is separate from `init` because plugin users receive skills globally and do not need local copies

**10. `govctl tag`**

Manages the project's controlled tag vocabulary.

Syntax:
- `govctl tag new <tag>`
- `govctl tag delete <tag>`
- `govctl tag list`

Behavior:
- `new`: registers a new tag in `gov/config.toml` under `[tags] allowed`. Tags MUST match `[a-z][a-z0-9-]*` (lowercase kebab-case). MUST error if the tag already exists.
- `delete`: removes a tag from the allowed list. MUST error if any artifact still references the tag.
- `list`: displays all registered tags with usage counts (how many artifacts reference each tag).

Artifact-level tagging uses existing resource verbs on taggable types (rfc, clause, adr, work, guard):
- `govctl {rfc|clause|adr|work|guard} add <ID> tags <tag>` — assign a tag to an artifact
- `govctl {rfc|clause|adr|work|guard} remove <ID> tags <tag>` — remove a tag from an artifact

**Rationale:**

These commands are global because they:
Expand All @@ -549,6 +572,8 @@ These commands are global because they:

`govctl init-skills` qualifies because it performs project-level initialization of agent assets (criterion 2).

`govctl tag` qualifies because it manages project-level configuration that applies across all resource types (criterion 1).

**Future Additions:**

New global commands MAY be added via RFC amendment. They MUST meet at least one criterion:
Expand All @@ -558,8 +583,6 @@ New global commands MAY be added via RFC amendment. They MUST meet at least one

*Since: v0.1.0*

*Since: v0.1.0*

### [RFC-0002:C-VERIFY-CONFIG] Verification Configuration (Normative) <a id="rfc-0002c-verify-config"></a>

The project config file `gov/config.toml` MAY include an optional `[verification]` section.
Expand All @@ -584,6 +607,10 @@ Work Item `verification.required_guards` remain effective regardless of the proj

## Changelog

### v0.7.0 (2026-04-09)

Add controlled-vocabulary tags: tags field on RFC/clause/ADR/work/guard, --tag filter on list, govctl tag new/delete/list for registry management (ADR-0040)

### v0.6.1 (2026-04-08)

Add --dir flag to init-skills for one-step directory override without config editing
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#:schema ../schema/adr.schema.json

[govctl]
id = "ADR-0039"
title = "Use SQLite FTS5 as read-only search index for governance artifacts"
status = "proposed"
date = "2026-04-09"
refs = [
"RFC-0002",
"RFC-0004",
]

[content]
context = """
govctl manages governance artifacts (RFCs, ADRs, clauses, work items, guards) as TOML files in `gov/`. As the corpus grows (currently 200+ artifacts, projected to reach 1000+ in active projects), finding artifacts by content becomes increasingly difficult.

### Problem Statement

Users need to answer questions like "which ADR discussed caching?", "which RFC clause mentions backward compatibility?", or "which work items reference RFC-0002?". Currently this requires:

- `grep` over raw TOML files (poor UX, no ranking, no stemming)
- `govctl list` + manual inspection (only searches titles)
- Memorizing artifact IDs

None of these scale or provide relevance-ranked results.

### Constraints

- [[RFC-0002]] establishes TOML files as the source of truth — any index must be derived, not authoritative
- [[RFC-0004]] governs concurrent write safety — the index must not interfere with the file locking protocol
- The index must work offline with no external services
- Rebuild must be fast enough to run transparently on every search query"""
decision = """
We will use **SQLite FTS5 with lazy incremental sync** as the search backend for `govctl search`.

### Design

1. **Index location:** `gov/.search.db` (gitignored, along with WAL sidecars `gov/.search.db-wal` and `gov/.search.db-shm`). Disposable — can be deleted and rebuilt transparently.

2. **Indexed content:** All artifact types (RFCs, clauses, ADRs, work items, guards). Each entry stores the artifact ID, type, title, and a concatenation of all human-readable text fields. Tokenized with Porter stemming for English morphological matching.

3. **Sync strategy — lazy incremental:**
- On every `govctl search`, compare content hashes of `gov/` TOML files against an index-side manifest
- New/changed files: parse and upsert into the search index
- Deleted files: remove from index
- Unchanged files: skip
- Missing or corrupt index: full rebuild (no error, just slower first query)

4. **No write-through optimization.** The lazy scan is correct in all cases and fast enough (~10ms at current scale). Adding write-through coupling between the artifact write path and the index is premature complexity.

5. **Concurrency:** SQLite WAL mode handles concurrent search invocations safely. If two `govctl search` calls trigger a sync simultaneously, both will complete without corruption.

6. **Explicit escape hatch:** `govctl search --reindex` forces a full rebuild.

### Why This Design

- Lazy sync avoids coupling between the write path and the index — files changed by manual editing, VCS operations, or govctl all sync identically
- A single read-only cache file is simpler than a persistent daemon or event-driven index
- The index is not covered by [[RFC-0004]] file locking because it is a derived cache, not a governance artifact
- `govctl init` should add `gov/.search.db*` to `.gitignore` to cover the database and WAL sidecars"""
consequences = """
### Positive

- Users can find artifacts by content with relevance ranking — "which ADR discussed caching?" returns ranked results instantly
- Porter stemming handles morphological variants (cache/caching/cached) without exact-match frustration
- Index is disposable and self-healing — delete `gov/.search.db` and the next search rebuilds it
- No daemon, no external service, no network — works fully offline
- Lazy sync means zero ceremony — no separate index-build step, no cache invalidation protocol

### Negative

- Adds `rusqlite` (bundled) as a dependency, increasing binary size by ~3MB (mitigation: feature-gate search behind a default-on cargo feature if binary size becomes a concern)
- First search after bulk file changes (e.g., `git checkout` switching branches, large merge) will be slower due to lazy-sync catch-up cost (mitigation: rebuild is still sub-second for 1000 artifacts; `--reindex` makes this explicit when needed)
- CJK text requires additional tokenizer configuration beyond the default Porter stemmer (mitigation: defer to a follow-up if CJK projects adopt govctl)
- The index file must be gitignored — if a user commits it accidentally, it will cause noisy diffs (mitigation: `govctl init` adds `gov/.search.db` to `.gitignore` by default)

### Neutral

- The search index introduces a second file format (SQLite) into the `gov/` directory alongside TOML, but it is explicitly non-authoritative and disposable"""

[[content.alternatives]]
text = "SQLite FTS5 with lazy incremental sync: single-file read-only index using rusqlite (bundled), Porter stemming, BM25 ranking, and content-hash-based incremental updates on each search query."
status = "accepted"
pros = [
"Battle-tested BM25 ranking out of the box",
"Single-file index, no daemon or external service",
"Porter stemming handles English morphology (cache/caching/cached)",
"rusqlite is mature with bundled compilation — no system SQLite dependency",
"Lazy sync means no separate build step or cache invalidation protocol",
]
cons = [
"Adds ~3MB to binary size from bundled SQLite",
"CJK segmentation requires additional tokenizer configuration",
]

[[content.alternatives]]
text = "Tantivy (Rust-native full-text search): Use the tantivy crate, a Lucene-inspired search engine written in Rust. Supports BM25, tokenizers, and schema-defined fields natively."
status = "rejected"
pros = [
"Pure Rust, no C dependency",
"More powerful query language (boolean, phrase, fuzzy)",
"Purpose-built for search — better performance at scale",
]
cons = [
"Much heavier dependency (~50 crates in dependency tree)",
"Index is a directory of segment files, not a single file",
"Overkill for <1000 documents",
]
rejection_reason = "Dependency weight and complexity are disproportionate to the scale of govctl's artifact corpus. SQLite FTS5 covers the requirements with a single well-understood dependency."

[[content.alternatives]]
text = "In-memory inverted index with no persistence: Build a simple inverted index on every search invocation by scanning all TOML files, tokenizing content, and ranking by term frequency. No disk cache."
status = "rejected"
pros = [
"Zero dependencies — no SQLite, no new crates",
"No cache invalidation problem — always fresh",
]
cons = [
"Full rebuild on every query (~100ms at 200 files, grows linearly)",
"No stemming or advanced tokenization without additional code",
"No BM25 — would need a custom ranking implementation",
]
rejection_reason = "Lacks stemming and BM25 ranking out of the box. Rebuild cost scales linearly and becomes noticeable beyond 500 artifacts. The UX gap versus FTS5 is significant for the marginal dependency savings."
Loading
Loading