diff --git a/.agents/skills/ln-build/SKILL.md b/.agents/skills/ln-build/SKILL.md index 40f166d1..93683e9a 100644 --- a/.agents/skills/ln-build/SKILL.md +++ b/.agents/skills/ln-build/SKILL.md @@ -91,6 +91,8 @@ Run the project's verification harness. All checks must pass. If the card proved After verification, reconcile canonical state every time. The reconciliation may end in a no-op, but skipping it is not allowed. +**Notation aid.** When the reconciliation records slice acceptance breakdowns, module sketches, call/dependency shapes, or schema-shaped invariants into canonical docs, use `pseudo` forms (`tree` for obligation decomposition; `chain` for call graphs; `graph` for cross-module relations; `data-shape` for sketched schemas). Preserve any `pseudo` artifacts already present in SPEC/PLAN — do not collapse them back into prose. + Traceability depth is **conditional**, not automatic. After the build lands and verification passes, ask: @@ -150,12 +152,19 @@ Before finishing reconciliation, perform a quick cross-skill check: if a later a ### Retire derivative artifacts -After reconciliation, garbage-collect exhausted temporary files instead of leaving breadcrumbs or tombstones: +After reconciliation, garbage-collect exhausted temporary files instead of leaving breadcrumbs or tombstones, but deletion is narrowly scoped. + +Default deletion target: + +- `memory/CARDS.md` — delete only when the execution queue is fully exhausted, superseded, or empty after reconciliation. + +Other volatile artifacts are **review-before-delete**, not automatic cleanup: + +- `HANDOFF.md` — delete only when it contains no unfinished transfer state and no future-context inventory that is not already captured in `memory/SPEC.md`, `memory/PLAN.md`, an active scope card, or a stable design memo. +- `memory/REFACTOR.md` — delete only when every listed refactor step is done/dropped and no future sequence depends on it. +- Provisional docs outside `memory/` (for example `docs/**/provisional*.md`, handoff plans, spike plans, or exploration inventories) — do **not** delete during `ln-build` cleanup unless the user explicitly asks or you first prove that all remaining future-facing inventory has been absorbed elsewhere. If only the current card is done but the artifact still contains later affordances, open questions, or scoping input, update it instead of deleting it. -- `HANDOFF.md` — keep only if unfinished volatile transfer state still exists; otherwise delete it -- `memory/CARDS.md` — keep only while queued scope cards still remain; otherwise delete it -- `memory/REFACTOR.md` — keep only while unfinished refactor steps still depend on it; otherwise delete it -- Do not create archive copies, numbered handoffs, or completion-pointer files +Before deleting anything other than `memory/CARDS.md`, name the file, state why no future agent would need it, and prefer asking the user when uncertain. Do not create archive copies, numbered handoffs, or completion-pointer files. ## Routing diff --git a/.agents/skills/ln-consult/SKILL.md b/.agents/skills/ln-consult/SKILL.md index 96af4b67..ca4161c2 100644 --- a/.agents/skills/ln-consult/SKILL.md +++ b/.agents/skills/ln-consult/SKILL.md @@ -30,6 +30,7 @@ Start the assessment with 2-4 bullets naming: - the active frontier item or nearby priority - volatile state or manual follow-up from handoff - the main open risk +- the cheapest tracer bullet that would score on proof of life, invariants, or uncertainty retirement (see `docs/praxis/ln-skills.md` §Tracer-bullet sequencing) ## Work-type classification @@ -72,9 +73,23 @@ Only recommend the bounded or direct-build exceptions when all of these are true - the containing seam is already named in the live docs - no durable requirement / assumption / decision / invariant change is expected - post-build reconciliation can plausibly be a no-op +- no high-impact unresolved `memory/SPEC.md` §Assumption is load-bearing for this work Only recommend the bounded serial exception when those same conditions hold and the next several commit-sized steps are obvious enough to queue without fresh planning. +## Tracer-bullet override + +When several routes fit the work, prefer the one that fires the **tracer bullet that tells you the most**. A tracer-bullet slice scores on three convergent axes (see `docs/praxis/ln-skills.md` §Tracer-bullet sequencing): proof of life, invariants, uncertainty. The best next slice scores on more than one. + +Given the repo's pre-release posture, attack uncertainty by building. Recommend a non-build route only when no buildable tracer bullet can carry the proof: + +- `ln-design` — module shape itself is uncertain and any slice would lock in the wrong seam +- `ln-oracles` — verification is too uncertain to distinguish a passing slice from a wrong one +- `ln-spike` — research-grade or external question (third-party API contract, vendor perf characteristic, library behavior under load) +- `ln-prototype` — feel, comparison, or UX-legibility question where playable variants beat real code + +Spikes are the escape hatch, not the default. + ## Routing table | Situation | Work type | Suggest | @@ -88,7 +103,7 @@ Only recommend the bounded serial exception when those same conditions hold and | One settled frontier item needs several small verified commits in sequence | bounded, hardening | `ln-scope` then serial `ln-build` loop, optionally via `memory/CARDS.md` | | Module interface needs exploration | structural | `ln-design` | | Full or light scope card exists, ready to code | bounded, hardening, bugfix | `ln-build` | -| Technical uncertainty blocks progress | any | `ln-spike` | +| Technical uncertainty blocks progress, or a cheap investigation could invalidate planned work | any | `ln-spike` | | Code works but needs restructuring | refactor | `ln-refactor` | | Code works but quality / architecture needs audit | any | `ln-review` | | Docs are stale, overweight, or milestone context needs cleanup | structural / maintenance | `ln-sync` | diff --git a/.agents/skills/ln-design/SKILL.md b/.agents/skills/ln-design/SKILL.md index c764fbff..1d06c508 100644 --- a/.agents/skills/ln-design/SKILL.md +++ b/.agents/skills/ln-design/SKILL.md @@ -31,7 +31,9 @@ Spawn 3+ sub-agents simultaneously. Each must produce a **radically different** - "Optimize for the most common case" - "Take inspiration from [specific paradigm or library]" -Each agent returns: **interface** (types, methods, params, invariants, ordering constraints, error modes, required configuration, and performance characteristics), **usage example**, **what it hides**, **seam / adapter strategy** where relevant, and **trade-offs**. +Each agent returns: **interface** (types, methods, params, invariants, ordering constraints, error modes, required configuration, and performance characteristics), **usage example**, **what it hides**, **seam / adapter strategy** where relevant, **trade-offs**, **load-bearing claims** (1–3 falsifiable beliefs the design rests on — for each, note whether it is already covered by `memory/SPEC.md` §Assumptions), and **cheapest tracer bullet** — the thinnest `ln-scope` slice whose landing would light up the seam and break if the claim is wrong. Fall back to `ln-spike` only when no buildable slice could carry the proof. + +**Notation aid.** Express each candidate module shape using `pseudo` (`graph` or `tree` for module relations, `data-shape` for interface shapes, `lanes` for cross-actor seams). Side-by-side `pseudo` artifacts make alternatives directly comparable in the same form rather than as divergent prose. ### 3. Present and compare @@ -43,8 +45,9 @@ Show each design sequentially, then compare in prose on: - **Ease of correct use** vs ease of misuse - **General-purpose vs specialized**: flexibility vs focus - **Implementation efficiency**: does the shape allow efficient internals? +- **Epistemic cost**: how much unvalidated reality this shape asks callers / sequencing to trust, and how cheaply that trust can be tested before committing -Highlight where designs diverge most. +Highlight where designs diverge most, including which design has the cheapest path to falsification if its load-bearing claims are wrong. ### 4. Synthesize @@ -52,7 +55,13 @@ The best design often combines insights from multiple options. Ask which shape b ## Output -Present the recommended module shape with rationale. If `memory/SPEC.md` exists, ensure names align with its lexicon. +Present the recommended module shape with rationale, plus: + +- the 1–3 load-bearing claims it rests on +- which of those are already covered by `memory/SPEC.md` §Assumptions and which need to be added there +- the recommended first tracer bullet — a thin `ln-scope` slice that would light up the seam and break if the chosen design's highest load-bearing claim is wrong; fall back to `ln-spike` only when no slice could carry the proof more cheaply + +If `memory/SPEC.md` exists, ensure names align with its lexicon. Do not invent a standalone design document unless the user explicitly asks for one. Durable design choices reconcile back into `memory/SPEC.md` and `memory/PLAN.md`. @@ -63,10 +72,11 @@ After choosing a design, present these options to the user (use `tool-ask-questi | # | Label | Target | Why | | --- | ------------- | ---------- | ---------------------------------------- | | 1 | Scope a slice | `ln-scope` | Design is chosen, define the first slice | -| 2 | Write a spec | `ln-spec` | Module needs a full spec before slicing | -| 3 | Grill it more | `ln-grill` | Design choice raised new questions | +| 2 | Spike first | `ln-spike` | The chosen design rests on a low-confidence load-bearing claim worth retiring before scoping | +| 3 | Write a spec | `ln-spec` | Module needs a full spec before slicing | +| 4 | Grill it more | `ln-grill` | Design choice raised new questions | -Recommended: **1** +Recommended: **1** — including when a load-bearing claim is low-confidence, because the preferred falsifier is a tracer-bullet slice that breaks if the claim is wrong. Recommend **2 (Spike first)** only when no buildable slice could carry the proof. --- *Adapted from [mattpocock/skills/design-an-interface](https://github.com/mattpocock/skills/tree/main/design-an-interface).* diff --git a/.agents/skills/ln-judo-review/SKILL.md b/.agents/skills/ln-judo-review/SKILL.md index 689db0f5..c6800522 100644 --- a/.agents/skills/ln-judo-review/SKILL.md +++ b/.agents/skills/ln-judo-review/SKILL.md @@ -44,6 +44,8 @@ Functional core / imperative shell (Gary Bernhardt): when independent work is ne If yes, name it. Do not settle for a cleaner version of the same messy idea when a much simpler idea is plausible. +**Notation aid.** When proposing a code-judo move, express it as paired `pseudo` artifacts — current shape (`tree` for module structure, `graph` for control/dependency, `chain` for call flow) → desired shape with the deleted branches, helpers, modes, or layers visibly absent. A concrete before/after pair shows whether complexity actually *vanishes* rather than relocates — which is the whole point of judo over rearrangement. Node/edge counts before vs after are honest metrics: a desired-state graph with fewer nodes and fewer edges than the current one is the artifact form of "deletions over rearrangements." + ## Tone Direct, serious, demanding. Not rude. Do not soften major maintainability issues into mild suggestions. Worked examples of the register: diff --git a/.agents/skills/ln-plan/SKILL.md b/.agents/skills/ln-plan/SKILL.md index 93d3c326..b1496060 100644 --- a/.agents/skills/ln-plan/SKILL.md +++ b/.agents/skills/ln-plan/SKILL.md @@ -18,6 +18,8 @@ Use **slice** for the buildable scope card produced by `ln-scope` and implemente The vertical-slicing instinct still applies at planning time: frontier items should cut through the relevant concerns of `memory/SPEC.md` instead of becoming layer-by-layer chores. The term "frontier" names their canonical/branch role; the term "slice" remains reserved for scoped execution. +**Notation aid.** Express the `Dependencies` block as `pseudo graph` rather than a hand-drawn tree — cross-edges (optional successors, on-promotion edges) and dependency-edge types (`-[hard]->`, `-[optional]->`, `-[on promotion]->`) stay visible, and horizon items go in an `unconnected` group so they're acknowledged without implying spine relations. See `pseudo references/graph.md` worked example "roadmap dependency graph." + ## Plan document shape Prefer the conflict-resistant mature shape: @@ -100,6 +102,29 @@ When a frontier completes, remove it from `Sequencing`, add a terse `Recently Co If live low-confidence assumptions block downstream work, stop the plan at that boundary. Plan spikes or thinner proving frontier items, not fantasy certainty. +### Tracer-bullet sequencing + +Sequencing is not only seam-driven. A good tracer-bullet frontier scores on three convergent axes (see `docs/praxis/ln-skills.md` §Tracer-bullet sequencing): **proof of life**, **invariants**, **uncertainty**. The strongest next frontier scores on more than one. + +When ranking candidates, weigh: + +- **blast radius** if a load-bearing assumption turns out false +- **reversibility cost** if discovered late vs early +- **validation cost** (cheap slice vs expensive end-to-end rework) +- **load-bearingness** (how many active/next frontiers depend on it) + +Annotate each `Active` / `Next` frontier definition with the relevant axes when they are in play: + +- `Retires: ` — collapses the assumption by landing +- `Depends on: (validated enough)` — assumption must be settled first +- `Blocked by: ` — load-bearing; do not start until retired +- `Lights up: ` — establishes a new end-to-end path +- `Stabilizes: ` — locates or fixes structure others will aim from + +**Spike exception.** Use `ln-spike` only when no buildable frontier could carry the proof. Do not insert ceremonial spikes when a tracer-bullet frontier exists. + +This sequencing pressure is distinct from "Epistemic horizon": that rule tells the planner to *stop* at fog; this rule tells the planner to **fire the tracer that tells you the most**. + ## Procedure 1. Read `memory/PLAN.md` if it exists. Identify existing frontier ids and retire/archive stale completed material into `docs/archive/PLAN_HISTORY.md`. @@ -146,7 +171,8 @@ After writing the plan, present these options to the user (use `tool-ask-questio | --- | ----------------- | ------------ | --- | | 1 | Scope next slice | `ln-scope` | The frontier is clear and ready to scope | | 2 | Design oracles | `ln-oracles` | Verification design needs explicit work | -| 3 | Grill it more | `ln-grill` | Planning surfaced unresolved product questions | -| 4 | Back to triage | `ln-consult` | Direction needs reassessment | +| 3 | Spike first | `ln-spike` | A load-bearing assumption should be retired before scoping | +| 4 | Grill it more | `ln-grill` | Planning surfaced unresolved product questions | +| 5 | Back to triage | `ln-consult` | Direction needs reassessment | -Recommended: **1** +Recommended: **1** unless tracer-bullet sequencing surfaced a question that no buildable frontier could answer cheaper than a spike (then **3**). diff --git a/.agents/skills/ln-refactor/SKILL.md b/.agents/skills/ln-refactor/SKILL.md index 56bc88e4..2374293f 100644 --- a/.agents/skills/ln-refactor/SKILL.md +++ b/.agents/skills/ln-refactor/SKILL.md @@ -36,6 +36,8 @@ What is wrong, from the developer's perspective. The target state, from the developer's perspective. +**Notation aid.** Express the structural delta as paired `pseudo` artifacts — `tree` current → `tree` desired, or `graph` current → `graph` desired — under the Problem Statement / Solution headings. The paired form makes the change concrete before commits begin and gives reviewers a single artifact to diff. + ## Commits Ordered list of tiny commits. Each described in plain English — no file paths or snippets. Each leaves the codebase working. diff --git a/.agents/skills/ln-review/SKILL.md b/.agents/skills/ln-review/SKILL.md index e3b826f8..bb5f869e 100644 --- a/.agents/skills/ln-review/SKILL.md +++ b/.agents/skills/ln-review/SKILL.md @@ -1,6 +1,6 @@ --- name: ln-review -description: "Audit code quality focusing on deep modules, naming, model hygiene, and architectural clarity. Use after a burst of development, when codebase structure needs assessment, or to make code more agent-navigable." +description: "Audit code quality focusing on deep modules, naming, model hygiene, topographic legibility, and architectural clarity. Use after a burst of development, when codebase structure needs assessment, or to make code more agent-navigable." argument-hint: "[area of codebase to review, or 'recent' for recently changed files]" --- @@ -20,21 +20,31 @@ If "recent" or unspecified, focus on recently modified files. Read `memory/SPEC.md` first when it exists. Use its lexicon for domain terms, and treat the live architecture register as the current decision record. Read `memory/PLAN.md` for active frontier context when the reviewed area touches active or near-horizon work. If ADRs or design docs exist in the touched area, respect them as supporting context, but do not introduce ADRs or sidecar decision logs by default; durable updates reconcile through `memory/SPEC.md` / `memory/PLAN.md`. +The lenses below are sub-passes. Apply each in turn; collect findings by category as you go. Each sub-pass owns one or more finding categories (named in parentheses). + +### Module depth (category: `depth`) + Apply Ousterhout's depth test: modules should have small interfaces hiding significant complexity. Modules that move together should live together — clusters of small files always used in concert are a single deep module waiting to be extracted. Use the deletion test for suspected shallow modules: if deleting the module makes complexity vanish, it was pass-through structure; if the same complexity reappears across multiple callers, the module was earning its keep. Prefer depth as leverage/locality, not line-count ratio. +### Seams and interfaces (categories: `seam`, `coupling`) + Treat the interface as the test surface. The interface is everything callers must know to use the module correctly: types, invariants, ordering constraints, error modes, required configuration, and performance characteristics. If callers or tests must reach past the interface to verify important behavior, the module shape is probably wrong. A good seam lets tests and callers cross the same public boundary. Apply seam discipline: one adapter usually means a hypothetical seam; two adapters make a real seam. Flag indirection introduced only for imagined future variation, especially when it spreads configuration, mocks, or ordering knowledge into callers. -When a finding is a deepening opportunity, present it as a candidate rather than a detailed design. Name the current shallow module shape, the deepened module that might replace it, what complexity would move behind the seam, and why that would improve locality, leverage, and the test surface. Do **not** propose detailed interfaces in `ln-review`; route selected deepening candidates to `ln-design` before scoping or refactoring. +When a finding here is a deepening opportunity, present it as a candidate rather than a detailed design. Name the current shallow module shape, the deepened module that might replace it, what complexity would move behind the seam, and why that would improve locality, leverage, and the test surface. Do **not** propose detailed interfaces in `ln-review`; route selected deepening candidates to `ln-design` before scoping or refactoring. + +### Core/shell boundary (category: `model`) Check the functional core / imperative shell boundary (Gary Bernhardt, "Boundaries"). Pure functions should stay pure. Flag when a pure function has acquired side effects or a growing parameter list — it has drifted into shell territory. +### Model integrity (category: `model`) + Make invalid states unrepresentable (Yaron Minsky). Split optional fields into distinct types. Use branded types for domain-distinct values. -### Oracle coverage +### Oracle coverage (category: `oracle-coverage`) If `memory/SPEC.md` §Oracle Strategy by Loop Tier exists, check whether recent work implemented the oracles declared by the relevant `memory/PLAN.md` frontier definition. If a full or light scope card is available in session context, use it as a higher-resolution slice supplement, not the primary source of truth. Look for: @@ -45,7 +55,9 @@ If `memory/SPEC.md` §Oracle Strategy by Loop Tier exists, check whether recent Collect gaps as numbered findings (category: `oracle-coverage`). -### Lexicon alignment +**Notation aid.** Map test artifacts against acceptance leaves with `pseudo matrix` (coverage variant): rows = obligation leaves from a `pseudo tree` decomposition of the frontier acceptance, columns = test artifacts. Gaps surface as `.` cells; partial coverage as `~`. Compact, scannable, and the matrix itself becomes a coverage artifact reviewers can re-run. + +### Lexicon alignment (category: `naming`) If `memory/SPEC.md` exists, survey how §Lexicon terms (both method and domain) appear across: @@ -56,6 +68,26 @@ If `memory/SPEC.md` exists, survey how §Lexicon terms (both method and domain) Collect misalignments as numbered findings (category: `naming`) with the canonical term, where the deviation occurs, and what it should be. Format these so they can be passed directly to `ln-refactor`. +### Topographic legibility (category: `topography`) + +The directory tree is a spatial artifact, read top-down by humans and agents during orientation — *before any file is opened*. Layout is its own design surface, peer to module depth. Three lenses fire here: + +- **Topographic legibility** — a stranger should be able to *walk* the tree (not grep it) and infer the shape of the territory: what kinds of things exist, where each kind lives, and how they relate. Directory names predict the *kind* of their children; file names predict their contents. +- **Chunking budget** — siblings at one level should fit working memory (~7±2). A directory with many peer entries blows the budget; nested grouping should restore it. **Mixed grain** among siblings (a domain concept next to a utility next to a config) is the same kind of smell — peers should be peers in kind, not just in location. +- **Orientation debt / navigation tax** — the failure mode. When the tree doesn't teach, every reader pays a search cost on first contact. The cost compounds invisibly because no test, type-check, or build catches it. The signal is "a stranger had to grep to find X" or "no two readers guess the same location for a new file." + +Concrete cues to look for: + +- Sibling counts well above ~9 with no clear sub-grouping +- Mixed-grain siblings (e.g., one file is a domain concept, the next is a utility, the next is config) +- Deep nesting that doesn't reflect conceptual depth (folders of folders with one file each) +- Generic bucket names (`utils/`, `helpers/`, `lib/`, `misc/`, `shared/`) that hide what lives inside +- File names that don't predict contents; directory names that don't predict their children's kind +- Fractal-pattern violations: a file outgrew its boundary but stayed flat instead of getting its same-named private folder (the pattern documented in `AGENTS.md`) +- Imports that cross conceptual layers in surprising directions, hinting that the tree is *lying* about the dependency shape + +Collect findings as numbered items (category: `topography`). Frame each as: what the reader sees today, what they would have to internalize to find things, and the smallest topographic move that would make the tree teach itself. Routing for coordinated layout changes goes through `ln-refactor`; a single misplaced file can be a `ln-scope` slice. + ## Output Present findings as numbered candidates. Use the compact form for ordinary findings: @@ -63,7 +95,7 @@ Present findings as numbered candidates. Use the compact form for ordinary findi ```md ## Review: [area] -1. **[Description]** — [category: depth|naming|model|coupling|seam|oracle-coverage] — [impact: low|medium|high] +1. **[Description]** — [category: depth|naming|model|coupling|seam|oracle-coverage|topography] — [impact: low|medium|high] [1-2 sentence explanation and suggested action] 2. ... @@ -92,7 +124,7 @@ After presenting findings, present these options to the user (use `tool-ask-ques | 3 | Plan a refactor | `ln-refactor` | Multiple findings need coordinated restructuring | | 4 | Back to triage | `ln-consult` | Review complete, no immediate action needed | -Recommended: **2** if the highest-impact finding is a deepening candidate, **1** if high-impact findings are concrete fixes, **4** otherwise. +Recommended: **2** if the highest-impact finding is a deepening candidate, **1** if high-impact findings are concrete fixes, **3** when multiple topographic or naming findings cluster into a single layout pass, **4** otherwise. --- *Draws from [mattpocock/skills/improve-codebase-architecture](https://github.com/mattpocock/skills/tree/main/improve-codebase-architecture) and [theswerd/aicode/skills/self-documenting-code](https://github.com/theswerd/aicode/blob/main/skills/self-documenting-code/SKILL.md).* diff --git a/.agents/skills/ln-scope/SKILL.md b/.agents/skills/ln-scope/SKILL.md index e0150bf5..b4c76b8b 100644 --- a/.agents/skills/ln-scope/SKILL.md +++ b/.agents/skills/ln-scope/SKILL.md @@ -101,10 +101,24 @@ Every boundary the slice passes through, entry to exit: ``` - RISK: [what might not work] → MITIGATION: [how to handle it] -- ASSUMPTION: [what we're assuming] → VALIDATE: [how we'll know] → [→ memory/SPEC.md §Assumptions] +- ASSUMPTION: [what we're assuming] + → IMPACT IF FALSE: [what breaks / rework cost / blast radius across queued cards or other frontiers] + → VALIDATE: [cheapest proof — spike, fixture, contract test, prototype] + → [→ memory/SPEC.md §Assumptions id] ``` -High-risk unvalidated assumption → suggest `ln-spike` before `ln-build`. +### Tracer-bullet check + +A good tracer-bullet slice scores on at least one of three convergent axes (see `docs/praxis/ln-skills.md` §Tracer-bullet sequencing): **proof of life** (lights up a new end-to-end path), **invariants** (locates or stabilizes a seam), **uncertainty** (retires a load-bearing assumption from `memory/SPEC.md` §Assumptions). The best slices score on more than one. + +If the slice depends on a high-impact assumption that landing it will not retire: + +1. **Reshape, don't defer.** Rework the slice so landing it *is* the proof — a tracer bullet that breaks if the assumption is wrong almost always beats a study step in this codebase. +2. **Spike exception.** Route to `ln-spike` only when no vertical slice would be cheaper than a pure probe (third-party API contract, vendor perf characteristic, research-grade unknown). + +"High-impact" means the assumption being false would force rework across more than this slice — invalidating queued cards, changing the chosen module shape from `ln-design`, or forcing a different frontier-level sequencing decision. + +A tracer bullet should *tell you something*. Build it. ### Acceptance Criteria @@ -113,6 +127,8 @@ High-risk unvalidated assumption → suggest `ln-spike` before `ln-build`. ✓ [test name] — [observable assertion] ``` +**Notation aid.** When acceptance is more than a handful of leaves, decompose it with `pseudo tree` (obligation decomposition variant) so each leaf maps to one assertion. Use `pseudo lanes` when the slice crosses actor boundaries; `pseudo state-machine` when it changes a lifecycle. + ### Verification Approach Name the oracle strategy for this slice. @@ -161,12 +177,22 @@ For light cards, include this section whenever the containing frontier definitio - [obligation] ``` +### Assumption dependency + +State one of: + +- `None` — this slice's correctness does not hinge on any live `memory/SPEC.md` §Assumptions +- `Depends on: ` — and a one-line note on why those assumptions are validated enough to build against + +If a light card would have to mark `Depends on:` a high-impact unvalidated assumption, promote to a full scope card and apply the **Tracer-bullet check**. + ### Promotion checklist If any answer is yes, stop treating the work as light and promote it to a full scope card before routing to `ln-build`. Do not quietly carry durable change under a light card. - [ ] Does this change a requirement? - [ ] Does this create, retire, or invalidate an assumption? +- [ ] Does this slice depend on an unvalidated high-impact assumption? - [ ] Does this make or reverse a non-trivial design decision? - [ ] Does this establish a new seam-level invariant? - [ ] Does this change a frontier-level cross-cutting obligation or verification architecture layer? @@ -201,4 +227,4 @@ After the scope card is complete, present these options to the user (use `tool-a | 5 | Revise plan | `ln-plan` | The work no longer fits the current frontier | | 6 | Back to triage | `ln-consult` | Scope revealed unclear state | -Recommended: **1** unless the promotion checklist fires or the verification approach is still unclear. If a short prepared queue is warranted, write it to `memory/CARDS.md` and let `ln-build` consume the next ready card from there. +Recommended: **1** in nearly all cases — including when the **Tracer-bullet check** fires, because the preferred resolution is to reshape, not defer. Recommend **3 (Spike first)** only when no vertical slice would be cheaper than a pure probe. Recommend **2 (Design oracles)** only when verification for the reshaped slice is still genuinely unclear. If a short prepared queue is warranted, write it to `memory/CARDS.md` and let `ln-build` consume the next ready card from there. diff --git a/.agents/skills/ln-spec/SKILL.md b/.agents/skills/ln-spec/SKILL.md index ce90d706..f4327629 100644 --- a/.agents/skills/ln-spec/SKILL.md +++ b/.agents/skills/ln-spec/SKILL.md @@ -44,6 +44,8 @@ When assigning the numeric part for a new item: If `$HOME` is unavailable or the basename is empty, ask the user for the suffix instead of inventing one. +**Notation aid.** Use `pseudo` when sketching module shapes, decomposing acceptance criteria into individually-testable obligations, or capturing schema-shaped invariants. `pseudo tree` for obligation decomposition; `pseudo data-shape` for module data; `pseudo graph` for cross-module relations. + ### SPEC shape Use the mature SPEC shape unless the existing project clearly predates it and the user only asked for a narrow patch: diff --git a/.agents/skills/ln-sync/SKILL.md b/.agents/skills/ln-sync/SKILL.md index 6f29aff2..d35309d3 100644 --- a/.agents/skills/ln-sync/SKILL.md +++ b/.agents/skills/ln-sync/SKILL.md @@ -31,6 +31,13 @@ Prefer `ln-sync` at these moments: | `memory/CARDS.md` | derivative execution queue | only unfinished prepared scope cards inside one frontier item | | `memory/REFACTOR.md` | derivative temporary execution plan | only unfinished refactor steps | +**Notation aid.** When refreshing SPEC or PLAN: + +- **Preserve existing `pseudo` artifacts** (tree, chain, graph, matrix, state-machine, data-shape, lanes). Do not collapse a `pseudo` form back into prose — these are denser, more diffable, and more agent-navigable than equivalent text. +- **Consolidate prose into `pseudo` forms** when prose has grown that meets the routing criteria (see `pseudo` SKILL routing chain) — paragraph-length acceptance criteria → `tree`, hand-drawn dependency tree with cross-edges hiding in prose → `graph`, scattered comparison bullets → `matrix`. +- **Apply smell-to-switch rules** when reshaping. An artifact may have outgrown its current family (e.g. a tree whose siblings now interact → graph). +- A change that *replaces* prose with an equivalent `pseudo` artifact counts as a sync improvement, not a content edit; surface it as such in the change summary. + ## Procedure ### 1. Read the current docs diff --git a/.agents/skills/pseudo/SKILL.md b/.agents/skills/pseudo/SKILL.md new file mode 100644 index 00000000..939e44c3 --- /dev/null +++ b/.agents/skills/pseudo/SKILL.md @@ -0,0 +1,126 @@ +--- +name: pseudo +description: "Sketch structures, flows, schemas, and decisions in minimal ASCII/YAML notation that humans and agents can co-edit. Use when planning before code — describing hierarchies, call flows, graphs, decision tables, state machines, data shapes, or actor sequences — and when a shared diagram would carry the design discussion better than prose." +argument-hint: "[family name (tree|chain|graph|matrix|state-machine|data-shape|lanes) or free-form relation to capture]" +--- + +# Pseudo + +A small typology of minimal notations for **shared design between humans and agents**. Each family captures one kind of structural relation, in a form that is cheap to type, cheap to diff, and cheap for either party to mutate. Pseudo is a *notation primitive* — `ln-spec`, `ln-design`, `ln-scope`, `ln-refactor`, and `ln-review` should reach for it whenever a sketch would beat prose. + +## Input + +Family name or free-form relation to capture: $ARGUMENTS + +## Family map + +| Family | Captures | Reference | +|---|---|---| +| **tree** | containment, hierarchy, decomposition | [references/tree.md](references/tree.md) | +| **chain** | linear flow, call stack, mainline reasoning | [references/chain.md](references/chain.md) | +| **graph** | fan-in / fan-out, cycles, dependencies | [references/graph.md](references/graph.md) | +| **matrix** | n×m comparison, decision tables, responsibility | [references/matrix.md](references/matrix.md) | +| **state-machine** | durable states + transitions | [references/state-machine.md](references/state-machine.md) | +| **data-shape** | schemas, types, instance shape | [references/data-shape.md](references/data-shape.md) | +| **lanes** | actors over time, request/response, async handoff | [references/lanes.md](references/lanes.md) | + +## Routing + +Three modes by `$ARGUMENTS`: + +1. **Empty** — emit the family map plus a one-line "what relation are you capturing?" prompt; route from the answer. +2. **Named family** (`tree`, `graph`, …) — load that reference and apply it to current context. +3. **Free-form intent** (`"before/after of the auth flow"`, `"how the worker retries"`) — route from intent → family using the chain below, then load the reference. + +### Routing chain (from intent to family) + +``` +What relation am I capturing? + -> "containment, hierarchy, decomposition (incl. obligations)" + -> tree + -> "linear flow with at most shallow branching" + -> chain + -> "actors over time, request/response, handoff" + -> lanes + -> "fan-in, fan-out, cycles, typed or static dependencies" + -> graph + -> "n×m comparison, conditions → actions, responsibility, coverage" + -> matrix + -> "durable named states with transitions between them" + -> state-machine + -> "schema, type, instance shape" + -> data-shape + x> none fit cleanly + -> ask the user; the relation may need two paired artifacts (e.g. graph + state-machine) +``` + +If two families both fit, prefer the one with the smaller artifact. If the artifact then strains, the smell-to-switch rules below catch it. + +## Shared overlay grammar + +Every family inherits the same small sigil set. Do not invent per-family alternatives. + +``` ++ - ~ added / removed / changed +? uncertain / proposal / needs confirmation (line-marker only) +! risk / blocker / hotspot +#id stable anchor — cross-references between artifacts +@owner owner / reviewer +[tags] compact metadata +-> ~> x> sync edge / async edge / error or fallback edge +<- return / response +... elided region (give a count if known) +``` + +**`?` collision rule.** In `data-shape`, suffix `?` means *optional type* (`avatarUrl: string?`). In every other family, `?` is the line-marker for *uncertain / proposal*. Never both in one block. + +## Authoring discipline + +- **One semantic fact per line** in source form. Comma-compressed versions are display sugar, not the canonical artifact. +- **ASCII is canonical; Unicode is rendering.** `->` and `└──` are aliases for `→` and `└──`; pick one per artifact and stay consistent. +- **Indentation = structure only.** Do not let column alignment carry meaning — alignment may aid reading, but the artifact must survive reformatting. +- **Anchors `#id` link artifacts.** A tree node can reference a graph edge can reference a matrix rule can reference a state transition. +- **Legend at top** when sigils multiply past ~5. Cheaper than glossing each use. + +## Cross-cutting moves + +These work across families. Reach for them before adding new notation: + +- **Before/after pairing** — two blocks under `## Current` / `## Desired`. The single most generic design move. +- **Delta inline** — `+`/`-`/`~` markers inside one block when the diff is small enough that two blocks would be wasteful. +- **Annotation column** — whitespace-aligned `[tag]` to the right of each line. +- **Anchor-attachment-after** — sketch the structure first; attach file/function/test anchors only once shape is settled. +- **Focus + elision** — show the touched area plus parent context, not the whole artifact. Use `... (N omitted)`. +- **`notes:` / `open:` footer keyed by anchors** — the non-YAML equivalent of "comments as channel." Keeps the left side stable and diffable. +- **Promote repeated notes into rules** — if the same annotation appears 3+ times, add a `legend:` or `_rules:` block. +- **Pairing variations** — current/desired, prod/test, happy/adversarial, steady-state/failure, schema/instance, rules/worked-examples. + +## Smell-to-switch rules + +When a sketch starts working against you, the family is usually wrong. Each reference includes a fuller list; the universal version: + +> **If a side note changes control flow, concurrency, ownership, or error semantics, it is not a footnote anymore — switch families.** + +Quick map of common smells: + +- Same conceptual child under two parents (tree) → **graph** +- Step has >1 meaningful branch, or branches rejoin (chain) → **graph** or **lanes** +- More than one actor matters, or request/response order is the point (chain or graph) → **lanes** +- Cells need sentences, not tokens (matrix) → prose, or split into smaller matrices +- "State" names are actually actions (`submit`, `approve`) (state-machine) → **chain** or **lanes** +- Cross-field rules dominate the schema (data-shape) → add `_rules:` block, or split into shape + state-machine +- Spatial layout itself carries meaning (graph) → escape to Mermaid + +## Escape hatches + +- **Mermaid / rendered diagram** — only when spatial layout itself carries meaning, or edge crossings actively slow reading. Try ASCII first; if it fights you, offer the user a Mermaid version or ask whether to start there. +- **Prose** — when the relation is genuinely narrative (rationale, motivation, decision history). Don't force structure on it. +- **Multiple small artifacts linked by `#id`** — beats one overloaded artifact almost every time. + +## Procedure when invoked + +1. Resolve the family (from `$ARGUMENTS` or by routing from intent). +2. Load the corresponding `references/.md`. +3. Apply its canonical form first; reach for variants only when canonical fails. +4. Use overlay grammar consistently with the rest of the document. +5. If a smell-to-switch tripwire fires, surface it explicitly and propose the new family rather than silently mutating the form. diff --git a/.agents/skills/pseudo/references/chain.md b/.agents/skills/pseudo/references/chain.md new file mode 100644 index 00000000..985a2e1a --- /dev/null +++ b/.agents/skills/pseudo/references/chain.md @@ -0,0 +1,171 @@ +# Pseudo: Chain + +Captures **linear flow, call stack, mainline reasoning** — one thing leading to the next, with shallow branching for fallbacks, errors, and fire-and-forget side effects. Top-down indented with arrows as the line prefix. + +## When to use + +- Call graph from a single entry point. +- Mainline happy-path with a few exceptions or async side effects. +- Step-by-step pseudocode or reasoning. +- Layered composition (handler → service → repo → driver). +- Paired flows (production vs test, happy vs error) — same form, two named blocks. + +## When NOT to use + +- Multiple actors hand off requests/responses → **lanes**. +- More than one meaningful branch that rejoins → **graph**. +- Order between independent steps doesn't matter → **graph**. +- Persistent state with named modes → **state-machine**. + +## Canonical form + +Top-down, two-space indent per level, edge marker as the line prefix. + +``` +HTTP handler + -> AuthService.verify + -> TokenStore.lookup + -> Redis GET + x> Postgres SELECT # fallback on miss + -> User.load + <- 200 SessionDTO +``` + +Top-down is canonical. Left-to-right chains are harder to edit, harder to wrap, and don't compose with nesting; treat them as a rendering, not a source form. + +## Variants + +### Guarded chain (conditional branches) + +``` +POST /login + -> validate + x> 400 invalid # error branch terminates + -> Auth.verify + -> Token.issue + <- 200 SessionDTO +``` + +### Async side-effect chain + +`~>` for fire-and-forget side effects that don't block the mainline. + +``` +POST /order + -> Order.create + ~> Analytics.track # async + ~> Mailer.confirmation # async + <- 202 accepted +``` + +### Fork/join-lite (mainline + parallel side-work) + +If the join semantics matter (waiting for all branches), switch to **graph** or **lanes**. + +``` +Process request + -> Order.save + ~> Email.send + ~> Analytics.emit + <- 202 accepted # no join — fire-and-forget +``` + +### Input/output chain (shape transforms) + +When types change through the chain and the transformation is the point: + +``` +req: LoginRequest + -> validate + -> normalizeEmail + -> Auth.verify + <- SessionDTO +``` + +### Paired chains (prod / test, happy / error) + +Two named blocks under separate headings, same form. The pattern from your Final call graph example. + +``` +### Production +HTTP handler + -> LinkCatalog.layerDurableObject + -> Effect RPC over Durable Object fetch + -> LinkCatalog.layer + -> LinkCatalogCoordinator + -> LinkCatalogStore + -> LinkCatalogSqlExecutor + -> PublicRedirectIndexService + +### Tests +HTTP handler + -> linkCatalogMemoryLayer + -> LinkCatalog.layer + -> LinkCatalogCoordinator + -> LinkCatalogStore.layerMemory + -> PublicRedirectIndexService.layerMemory +``` + +## Annotation patterns + +- **`#id` on a step** makes it linkable: `-> Auth.verify #verify` → referenced from a graph or matrix as `#verify`. +- **`[tag]` column** for compact metadata aligned to the right. +- **Trailing `# note`** for local context; promote into structure if it changes control flow. +- **Diff markers `+` / `-` / `~`** as line prefix or marker before the arrow. +- **`?`** for uncertain step, **`!`** for risky/hotspot. +- **`@owner`** when ownership shifts between steps. +- **`notes:` / `open:` footer** keyed by anchors. + +## Smell-to-switch tripwires + +The universal rule applies sharpest here: + +> **If a side note changes control flow, concurrency, ownership, or error semantics, it is not a footnote anymore — switch families.** + +Concrete tripwires: + +- **More than one meaningful branch at a step.** → **graph** or **lanes**. +- **Branches rejoin** (you need a join node). → **graph**. +- **"Who does this step?" becomes interesting.** → **lanes**. +- **Timeouts, retries, backpressure become first-class.** → **graph** with typed nodes, or **state-machine**. +- **You start writing parenthetical "(also calls X)" notes.** → branching has exceeded chain capacity. +- **Sync/async distinction matters and you keep forgetting which is which.** → use `~>` consistently, or escape to **lanes**. + +## Anti-patterns + +- **Mixing sync and async edges with the same arrow.** Use `->` / `~>` / `x>` consistently or readers infer wrong defaults. +- **Implicit fan-out via parentheticals.** The "(also calls X)" smell — that's the family-switch trigger. +- **Left-to-right diagrams that wrap.** Unreadable; not editable. +- **Chains longer than a screen.** Split by anchor reference; link the sub-chains. +- **Mixing levels of abstraction** in one chain (HTTP handler → SQL query → byte buffer). Pick one level per chain. + +## Escape hatches + +- **Graph** when branching exceeds chain capacity (>1 meaningful branch, joins matter). +- **Lanes** when more than one actor is in play. +- **State-machine** when the chain is really a transition with retries/timeouts. +- **Split + anchor** when length exceeds a screen. + +## Worked example: HTTP login with guards and async logging + +``` +POST /login #login + -> validate body + x> 400 invalid_request + -> Auth.verify + -> TokenStore.lookup + -> Redis GET + x> Postgres SELECT # fallback on cache miss + -> User.load + x> 401 invalid_credentials + -> Session.issue + ~> Audit.log # async, fire-and-forget + <- 200 SessionDTO + +open: + - #login: should Audit.log failure surface as 500 or stay silent? +``` + +## Worked example: production / test pairing + +See the Paired chains variant above (Final call graph example). Same form, two named blocks. The shared steps in both blocks are the contract; the divergence is the layer substitution under test. diff --git a/.agents/skills/pseudo/references/data-shape.md b/.agents/skills/pseudo/references/data-shape.md new file mode 100644 index 00000000..0da93179 --- /dev/null +++ b/.agents/skills/pseudo/references/data-shape.md @@ -0,0 +1,250 @@ +# Pseudo: Data-shape + +Captures **schemas, types, and instance shape** — what fields a thing has, what types they take, what's optional, what variants exist. Uses **valid YAML** so that comments survive copy-paste, sub-trees can be snippeted in chat, and yamllint/parsers keep working. + +## When to use + +- Sketching a schema before translating to Zod, TypeScript, JSON Schema, SQL, etc. +- Negotiating field shape with the user via comments and snippeted sub-trees. +- Documenting the shape of an instance for examples or fixtures. +- Capturing a discriminated union or variant set. + +## When NOT to use + +- Cross-field rules dominate the shape → keep the shape but move rules into `_rules:` block, or split shape + state-machine. +- The "shape" is really a workflow → **state-machine** or **chain**. +- Multiple shapes related by composition or inheritance → multiple data-shape blocks linked by `#id`. + +## Canonical form + +Each line is `key: type` plus optional trailing comment. Nesting uses YAML indentation. Optional fields take suffix `?`. + +```yaml +user: + id: string # uuid + email: string # unique, lowercased + tier: enum # free | paid | trial + createdAt: datetime + profile: + name: string + avatarUrl: string? # optional +``` + +## YAML-validity rule (and what it costs) + +Data-shape stays valid YAML so that comments survive copy-paste, sub-trees can be snippeted, and yamllint/parsers keep working. This costs one thing: **the overlay grammar's line-position markers (`+`, `-`, `~`, `!`, `?`) cannot appear at the key position — they must live inside comments.** Every other family allows them on the line itself. + +| Marker | Other families (line position) | Data-shape (comment position) | +|---|---|---| +| added | `+ trialEndsAt: datetime?` | `# + trialEndsAt: datetime?` | +| removed | `- legacyPlan: string` | `# - legacyPlan: string` | +| changed | `~ tier: enum` | `# ~ tier: enum (was string)` | +| risk | `! amount: number` | `amount: number # ! check rounding` | +| proposal | `? trialEndsAt: datetime?` | `# ? trialEndsAt: datetime?` | + +## The `?` collision rule + +Suffix `?` on a type means *optional field* — it's part of the type vocabulary (`string?`, `datetime?`). + +Proposal `?` lives only in the comment-prefix form `# ?`. The two never collide because one is *after* the type and one is *before* a commented-out line. + +```yaml +user: + tier: enum # free | paid | trial + # ? trialEndsAt: datetime? # proposal: required when tier=trial +``` + +The `# ?` prefix marks proposal; the trailing `?` on `datetime?` marks optional type. + +## Type vocabulary (small and stable) + +Stay close to this set to keep translation to Zod/TS/SQL mechanical: + +``` +string number boolean datetime date duration +enum # values in trailing comment +literal "value" # for discriminator keys +T? # optional T +T[] # array of T +map # keyed map +ref # foreign reference +oneOf: # discriminated union +allOf: # intersection +``` + +Annotate constraints in trailing comments (`# unique`, `# >= 0`, `# regex /.../`) rather than inventing type syntax. + +## Variants + +### Discriminated union + +```yaml +event: + oneOf: + - kind: literal "login" + userId: ref + ip: string + - kind: literal "logout" + userId: ref + - kind: literal "purchase" + userId: ref + sku: string + amount: number # cents +``` + +Each variant gets a discriminator field (`kind:` here) with a `literal` type. + +### Defaults, computed, readonly + +Mark in trailing comments using a small vocabulary so translation stays mechanical: + +```yaml +order: + id: string # readonly, uuid + createdAt: datetime # default: now() + slug: string # computed from title + status: enum # default: "draft" | submitted | active +``` + +### `_rules:` block (cross-field invariants) + +When the same constraint touches multiple fields, promote it. Comments become normative only when they live here. + +```yaml +user: + email: string + tier: enum # free | paid | trial + trialEndsAt: datetime? + avatarUrl: string? + +_rules: + - email is unique + - trialEndsAt required when tier = "trial" + - avatarUrl must start with "https://" +``` + +If `_rules:` grows past ~7 entries, split the shape or escape to a state-machine. + +### Refs and collections + +```yaml +team: + id: string + ownerId: ref + memberIds: ref[] + roles: map, enum> # role values in trailing _rules +``` + +### Instance / example + +Same syntax, but values replace types. Mark the block so readers don't confuse it with a schema. + +```yaml +# example: user +user: + id: "01HXYZ..." + email: "luke@example.com" + tier: "trial" + createdAt: "2026-05-30T12:00:00Z" +``` + +Pair schema + instance under separate headings (`## Shape` / `## Example`) when both are useful. + +## Annotation patterns + +- **Top-level keys ARE anchors.** `session:` is referenced from other artifacts as `#session`. No inline anchor syntax — `session: #session` would parse as `session: null` with a stray comment. +- **Diff markers live in comments** (per the YAML-validity rule). Inline before/after: + + ```yaml + user: + id: string + email: string + tier: enum # ~ was: string — restricted to enum + # + trialEndsAt: datetime? + # - legacyPlan: string + ``` + + Two conventions for inline diff: `# ~` / `# +` / `# -` as *comment prefix* for new/removed lines; `# ~ note` as *trailing comment* on a line whose type changed in place. + +- **Risk marker** as trailing comment: `amount: number # ! check rounding`. +- **Collaborative-edit comments** — the killer move. Both parties edit; file still parses. + + ```yaml + user: + email: string + # luke: lowercase on read or on write? + tier: enum # free | paid | trial + # agent: trial needs an expiry. proposal: + # + trialEndsAt: datetime? + ``` + +## Smell-to-switch tripwires + +- **Comments carry business logic.** Promote into `_rules:` or split into shape + state-machine. +- **More than ~3 `oneOf` variants with overlapping fields.** Consider an `allOf:` base plus distinct extensions. +- **Cross-field rules outnumber fields.** The model is really a state-machine or a graph; the shape is just its persistence projection. +- **Mixing schema and instance in one block.** Split into two blocks under separate headings. +- **The same nested structure appears in three+ places.** Extract a named shape with `#id` and reference it. + +## Anti-patterns + +- **Inventing type syntax** (`string`, `int(>=0)`). Use the small type vocabulary plus trailing-comment constraints. +- **Re-using `?` for both optional and uncertain in the same block.** Always ambiguous. Use the `# ?` comment-prefix convention for proposals. +- **Trailing comments doing real work.** If the comment changes whether code is correct, it's a rule, not a comment. +- **One mega-shape.** If a shape needs more than ~15 fields, it's probably two shapes plus a reference. +- **Mixing styles between siblings.** `userId: string` in one place and `user_id: String` in another. Pick one casing per artifact. + +## Escape hatches + +- **Multiple shapes linked by `#id`** — almost always beats one large shape. +- **Real schema language** (Zod, TS interface, JSON Schema) — once the shape stabilizes, translate. Pseudo data-shape is a *negotiation form*, not a production artifact. +- **Pair shape + state-machine + `_rules:`** when the data model is genuinely stateful; don't try to encode lifecycle in optionality. + +## Worked example: full negotiation cycle + +Starting sketch by the user: + +```yaml +session: + id: string + userId: string + createdAt: datetime + expiresAt: datetime +``` + +Agent's response — proposed changes inline, valid YAML preserved: + +```yaml +session: + id: string # uuid + userId: ref # was: string + createdAt: datetime # default: now() + expiresAt: datetime + # agent: tier-dependent expiry. proposal: + # + tier: enum # free | paid (frozen at session create) + # + revokedAt: datetime? # nullable; set on logout + # rules? + # _rules: + # - expiresAt > createdAt + # - revokedAt > createdAt when set + +# ? should sessions know about tier, or look up via userId? +``` + +Agreed final form: + +```yaml +session: + id: string # uuid, readonly + userId: ref + createdAt: datetime # default: now() + expiresAt: datetime + revokedAt: datetime? # set on logout + +_rules: + - expiresAt > createdAt + - revokedAt > createdAt when set + - lookup tier via User.tier (not denormalized here) +``` + +The top-level key `session:` is its own anchor. A state-machine can reference it as `#session` (`active -[expire]-> expired # of #session`) and a graph can place it as `#session` (`auth -> #session -> store`). diff --git a/.agents/skills/pseudo/references/graph.md b/.agents/skills/pseudo/references/graph.md new file mode 100644 index 00000000..38f7c5a2 --- /dev/null +++ b/.agents/skills/pseudo/references/graph.md @@ -0,0 +1,248 @@ +# Pseudo: Graph + +Captures **fan-in, fan-out, cycles, and typed dependencies** between nodes. Use when the relations between things are the point — not their containment (`tree`), linear order (`chain`), or interaction across actors (`lanes`). + +## When to use + +- Multiple inputs feed one node, or one node feeds multiple outputs. +- Cycles or feedback edges exist. +- Edge labels carry meaning (guards, conditions, retry semantics). +- Node *types* matter (service vs queue vs store vs trigger). + +## When NOT to use + +- The relations are strictly hierarchical → **tree**. +- One actor does one thing at a time in order → **chain**. +- Multiple actors hand off requests/responses → **lanes**. +- Edges are dense enough that adjacency would be clearer → **matrix** (adjacency variant). + +## Canonical form + +**Paired node-list + edge-list.** ASCII boxes are a rendering; lists are the source. Boxes don't diff cleanly and one rename breaks the layout — lists diff line-by-line and grow gracefully. + +This is a bespoke line-grammar, not YAML. Node lines are `name [: type] [[tag]] [#anchor]`. Edge lines are `src dst [# note]`. Multi-source or multi-target shorthand: `a, b -> c` or `a -> b, c` (commas separate node names; node names are bare words). + +When all nodes share the same kind (a dependency graph of frontier items; a tree of React components; a set of lifecycle hooks), **omit the type entirely** — `name` or `name [tag]` is enough. Type is for when node *classes* carry meaning and the reader needs to keep them separate. + +``` +nodes: + http: trigger + cron: trigger + proc: handler + log: sink + cache: store + notify: worker + done: terminal + +edges: + http -> proc + cron -> proc + proc -> log + proc ~> cache # async + proc ~> notify # async + log, cache, notify -> done +``` + +Column alignment within `nodes:` is reader-friendly but not load-bearing — the colon is the separator. + +## Variants + +### Typed-node graph + +When node classes matter, declare them explicitly so the reader doesn't conflate a service with a state with a table. The type slot is free text — pick a small vocabulary per artifact (`service`, `queue`, `store`, `job`, `trigger`, `sink`, `terminal` is a reasonable starter set). + +``` +nodes: + api: service + queue: queue + worker: job + db: store +``` + +### Labeled / guarded edges + +``` +proc -[if cached]-> done +proc -[if miss]-> fetch +fetch x[on error]-> retry +``` + +`-[label]->` for conditions and guards; `x[label]->` for error or fallback transitions. The bracketed form is parallel for both; the leading `x` flags the error semantic. + +### Subgraph / cluster + +Group nodes when topology has obvious regions. Top-level nodes and groups live under separate keys; edges still live in the flat list (groups are organizational, not semantic). + +``` +nodes: + proc: handler + done: terminal + +groups: + ingest: + http: trigger + cron: trigger + outputs: + log: sink + cache: store + notify: worker +``` + +### Multiplicity + +``` +client[*] -> api +proc -> notify[*] +``` + +### Cycles / feedback + +Allowed in `graph`. Mark explicitly with a label so readers don't read it as a typo. + +``` +queue ~> worker +worker -[retry on fail]-> queue +``` + +### Dependency-graph edges (static, not runtime) + +The default edge vocabulary (`->` sync, `~>` async, `x>` error) is tuned for **runtime flow**. For **static dependencies** — "X must precede Y," "Y is an optional successor of X" — use the labeled form so the dependency type stays explicit: + +``` +edges: + pi-ui-extension-patterns -[hard]-> sealed-pi-profile-runtime-state + sealed-pi-profile-runtime-state -[hard]-> graph-data-plane + agent-graph-integration -[optional]-> subagents-for-proposal-diversity + graph-data-plane -[on promotion]-> oracle-design-plan-graphs +``` + +A small starter vocabulary: `-[hard]->`, `-[optional]->`, `-[on promotion]->`, `-[blocking]->`. Pick the smallest set the artifact needs and declare it in a legend if it grows past ~4. + +Don't overload `~>` for "soft dependency" — it means *async runtime edge* everywhere else and the collision is confusing. + +### Adjacency matrix (dense graphs) + +When edge-list grows past ~30 edges and the graph is densely connected, escape to **matrix** with rows = source, cols = target. Keep the node list intact above the matrix. + +## Annotation patterns + +- `#id` on a node makes it linkable from other artifacts: `proc: handler #proc` so a state-machine transition can say `pending -[handled by #proc]-> active`. Anchor follows the type (or the name, when type is omitted). +- Right-gutter `# note` for local context. Promote into the edge label if the note changes routing. +- **Unconnected nodes** (no edges in or out) belong in a named group so readers don't search for missing edges. Common names: `unconnected`, `for-acknowledgment`, `horizon`. Use when a node exists in the picture for completeness — e.g. horizon items in a roadmap dependency graph — but doesn't participate in the active relations. +- `?`, `!`, `+`, `-`, `~` markers on node or edge lines work the same as everywhere else (uncertain, risk, added, removed, changed). +- `notes:` / `open:` footer keyed by anchors for discussion that doesn't belong on the line itself: + + ``` + open: + - #notify: confirm delivery guarantee — at-least-once or exactly-once? + - #cache: TTL still TBD + ``` + +## Smell-to-switch tripwires + +- **Node types mixed arbitrarily.** A service, a state, a DB table, and a file path in one untyped graph — picture looks coherent, isn't. Fix: type the nodes, or split into multiple typed graphs. +- **"When" matters more than "what depends on what."** You keep wanting to say "first this, then that, then later that." → **chain** or **lanes**. +- **Edge labels carry most of the meaning.** The graph degenerates into a wiring diagram for the labels. → **state-machine** or **matrix**. +- **More than one actor.** "API calls worker calls DB" — ownership boundary matters. → **lanes**. +- **The graph keeps gaining edges with new prose.** → split into subgraphs linked by `#id`, or escape to Mermaid. + +## Anti-patterns + +- **Drawing boxes by hand for >~5 nodes.** Source becomes unmaintainable; one rename breaks the whole layout. Use node/edge lists. +- **Mixing `dag` and graph semantics.** This skill family is `graph` and cycles are allowed. If you mean strictly acyclic, say so in a comment (`# acyclic`); don't rely on ASCII layout to imply it. +- **Implicit edge types.** `->` everywhere when half should be `~>` (async) or `x>` (error). The reader will infer wrong defaults. +- **One mega-graph.** If the legend grows past ~7 node types, you have two graphs, not one. + +## Escape hatches + +- **Mermaid** — when spatial layout itself carries meaning (e.g. swim arrangement, geographic clustering) or when readers struggle to trace edges in text. Offer the user the option; don't decide unilaterally. +- **Tree + cross-refs** — when 80% of the relations are containment and 20% are non-tree. A tree with a few `->#anchor` cross-refs is easier to read than a graph. +- **Multiple small graphs** linked by `#id` beats one large one almost always. + +## Worked example: trigger-and-fanout pipeline + +``` +nodes: + http: trigger + cron: trigger + proc: handler #proc + log: sink + cache: store + notify: worker + done: terminal + +edges: + http, cron -> proc + proc -> log + proc ~> cache # async, fire-and-forget + proc ~> notify # async, at-least-once + log, cache, notify -> done + +open: + - #proc: idempotency key shape still TBD +``` + +## Worked example: roadmap dependency graph (untyped nodes, static edges) + +Exercises three patterns: type omitted (all nodes are frontier items), dependency-graph edge labels, and the `unconnected` group for horizon items. + +``` +nodes: + pi-ui-extension-patterns [in-progress] + sealed-pi-profile-runtime-state [not-started] + graph-data-plane [paused] + agent-graph-integration [not-started] + subagents-for-proposal-diversity [deferred] + authority-model [not-started] + turn-boundary-reconciliation [not-started] + coherence-first-class [not-started] + compaction-and-conflict-widening [not-started] + probes-and-transcripts-evolution [continuous, parallel] + +edges: + pi-ui-extension-patterns -[hard]-> sealed-pi-profile-runtime-state + sealed-pi-profile-runtime-state -[hard]-> graph-data-plane + graph-data-plane -[hard]-> agent-graph-integration + agent-graph-integration -[hard]-> authority-model + agent-graph-integration -[hard]-> turn-boundary-reconciliation + agent-graph-integration -[optional]-> subagents-for-proposal-diversity + turn-boundary-reconciliation -[hard]-> coherence-first-class + coherence-first-class -[hard]-> compaction-and-conflict-widening + graph-data-plane -[on promotion]-> oracle-design-plan-graphs + +groups: + unconnected: + flue-pattern-adoption + oracle-design-plan-graphs + framework-direction-stubs + geolog-and-petri-execution + +notes: + - probes-and-transcripts-evolution runs in parallel across all frontiers; not a spine edge. + - unconnected items are surfaced for acknowledgment, not active dependency. + +open: + - confirm whether sealed-pi-profile-runtime-state -[optional]-> subagents matters + (sandbox sealing precedes subprocess fan-out, even if m5 is the primary gate). +``` + +## Worked example: retry loop with typed nodes + +``` +nodes: + api: service + queue: queue #q + worker: job + dlq: queue + store: store + +edges: + api -> queue + queue ~> worker + worker -> store + worker x[after 3 retries]-> dlq + worker -[on retry]-> queue # explicit cycle + +notes: + - #q: visibility timeout = 30s +``` diff --git a/.agents/skills/pseudo/references/lanes.md b/.agents/skills/pseudo/references/lanes.md new file mode 100644 index 00000000..64de35f4 --- /dev/null +++ b/.agents/skills/pseudo/references/lanes.md @@ -0,0 +1,212 @@ +# Pseudo: Lanes + +Captures **actors over time** — request/response, async handoff between services, conversational protocols, ownership boundaries. The family for the relation `chain` and `graph` can't cleanly express: *who* does *what*, *when*, *to whom*. + +## When to use + +- More than one actor or system in the flow. +- Request/response order matters. +- Async handoff with timing implications. +- Conversational protocol (client ↔ server, multi-party). +- Chain is collapsing because lane boundaries matter ("who owns this step?"). + +## When NOT to use + +- Single-actor sequence → **chain**. +- Pure dependency structure, time doesn't matter → **graph**. +- Lifecycle of a single resource (its named modes) → **state-machine** on that actor. + +## Canonical form + +One message per line, in time order. Each line is `sender recipient: message [#anchor]`. Actor names are bare words. + +``` +client -> api: POST /login #S1 +api -> db: SELECT user #S2 +api -> hasher: verify(password) #S3 +api ~> mailer: send notification #S4 # async +api <- client: 200 SessionDTO #S5 +``` + +Edge types (same set as elsewhere): + +- `->` synchronous request +- `~>` async / fire-and-forget +- `<-` response (sender on left = responder) +- `x>` error / rejection / timeout + +## Variants + +### Numbered messages + +`#Sn` step anchors let other artifacts reference specific messages. Use when cross-referencing matters — e.g. a state-machine transition triggered by `#S3`, or a chain that handles the `#S2` response. + +### With actor declarations + +Declare actors when type matters or names need disambiguation. Same pattern as `graph` node typing. + +``` +actors: + client: browser + api: service + worker: job + db: store + mailer: external + +messages: + client -> api: POST /login #S1 + api -> db: SELECT user #S2 + api -> client: 202 accepted #S3 + api ~> worker: enqueue(notify) #S4 + worker ~> mailer: send #S5 +``` + +### Parallel / async fork + +Async messages on the same originating step don't need to wait. Stack them without indenting. + +``` +api -> db: INSERT order #S1 +api ~> mailer: confirmation #S2 # parallel async +api ~> ledger: record #S3 # parallel async +api <- client: 202 accepted #S4 +``` + +If join semantics matter (waiting for both async results), escape to **graph** with explicit join. + +### Loops and conditional blocks + +Group messages under a labeled block. Indent the block body by two spaces. + +``` +client -> api: POST /upload #S1 + +loop while not complete: + client -> api: PUT chunk N #S2 + api -> client: 200 ack #S3 + +client -> api: POST /commit #S4 +api -> client: 201 created #S5 +``` + +``` +client -> api: POST /login #S1 + +alt success: + api -> client: 200 SessionDTO #S2a +alt invalid: + api -> client: 401 unauthorized #S2b +alt locked: + api -> client: 423 locked #S2c +``` + +### Time annotations + +When relative or absolute time matters, use trailing comments: + +``` +client -> api: POST /charge #S1 +api -> psp: capture #S2 # blocking, ≤2s budget +api ~> ledger: record #S3 # async +api -> client: 200 ok #S4 # T+~300ms typical +``` + +## Annotation patterns + +- **`#Sn` step anchors** for cross-reference from other artifacts. +- **`actors:` block** for typed actor declarations when needed. +- **`# note`** trailing comments for local context. +- **Diff markers `+` / `-` / `~`** as line prefix when comparing versions of a protocol. +- **`?` for uncertain timing/order, `!` for risk** (e.g. "this leaks PII over the wire"). +- **`notes:` / `open:` footer** keyed by step anchors. + +``` +notes: + - #S2: PSP capture is the only blocking dependency in the hot path + - #S3: ledger write is eventually consistent; reconcile via separate job + +open: + - confirm idempotency key flows through #S2 → PSP +``` + +## Smell-to-switch tripwires + +- **Single actor** — only one column has any messages → **chain**. +- **Time genuinely doesn't matter**, only dependency → **graph**. +- **An actor has rich named modes** (its behavior depends on its state) → **state-machine** on that actor, referenced from lanes. +- **Many actors with similar roles** (e.g. `worker1`, `worker2`, `worker3`) → typed **graph** with multiplicity, not lanes. +- **Sequence becomes a tangle of arrows crossing back and forth** → escape to Mermaid `sequenceDiagram` or split scenarios. + +## Anti-patterns + +- **Sync and async with the same arrow.** Use `->` / `~>` / `x>` consistently. +- **Omitting the response** for synchronous calls. Reader can't tell if it's fire-and-forget. +- **Mixing logical actors and physical processes** in one diagram (`Frontend` next to `nginx worker #3`). Pick one level. +- **Too-fine granularity** — every internal function call as a message. Lanes are for cross-actor messages; intra-actor calls belong in a **chain**. +- **Too-coarse granularity** — `client -> server: do the thing`. Useless. Name the message. + +## Escape hatches + +- **Mermaid `sequenceDiagram`** when actor count > 5 or message count > 20, or when activation/lifeline rendering would actively help. +- **Multiple lanes diagrams** for distinct scenarios (login vs renewal vs error-recovery). Linked by anchor. +- **State-machine on one actor** + lanes around it, when that actor's modes drive the protocol. +- **Graph + lanes pair** — the graph shows static dependencies; lanes show the runtime conversation across the same actors. + +## Worked example: OAuth login + +``` +actors: + user: human + app: service # our service + provider: external # OAuth provider + db: store + +messages: + user -> app: click "Login with Provider" #S1 + app -> user: 302 to provider/authorize #S2 + user -> provider: GET authorize?... #S3 + provider -> user: consent screen #S4 + user -> provider: approve #S5 + provider -> user: 302 to app/callback?code=... #S6 + user -> app: GET /callback?code=... #S7 + app -> provider: POST token (exchange code) #S8 + app <- provider: { access_token, id_token } #S9 + app -> db: upsert user #S10 + app ~> ledger: log auth event #S11 # async + app -> user: 302 to /home + session cookie #S12 + +notes: + - #S8: server-to-server; never expose code or secret to the browser. + - #S11: failure here does not block login. + +open: + - PKCE flow variant — separate diagram? +``` + +## Worked example: retry with backoff between services + +``` +actors: + api: service + worker: job + db: store + dlq: queue + +messages: + api -> worker: enqueue(task #t42) #S1 + worker -> db: UPDATE task running #S2 + worker x> db: transient error #S3 # attempt 1 + worker -> worker: backoff 1s #S4 + worker x> db: transient error #S5 # attempt 2 + worker -> worker: backoff 2s #S6 + worker -> db: UPDATE task success #S7 # attempt 3 ok + worker ~> api: callback complete #S8 + +# Failure path (alternative scenario, ≥3 attempts fail): + worker x> db: transient error #S5' + worker -> dlq: push task #t42 #S6' + worker ~> api: callback failed #S7' + +notes: + - retry counts and backoff schedule live in the worker config, not in this diagram +``` diff --git a/.agents/skills/pseudo/references/matrix.md b/.agents/skills/pseudo/references/matrix.md new file mode 100644 index 00000000..c58e4801 --- /dev/null +++ b/.agents/skills/pseudo/references/matrix.md @@ -0,0 +1,193 @@ +# Pseudo: Matrix + +Captures **n×m relationships in a compact grid** — options × criteria, conditions × actions, responsibilities × steps, scenarios × subsystems, source × target. The same pipe-delimited form, but the *semantics* vary by sub-form. Be explicit about which sub-form you're using. + +## When to use + +- Comparing options against a fixed set of criteria. +- Specifying rules as conditions → actions. +- Capturing who-does-what across steps (responsibility). +- Tracking coverage of scenarios across subsystems. +- Adjacency for dense graphs. + +## When NOT to use + +- Cells need sentences, not tokens → split into smaller matrices, or escape to prose. +- Row order secretly encodes time or priority → **chain** or **state-machine**. +- Rules nest with conditional sub-branches → decision tree (not in this typology — escape to prose or split into multiple matrices). + +## Canonical form + +Pipe-delimited grid: header row, separator row, body rows. Cell values are tokens, not sentences. + +``` + | tree | chain | graph | matrix +------------------|------|-------|-------|-------- +hierarchy | + | . | ~ | . +linear flow | . | + | ~ | . +fan-in / fan-out | . | . | + | . +n×m comparison | . | . | . | + +diff-friendly | + | + | - | + +``` + +ASCII-friendly cell vocabulary: + +``` ++ strong fit / yes / required +~ partial / depends / optional +- poor fit / no / forbidden +? unknown / TBD +. intentionally blank (vs typo or unconsidered) +``` + +The `.` is doing real work: it distinguishes *"considered and not applicable"* from *"haven't thought about it"* (truly blank). Use it. + +## Sub-forms (semantically distinct) + +### Comparison grid (options × criteria) + +Rows are options; columns are criteria; cells are fit values. No ordering implied; no execution semantics. The canonical-form example above is a comparison grid. + +### Decision table (conditions → actions) + +Rows are rules; left columns are conditions; columns prefixed with `→` are actions/outputs. **Always declare the match policy** above the table: + +``` +policy: first-match + +rule | tier | age | → action | → notify +-----|-------|--------|-------------------|---------- +R1 | free | <30d | allow | none +R2 | free | ≥30d | prompt-upgrade | banner +R3 | trial | <14d | allow | countdown +R4 | trial | ≥14d | block | email +R5 | paid | any | allow | none +``` + +Match policies: + +- **first-match** — rows in order; first matching row wins. Gives priority semantics. +- **exclusive** — at most one rule matches; overlapping rules are a bug. +- **cumulative** — all matching rules apply; effects compose. + +Without `policy:`, readers can't tell whether you mean priority, partitioning, or composition. + +Use `rule | ...` first column with `#R1`-style IDs so transitions in a state-machine or steps in a chain can reference rules by anchor. + +### Responsibility matrix (steps × actors) + +Steps as rows; actors as columns. Standard RACI vocabulary (`R` responsible, `A` accountable, `C` consulted, `I` informed), or simpler `R` / `.` if only ownership matters. + +``` +step | api | worker | ops +--------------|-----|--------|----- +enqueue | R | . | . +process | . | R | C +retry | . | R | A +dead-letter | . | . | R +``` + +### Coverage matrix (scenarios × subsystems) + +Scenarios as rows; subsystems as columns; `+` marks involvement. Used for test coverage planning, change-impact analysis, regression scope. + +``` +scenario | auth | billing | email +----------------|------|---------|------ +signup | + | . | + +renewal | . | + | + +password reset | + | . | + +``` + +### Adjacency matrix (dense graphs) + +Escape hatch from **graph** when edge-list exceeds ~30 edges in a densely connected graph. Rows = source, columns = target, cells = edge type (`+` / `~>` / `x>` / `.`). + +``` + | http | proc | log | cache | done +-------|------|------|-----|-------|------ +http | . | + | . | . | . +proc | . | . | + | ~> | . +log | . | . | . | . | + +cache | . | . | . | . | + +``` + +Keep the node-list (from `graph`) above the matrix so node types stay visible. + +## Annotation patterns + +- **`→` prefix on column headers** marks outputs (decision tables) vs inputs. +- **`rules:` / `examples:` label** at the top of the table to mark normative vs illustrative. Without this, implementers fill gaps as "don't care." +- **`policy:` above decision tables** (mandatory). +- **`#Rn` row anchors** in the first column for cross-reference from other artifacts. +- **`notes:` / `open:` footer** keyed by row anchors for per-row prose. +- **Diff markers `+` / `-` / `~` and risk `!`, uncertain `?`** can prefix the row identifier or sit in cells (where they don't collide with cell vocabulary — be careful here). + +``` +notes: + - #R4: should "block" also revoke active sessions, or only block new ones? + +open: + - confirm trial transitions are pure age-based (no grace period) +``` + +## Smell-to-switch tripwires + +- **Row order secretly encodes time** (rows are really steps) → **chain** or **state-machine**. +- **Cells need sentences, not tokens** → split into smaller matrices, or escape to prose. +- **Footnotes proliferate** to handle exceptions → the rule set is wrong; restructure. +- **Reader needs to know *why* a row matched**, not just *that* it did → use anchors + a `notes:` block, or escape to prose. +- **Decision table without policy** → ambiguous semantics; either declare policy or switch to prose. + +## Anti-patterns + +- **Decision table without `policy:`** — readers infer wrong defaults. +- **Missing `rules:` / `examples:` label** — gaps get implemented as "don't care." +- **Sentences in cells** — split or escape. +- **Mega-matrix** (>10 columns or >20 rows) — split by dimension. +- **Using blank cells and `.` interchangeably** — loses the "considered" vs "unconsidered" distinction. +- **Mixed sub-forms in one table** (comparison values mingled with decision actions). Pick one. + +## Escape hatches + +- **Decision tree** when rules nest beyond what columns can carry. (Not in this typology; render as a **chain** with guards or as prose.) +- **State-machine** when rules are stateful. +- **Prose** when narrative dominates ("under these special conditions, …"). +- **Multiple smaller matrices** linked by anchor — almost always better than one mega-matrix. + +## Worked example: decision table with first-match policy + +``` +policy: first-match +context: signup eligibility + +rule | tier | age | region | → action | → notify +-----|-------|--------|--------|-------------------|---------- +R1 | - | - | EU | require-consent | inline +R2 | trial | ≥14d | any | block | email +R3 | trial | <14d | any | allow | countdown +R4 | free | <30d | any | allow | none +R5 | free | ≥30d | any | prompt-upgrade | banner +R6 | paid | any | any | allow | none + +notes: + - #R1: applies regardless of tier; EU consent is the first gate. + - #R2-R3: trial transitions are pure age-based — confirm with product. + +open: + - what about trial users in EU on day 14? R1 should win under first-match. +``` + +## Worked example: responsibility matrix + +``` +context: production deploy steps + +step | dev | release-eng | ops | exec +------------------|-----|-------------|-----|------ +write changelog | R | . | . | . +cut release | C | R | . | I +canary deploy | C | R | A | . +full deploy | . | C | R | I +incident response | C | . | R | A +``` diff --git a/.agents/skills/pseudo/references/state-machine.md b/.agents/skills/pseudo/references/state-machine.md new file mode 100644 index 00000000..e7f2bd2d --- /dev/null +++ b/.agents/skills/pseudo/references/state-machine.md @@ -0,0 +1,240 @@ +# Pseudo: State-machine + +Captures **durable states and the transitions between them** — workflow phases, lifecycle, protocol modes. Use only when the system *stores* the named states; if it doesn't, you have a chain or a sequence pretending to be a machine. + +## When to use + +- Persistent lifecycle (`draft → pending → active → archived`). +- Protocol modes (`disconnected → connecting → connected → reconnecting`). +- Resource status that drives behavior elsewhere. +- Workflows where transitions have guards, effects, or both. + +## When NOT to use + +- "States" are actually actions (`submit`, `approve`, `notify`) → **chain**. +- Process steps that don't persist anywhere → **chain** or **lanes**. +- Multiple independent state dimensions → **multiple machines**, not one flattened machine. +- Guards dominate every transition → **matrix** decision table referenced from the machine. + +## Canonical form + +Two shapes are canonical; pick by transition complexity. + +### Arrow form (≲8 transitions, few guards) + +``` +draft -[submit]-> pending +pending -[approve]-> active +pending -[reject]-> draft +active -[archive]-> archived +active -[expire after 90d]-> expired +expired -[renew (if tier=paid)]-> active +``` + +`-[label]->` for normal transitions; `x[label]->` for failure/rejection transitions (parallels the **graph** edge syntax). + +### Table form (guards or effects multiply) + +``` +states: draft, pending, active, archived, expired + +transitions: + +from | event | to | guard | effect +---------|----------|-----------|----------------|------------------ +draft | submit | pending | | notify reviewer +pending | approve | active | hasReviewer | issue token +pending | reject | draft | | clear submission +active | archive | archived | | +active | expire | expired | age ≥ 90d | revoke token +expired | renew | active | tier = paid | reissue token +``` + +Either form is canonical; the table form scales better past ~8 transitions. + +## Variants + +### States block + transitions block (structure first) + +Declare the state set explicitly when the machine has more than ~5 states or composite states. Then transitions reference them: + +``` +states: + draft + pending + active + archived + expired + +transitions: + draft -[submit]-> pending + ... +``` + +Avoids surprise states that exist only as a typo in the transitions block. + +### Composite / nested states + +Don't flatten a hierarchy that has meaning. Declare nesting: + +``` +states: + draft + pending + active/ + normal + suspended + archived + +transitions: + active.normal -[suspend]-> active.suspended + active.suspended -[resume]-> active.normal + active -[archive]-> archived # from any sub-state +``` + +Transitions on the parent (`active`) apply from any sub-state. + +### Entry / exit / invariant hooks + +When effects always run on entering or leaving a state, hoist them out of every transition: + +``` +state: active + on-enter: issueToken + on-exit: revokeToken + invariant: hasReviewer +``` + +Then individual transitions don't need to repeat `issue token` / `revoke token`. + +### Wildcard / default transitions + +Use sparingly — `*` matches any source state: + +``` +* -[timeout]-> expired # any state can expire on timeout +* -[purge]-> archived # admin override from anywhere +``` + +### Orthogonal dimensions (multiple machines) + +When state has independent dimensions, do **not** flatten them. Define separate machines: + +``` +## connection machine +disconnected -[connect]-> connecting +connecting -[ok]-> connected +connecting x[fail]-> disconnected +connected -[drop]-> reconnecting +reconnecting -[ok]-> connected + +## auth machine +unauthenticated -[login]-> authenticated +authenticated -[logout]-> unauthenticated +authenticated -[expire]-> unauthenticated + +## sync machine +clean -[edit]-> dirty +dirty -[push]-> syncing +syncing -[ok]-> clean +syncing x[fail]-> dirty +``` + +Cross-machine guards reference other machines by name: `guard: connection.connected`. + +## Annotation patterns + +- **`#id` on states or transitions** for cross-reference. Transition IDs (`#T7`) let a matrix or chain say "see transition #T7." +- **`[tag]` column** on transition rows (table form) for metadata. +- **Diff markers `+` / `-` / `~`** as line prefix on transition lines. +- **`?` for uncertain transitions, `!` for risky/hotspot transitions.** +- **`@owner`** when ownership of a state's invariants varies. +- **`notes:` / `open:` footer** keyed by anchors. + +``` +transitions: + pending -[approve]-> active #T7 ! requires audit log + active -[expire]-> expired #T8 + +notes: + - #T7: who issues the audit entry — the API or the worker? +``` + +## Smell-to-switch tripwires + +- **"Where does this state live?"** — can't answer → it's a **chain** or **lanes**, not a state-machine. Good test. +- **State names are verbs / actions** (`submit`, `approve`, `notify`) → **chain**. +- **Orthogonal dimensions multiply states** into combos like `review_pending_billing_active_sync_dirty` → split into multiple machines. +- **Every transition has a long guard expression** → the guards belong in a **matrix** decision table; the machine references it. +- **Transitions are really "calls"** to other components → **chain** or **graph**. +- **You can't list the state set explicitly** without scanning all transitions → declare states first. + +## Anti-patterns + +- **Flattening orthogonal dimensions** into one Cartesian-product state set. Always wrong. +- **Modeling a workflow as states when the system doesn't store them.** The reader will look for the state column in the DB and find nothing. +- **Mixing event names and condition expressions** in the event column. Events name *what happened*; conditions go in `guard`. +- **Forgetting effects.** State changed AND something else happened (token issued, email sent). Capture both; effects are first-class. +- **Wildcard `*` everywhere.** If most transitions are wildcards, the structure isn't pulling its weight. + +## Escape hatches + +- **Multiple state machines** for orthogonal dimensions — almost always. +- **Matrix decision table** for guard-heavy transitions; the machine row references rule IDs. +- **Lanes** when the "states" are really protocol modes between two actors. +- **Chain** when the "states" are really sequential steps. + +## Worked example: subscription lifecycle + +``` +states: + trial + active + past_due + canceled + expired + +transitions: + +from | event | to | guard | effect +---------|-----------------|-----------|-----------------|--------------------- +trial | upgrade | active | payment ok | provision; bill +trial | expire | expired | age ≥ 14d | suspend access +active | payment_fail | past_due | | notify; retry sched +past_due | payment_ok | active | | clear flag +past_due | exhaust_retries | canceled | retries ≥ 3 | revoke access +active | cancel_user | canceled | | end of period +canceled | resubscribe | active | within 30d | reactivate; bill +canceled | purge | expired | age ≥ 30d | delete data + +notes: + - guard `within 30d`: measured from canceled-at timestamp on the row. + - `expired` is terminal in this machine; renewal goes through a fresh trial. +``` + +## Worked example: orthogonal dimensions + +``` +## sync machine (per document) + +states: clean, dirty, syncing, conflict + +clean -[local-edit]-> dirty +dirty -[push]-> syncing +syncing -[ok]-> clean +syncing x[remote-change]-> conflict +conflict -[resolve]-> dirty + +## presence machine (per user) + +states: away, active, focused + +away -[input]-> active +active -[focus-app]-> focused +focused -[blur]-> active +active -[idle 5m]-> away +focused -[idle 5m]-> away + +# These two machines are independent. Don't flatten into +# { clean+away, clean+active, ..., conflict+focused } — 12 combos no one wants. +``` diff --git a/.agents/skills/pseudo/references/tree.md b/.agents/skills/pseudo/references/tree.md new file mode 100644 index 00000000..8ca6426f --- /dev/null +++ b/.agents/skills/pseudo/references/tree.md @@ -0,0 +1,226 @@ +# Pseudo: Tree + +Captures **containment, hierarchy, decomposition** — parent/child relations where each child belongs to exactly one parent. Files in folders, components in components, sections in documents, decompositions of a problem space. + +## When to use + +- Pure containment: child cannot exist outside its parent. +- Decomposition: breaking one thing into ordered or unordered parts. +- Outline of a document, codebase, UI, decision space. +- **Obligation decomposition** — breaking a paragraph-length contract or acceptance criterion into categories with individually testable leaves. +- Before/after of any of the above (the most common pairing). + +## When NOT to use + +- Same conceptual child appears under two parents → **graph**. +- Sibling order or sibling interaction is the point → ordered-tree variant if order only; **chain** / **lanes** / **graph** if interaction. +- Parent/child looks like flow, not containment → **chain** or **graph**. + +## Canonical form + +ASCII box-drawing (or Unicode equivalent — pick one per artifact). Indentation is structural; box characters are visual aids that survive copy/paste. + +``` +auth/ +├── login.ts +├── session.ts +└── providers/ + ├── oauth.ts + └── credentials.ts +``` + +Rule of thumb: if you can answer *"can this node exist without its parent?"* with **yes**, you have the wrong family — it's probably a graph. + +## Variants + +### Annotated tree (metadata column) + +``` +auth/ +├── login.ts [entry; rate-limited] @auth-team +├── session.ts [token mint + verify] +└── providers/ + ├── oauth.ts [external; cached 5m] @auth-team + └── credentials.ts [bcrypt; pepper from env] @auth-team +``` + +Column alignment is reader-friendly but not load-bearing. + +### Delta tree (inline diff) + +`+`/`-`/`~` line markers when before/after is small enough to fit in one block — often cheaper than two stacked blocks. + +``` +auth/ + ├── login.ts [split handler + service] ~ + ├── session.ts + └── providers/ + ├── oauth.ts + ├── credentials.ts + └── magic-link.ts + +risk.ts + +legacy-auth.ts - +``` + +### Ordered tree (sibling order matters) + +Number the children explicitly: + +``` +pipeline/ +├── 1_parse +├── 2_normalize +├── 3_validate +└── 4_emit +``` + +### Cross-ref tree (mostly hierarchy, a few non-tree edges) + +When 80% of relations are containment and 20% are references, use `->#anchor` on the lines that need it. Beats switching to graph for one or two edges. + +``` +auth/ #auth +├── login.ts +└── session.ts -> #token-store + +cache/ #token-store +└── redis.ts +``` + +### Cardinality / multiplicity + +``` +providers/ [1..n] +sessions/ [0..*] per user +``` + +### Focused tree with elision + +Show the touched area plus parent context, not the whole tree. + +``` +auth/ +└── providers/ + ├── oauth.ts + └── ... (3 omitted) +``` + +## Annotation patterns + +- **`[tag]` column** for compact metadata, whitespace-aligned. +- **`#id` anchors** on nodes for cross-references from other artifacts. +- **`@owner`** for ownership when it varies across the tree. +- **`notes:` / `open:` footer** keyed by anchors for discussion that doesn't belong inline. +- **Diff markers `+` / `-` / `~`** as line prefixes or trailing markers. +- **`?` line-marker** for uncertain branches, **`!`** for risky/hotspot nodes. + +``` +notes: + - #token-store: Redis or in-memory for tests? + - #auth: all rate limits enforced at this boundary +``` + +## Smell-to-switch tripwires + +- **The same conceptual child appears under two parents.** Tree is lying. → **graph**. +- **Sibling A depends on sibling B.** → **graph** for the dependency, or **chain** if linear. +- **Order or timing between siblings matters beyond "list order."** → ordered tree if order only; **chain** / **state-machine** if timing. +- **Parent/child relation reads as "calls" or "becomes" rather than "contains."** → wrong family entirely. +- **You start drawing extra connectors between nodes at the same level.** → **graph**. + +## Anti-patterns + +- **Tree-shaped diagram of a flow.** Tree looks clean even when lying. Reach for chain or graph instead. +- **Mixing containment levels** (file → function → variable in one tree). Pick one level per tree; link levels via anchors. +- **Inventing extra connectors** between sibling or distant nodes. That's the smell; switch family. +- **One mega-tree.** Split by subsystem; link via `#id`. +- **Comments doing what the structure should** (the same "see X" note attached to many nodes). Use a `_rules:`-style footer or split the tree. + +## Escape hatches + +- **Tree + cross-refs** for 1-2 non-tree edges. +- **Multiple small trees linked by `#id`** beats one large tree almost always. +- **Graph** when containment isn't the primary relation. +- **Stacked before/after blocks** when delta-tree gets unreadable (more than ~5 markers). + +## Worked example: current vs desired UX flow + +The pattern your UX Flow Plan codifies — two trees under `## Current` and `## Desired`, with anchors attached *after* the structure is settled. + +``` +## Current + +User action: click "Forgot password" +└── System behavior: redirect to /reset + └── Existing layer: pages/reset.tsx + └── Anchor: components/ResetForm.tsx +``` + +``` +## Desired + +User action: click "Forgot password" +└── System behavior: modal opens in place + ├── New layer: useResetFlow hook + │ └── Anchor: hooks/useResetFlow.ts + └── Reused layer: components/Modal.tsx + └── Anchor: components/ResetForm.tsx (extracted as ResetModalContent) +``` + +## Worked example: obligation decomposition + +A high-value use of `tree` in this codebase is breaking a paragraph-length acceptance criterion or contract obligation into a scannable hierarchy. The tree captures *categories of what must hold*, with each leaf an individually testable obligation. + +Source: a single sentence with ~12 semicolon-separated acceptance clauses for a milestone integration. + +``` +agent-graph-integration acceptance: +├── command-routing +│ ├── agent CRUD → CommandExecutor +│ ├── elicitor capture → CommandExecutor +│ ├── reviewer writes → CommandExecutor (target: #reconciliation_need only) +│ └── acceptReviewSet: batch atomic (one LSN, one change-log entry) +├── exchange entries (custom) +│ ├── brunch.establishment_offer [must carry: lens] +│ └── brunch.elicitor_intent_hint [must carry: lens] +├── capture rules +│ ├── high-confidence extractive facts → commit +│ ├── readiness/posture updates → commit +│ └── low-confidence implications → stay in preface +├── proposal rules +│ ├── carry support/grounding coverage +│ ├── carry epistemic_status +│ └── only dry-run-valid → reviewable review-set +├── reviewer policy +│ ├── advisory only (writes only #reconciliation_need) +│ └── initial POC trigger/scope recorded in docs/tests (not implicit) +├── architectural invariants +│ ├── no direct DB access +│ ├── no caller-side authority bypass outside command layer +│ └── reviewer write-target boundary enforced +├── cross-surface +│ └── same change observed across TUI and web +└── async substrate (conditional) + └── if observer/auditor queues land → backstops only, not primary capture +``` + +Each leaf is small enough to become one test or one assertion. The categories let a reviewer scan for missing dimensions (e.g. "did we cover the cross-surface obligation?") rather than re-parsing a sentence. + +## Worked example: delta tree for a small refactor + +``` +auth/ + ├── login.ts ~ # split into handler + service + │ ├── handler.ts + + │ └── service.ts + + ├── session.ts + ├── providers/ + │ ├── oauth.ts + │ └── credentials.ts + └── magic-link.ts + + +legacy-auth.ts - + +open: + - confirm session.ts stays a single module after split +``` diff --git a/.brunch-fixtures/README.md b/.brunch-fixtures/README.md deleted file mode 100644 index bd8dfe73..00000000 --- a/.brunch-fixtures/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# `.brunch-fixtures/` - -Curated test inputs and captured golden runs for the Brunch POC. - -This directory is the on-disk home of the fixture strategy described in -[docs/architecture/fixture-strategy.md](../docs/architecture/fixture-strategy.md). - -## Layout - -``` -.brunch-fixtures/ -├── briefs/ # Curated product briefs (JSON) -│ ├── brief-001-identity-reference.json -│ ├── brief-002-state-lifecycle.json -│ ├── brief-003-derived-views.json -│ └── ... -└── / - └── / - ├── .jsonl # Captured transcript - ├── .meta.json # Brief id, driver mode, session, projection summary - ├── .graph.json # Deferred until the graph plane exists - └── .coherence.json # Deferred until coherence is first-class -``` - -## Status - -The first M1 briefs live under `briefs/` as JSON files. Captured runs are added -under each brief id by the JSON-RPC stdio fixture driver. - -## Conventions - -- Briefs are short, human-readable JSON; the captured runs are the heavy data. -- Brief ids are kebab-case and stable; runs are timestamped, content-hashed, or - deterministic for reviewable scripted captures. -- Replay regression runs check transcript reproduction first. Property and - adversarial / generative checks come online as later milestones provide graph - and coherence artifacts. diff --git a/.brunch-fixtures/brief-001/scripted-001/scripted-001.jsonl b/.brunch-fixtures/brief-001/scripted-001/scripted-001.jsonl deleted file mode 100644 index f12ac2fb..00000000 --- a/.brunch-fixtures/brief-001/scripted-001/scripted-001.jsonl +++ /dev/null @@ -1,4 +0,0 @@ -{"type":"session","version":3,"id":"019e4a13-1a50-7eb3-a4f7-644d4bff42bd","timestamp":"2026-05-21T10:27:06.448Z","cwd":"/Users/lunelson/Code/hashintel/brunch-next"} -{"type":"custom","customType":"brunch.session_binding","data":{"schemaVersion":1,"sessionId":"019e4a13-1a50-7eb3-a4f7-644d4bff42bd","specId":"spec-a3e8371c-af78-45ee-8b1a-7752317caa35","specTitle":"Team knowledge cards"},"id":"fc95cd0b","parentId":null,"timestamp":"2026-05-21T10:27:06.449Z"} -{"type":"custom_message","customType":"brunch.elicitation_prompt","content":"Elicitation prompt for brief-001 — Team knowledge cards: A small team wants a shared workspace for knowledge cards. Each card has a stable identity, a human-readable title, and may link to other cards even when titles change.","display":true,"id":"101e1d31","parentId":"fc95cd0b","timestamp":"2026-05-21T10:27:06.451Z"} -{"type":"message","id":"71549d11","parentId":"101e1d31","timestamp":"2026-05-21T10:27:06.451Z","message":{"role":"user","content":"I care about renaming cards without breaking links.\nTwo cards can have similar titles, so titles cannot be the only reference.","timestamp":1779321600000}} diff --git a/.brunch-fixtures/brief-001/scripted-001/scripted-001.meta.json b/.brunch-fixtures/brief-001/scripted-001/scripted-001.meta.json deleted file mode 100644 index f32de479..00000000 --- a/.brunch-fixtures/brief-001/scripted-001/scripted-001.meta.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "schemaVersion": 1, - "briefId": "brief-001", - "runId": "scripted-001", - "timestamp": "2026-05-21T00:00:00.000Z", - "brunchVersion": "0.0.0", - "session": { - "id": "019e4a13-1a50-7eb3-a4f7-644d4bff42bd", - "sourceFile": "/Users/lunelson/Code/hashintel/brunch-next/.brunch/sessions/2026-05-21T10-27-06-448Z_019e4a13-1a50-7eb3-a4f7-644d4bff42bd.jsonl" - }, - "driver": { - "mode": "scripted-deterministic" - }, - "projectionSummary": { - "status": "ready", - "exchangeCount": 1, - "openPrompt": false - }, - "artifacts": { - "jsonl": "scripted-001.jsonl", - "graph": { - "status": "deferred" - }, - "coherence": { - "status": "deferred" - } - } -} diff --git a/.brunch-fixtures/brief-002/scripted-001/scripted-001.jsonl b/.brunch-fixtures/brief-002/scripted-001/scripted-001.jsonl deleted file mode 100644 index a8831a6e..00000000 --- a/.brunch-fixtures/brief-002/scripted-001/scripted-001.jsonl +++ /dev/null @@ -1,4 +0,0 @@ -{"type":"session","version":3,"id":"019e4a13-1a57-77cd-8c77-43922404adca","timestamp":"2026-05-21T10:27:06.455Z","cwd":"/Users/lunelson/Code/hashintel/brunch-next"} -{"type":"custom","customType":"brunch.session_binding","data":{"schemaVersion":1,"sessionId":"019e4a13-1a57-77cd-8c77-43922404adca","specId":"spec-b37cabd4-12f5-4cea-bcbf-90a170e6f058","specTitle":"Approval workflow for vendor invoices"},"id":"99670cde","parentId":null,"timestamp":"2026-05-21T10:27:06.455Z"} -{"type":"custom_message","customType":"brunch.elicitation_prompt","content":"Elicitation prompt for brief-002 — Approval workflow for vendor invoices: A finance team needs invoices to move from draft to submitted to approved or rejected. Only budget owners can approve, and rejected invoices can be revised and resubmitted.","display":true,"id":"99dc94b3","parentId":"99670cde","timestamp":"2026-05-21T10:27:06.455Z"} -{"type":"message","id":"e0baa29d","parentId":"99dc94b3","timestamp":"2026-05-21T10:27:06.455Z","message":{"role":"user","content":"Rejected invoices are not terminal; they can go back to draft.\nApproved invoices should not be edited without reopening the workflow.","timestamp":1779321600000}} diff --git a/.brunch-fixtures/brief-002/scripted-001/scripted-001.meta.json b/.brunch-fixtures/brief-002/scripted-001/scripted-001.meta.json deleted file mode 100644 index dc67b14a..00000000 --- a/.brunch-fixtures/brief-002/scripted-001/scripted-001.meta.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "schemaVersion": 1, - "briefId": "brief-002", - "runId": "scripted-001", - "timestamp": "2026-05-21T00:00:00.000Z", - "brunchVersion": "0.0.0", - "session": { - "id": "019e4a13-1a57-77cd-8c77-43922404adca", - "sourceFile": "/Users/lunelson/Code/hashintel/brunch-next/.brunch/sessions/2026-05-21T10-27-06-455Z_019e4a13-1a57-77cd-8c77-43922404adca.jsonl" - }, - "driver": { - "mode": "scripted-deterministic" - }, - "projectionSummary": { - "status": "ready", - "exchangeCount": 1, - "openPrompt": false - }, - "artifacts": { - "jsonl": "scripted-001.jsonl", - "graph": { - "status": "deferred" - }, - "coherence": { - "status": "deferred" - } - } -} diff --git a/.brunch-fixtures/brief-003/scripted-001/scripted-001.jsonl b/.brunch-fixtures/brief-003/scripted-001/scripted-001.jsonl deleted file mode 100644 index 76e4a2d5..00000000 --- a/.brunch-fixtures/brief-003/scripted-001/scripted-001.jsonl +++ /dev/null @@ -1,4 +0,0 @@ -{"type":"session","version":3,"id":"019e4a13-1a59-712e-82ff-78900f655901","timestamp":"2026-05-21T10:27:06.457Z","cwd":"/Users/lunelson/Code/hashintel/brunch-next"} -{"type":"custom","customType":"brunch.session_binding","data":{"schemaVersion":1,"sessionId":"019e4a13-1a59-712e-82ff-78900f655901","specId":"spec-b804de5f-762e-4d27-a538-8b8d38139bec","specTitle":"Project dashboard rollups"},"id":"74df4b55","parentId":null,"timestamp":"2026-05-21T10:27:06.457Z"} -{"type":"custom_message","customType":"brunch.elicitation_prompt","content":"Elicitation prompt for brief-003 — Project dashboard rollups: A product lead wants a dashboard that rolls task status, blockers, and recent decisions up from individual project notes into one current view.","display":true,"id":"72fa4dfe","parentId":"74df4b55","timestamp":"2026-05-21T10:27:06.457Z"} -{"type":"message","id":"d47729ba","parentId":"72fa4dfe","timestamp":"2026-05-21T10:27:06.457Z","message":{"role":"user","content":"If the source note changes, the dashboard should not silently stay stale.\nRecent decisions should show where they came from.","timestamp":1779321600000}} diff --git a/.brunch-fixtures/brief-003/scripted-001/scripted-001.meta.json b/.brunch-fixtures/brief-003/scripted-001/scripted-001.meta.json deleted file mode 100644 index df02c92b..00000000 --- a/.brunch-fixtures/brief-003/scripted-001/scripted-001.meta.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "schemaVersion": 1, - "briefId": "brief-003", - "runId": "scripted-001", - "timestamp": "2026-05-21T00:00:00.000Z", - "brunchVersion": "0.0.0", - "session": { - "id": "019e4a13-1a59-712e-82ff-78900f655901", - "sourceFile": "/Users/lunelson/Code/hashintel/brunch-next/.brunch/sessions/2026-05-21T10-27-06-457Z_019e4a13-1a59-712e-82ff-78900f655901.jsonl" - }, - "driver": { - "mode": "scripted-deterministic" - }, - "projectionSummary": { - "status": "ready", - "exchangeCount": 1, - "openPrompt": false - }, - "artifacts": { - "jsonl": "scripted-001.jsonl", - "graph": { - "status": "deferred" - }, - "coherence": { - "status": "deferred" - } - } -} diff --git a/.brunch-fixtures/briefs/brief-001-identity-reference.json b/.brunch-fixtures/briefs/brief-001-identity-reference.json deleted file mode 100644 index a7383271..00000000 --- a/.brunch-fixtures/briefs/brief-001-identity-reference.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "schemaVersion": 1, - "id": "brief-001", - "title": "Team knowledge cards", - "kernelTags": ["identity-reference", "containment-topology"], - "productBrief": "A small team wants a shared workspace for knowledge cards. Each card has a stable identity, a human-readable title, and may link to other cards even when titles change.", - "expectedStructuralObservations": [ - "Cards need stable IDs separate from mutable titles.", - "Links should target card identity rather than display text." - ], - "scriptedUserNotes": [ - "I care about renaming cards without breaking links.", - "Two cards can have similar titles, so titles cannot be the only reference." - ], - "deferredExpectations": { - "graph": "Later graph fixtures should produce identity/reference nodes and link invariants.", - "coherence": "Later coherence checks should flag title-anchored references as weak evidence." - } -} diff --git a/.brunch-fixtures/briefs/brief-002-state-lifecycle.json b/.brunch-fixtures/briefs/brief-002-state-lifecycle.json deleted file mode 100644 index 9370e358..00000000 --- a/.brunch-fixtures/briefs/brief-002-state-lifecycle.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "schemaVersion": 1, - "id": "brief-002", - "title": "Approval workflow for vendor invoices", - "kernelTags": ["state-lifecycle", "authority-capability"], - "productBrief": "A finance team needs invoices to move from draft to submitted to approved or rejected. Only budget owners can approve, and rejected invoices can be revised and resubmitted.", - "expectedStructuralObservations": [ - "Invoice states and legal transitions must be explicit.", - "Approval authority depends on the budget owner role." - ], - "scriptedUserNotes": [ - "Rejected invoices are not terminal; they can go back to draft.", - "Approved invoices should not be edited without reopening the workflow." - ], - "deferredExpectations": { - "graph": "Later graph fixtures should capture lifecycle states, transitions, and authority predicates.", - "coherence": "Later coherence checks should flag contradictory terminality claims." - } -} diff --git a/.brunch-fixtures/briefs/brief-003-derived-views.json b/.brunch-fixtures/briefs/brief-003-derived-views.json deleted file mode 100644 index 1c6abe26..00000000 --- a/.brunch-fixtures/briefs/brief-003-derived-views.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "schemaVersion": 1, - "id": "brief-003", - "title": "Project dashboard rollups", - "kernelTags": ["derived-data-views", "temporal-history"], - "productBrief": "A product lead wants a dashboard that rolls task status, blockers, and recent decisions up from individual project notes into one current view.", - "expectedStructuralObservations": [ - "Dashboard values are derived from underlying notes and decisions.", - "The system needs evidence for when a rollup was last refreshed." - ], - "scriptedUserNotes": [ - "If the source note changes, the dashboard should not silently stay stale.", - "Recent decisions should show where they came from." - ], - "deferredExpectations": { - "graph": "Later graph fixtures should capture source-to-view derivation edges and evidence anchors.", - "coherence": "Later coherence checks should flag stale projections when source facts change." - } -} diff --git a/.fixtures/README.md b/.fixtures/README.md new file mode 100644 index 00000000..6bb26280 --- /dev/null +++ b/.fixtures/README.md @@ -0,0 +1,29 @@ +# `.fixtures/` + +Current probe artifacts and transcript evidence for the Brunch POC. + +The active convention is **probe first, transcript-backed**: each committed run +must have a probe id, a run id, executable/reportable oracle output, and the +transcript artifact needed for human review. Brief-based golden fixtures may +return later, but they should be generated through this probe/transcript path +rather than a separate brief-library subsystem. + +See [`docs/architecture/probes-and-transcripts.md`](../docs/architecture/probes-and-transcripts.md) +for the current architecture. + +## Layout + +``` +.fixtures/ +└── runs/ + └── / + └── / + ├── session.jsonl # Source transcript / canonical run evidence + ├── transcript.md # Human-readable semantic rendering + └── report.json # Probe report and artifact paths +``` + +## Current runs + +- `runs/public-rpc-parity/2026-05-29-public-rpc-parity/` — FE-744 public Brunch + JSON-RPC structured-exchange parity proof. diff --git a/.fixtures/runs/public-rpc-parity/2026-05-29-public-rpc-parity/report.json b/.fixtures/runs/public-rpc-parity/2026-05-29-public-rpc-parity/report.json new file mode 100644 index 00000000..c90566da --- /dev/null +++ b/.fixtures/runs/public-rpc-parity/2026-05-29-public-rpc-parity/report.json @@ -0,0 +1,33 @@ +{ + "schemaVersion": 1, + "probeId": "public-rpc-parity", + "runId": "2026-05-29-public-rpc-parity", + "generatedAt": "2026-05-29T13:30:38.654Z", + "mission": "Drive deterministic Brunch structured-exchange permutations through public JSON-RPC only.", + "evaluationFocus": "Tuple transcript/projection parity for current structured-exchange modes without raw Pi RPC or legacy prompt/response entries.", + "maxTurnBudget": 3, + "completedTurns": 3, + "friction": [], + "cwd": "/var/folders/2c/ptn6jcrj61lck_yzfz_p3b5m0000gn/T/brunch-public-rpc-parity-Y7G3Y6", + "specId": "spec-98433c35-3e61-4ab7-9c4f-72331e210aa2", + "sessionId": "019e73ee-02c2-7d43-90e5-7de4cd6ed486", + "toolCoverage": [ + "present_options", + "present_question", + "request_answer", + "request_choice", + "request_choices" + ], + "exchangeIds": [ + "deterministic-grounding-choice-1", + "deterministic-grounding-text-2", + "deterministic-grounding-multi-3" + ], + "transcriptDisplayRows": 6, + "artifacts": { + "runDir": ".fixtures/runs/public-rpc-parity/2026-05-29-public-rpc-parity", + "sessionJsonl": ".fixtures/runs/public-rpc-parity/2026-05-29-public-rpc-parity/session.jsonl", + "transcriptMarkdown": ".fixtures/runs/public-rpc-parity/2026-05-29-public-rpc-parity/transcript.md", + "reportJson": ".fixtures/runs/public-rpc-parity/2026-05-29-public-rpc-parity/report.json" + } +} diff --git a/.fixtures/runs/public-rpc-parity/2026-05-29-public-rpc-parity/session.jsonl b/.fixtures/runs/public-rpc-parity/2026-05-29-public-rpc-parity/session.jsonl new file mode 100644 index 00000000..20177cbe --- /dev/null +++ b/.fixtures/runs/public-rpc-parity/2026-05-29-public-rpc-parity/session.jsonl @@ -0,0 +1,8 @@ +{"type":"session","version":3,"id":"019e73ee-02c2-7d43-90e5-7de4cd6ed486","timestamp":"2026-05-29T13:30:38.658Z","cwd":"/var/folders/2c/ptn6jcrj61lck_yzfz_p3b5m0000gn/T/brunch-public-rpc-parity-Y7G3Y6"} +{"type":"custom","customType":"brunch.session_binding","data":{"schemaVersion":1,"sessionId":"019e73ee-02c2-7d43-90e5-7de4cd6ed486","specId":"spec-98433c35-3e61-4ab7-9c4f-72331e210aa2","specTitle":"Public RPC parity spec"},"id":"18ac5486","parentId":null,"timestamp":"2026-05-29T13:30:38.658Z"} +{"type":"message","id":"c942d24e","parentId":"18ac5486","timestamp":"2026-05-29T13:30:38.662Z","message":{"role":"toolResult","toolCallId":"deterministic-grounding-choice-1:present_options","toolName":"present_options","content":[{"type":"text","text":"## Is this a new product or feature from scratch?\n\nChoose the best starting context so later elicitation can ask useful follow-ups.\n\n### 1. Start a new spec workspace from a blank slate.\n\n**Rationale:** This keeps the parity run focused on initial grounding.\n\n\n\n### 2. Ground the spec in existing implementation constraints.\n\n**Rationale:** Existing code changes what the elicitor should inspect next.\n\n\n\n### 3. Connect this work to a prior specification thread.\n\n**Rationale:** Continuity matters when prior graph intent exists.\n\n"}],"details":{"schema":"brunch.structured_exchange.present","schemaVersion":1,"exchangeId":"deterministic-grounding-choice-1","presentTool":"present_options","kind":"options","status":"presented","expectedRequest":{"tool":"request_choice","required":true},"createdAtToolCallId":"deterministic-grounding-choice-1:present_options","prompt":"Is this a new product or feature from scratch?","details":"Choose the best starting context so later elicitation can ask useful follow-ups.","lens":"step-by-step","options":[{"id":"new-from-scratch","label":"Yes — this is new from scratch","content":"Start a new spec workspace from a blank slate.","rationale":"This keeps the parity run focused on initial grounding."},{"id":"existing-codebase","label":"No — this builds on existing code","content":"Ground the spec in existing implementation constraints.","rationale":"Existing code changes what the elicitor should inspect next."},{"id":"relates-to-existing-spec","label":"It relates to an existing spec","content":"Connect this work to a prior specification thread.","rationale":"Continuity matters when prior graph intent exists."}]},"isError":false,"timestamp":0}} +{"type":"message","id":"b71e722c","parentId":"c942d24e","timestamp":"2026-05-29T13:30:38.664Z","message":{"role":"toolResult","toolCallId":"deterministic-grounding-choice-1:request_choice","toolName":"request_choice","isError":false,"timestamp":0,"content":[{"type":"text","text":"### Response\n\n- Yes — this is new from scratch\n\nComment:\n\n> Chosen by deterministic public-RPC proof."}],"details":{"schema":"brunch.structured_exchange.request","schemaVersion":1,"exchangeId":"deterministic-grounding-choice-1","requestTool":"request_choice","status":"answered","respondsTo":{"exchangeId":"deterministic-grounding-choice-1","presentTool":"present_options"},"createdAtToolCallId":"deterministic-grounding-choice-1:request_choice","comment":"Chosen by deterministic public-RPC proof.","choice":{"id":"new-from-scratch","label":"Yes — this is new from scratch","content":"Start a new spec workspace from a blank slate.","rationale":"This keeps the parity run focused on initial grounding."}}}} +{"type":"message","id":"ddfb64a4","parentId":"b71e722c","timestamp":"2026-05-29T13:30:38.664Z","message":{"role":"toolResult","toolCallId":"deterministic-grounding-text-2:present_question","toolName":"present_question","content":[{"type":"text","text":"## What are we specifying?\n\nThis covers the text-answer permutation in Brunch's deterministic public-RPC structured-exchange parity proof."}],"details":{"schema":"brunch.structured_exchange.present","schemaVersion":1,"exchangeId":"deterministic-grounding-text-2","presentTool":"present_question","kind":"question","status":"presented","expectedRequest":{"tool":"request_answer","required":true},"createdAtToolCallId":"deterministic-grounding-text-2:present_question","prompt":"What are we specifying?","details":"This covers the text-answer permutation in Brunch's deterministic public-RPC structured-exchange parity proof.","lens":"step-by-step","options":[]},"isError":false,"timestamp":0}} +{"type":"message","id":"18b8c603","parentId":"ddfb64a4","timestamp":"2026-05-29T13:30:38.666Z","message":{"role":"toolResult","toolCallId":"deterministic-grounding-text-2:request_answer","toolName":"request_answer","isError":false,"timestamp":0,"content":[{"type":"text","text":"### Response\n\nAnswer for deterministic-grounding-text-2"}],"details":{"schema":"brunch.structured_exchange.request","schemaVersion":1,"exchangeId":"deterministic-grounding-text-2","requestTool":"request_answer","status":"answered","respondsTo":{"exchangeId":"deterministic-grounding-text-2","presentTool":"present_question"},"createdAtToolCallId":"deterministic-grounding-text-2:request_answer","answer":"Answer for deterministic-grounding-text-2"}}} +{"type":"message","id":"dbbbe5c9","parentId":"18b8c603","timestamp":"2026-05-29T13:30:38.667Z","message":{"role":"toolResult","toolCallId":"deterministic-grounding-multi-3:present_options","toolName":"present_options","content":[{"type":"text","text":"## Which proof qualities matter for this parity run?\n\nSelect all qualities the deterministic structured-exchange permutation proof should preserve.\n\n### 1. Pi JSONL keeps every present/request tuple recoverable.\n\n**Rationale:** The transcript is the durable source of truth.\n\n\n\n### 2. Brunch projections preserve semantic option artifacts.\n\n**Rationale:** Public clients depend on projected structured exchange data.\n\n\n\n### 3. Another proof quality should be captured in the note.\n\n**Rationale:** Other requires a comment so the transcript stays explicit.\n\n\n\n### 4. No additional proof qualities matter for this run.\n\n**Rationale:** None requires a comment to avoid silent dismissal.\n\n"}],"details":{"schema":"brunch.structured_exchange.present","schemaVersion":1,"exchangeId":"deterministic-grounding-multi-3","presentTool":"present_options","kind":"options","status":"presented","expectedRequest":{"tool":"request_choices","required":true},"createdAtToolCallId":"deterministic-grounding-multi-3:present_options","prompt":"Which proof qualities matter for this parity run?","details":"Select all qualities the deterministic structured-exchange permutation proof should preserve.","lens":"step-by-step","options":[{"id":"transcript","label":"Transcript fidelity","content":"Pi JSONL keeps every present/request tuple recoverable.","rationale":"The transcript is the durable source of truth."},{"id":"projection","label":"Projection fidelity","content":"Brunch projections preserve semantic option artifacts.","rationale":"Public clients depend on projected structured exchange data."},{"id":"other","label":"Other","content":"Another proof quality should be captured in the note.","rationale":"Other requires a comment so the transcript stays explicit."},{"id":"none","label":"None","content":"No additional proof qualities matter for this run.","rationale":"None requires a comment to avoid silent dismissal."}]},"isError":false,"timestamp":0}} +{"type":"message","id":"f9b545bc","parentId":"dbbbe5c9","timestamp":"2026-05-29T13:30:38.668Z","message":{"role":"toolResult","toolCallId":"deterministic-grounding-multi-3:request_choices","toolName":"request_choices","isError":false,"timestamp":0,"content":[{"type":"text","text":"### Response\n\n- Transcript fidelity\n- Other\n\nComment:\n\n> Other: keep a compact blocker/friction report."}],"details":{"schema":"brunch.structured_exchange.request","schemaVersion":1,"exchangeId":"deterministic-grounding-multi-3","requestTool":"request_choices","status":"answered","respondsTo":{"exchangeId":"deterministic-grounding-multi-3","presentTool":"present_options"},"createdAtToolCallId":"deterministic-grounding-multi-3:request_choices","comment":"Other: keep a compact blocker/friction report.","choices":[{"id":"transcript","label":"Transcript fidelity","content":"Pi JSONL keeps every present/request tuple recoverable.","rationale":"The transcript is the durable source of truth."},{"id":"other","label":"Other","content":"Another proof quality should be captured in the note.","rationale":"Other requires a comment so the transcript stays explicit."}]}}} diff --git a/.fixtures/runs/public-rpc-parity/2026-05-29-public-rpc-parity/transcript.md b/.fixtures/runs/public-rpc-parity/2026-05-29-public-rpc-parity/transcript.md new file mode 100644 index 00000000..b9a785c4 --- /dev/null +++ b/.fixtures/runs/public-rpc-parity/2026-05-29-public-rpc-parity/transcript.md @@ -0,0 +1,104 @@ +# Transcript — session.jsonl + +## Session + +- session: 019e73ee-02c2-7d43-90e5-7de4cd6ed486 +- cwd: /var/folders/2c/ptn6jcrj61lck_yzfz_p3b5m0000gn/T/brunch-public-rpc-parity-Y7G3Y6 + +## Session binding + +```json +{ + "schemaVersion": 1, + "sessionId": "019e73ee-02c2-7d43-90e5-7de4cd6ed486", + "specId": "spec-98433c35-3e61-4ab7-9c4f-72331e210aa2", + "specTitle": "Public RPC parity spec" +} +``` + +## Exchange deterministic-grounding-choice-1 — prompt (present_options → request_choice) + +## Is this a new product or feature from scratch? + +Choose the best starting context so later elicitation can ask useful follow-ups. + +### 1. Start a new spec workspace from a blank slate. + +**Rationale:** This keeps the parity run focused on initial grounding. + + + +### 2. Ground the spec in existing implementation constraints. + +**Rationale:** Existing code changes what the elicitor should inspect next. + + + +### 3. Connect this work to a prior specification thread. + +**Rationale:** Continuity matters when prior graph intent exists. + + + +## Exchange deterministic-grounding-choice-1 — response (request_choice, answered) + +### Response + +- Yes — this is new from scratch + +Comment: + +> Chosen by deterministic public-RPC proof. + +## Exchange deterministic-grounding-text-2 — prompt (present_question → request_answer) + +## What are we specifying? + +This covers the text-answer permutation in Brunch's deterministic public-RPC structured-exchange parity proof. + +## Exchange deterministic-grounding-text-2 — response (request_answer, answered) + +### Response + +Answer for deterministic-grounding-text-2 + +## Exchange deterministic-grounding-multi-3 — prompt (present_options → request_choices) + +## Which proof qualities matter for this parity run? + +Select all qualities the deterministic structured-exchange permutation proof should preserve. + +### 1. Pi JSONL keeps every present/request tuple recoverable. + +**Rationale:** The transcript is the durable source of truth. + + + +### 2. Brunch projections preserve semantic option artifacts. + +**Rationale:** Public clients depend on projected structured exchange data. + + + +### 3. Another proof quality should be captured in the note. + +**Rationale:** Other requires a comment so the transcript stays explicit. + + + +### 4. No additional proof qualities matter for this run. + +**Rationale:** None requires a comment to avoid silent dismissal. + + + +## Exchange deterministic-grounding-multi-3 — response (request_choices, answered) + +### Response + +- Transcript fidelity +- Other + +Comment: + +> Other: keep a compact blocker/friction report. diff --git a/.oxfmtrc.json b/.oxfmtrc.json index 2101b2ed..ac19e92d 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -3,5 +3,5 @@ "printWidth": 110, "singleQuote": true, "sortImports": true, - "ignorePatterns": ["*.md", "docs/**", "memory/**", "archive/**", ".brunch-fixtures/**", "dist/**"] + "ignorePatterns": ["*.md", "docs/**", "memory/**", "archive/**", ".fixtures/**", "dist/**"] } diff --git a/.oxlintrc.json b/.oxlintrc.json index aa1bf094..7c1b225e 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -7,5 +7,5 @@ "rules": { "typescript/no-deprecated": "error" }, - "ignorePatterns": ["dist", "archive", "docs", "memory", ".brunch-fixtures", "scripts"] + "ignorePatterns": ["dist", "archive", "docs", "memory", ".fixtures", "scripts"] } diff --git a/.pi/components/.gitkeep b/.pi/components/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/.pi/extensions/.gitkeep b/.pi/extensions/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/@types/oxfmt_configuration_schema.json b/@types/oxfmt_configuration_schema.json new file mode 100644 index 00000000..ee3ded8a --- /dev/null +++ b/@types/oxfmt_configuration_schema.json @@ -0,0 +1,648 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Oxfmtrc", + "description": "Configuration options for the Oxfmt.\n\nMost options are the same as Prettier's options, but not all of them.\nIn addition, some options are our own extensions.", + "type": "object", + "properties": { + "arrowParens": { + "description": "Include parentheses around a sole arrow function parameter.\n\n- Default: `\"always\"`", + "allOf": [ + { + "$ref": "#/definitions/ArrowParensConfig" + } + ], + "markdownDescription": "Include parentheses around a sole arrow function parameter.\n\n- Default: `\"always\"`" + }, + "bracketSameLine": { + "description": "Put the `>` of a multi-line HTML (HTML, JSX, Vue, Angular) element at the end of the last line,\ninstead of being alone on the next line (does not apply to self closing elements).\n\n- Default: `false`", + "type": "boolean", + "markdownDescription": "Put the `>` of a multi-line HTML (HTML, JSX, Vue, Angular) element at the end of the last line,\ninstead of being alone on the next line (does not apply to self closing elements).\n\n- Default: `false`" + }, + "bracketSpacing": { + "description": "Print spaces between brackets in object literals.\n\n- Default: `true`", + "type": "boolean", + "markdownDescription": "Print spaces between brackets in object literals.\n\n- Default: `true`" + }, + "embeddedLanguageFormatting": { + "description": "Control whether to format embedded parts (For example, CSS-in-JS, or JS-in-Vue, etc.) in the file.\n\nNOTE: XXX-in-JS support is incomplete.\n\n- Default: `\"auto\"`", + "allOf": [ + { + "$ref": "#/definitions/EmbeddedLanguageFormattingConfig" + } + ], + "markdownDescription": "Control whether to format embedded parts (For example, CSS-in-JS, or JS-in-Vue, etc.) in the file.\n\nNOTE: XXX-in-JS support is incomplete.\n\n- Default: `\"auto\"`" + }, + "endOfLine": { + "description": "Which end of line characters to apply.\n\nNOTE: `\"auto\"` is not supported.\n\n- Default: `\"lf\"`\n- Overrides `.editorconfig.end_of_line`", + "allOf": [ + { + "$ref": "#/definitions/EndOfLineConfig" + } + ], + "markdownDescription": "Which end of line characters to apply.\n\nNOTE: `\"auto\"` is not supported.\n\n- Default: `\"lf\"`\n- Overrides `.editorconfig.end_of_line`" + }, + "htmlWhitespaceSensitivity": { + "description": "Specify the global whitespace sensitivity for HTML, Vue, Angular, and Handlebars.\n\n- Default: `\"css\"`", + "allOf": [ + { + "$ref": "#/definitions/HtmlWhitespaceSensitivityConfig" + } + ], + "markdownDescription": "Specify the global whitespace sensitivity for HTML, Vue, Angular, and Handlebars.\n\n- Default: `\"css\"`" + }, + "ignorePatterns": { + "description": "Ignore files matching these glob patterns.\nPatterns are based on the location of the Oxfmt configuration file.\n\n- Default: `[]`", + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "Ignore files matching these glob patterns.\nPatterns are based on the location of the Oxfmt configuration file.\n\n- Default: `[]`" + }, + "insertFinalNewline": { + "description": "Whether to insert a final newline at the end of the file.\n\n- Default: `true`\n- Overrides `.editorconfig.insert_final_newline`", + "type": "boolean", + "markdownDescription": "Whether to insert a final newline at the end of the file.\n\n- Default: `true`\n- Overrides `.editorconfig.insert_final_newline`" + }, + "jsxSingleQuote": { + "description": "Use single quotes instead of double quotes in JSX.\n\n- Default: `false`", + "type": "boolean", + "markdownDescription": "Use single quotes instead of double quotes in JSX.\n\n- Default: `false`" + }, + "objectWrap": { + "description": "How to wrap object literals when they could fit on one line or span multiple lines.\n\nBy default, formats objects as multi-line if there is a newline prior to the first property.\nAuthors can use this heuristic to contextually improve readability, though it has some downsides.\n\n- Default: `\"preserve\"`", + "allOf": [ + { + "$ref": "#/definitions/ObjectWrapConfig" + } + ], + "markdownDescription": "How to wrap object literals when they could fit on one line or span multiple lines.\n\nBy default, formats objects as multi-line if there is a newline prior to the first property.\nAuthors can use this heuristic to contextually improve readability, though it has some downsides.\n\n- Default: `\"preserve\"`" + }, + "overrides": { + "description": "File-specific overrides.\nWhen a file matches multiple overrides, the later override takes precedence (array order matters).\n\n- Default: `[]`", + "type": "array", + "items": { + "$ref": "#/definitions/OxfmtOverrideConfig" + }, + "markdownDescription": "File-specific overrides.\nWhen a file matches multiple overrides, the later override takes precedence (array order matters).\n\n- Default: `[]`" + }, + "printWidth": { + "description": "Specify the line length that the printer will wrap on.\n\nIf you don't want line wrapping when formatting Markdown, you can set the `proseWrap` option to disable it.\n\n- Default: `100`\n- Overrides `.editorconfig.max_line_length`", + "type": "integer", + "format": "uint16", + "minimum": 0.0, + "markdownDescription": "Specify the line length that the printer will wrap on.\n\nIf you don't want line wrapping when formatting Markdown, you can set the `proseWrap` option to disable it.\n\n- Default: `100`\n- Overrides `.editorconfig.max_line_length`" + }, + "proseWrap": { + "description": "How to wrap prose.\n\nBy default, formatter will not change wrapping in markdown text since some services use a linebreak-sensitive renderer, e.g. GitHub comments and BitBucket.\nTo wrap prose to the print width, change this option to \"always\".\nIf you want to force all prose blocks to be on a single line and rely on editor/viewer soft wrapping instead, you can use \"never\".\n\n- Default: `\"preserve\"`", + "allOf": [ + { + "$ref": "#/definitions/ProseWrapConfig" + } + ], + "markdownDescription": "How to wrap prose.\n\nBy default, formatter will not change wrapping in markdown text since some services use a linebreak-sensitive renderer, e.g. GitHub comments and BitBucket.\nTo wrap prose to the print width, change this option to \"always\".\nIf you want to force all prose blocks to be on a single line and rely on editor/viewer soft wrapping instead, you can use \"never\".\n\n- Default: `\"preserve\"`" + }, + "quoteProps": { + "description": "Change when properties in objects are quoted.\n\n- Default: `\"as-needed\"`", + "allOf": [ + { + "$ref": "#/definitions/QuotePropsConfig" + } + ], + "markdownDescription": "Change when properties in objects are quoted.\n\n- Default: `\"as-needed\"`" + }, + "semi": { + "description": "Print semicolons at the ends of statements.\n\n- Default: `true`", + "type": "boolean", + "markdownDescription": "Print semicolons at the ends of statements.\n\n- Default: `true`" + }, + "singleAttributePerLine": { + "description": "Enforce single attribute per line in HTML, Vue, and JSX.\n\n- Default: `false`", + "type": "boolean", + "markdownDescription": "Enforce single attribute per line in HTML, Vue, and JSX.\n\n- Default: `false`" + }, + "singleQuote": { + "description": "Use single quotes instead of double quotes.\n\nFor JSX, you can set the `jsxSingleQuote` option.\n\n- Default: `false`", + "type": "boolean", + "markdownDescription": "Use single quotes instead of double quotes.\n\nFor JSX, you can set the `jsxSingleQuote` option.\n\n- Default: `false`" + }, + "sortImports": { + "description": "Sort import statements.\n\nUsing the similar algorithm as [eslint-plugin-perfectionist/sort-imports](https://perfectionist.dev/rules/sort-imports).\nFor details, see each field's documentation.\n\n- Default: Disabled", + "allOf": [ + { + "$ref": "#/definitions/SortImportsConfig" + } + ], + "markdownDescription": "Sort import statements.\n\nUsing the similar algorithm as [eslint-plugin-perfectionist/sort-imports](https://perfectionist.dev/rules/sort-imports).\nFor details, see each field's documentation.\n\n- Default: Disabled" + }, + "sortPackageJson": { + "description": "Sort `package.json` keys.\n\nThe algorithm is NOT compatible with [prettier-plugin-sort-packagejson](https://github.com/matzkoh/prettier-plugin-packagejson).\nBut we believe it is clearer and easier to navigate.\nFor details, see each field's documentation.\n\n- Default: `true`", + "allOf": [ + { + "$ref": "#/definitions/SortPackageJsonUserConfig" + } + ], + "markdownDescription": "Sort `package.json` keys.\n\nThe algorithm is NOT compatible with [prettier-plugin-sort-packagejson](https://github.com/matzkoh/prettier-plugin-packagejson).\nBut we believe it is clearer and easier to navigate.\nFor details, see each field's documentation.\n\n- Default: `true`" + }, + "sortTailwindcss": { + "description": "Sort Tailwind CSS classes.\n\nUsing the same algorithm as [prettier-plugin-tailwindcss](https://github.com/tailwindlabs/prettier-plugin-tailwindcss).\nOption names omit the `tailwind` prefix used in the original plugin (e.g., `config` instead of `tailwindConfig`).\nFor details, see each field's documentation.\n\n- Default: Disabled", + "allOf": [ + { + "$ref": "#/definitions/SortTailwindcssConfig" + } + ], + "markdownDescription": "Sort Tailwind CSS classes.\n\nUsing the same algorithm as [prettier-plugin-tailwindcss](https://github.com/tailwindlabs/prettier-plugin-tailwindcss).\nOption names omit the `tailwind` prefix used in the original plugin (e.g., `config` instead of `tailwindConfig`).\nFor details, see each field's documentation.\n\n- Default: Disabled" + }, + "tabWidth": { + "description": "Specify the number of spaces per indentation-level.\n\n- Default: `2`\n- Overrides `.editorconfig.indent_size`", + "type": "integer", + "format": "uint8", + "minimum": 0.0, + "markdownDescription": "Specify the number of spaces per indentation-level.\n\n- Default: `2`\n- Overrides `.editorconfig.indent_size`" + }, + "trailingComma": { + "description": "Print trailing commas wherever possible in multi-line comma-separated syntactic structures.\n\nA single-line array, for example, never gets trailing commas.\n\n- Default: `\"all\"`", + "allOf": [ + { + "$ref": "#/definitions/TrailingCommaConfig" + } + ], + "markdownDescription": "Print trailing commas wherever possible in multi-line comma-separated syntactic structures.\n\nA single-line array, for example, never gets trailing commas.\n\n- Default: `\"all\"`" + }, + "useTabs": { + "description": "Indent lines with tabs instead of spaces.\n\n- Default: `false`\n- Overrides `.editorconfig.indent_style`", + "type": "boolean", + "markdownDescription": "Indent lines with tabs instead of spaces.\n\n- Default: `false`\n- Overrides `.editorconfig.indent_style`" + }, + "vueIndentScriptAndStyle": { + "description": "Whether or not to indent the code inside `