Skip to content

feat: rule engine — force-extract QuickSight sheets into per-category files (#15)#19

Merged
NickMoignard merged 2 commits into
masterfrom
feat/15-rule-engine-foreextract
Jul 11, 2026
Merged

feat: rule engine — force-extract QuickSight sheets into per-category files (#15)#19
NickMoignard merged 2 commits into
masterfrom
feat/15-rule-engine-foreextract

Conversation

@NickMoignard

Copy link
Copy Markdown
Owner

What & why

Second slice of preset-based semantic explosion (#15). Turns "explode by size" into "explode meaningfully" for QuickSight: each dashboard/analysis sheet gets its own folder + details file, and each subcomponent category becomes its own whole file — regardless of size. Built on the preset abstraction from #14.

How

  • Path-anchored rule engine (internal/preset/rule.go): a Rule matches the walked path segment-for-segment (literal key, * any-key, [] any-seq-element). Actions: ForceExtract (+ StopRecurse). First match wins.
  • Engine threads the path (internal/split/split.go): process/extractChild carry a []preset.Step. A ForceExtract match bypasses both the size and depth gates; StopRecurse writes the node whole (no descent). The depth gate moved from process into extractChild so a forced node deeper than MaxDepth still extracts (a sheet category sits at depth 4, under the default depth 3), while unforced deep nodes stay inline exactly as before.
  • QuickSight rules: Definition, Definition.Sheets, Definition.Sheets[] force-extract; the six sheet categories force-extract + StopRecurse. Root-anchored, so they only match the dashboard/analysis shape and are inert on datasets/other docs.

Result (real Analysis fixture)

definition/sheets/customer-analysis.yml
definition/sheets/customer-analysis/{filtercontrols,layouts,parametercontrols,sheetcontrollayouts,textboxes,visuals}.yml

Forced extraction reuses the existing !include/anchor-closure machinery, so it's lossless by construction — the quicksight-preset join is byte-identical to the ruleless join, and a round-trip test asserts it.

Verification

  • go build ./..., go test ./..., golangci-lint run all green.
  • New tests: rule matcher (AnyKey/AnySeq/length/first-wins), forced sheet layout on a synthetic small dashboard (proves force, not size), category-written-whole, absent-category-no-file, and a full round-trip. New preset-quicksight-dashboard.txtar end-to-end.
  • Existing MaxDepthStopsRecursion etc. still pass (unforced behavior unchanged).

Notes

Closes #15.

🤖 Generated with Claude Code

NickMoignard and others added 2 commits July 12, 2026 00:13
… files

Add a path-anchored rule layer to the split walk. The engine now threads the
walked path (mapping keys / sequence steps) to each node and consults the resolved
preset's rules before the size/depth gate. A ForceExtract rule extracts a node
regardless of MaxLines/MaxDepth; StopRecurse writes it whole (one file, no further
splitting).

The quicksight preset uses this to give a dashboard/analysis a stable, readable
layout: the Definition and Sheets list always split out, each sheet becomes its
own folder + a details file, and each present subcomponent category (Visuals,
FilterControls, ParameterControls, Layouts, SheetControlLayouts, TextBoxes) is
written whole to one file inside that folder. Rules are root-anchored so they only
match the dashboard/analysis shape and never fire on other documents.

The depth gate moved from process() into extractChild() so a forced node deeper
than MaxDepth (a sheet category sits at depth 4, under the default depth 3) still
extracts; unforced nodes past the threshold stay inline exactly as before. Forced
extraction reuses the existing include/anchor machinery, so it is lossless by
construction — covered by a round-trip test.

Closes #15.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address review of the rule-engine slice:
- Add an explicit regression test that an unforced node past MaxDepth stays inline
  after moving the depth gate from process() into extractChild().
- Add a two-sheet (shared Name) test covering slug dedup and the file/folder
  coexistence (overview.yml next to overview/), with a round-trip assertion.
- Document that ForceExtract applies to complex nodes only (scalar sidecars come
  later) and that a StopRecurse node shadows any rule whose path lies inside it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a path-anchored rule engine to the split walker and wires QuickSight preset rules so dashboards/analyses are exploded into a predictable per-sheet folder layout (plus per-category files) regardless of node size, while keeping unforced size/depth behavior unchanged.

Changes:

  • Introduce preset.Rule / preset.Rules path matching (literal, *, []) with ForceExtract and StopRecurse.
  • Thread a logical []preset.Step path through split recursion and apply rules to bypass MaxLines/MaxDepth (and optionally stop recursion).
  • Add QuickSight preset rules + tests (unit tests + testscript) to ensure forced sheet/category layout and round-trip fidelity.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
internal/split/split.go Threads path through recursion and applies preset rules to force extraction / stop recursion.
internal/split/split_test.go Adds regression + QuickSight rule behavior tests (forced layout, dedupe, round-trip).
internal/preset/rule.go Implements rule/path matching primitives (Step, Rule, Rules.Match).
internal/preset/rule_test.go Tests AnyKey/AnySeq matching, first-wins, and QuickSight rule presence.
internal/preset/preset.go Adds Rules to presets and defines QuickSight rule set for sheets/categories.
internal/cli/testdata/script/preset-quicksight-dashboard.txtar End-to-end script verifying QuickSight forced layout and join/verify behavior.
internal/cli/commands.go Ensures resolved split options include preset rules (rules not overridden by config/flags).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +286 to +288
if _, err := os.Stat(filepath.Join(out, "definition/sheets/overview/filtercontrols.yml")); err == nil {
t.Error("filtercontrols.yml should not exist: the sheet has no FilterControls")
}
@NickMoignard NickMoignard merged commit 5dbc8e9 into master Jul 11, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rule engine: ForceExtract for QuickSight sheet folders + per-category files

2 participants