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
24 changes: 20 additions & 4 deletions .claude/skills/rootline/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,32 @@ Use `rootline skill status --source <primary-rootline-checkout>` before remediat

## Governance Boundary (root marker)

Schema discovery walks up from the target collecting `.stem` files and stops at a `.stem` that declares `root: true`, or at the filesystem root. `.git` is not a boundary. A project must declare where it starts with a `root: true` marker in its top-level `.stem`.

- Governed commands (`validate`, `fix`, `query`, `tree`, `graph`, `describe`, `explain`, `set`, `stats`) fail on a tree with no `.stem` or an unparseable one. Without a terminal they exit non-zero; the error names the exact one-line fix.
- If a governed command reports "Schema discovery reached the filesystem root without finding a declared boundary", the fix is to add `root: true` to the project's top-level `.stem` (one line). Then retry. Do **not** use `init --force` to migrate an existing project — it re-infers and overwrites the schema.
Schema discovery walks up from the target collecting `.stem` files and stops at a `.stem` that declares `root: true`, or at the filesystem root. `.git` is not a boundary. A schema-governed project must declare where it starts with a `root: true` marker in its top-level `.stem`.

- **Schemaless exploration**: `query` without `--sort`, `tree`, `graph`, and `graph --check` work when no `.stem` files are discovered. They read Markdown records, frontmatter, and links without creating a schema; schema-derived and aggregated fields are unavailable.
- **Schema-required commands**: `validate`, `fix`, `describe`, `explain`, `new`, `set`, and `stats` require a `.stem` schema and fail or report diagnostics when none exists. `stats` is not a schemaless counting alternative.
- **Query sorting**: `query --sort` requires a `.stem` schema, even when sorting a built-in field such as `path`. Omit `--sort` for schemaless queries.
- **Existing schema chains**: for non-exempt commands, any discovered `.stem` chain must be parseable and terminate at `root: true`. A malformed `.stem` always fails, including for exploration commands; it is not schemaless mode.
- **Missing declared boundary**: an existing chain without `root: true` is not schemaless mode. Without a terminal (non-TTY), the command exits non-zero. On a terminal (TTY), Rootline offers to add the marker; accepting writes `root: true` to the proposed project root's `.stem` and continues. Do not accept that prompt or write the marker without explicit user authorization.
- If a command reports "Schema discovery reached the filesystem root without finding a declared boundary", the fix is to add `root: true` to the project's top-level `.stem` (one line), with explicit user authorization, then retry; accepting the authorized TTY prompt performs that repair and continues automatically. Do **not** use `init --force` to migrate an existing project — it re-infers and overwrites the schema.
- `rootline init` writes `root: true` for new projects, so they never hit this.
- `init`, template installation, and write-producing `migrate` modes replace each
generated file atomically. This is a per-file guarantee, not rollback for an
entire multi-file run.
- Bootstrap commands (`schema propose`, `analyze`) work without any `.stem` — they derive one. `init` and `migrate` also run without a marker.

For a small schemaless example, use a temporary directory whose ancestors have no `.stem` files:

```bash
dir="$(mktemp -d)"
printf '%s\n' '# First' '[[second]]' > "$dir/first.md"
printf '%s\n' '# Second' > "$dir/second.md"
rootline query "$dir" --where "type == 'markdown'" --select path -o json
rootline tree "$dir" -o json
rootline graph "$dir" -o json
rootline graph "$dir" --check
```

## Inheritance: a child never removes

A child `.stem` adds fields or narrows what a parent declared. It never removes
Expand Down
12 changes: 10 additions & 2 deletions .claude/skills/rootline/ref-query.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ Supported operators: `==`, `!=`, `in`, `not in`, `contains`, `&&`, `||`. Use `fi

Use `query` to return records matching frontmatter, derived, aggregated, body, or built-in fields.

Without any discovered `.stem`, `query` works on Markdown records, frontmatter, and built-in fields. Schema-derived and aggregated fields are unavailable. `query --sort` requires a `.stem` schema, even for built-in fields such as `path`; omit `--sort` for schemaless queries. Any discovered schema chain must be parseable and terminate at `root: true` — a broken or undeclared chain is not schemaless mode.

### Usage

```bash
Expand Down Expand Up @@ -173,6 +175,8 @@ Rules:

Use `tree` to show hierarchy and recursive record totals.

`tree` works without any discovered `.stem`; any discovered schema chain must be parseable and terminate at `root: true`.

```bash
rootline tree <dir> -o table
rootline tree <dir> --where "estado != 'Completed'" -o table
Expand All @@ -183,19 +187,23 @@ Use table output for humans and JSON for programmatic handling. Table format (`-

## stats

Use `stats` for aggregate counts.
Use `stats` for the total record count after any `--where` filters.

`stats` requires a `.stem` schema with a declared `root: true` boundary; it does not support schemaless counting. Use `rootline query <dir> --count -o json` to count records without a schema.

```bash
rootline stats <dir> -o json
rootline stats <dir> --where "tipo == 'task'" -o json
```

JSON contains counts grouped by `estado`, `tipo`, and total records.
JSON emits the version 2 contract `{version, kind, total}` with kind `rootline/stats`; it does not group counts by `estado` or `tipo`.

## explain

Use `explain` when the user asks why a field has a value, where a schema-backed field is defined, or how derived/aggregate values are computed.

`explain` requires a `.stem` schema with a declared `root: true` boundary.

```bash
rootline explain <file.md> -o json
```
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Rel

### Fixed

- Corrected the canonical Rootline skill's governance guidance: `query` without `--sort`, `tree`, `graph`, and `graph --check` support schemaless records; `query --sort` and schema-dependent commands, including `stats`, require a schema. Documented the distinction between no `.stem` and an invalid or undeclared schema boundary, added runnable schemaless examples, and pinned the guidance with documentation contract assertions. Command behavior and installed skill symlinks are unchanged. Closes #295.
- `query --count -o table` now renders one `Count` column and one decimal row instead of silently emitting JSON; `-o json` preserves the version-1 `rootline/count` envelope. Query rejects count with `jsonl`/`csv`, row formats without `--select`, and `--count` with `--select` before schema discovery or scanning, while preserving JSON-only `--field` validation precedence. Closes #294.
- `concat` now renders strings unchanged, integers as base-10 text, and booleans as `true`/`false` instead of reflection debug output. Nil remains empty text; unsupported types, including lists, maps, and floats, return an explicit expression evaluation error. Closes #293.
- Bare `rootline repair` now shows grouping-command help instead of executing apply logic and failing with `--report is required`. Help and the README identify version 1 `rootline/proposals` reports generated by `rootline fix --all <dir> --dry-run -o json`; `repair apply` behavior and flags are unchanged. Closes #292.
Expand Down
65 changes: 65 additions & 0 deletions cmd/rootline/skill_documentation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,68 @@ func TestSkillDistributionDocumentationContract(t *testing.T) {
}
}
}

func TestSkillGovernanceBoundaryDocumentationContract(t *testing.T) {
repo := documentationContractRepoRoot(t)
path := ".claude/skills/rootline/SKILL.md"
text := string(documentationContractRead(t, repo, path))
boundary := sectionFrom(text, "## Governance Boundary (root marker)")
assertScopeContainsAll(t, path+" governance boundary", boundary, []string{
"`.git` is not a boundary",
"`query` without `--sort`, `tree`, `graph`, and `graph --check` work when no `.stem` files are discovered",
"`validate`, `fix`, `describe`, `explain`, `new`, `set`, and `stats` require a `.stem` schema",
"`stats` is not a schemaless counting alternative",
"`query --sort` requires a `.stem` schema, even when sorting a built-in field such as `path`",
"any discovered `.stem` chain must be parseable and terminate at `root: true`",
"A malformed `.stem` always fails, including for exploration commands",
"an existing chain without `root: true` is not schemaless mode",
"Without a terminal (non-TTY), the command exits non-zero",
"On a terminal (TTY), Rootline offers to add the marker",
"accepting writes `root: true` to the proposed project root's `.stem` and continues",
"Do not accept that prompt or write the marker without explicit user authorization",
"Do **not** use `init --force`",
"Bootstrap commands (`schema propose`, `analyze`) work without any `.stem`",
"`init` and `migrate` also run without a marker",
"temporary directory whose ancestors have no `.stem` files",
"dir=\"$(mktemp -d)\"",
"printf '%s\\n' '# First' '[[second]]' > \"$dir/first.md\"",
"printf '%s\\n' '# Second' > \"$dir/second.md\"",
"rootline query \"$dir\" --where \"type == 'markdown'\" --select path -o json",
"rootline tree \"$dir\" -o json",
"rootline graph \"$dir\" -o json",
"rootline graph \"$dir\" --check",
})
for _, stale := range []string{
"Governed commands (`validate`, `fix`, `query`, `tree`, `graph`, `describe`, `explain`, `set`, `stats`) fail on a tree with no `.stem`",
"fail on a tree with no `.stem` or an unparseable one",
} {
if strings.Contains(text, stale) {
t.Errorf("%s contains stale blanket schema requirement %q", path, stale)
}
}

path = ".claude/skills/rootline/ref-query.md"
text = string(documentationContractRead(t, repo, path))
assertScopeContainsAll(t, path+" query", sectionFrom(text, "## query"), []string{
"Without any discovered `.stem`, `query` works",
"`query --sort` requires a `.stem` schema, even for built-in fields such as `path`",
"Any discovered schema chain must be parseable and terminate at `root: true`",
})
assertScopeContainsAll(t, path+" tree", sectionFrom(text, "## tree"), []string{
"`tree` works without any discovered `.stem`",
"any discovered schema chain must be parseable and terminate at `root: true`",
})
assertScopeContainsAll(t, path+" stats", sectionFrom(text, "## stats"), []string{
"`stats` requires a `.stem` schema with a declared `root: true` boundary",
"it does not support schemaless counting",
"rootline query <dir> --count -o json",
"version 2 contract `{version, kind, total}` with kind `rootline/stats`",
"it does not group counts by `estado` or `tipo`",
})
if strings.Contains(text, "JSON contains counts grouped by `estado`, `tipo`, and total records") {
t.Errorf("%s contains stale grouped stats output contract", path)
}
assertScopeContainsAll(t, path+" explain", sectionFrom(text, "## explain"), []string{
"`explain` requires a `.stem` schema with a declared `root: true` boundary",
})
}
Loading