feat: cairn context command, cairn-dev skill, blueprint decision gate (#64, #68) - #92
Conversation
📝 WalkthroughSummary by CodeRabbit
WalkthroughAdds blueprint shape change detection and gating with decision coverage. Persists node fingerprints across runs, detects structural modifications, and gates changes without decision artefact coverage via CA002 finding. Introduces CLI context command and query API handler to expose system metadata. Includes comprehensive documentation and skill guide. ChangesBlueprint Shape Change Detection & Context
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
Important Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional. ❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
This stack of pull requests is managed by Graphite. Learn more about stacking. |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (2)
src/cli/mod.rs (1)
329-329: ⚡ Quick winAdd
contextto the core command human/json test matrix
contextis now part of shared JSON routing and command dispatch, but it is not included intest_cli_core_commands_support_human_and_json_output. Adding it will catch regressions in both render paths.Suggested test addition
let cases = [ ("get", vec!["get", "app.api"]), + ("context", vec!["context"]), ( "neighbourhood", vec!["neighbourhood", "app.api", "--include-todos"], ),🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/cli/mod.rs` at line 329, The test test_cli_core_commands_support_human_and_json_output is missing the "context" command in its core command matrix; update the test's list/array of commands (the sequence currently containing items like "help", "version", etc.) to include "context" so the test iterates over and validates both human and JSON output for that command as well; locate the command vector/iterator in src/cli/mod.rs within the test function and add the string "context" to that collection.src/query_api/handlers.rs (1)
229-243: 💤 Low valueConsider consolidating the System node lookup.
The code iterates over
scan_result.graph.nodes.values()twice (lines 229-234 and 238-243) to find the System node. A single lookup would be slightly more efficient.♻️ Proposed refactor
+ let system_node = scan_result + .graph + .nodes + .values() + .find(|n| n.kind == crate::blueprint::ast::NodeKind::System); + - let system_name = scan_result - .graph - .nodes - .values() - .find(|n| n.kind == crate::blueprint::ast::NodeKind::System) - .map_or("unknown", |n| n.name.as_str()); + let system_name = system_node.map_or("unknown", |n| n.name.as_str()); let edge_count: usize = scan_result.graph.outbound.values().map(Vec::len).sum(); - let system_description = scan_result - .graph - .nodes - .values() - .find(|n| n.kind == crate::blueprint::ast::NodeKind::System) - .map_or("", |n| n.description.as_str()); + let system_description = system_node.map_or("", |n| n.description.as_str());🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/query_api/handlers.rs` around lines 229 - 243, The code currently calls scan_result.graph.nodes.values().find(...) twice to get the System node; instead perform a single lookup by assigning the result to a local Option (e.g., let system_node = scan_result.graph.nodes.values().find(|n| n.kind == crate::blueprint::ast::NodeKind::System)); then derive system_name and system_description from that Option (using map_or("unknown", |n| n.name.as_str()) and map_or("", |n| n.description.as_str()) respectively). Keep edge_count as-is and ensure you reference the same system_node variable wherever needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.claude/skills/cairn-dev/references/blueprint-syntax.md:
- Around line 9-139: The Markdown file contains untyped fenced code blocks
(triple backticks) that trigger MD040; update each fence to include an
appropriate language identifier (e.g., ```text for plain examples, ```blueprint
for the blueprint syntax blocks, or ```yaml for YAML-like sections). Locate the
untyped fences in the sections such as the top-level example (the initial
grammar block starting with "# Comments start with #"), the Node
declarations/Module examples (the blocks showing System/Container/Module/Actor),
and the Complete example, and change each opening ``` to include the proper
identifier while leaving the block contents unchanged.
In @.claude/skills/cairn-dev/references/finding-codes.md:
- Around line 72-85: Update the "## Registry format" section to match the actual
registry file by replacing the table example with the current bullet/inline
format (e.g., a fenced code block showing "CXNNN -- one-line description --
phase/issue") and by updating the category list to include the real prefixes
used in openspec/registries/error-codes.md (CP, CK, CA, CC, CH, CE, CT, CM, CS,
CB, CD, CO, etc.) instead of the old CS/CI/... set; also add a language tag to
the fenced code block (e.g., ```md) so markdownlint is satisfied and ensure the
section references the registry filename "openspec/registries/error-codes.md"
for clarity.
In @.claude/skills/cairn-dev/SKILL.md:
- Around line 231-235: The fenced code block in the error correction template
inside .claude/skills/cairn-dev/SKILL.md is missing a language specifier; update
the fenced block around the line that currently contains "The cairn-dev skill
told me to [X], but cairn actually [Y]." to include a language identifier (e.g.,
add "text" after the opening ```), so the block reads ```text ... ```, ensuring
the code fence is annotated for proper rendering.
- Around line 82-101: The fenced code block showing the blueprint starting with
the line "System <TypeLabel> \"<description>\" id \"<dotted-id>\" [`@tag`...] {"
should include a language specifier after the opening triple backticks; update
the opening fence to use a common identifier such as ```text or a custom one
like ```blueprint so tools can render the DSL consistently (e.g., change ``` to
```text or ```blueprint at the top of the block that begins with "System
<TypeLabel> ...").
In `@src/cli/render.rs`:
- Around line 436-450: The "Modules:" heading is misleading because the loop
over scan_result.graph.nodes.values() renders all node kinds; either rename the
header string "Modules:\n" to "Nodes:\n" or filter the loop to only render
module nodes by checking node.kind == NodeKind::Module before writing; update
the header or add the filter in the loop that writes each node (the
write/writeln call using node.id, node.name, node.state, node.paths) so the
label matches the rendered data.
---
Nitpick comments:
In `@src/cli/mod.rs`:
- Line 329: The test test_cli_core_commands_support_human_and_json_output is
missing the "context" command in its core command matrix; update the test's
list/array of commands (the sequence currently containing items like "help",
"version", etc.) to include "context" so the test iterates over and validates
both human and JSON output for that command as well; locate the command
vector/iterator in src/cli/mod.rs within the test function and add the string
"context" to that collection.
In `@src/query_api/handlers.rs`:
- Around line 229-243: The code currently calls
scan_result.graph.nodes.values().find(...) twice to get the System node; instead
perform a single lookup by assigning the result to a local Option (e.g., let
system_node = scan_result.graph.nodes.values().find(|n| n.kind ==
crate::blueprint::ast::NodeKind::System)); then derive system_name and
system_description from that Option (using map_or("unknown", |n|
n.name.as_str()) and map_or("", |n| n.description.as_str()) respectively). Keep
edge_count as-is and ensure you reference the same system_node variable wherever
needed.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: a6fefb53-8a9c-4bff-97b7-0314e6727bb1
⛔ Files ignored due to path filters (1)
tests/snapshots/wire_format_snapshots__api_meta.snapis excluded by!**/*.snap
📒 Files selected for processing (14)
.claude/skills/cairn-dev/SKILL.md.claude/skills/cairn-dev/references/artefact-schemas.md.claude/skills/cairn-dev/references/blueprint-syntax.md.claude/skills/cairn-dev/references/finding-codes.mdCLAUDE.mdopenspec/registries/error-codes.mdsrc/cli/mod.rssrc/cli/render.rssrc/query_api/handlers.rssrc/query_api/mod.rssrc/query_api/registry.rssrc/scanner/mod.rssrc/scanner/state.rstests/kernel.rs
| ``` | ||
| # Comments start with # | ||
|
|
||
| System <TypeLabel> "<description>" id "<dotted-id>" [@tag...] { | ||
| <child nodes> | ||
| } | ||
|
|
||
| # Edges | ||
| from.id -> to.id "relationship label" | ||
| ``` | ||
|
|
||
| ## Node declarations | ||
|
|
||
| Four node kinds, each nestable inside System or Container: | ||
|
|
||
| ### System (top-level only) | ||
|
|
||
| ``` | ||
| System <TypeLabel> "<description>" id "<root-id>" [@tag...] { | ||
| # Contains Containers, Modules, or Actors | ||
| } | ||
| ``` | ||
|
|
||
| The system node is the root. Its `id` is the prefix for all child IDs. | ||
|
|
||
| ### Container (grouping) | ||
|
|
||
| ``` | ||
| Container <TypeLabel> "<description>" id "<parent-id>.<name>" [@tag...] { | ||
| # Contains Modules, other Containers, or Actors | ||
| } | ||
| ``` | ||
|
|
||
| Containers have no code targets. They group related modules. | ||
|
|
||
| ### Module (leaf with code) | ||
|
|
||
| ``` | ||
| Module <TypeLabel> "<description>" id "<parent-id>.<name>" [@tag...] { | ||
| path "<relative-path>" # required, can repeat for multiple paths | ||
| contract "<path>" # optional | ||
| decisions "<directory>" # optional | ||
| todos "<directory>" # optional | ||
| research "<directory>" # optional | ||
| sources "<directory>" # optional | ||
| reviews "<directory>" # optional | ||
| } | ||
| ``` | ||
|
|
||
| Modules are the primary leaf nodes. Each `path` line maps to files or directories that the reconciler scans. | ||
|
|
||
| ### Actor (external entity) | ||
|
|
||
| ``` | ||
| Actor <TypeLabel> "<description>" id "<parent-id>.<name>" [@tag...] { | ||
| # Usually no fields; represents external systems or users | ||
| } | ||
| ``` | ||
|
|
||
| ## Edge declarations | ||
|
|
||
| Edges are declared outside any block, at file scope: | ||
|
|
||
| ``` | ||
| from.node.id -> to.node.id "description of the relationship" | ||
| ``` | ||
|
|
||
| Both node IDs must exist in the node declarations above. Edges form a directed graph used for dependency analysis, cycle detection, and topological ordering. | ||
|
|
||
| ## ID conventions | ||
|
|
||
| - IDs use dotted notation: `system.container.module` | ||
| - The system ID is the root prefix | ||
| - Child IDs extend the parent: if the system is `myapp`, a kernel container is `myapp.kernel`, and a parser module inside it is `myapp.kernel.parser` | ||
| - IDs are case-sensitive | ||
|
|
||
| ## Tags | ||
|
|
||
| Tags are prefixed with `@` and are informational annotations: | ||
|
|
||
| ``` | ||
| Module Parser "Parses input files" id "myapp.parser" @core @v2 { | ||
| path "./src/parser" | ||
| } | ||
| ``` | ||
|
|
||
| Tags don't affect behavior. They're useful for filtering and documentation. | ||
|
|
||
| ## Path declarations | ||
|
|
||
| - Paths are relative to the repository root | ||
| - A path can point to a file (`./src/main.rs`) or a directory (`./src/parser`) | ||
| - Directory paths claim all files recursively under that directory | ||
| - Multiple `path` lines are allowed per module | ||
| - Files not claimed by any module's path are reported as `CAIRN_ORPHANED_FILE` | ||
| - Paths referencing nonexistent files are reported as `CAIRN_GHOST_FILE` | ||
|
|
||
| ## Complete example | ||
|
|
||
| ``` | ||
| System MyApp "A web application with API and frontend" id "myapp" @webapp { | ||
|
|
||
| Container Backend "Server-side services" id "myapp.backend" @server { | ||
|
|
||
| Module API "REST API endpoints and routing" id "myapp.backend.api" @http { | ||
| path "./src/api" | ||
| contract "meta/contracts/api.md" | ||
| decisions "meta/decisions/api" | ||
| todos "meta/todos/api" | ||
| } | ||
|
|
||
| Module Database "Data access layer and migrations" id "myapp.backend.db" { | ||
| path "./src/db" | ||
| path "./migrations" | ||
| } | ||
| } | ||
|
|
||
| Module Config "Shared configuration and environment" id "myapp.config" { | ||
| path "./src/config.rs" | ||
| } | ||
|
|
||
| Actor ExternalPayment "Third-party payment processor" id "myapp.payment" @external { | ||
| } | ||
| } | ||
|
|
||
| # Dependencies | ||
| myapp.backend.api -> myapp.backend.db "Queries and persists data" | ||
| myapp.backend.api -> myapp.config "Reads configuration" | ||
| myapp.backend.api -> myapp.payment "Processes payments" | ||
| myapp.backend.db -> myapp.config "Reads connection settings" | ||
| ``` |
There was a problem hiding this comment.
Add language identifiers to fenced code blocks
The new code fences are untyped, which triggers MD040. Please annotate each fence (text, blueprint, or yaml as appropriate) for lint-clean docs.
Example fix pattern
-```
+```text
# Comments start with #
...
-```
+```
-```
+```text
System <TypeLabel> "<description>" id "<root-id>" [`@tag`...] {
...
-```
+```🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 9-9: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
[warning] 26-26: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
[warning] 36-36: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
[warning] 46-46: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
[warning] 62-62: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
[warning] 72-72: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
[warning] 89-89: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
[warning] 108-108: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.claude/skills/cairn-dev/references/blueprint-syntax.md around lines 9 -
139, The Markdown file contains untyped fenced code blocks (triple backticks)
that trigger MD040; update each fence to include an appropriate language
identifier (e.g., ```text for plain examples, ```blueprint for the blueprint
syntax blocks, or ```yaml for YAML-like sections). Locate the untyped fences in
the sections such as the top-level example (the initial grammar block starting
with "# Comments start with #"), the Node declarations/Module examples (the
blocks showing System/Container/Module/Actor), and the Complete example, and
change each opening ``` to include the proper identifier while leaving the block
contents unchanged.
| ## Registry format | ||
|
|
||
| Error codes are registered in `openspec/registries/error-codes.md` with the format: | ||
|
|
||
| ``` | ||
| | CXNNN | CAIRN_FULL_CODE_NAME | severity | description | issue # | | ||
| ``` | ||
|
|
||
| Categories: | ||
| - `CS` - Scanner/structural findings | ||
| - `CI` - Interface findings | ||
| - `CA` - Artefact findings | ||
| - `CH` - Hook findings | ||
| - `CC` - CLI findings |
There was a problem hiding this comment.
Registry guidance here is out of sync with the actual registry file
This section says the registry uses a table format and CS/CI/... prefixes, but openspec/registries/error-codes.md currently uses bullet entries and categories like CP/CK/CA/.... Also, the fenced block at Line 76 should declare a language to satisfy markdownlint.
Proposed doc fix
-Error codes are registered in `openspec/registries/error-codes.md` with the format:
+Error codes are registered in `openspec/registries/error-codes.md` using bullet entries:
-```
-| CXNNN | CAIRN_FULL_CODE_NAME | severity | description | issue # |
+```md
+- CXNNN -- one-line description -- phase/issueCategories:
-- CS - Scanner/structural findings
-- CI - Interface findings
-- CA - Artefact findings
-- CH - Hook findings
-- CC - CLI findings
+- CP - Parser
+- CK - Kernel/Map
+- CA - Artefacts
+- CC - Changes
+- CH - Hooks
+- CE - Edges
+- CT - Targets
+- CM - MCP
+- CS - Summariser
+- CB - Brownfield
+- CD - Distribution
+- CO - CLI output / I/O
</details>
<!-- suggestion_start -->
<details>
<summary>📝 Committable suggestion</summary>
> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
```suggestion
## Registry format
Error codes are registered in `openspec/registries/error-codes.md` using bullet entries:
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 76-76: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.claude/skills/cairn-dev/references/finding-codes.md around lines 72 - 85,
Update the "## Registry format" section to match the actual registry file by
replacing the table example with the current bullet/inline format (e.g., a
fenced code block showing "CXNNN -- one-line description -- phase/issue") and by
updating the category list to include the real prefixes used in
openspec/registries/error-codes.md (CP, CK, CA, CC, CH, CE, CT, CM, CS, CB, CD,
CO, etc.) instead of the old CS/CI/... set; also add a language tag to the
fenced code block (e.g., ```md) so markdownlint is satisfied and ensure the
section references the registry filename "openspec/registries/error-codes.md"
for clarity.
| ``` | ||
| System <TypeLabel> "<description>" id "<dotted-id>" [@tag...] { | ||
|
|
||
| Container <TypeLabel> "<description>" id "<dotted-id>" [@tag...] { | ||
|
|
||
| Module <TypeLabel> "<description>" id "<dotted-id>" [@tag...] { | ||
| path "<relative-path>" # multiple path lines allowed | ||
| contract "<path>" | ||
| decisions "<dir>" | ||
| todos "<dir>" | ||
| research "<dir>" | ||
| sources "<dir>" | ||
| reviews "<dir>" | ||
| } | ||
| } | ||
| } | ||
|
|
||
| # Edges (outside blocks) | ||
| from.id -> to.id "relationship label" | ||
| ``` |
There was a problem hiding this comment.
Add language specifier to fenced code block.
The blueprint syntax example lacks a language identifier. While this is a custom DSL, using a common identifier like text or a custom one like blueprint helps tools render it consistently.
📝 Proposed fix
-```
+```text
System <TypeLabel> "<description>" id "<dotted-id>" [`@tag`...] {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ``` | |
| System <TypeLabel> "<description>" id "<dotted-id>" [@tag...] { | |
| Container <TypeLabel> "<description>" id "<dotted-id>" [@tag...] { | |
| Module <TypeLabel> "<description>" id "<dotted-id>" [@tag...] { | |
| path "<relative-path>" # multiple path lines allowed | |
| contract "<path>" | |
| decisions "<dir>" | |
| todos "<dir>" | |
| research "<dir>" | |
| sources "<dir>" | |
| reviews "<dir>" | |
| } | |
| } | |
| } | |
| # Edges (outside blocks) | |
| from.id -> to.id "relationship label" | |
| ``` |
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 82-82: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.claude/skills/cairn-dev/SKILL.md around lines 82 - 101, The fenced code
block showing the blueprint starting with the line "System <TypeLabel>
\"<description>\" id \"<dotted-id>\" [`@tag`...] {" should include a language
specifier after the opening triple backticks; update the opening fence to use a
common identifier such as ```text or a custom one like ```blueprint so tools can
render the DSL consistently (e.g., change ``` to ```text or ```blueprint at the
top of the block that begins with "System <TypeLabel> ...").
| ``` | ||
| The cairn-dev skill told me to [X], but cairn actually [Y]. | ||
| Please update the skill at .claude/skills/cairn-dev/SKILL.md | ||
| to correct [specific section]. | ||
| ``` |
There was a problem hiding this comment.
Add language specifier to fenced code block.
The error correction template lacks a language identifier.
📝 Proposed fix
-```
+```text
The cairn-dev skill told me to [X], but cairn actually [Y].📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ``` | |
| The cairn-dev skill told me to [X], but cairn actually [Y]. | |
| Please update the skill at .claude/skills/cairn-dev/SKILL.md | |
| to correct [specific section]. | |
| ``` |
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 231-231: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.claude/skills/cairn-dev/SKILL.md around lines 231 - 235, The fenced code
block in the error correction template inside .claude/skills/cairn-dev/SKILL.md
is missing a language specifier; update the fenced block around the line that
currently contains "The cairn-dev skill told me to [X], but cairn actually [Y]."
to include a language identifier (e.g., add "text" after the opening ```), so
the block reads ```text ... ```, ensuring the code fence is annotated for proper
rendering.
| "{} ({} nodes, {} edges)\n{}\n\nFindings: {} errors, {} warnings\n\nModules:\n", | ||
| system_name, | ||
| scan_result.graph.nodes.len(), | ||
| edge_count, | ||
| system_desc, | ||
| errors, | ||
| warnings, | ||
| ); | ||
|
|
||
| for node in scan_result.graph.nodes.values() { | ||
| let paths = node.paths.join(", "); | ||
| writeln!( | ||
| out, | ||
| " {} ({}) [{:?}] {}", | ||
| node.id, node.name, node.state, paths |
There was a problem hiding this comment.
Modules: heading does not match the data being rendered
The section currently lists all node kinds, not just modules. Either rename the header to Nodes: or filter the loop to module nodes only.
Minimal wording fix
- "{} ({} nodes, {} edges)\n{}\n\nFindings: {} errors, {} warnings\n\nModules:\n",
+ "{} ({} nodes, {} edges)\n{}\n\nFindings: {} errors, {} warnings\n\nNodes:\n",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "{} ({} nodes, {} edges)\n{}\n\nFindings: {} errors, {} warnings\n\nModules:\n", | |
| system_name, | |
| scan_result.graph.nodes.len(), | |
| edge_count, | |
| system_desc, | |
| errors, | |
| warnings, | |
| ); | |
| for node in scan_result.graph.nodes.values() { | |
| let paths = node.paths.join(", "); | |
| writeln!( | |
| out, | |
| " {} ({}) [{:?}] {}", | |
| node.id, node.name, node.state, paths | |
| "{} ({} nodes, {} edges)\n{}\n\nFindings: {} errors, {} warnings\n\nNodes:\n", | |
| system_name, | |
| scan_result.graph.nodes.len(), | |
| edge_count, | |
| system_desc, | |
| errors, | |
| warnings, | |
| ); | |
| for node in scan_result.graph.nodes.values() { | |
| let paths = node.paths.join(", "); | |
| writeln!( | |
| out, | |
| " {} ({}) [{:?}] {}", | |
| node.id, node.name, node.state, paths |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/cli/render.rs` around lines 436 - 450, The "Modules:" heading is
misleading because the loop over scan_result.graph.nodes.values() renders all
node kinds; either rename the header string "Modules:\n" to "Nodes:\n" or filter
the loop to only render module nodes by checking node.kind == NodeKind::Module
before writing; update the header or add the filter in the loop that writes each
node (the write/writeln call using node.id, node.name, node.state, node.paths)
so the label matches the rendered data.
There was a problem hiding this comment.
Pull request overview
This PR adds a new cairn context command (CLI + query API tool) and introduces a “blueprint shape change” decision gate by persisting a blueprint snapshot to .cairn/state and emitting a blocking finding when structural changes aren’t covered by an active decision.
Changes:
- Add blueprint snapshot read/write state and enforce
CAIRN_BLUEPRINT_CHANGE_NO_DECISIONon node add/remove/reparent/kind-change (gated by presence of prior snapshot + at least one decision). - Add
contextto the query API tool registry and CLI dispatch, including JSON + human renderers. - Update tests/snapshots and documentation/registries (error code allocation + new
cairn-devskill docs).
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/snapshots/wire_format_snapshots__api_meta.snap | Snapshot updates for API meta/tool registry (includes new context tool). |
| tests/kernel.rs | Adds test coverage for the new blueprint shape-change decision gate. |
| src/scanner/state.rs | Introduces blueprint snapshot schema + read/write helpers. |
| src/scanner/mod.rs | Computes blueprint snapshot, checks for uncovered shape changes, writes snapshot on scan. |
| src/query_api/registry.rs | Registers the new context tool. |
| src/query_api/mod.rs | Threads loaded config into tool execution and dispatches context. |
| src/query_api/handlers.rs | Implements context_json response payload. |
| src/cli/render.rs | Adds human-readable cairn context output. |
| src/cli/mod.rs | Wires context into CLI dispatch and shared JSON routing. |
| openspec/registries/error-codes.md | Allocates CA002 for CAIRN_BLUEPRINT_CHANGE_NO_DECISION. |
| CLAUDE.md | Adds workflow guidance and references the new cairn-dev skill. |
| .claude/skills/cairn-dev/SKILL.md | New skill documentation for cairn development workflows. |
| .claude/skills/cairn-dev/references/finding-codes.md | New reference doc including CA002 semantics. |
| .claude/skills/cairn-dev/references/blueprint-syntax.md | New blueprint syntax reference doc. |
| .claude/skills/cairn-dev/references/artefact-schemas.md | New artefact schema reference doc. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if !path.exists() { | ||
| return Ok(BlueprintSnapshot::default()); | ||
| } | ||
| let content = fs::read_to_string(path)?; |
| let mut out = format!( | ||
| "{} ({} nodes, {} edges)\n{}\n\nFindings: {} errors, {} warnings\n\nModules:\n", | ||
| system_name, | ||
| scan_result.graph.nodes.len(), | ||
| edge_count, | ||
| system_desc, | ||
| errors, | ||
| warnings, | ||
| ); | ||
|
|
||
| for node in scan_result.graph.nodes.values() { | ||
| let paths = node.paths.join(", "); |

No description provided.