- position?: { x: number; y: number }
- classes?: string
-}
-
-const pickerEl = requireElement("scenario-picker")
-const summaryEl = requireElement("scenario-summary")
-const blockedEl = requireElement("blocked-list")
-const selectionEl = requireElement("selection-detail")
-const completionEl = requireElement("completion-detail")
-
-let selectedScenario = scenarios[0]
-
-const cy = cytoscape({
- container: requireElement("cy"),
- elements: [],
- layout: { name: "preset" },
- minZoom: 0.45,
- maxZoom: 1.8,
- wheelSensitivity: 0.2,
- style: [
- {
- selector: "node",
- style: {
- label: "data(label)",
- "font-family": "Inter, ui-sans-serif, system-ui, sans-serif",
- "font-size": 11,
- "text-wrap": "wrap",
- "text-max-width": 104,
- "text-valign": "center",
- "text-halign": "center",
- color: "#000000",
- width: 112,
- height: 52,
- "border-width": 2,
- "border-color": "#c7d2fe",
- "background-color": "#f8fafc",
- },
- },
- {
- selector: ":parent",
- style: {
- label: "data(label)",
- shape: "round-rectangle",
- "text-valign": "top",
- "text-halign": "left",
- "text-margin-x": 12,
- "text-margin-y": 8,
- "font-size": 13,
- "font-weight": 800,
- color: "#000000",
- padding: 28,
- "background-opacity": 0.12,
- "border-width": 2,
- "border-style": "dashed",
- },
- },
- {
- selector: "node.place",
- style: {
- shape: "ellipse",
- width: 86,
- height: 86,
- "text-max-width": 72,
- },
- },
- {
- selector: "node.transition",
- style: {
- shape: "rectangle",
- width: 86,
- height: 46,
- "background-color": "#111827",
- color: "#ffffff",
- "border-color": "#111827",
- },
- },
- {
- selector: "node.marked",
- style: {
- "background-color": "#16a34a",
- "border-color": "#14532d",
- "border-width": 5,
- color: "#000000",
- },
- },
- {
- selector: "node.missing",
- style: {
- "background-color": "#ffffff",
- "background-opacity": 0.42,
- "border-color": "#94a3b8",
- "border-style": "dashed",
- "border-width": 2,
- color: "#000000",
- opacity: 0.72,
- },
- },
- {
- selector: "node.transition:not(.fired):not(.enabled):not(.blocked)",
- style: {
- "background-color": "#ffffff",
- "background-opacity": 0.46,
- "border-color": "#94a3b8",
- "border-style": "dashed",
- color: "#000000",
- opacity: 0.72,
- },
- },
- {
- selector: "node.fired",
- style: {
- "background-color": "#4338ca",
- "border-color": "#1e1b4b",
- "border-width": 5,
- color: "#000000",
- },
- },
- {
- selector: "node.enabled",
- style: {
- "background-color": "#15803d",
- "border-color": "#14532d",
- "border-width": 5,
- color: "#000000",
- },
- },
- {
- selector: "node.blocked",
- style: {
- "background-color": "#dc2626",
- "border-color": "#7f1d1d",
- "border-width": 5,
- color: "#000000",
- },
- },
- {
- selector: ".lane-mechanical",
- style: { "border-color": "#60a5fa", "background-color": "#60a5fa" },
- },
- {
- selector: ".lane-oracle",
- style: { "border-color": "#a78bfa", "background-color": "#a78bfa" },
- },
- {
- selector: ".lane-design",
- style: { "border-color": "#f59e0b", "background-color": "#f59e0b" },
- },
- {
- selector: ".lane-semantic",
- style: { "border-color": "#10b981", "background-color": "#10b981" },
- },
- {
- selector: ".lane-revision",
- style: { "border-color": "#ef4444", "background-color": "#ef4444" },
- },
- {
- selector: "edge",
- style: {
- width: 2,
- "curve-style": "bezier",
- "target-arrow-shape": "triangle",
- "target-arrow-color": "#64748b",
- "line-color": "#94a3b8",
- label: "data(label)",
- "font-size": 9,
- color: "#64748b",
- },
- },
- {
- selector: ":selected",
- style: {
- "overlay-color": "#0f172a",
- "overlay-opacity": 0.12,
- "overlay-padding": 8,
- },
- },
- ],
-})
-
-renderScenarioPicker()
-render(selectedScenario)
-
-cy.on("tap", "node, edge", (event) => {
- renderSelection(event.target.data())
-})
-
-cy.on("tap", (event) => {
- if (event.target === cy) {
- selectionEl.className = "empty"
- selectionEl.textContent = "Select a place, transition, or arc."
- }
-})
-
-function renderScenarioPicker(): void {
- pickerEl.innerHTML = ""
-
- for (const scenario of scenarios) {
- const button = document.createElement("button")
- button.type = "button"
- button.textContent = scenario.label
- button.className = scenario.id === selectedScenario.id ? "active" : ""
- button.addEventListener("click", () => {
- selectedScenario = scenario
- renderScenarioPicker()
- render(scenario)
- })
- pickerEl.append(button)
- }
-}
-
-function render(scenario: Scenario): void {
- cy.elements().remove()
- cy.add(toCyElements(sliceNet, scenario))
- cy.fit(undefined, 42)
- renderSummary(scenario)
- renderBlocked(sliceNet, scenario)
- renderCompletion(sliceNet, scenario)
- selectionEl.className = "empty"
- selectionEl.textContent = "Select a place, transition, or arc."
-}
-
-function toCyElements(net: PetriNet, scenario: Scenario): CyElement[] {
- const states = transitionStates(net, scenario)
- const blockedIds = new Set(
- states.filter((state) => !state.enabled && !state.fired).map((state) => state.id),
- )
- const enabledIds = new Set(states.filter((state) => state.enabled).map((state) => state.id))
- const firedIds = scenario.firedTransitions
-
- const laneElements: CyElement[] = ["mechanical", "oracle", "design", "semantic", "revision"].map(
- (lane) => ({
- data: {
- id: `lane_${lane}`,
- label: laneLabel(lane),
- kind: "lane",
- },
- classes: `lane-parent lane-${lane}`,
- }),
- )
-
- const placeElements: CyElement[] = net.places.map((place) => {
- const classes = ["place", `lane-${place.lane}`]
- if (scenario.marking.has(place.id)) classes.push("marked")
- else classes.push("missing")
-
- return {
- data: {
- ...place,
- parent: `lane_${place.lane}`,
- kind: "place",
- tokenPresent: scenario.marking.has(place.id),
- note: scenario.notesById[place.id],
- },
- position: { x: place.x, y: place.y },
- classes: classes.join(" "),
- }
- })
-
- const transitionElements: CyElement[] = net.transitions.map((transition) => {
- const classes = ["transition", `lane-${transition.lane}`]
- if (firedIds.has(transition.id)) classes.push("fired")
- else if (enabledIds.has(transition.id)) classes.push("enabled")
- else if (blockedIds.has(transition.id)) classes.push("blocked")
-
- const state = states.find((candidate) => candidate.id === transition.id)
-
- return {
- data: {
- ...transition,
- parent: `lane_${transition.lane}`,
- kind: "transition",
- fired: firedIds.has(transition.id),
- enabled: enabledIds.has(transition.id),
- missingInputs: state?.missingInputs.map((place) => place.label) ?? [],
- note: scenario.notesById[transition.id],
- },
- position: { x: transition.x, y: transition.y },
- classes: classes.join(" "),
- }
- })
-
- const arcElements: CyElement[] = net.arcs.map((arc) => ({
- data: {
- ...arc,
- kind: "arc",
- },
- }))
-
- return [...laneElements, ...placeElements, ...transitionElements, ...arcElements]
-}
-
-function renderSummary(scenario: Scenario): void {
- summaryEl.innerHTML = `
- ${escapeHtml(scenario.headline)}
- ${escapeHtml(scenario.summary)}
- Value probe: ${escapeHtml(scenario.valueProbe)}
- `
-}
-
-function renderBlocked(net: PetriNet, scenario: Scenario): void {
- const blocked = blockedTransitions(net, scenario)
-
- if (blocked.length === 0) {
- blockedEl.innerHTML = `No blocked transitions in this final marking.
`
- return
- }
-
- blockedEl.innerHTML = blocked
- .map((state) => {
- const transition = net.transitions.find((candidate) => candidate.id === state.id)
- if (!transition) return ""
- const missing = state.missingInputs.map((place) => `${escapeHtml(place.label)}`).join("")
- const note = scenario.notesById[state.id]
- ? `${escapeHtml(scenario.notesById[state.id])}
`
- : ""
-
- return `
-
- ${escapeHtml(transition.label)}
- ${note}
- Missing inputs:
-
-
- `
- })
- .join("")
-}
-
-function renderCompletion(net: PetriNet, scenario: Scenario): void {
- const status = completionStatus(net, scenario)
-
- if (status.done) {
- completionEl.innerHTML = `
- PlanDoneAccepted reached.
- The terminal marking has all required semantic and mechanical tokens.
- `
- return
- }
-
- completionEl.innerHTML = `
- PlanDoneAccepted is not reachable from this marking.
- Missing terminal inputs:
- ${status.missing.map((place) => `- ${escapeHtml(place.label)}
`).join("")}
- `
-}
-
-function renderSelection(data: Record): void {
- const kind = String(data.kind)
-
- if (kind === "lane") {
- selectionEl.className = ""
- selectionEl.innerHTML = `
- ${escapeHtml(String(data.label))}
- This compound node groups one orchestration lane. Unlike the earlier CSS background bands, it is part of the graph and zooms/pans with the Petri net.
- `
- return
- }
-
- if (kind === "arc") {
- selectionEl.className = ""
- selectionEl.innerHTML = `
- Arc
-
- - Source
- ${escapeHtml(String(data.source))}
- - Target
- ${escapeHtml(String(data.target))}
-
- `
- return
- }
-
- if (kind === "transition") {
- const transitionId = String(data.id)
- const transition = sliceNet.transitions.find((candidate) => candidate.id === transitionId)
- const inputs = transition ? inputsFor(sliceNet, transition.id) : []
- const outputs = transition ? outputsFor(sliceNet, transition.id) : []
- selectionEl.className = ""
- selectionEl.innerHTML = `
- ${escapeHtml(String(data.label))}
-
- ${data.fired ? "fired" : data.enabled ? "enabled" : "blocked"}
-
- ${escapeHtml(String(data.description ?? ""))}
- ${data.guard ? `Guard: ${escapeHtml(String(data.guard))}
` : ""}
- ${renderList("Compiled from", asStringArray(data.compiledFrom))}
- ${renderList("Consumes", inputs.map((place) => place.label))}
- ${renderList("Produces", outputs.map((place) => place.label))}
- ${renderList("Missing", asStringArray(data.missingInputs))}
- ${data.note ? `${escapeHtml(String(data.note))}
` : ""}
- `
- return
- }
-
- selectionEl.className = ""
- selectionEl.innerHTML = `
- ${escapeHtml(String(data.label))}
-
- ${data.tokenPresent ? "token present" : "token missing"}
-
- ${escapeHtml(String(data.description ?? ""))}
- ${data.tokenLabel ? `Token: ${escapeHtml(String(data.tokenLabel))}
` : ""}
- ${data.semanticRef ? `Semantic ref: ${escapeHtml(String(data.semanticRef))}
` : ""}
- ${data.note ? `${escapeHtml(String(data.note))}
` : ""}
- `
-}
-
-function renderList(title: string, values: string[]): string {
- if (values.length === 0) return ""
- return `${escapeHtml(title)}:
${values
- .map((value) => `- ${escapeHtml(value)}
`)
- .join("")}
`
-}
-
-function asStringArray(value: unknown): string[] {
- return Array.isArray(value) ? value.map(String) : []
-}
-
-function laneLabel(lane: string): string {
- if (lane === "mechanical") return "Mechanical execution"
- if (lane === "oracle") return "Oracle satisfaction"
- if (lane === "design") return "Design exercise"
- if (lane === "semantic") return "Semantic completion"
- return "Revision / risk"
-}
-
-function requireElement(id: string): HTMLElement {
- const element = document.getElementById(id)
- if (!element) throw new Error(`Missing element #${id}`)
- return element
-}
-
-function escapeHtml(value: string): string {
- return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">")
-}
diff --git a/archive/docs/next/prototypes/plan-petri-visualization/src/model.ts b/archive/docs/next/prototypes/plan-petri-visualization/src/model.ts
deleted file mode 100644
index 37973d721..000000000
--- a/archive/docs/next/prototypes/plan-petri-visualization/src/model.ts
+++ /dev/null
@@ -1,109 +0,0 @@
-export type Lane = "mechanical" | "oracle" | "design" | "semantic" | "revision"
-
-export type Place = {
- id: string
- label: string
- lane: Lane
- description: string
- tokenLabel?: string
- semanticRef?: string
- x: number
- y: number
-}
-
-export type Transition = {
- id: string
- label: string
- lane: Lane
- description: string
- compiledFrom?: string[]
- guard?: string
- x: number
- y: number
-}
-
-export type Arc = {
- id: string
- source: string
- target: string
- label?: string
-}
-
-export type PetriNet = {
- places: Place[]
- transitions: Transition[]
- arcs: Arc[]
-}
-
-export type Scenario = {
- id: string
- label: string
- headline: string
- summary: string
- valueProbe: string
- marking: Set
- firedTransitions: Set
- notesById: Record
-}
-
-export type TransitionState = {
- id: string
- enabled: boolean
- fired: boolean
- missingInputs: Place[]
-}
-
-export function inputsFor(net: PetriNet, transitionId: string): Place[] {
- const inputIds = net.arcs
- .filter((arc) => arc.target === transitionId)
- .map((arc) => arc.source)
-
- return inputIds
- .map((id) => net.places.find((place) => place.id === id))
- .filter((place): place is Place => place !== undefined)
-}
-
-export function outputsFor(net: PetriNet, transitionId: string): Place[] {
- const outputIds = net.arcs
- .filter((arc) => arc.source === transitionId)
- .map((arc) => arc.target)
-
- return outputIds
- .map((id) => net.places.find((place) => place.id === id))
- .filter((place): place is Place => place !== undefined)
-}
-
-export function transitionStates(net: PetriNet, scenario: Scenario): TransitionState[] {
- return net.transitions.map((transition) => {
- const missingInputs = inputsFor(net, transition.id).filter(
- (place) => !scenario.marking.has(place.id),
- )
-
- return {
- id: transition.id,
- enabled: missingInputs.length === 0,
- fired: scenario.firedTransitions.has(transition.id),
- missingInputs,
- }
- })
-}
-
-export function blockedTransitions(net: PetriNet, scenario: Scenario): TransitionState[] {
- return transitionStates(net, scenario).filter(
- (state) => !state.enabled && !state.fired && state.missingInputs.length > 0,
- )
-}
-
-export function completionStatus(net: PetriNet, scenario: Scenario): {
- done: boolean
- missing: Place[]
-} {
- const declareDone = net.transitions.find((transition) => transition.id === "t_declare_done")
- if (!declareDone) return { done: false, missing: [] }
-
- const missing = inputsFor(net, declareDone.id).filter((place) => !scenario.marking.has(place.id))
- return {
- done: scenario.marking.has("p_plan_done"),
- missing,
- }
-}
diff --git a/archive/docs/next/prototypes/plan-petri-visualization/src/scenarios.ts b/archive/docs/next/prototypes/plan-petri-visualization/src/scenarios.ts
deleted file mode 100644
index ceac66892..000000000
--- a/archive/docs/next/prototypes/plan-petri-visualization/src/scenarios.ts
+++ /dev/null
@@ -1,489 +0,0 @@
-import type { PetriNet, Scenario } from "./model"
-
-const laneY = {
- mechanical: 80,
- oracle: 210,
- design: 340,
- semantic: 470,
- revision: 600,
-} as const
-
-export const sliceNet: PetriNet = {
- places: [
- {
- id: "p_slice_selected",
- label: "Slice selected",
- lane: "mechanical",
- description: "The active plan slice has been chosen for orchestration.",
- tokenLabel: "P1",
- semanticRef: "plan_node P1",
- x: 40,
- y: laneY.mechanical,
- },
- {
- id: "p_context_ready",
- label: "Context pack ready",
- lane: "mechanical",
- description: "A bounded context pack has been generated from the current workspace graph.",
- tokenLabel: "G42",
- semanticRef: "contextPackRef + graphRevision",
- x: 230,
- y: laneY.mechanical,
- },
- {
- id: "p_code_ready",
- label: "Code artifact ready",
- lane: "mechanical",
- description: "Implementation artifacts exist. This is mechanical progress, not semantic completion.",
- tokenLabel: "artifact",
- x: 430,
- y: laneY.mechanical,
- },
- {
- id: "p_verify_passed",
- label: "Verify passed",
- lane: "mechanical",
- description: "The inner/gate verification command passed for the artifact.",
- tokenLabel: "report",
- x: 630,
- y: laneY.mechanical,
- },
- {
- id: "p_oracle_required",
- label: "Oracle required",
- lane: "oracle",
- description: "The plan graph has a gating oracle relation for this slice.",
- tokenLabel: "O1",
- semanticRef: "plan.verified_by_oracle P1 -> O1",
- x: 230,
- y: laneY.oracle,
- },
- {
- id: "p_oracle_evidence",
- label: "Oracle evidence ready",
- lane: "oracle",
- description: "A report or artifact is available that can be assessed against the oracle check.",
- tokenLabel: "evidence",
- x: 430,
- y: laneY.oracle,
- },
- {
- id: "p_oracle_satisfied",
- label: "Oracle satisfied",
- lane: "oracle",
- description: "Judgment-bearing token: evidence satisfies the required oracle check for this graph revision.",
- tokenLabel: "O1 ✓",
- x: 630,
- y: laneY.oracle,
- },
- {
- id: "p_design_required",
- label: "Design target required",
- lane: "design",
- description: "The plan graph says this slice must introduce or exercise a design node.",
- tokenLabel: "D2",
- semanticRef: "plan.exercises_design P1 -> D2",
- x: 230,
- y: laneY.design,
- },
- {
- id: "p_design_evidence",
- label: "Design evidence ready",
- lane: "design",
- description: "Evidence exists that can be reviewed for whether the intended seam/module was exercised.",
- tokenLabel: "trace",
- x: 430,
- y: laneY.design,
- },
- {
- id: "p_design_exercised",
- label: "Design exercised",
- lane: "design",
- description: "Judgment-bearing token: the implementation actually crossed the intended design seam.",
- tokenLabel: "D2 ✓",
- x: 630,
- y: laneY.design,
- },
- {
- id: "p_intent_current",
- label: "Intent current",
- lane: "semantic",
- description: "The targeted intent nodes are still current for the graph revision used by the work.",
- tokenLabel: "R1/C1",
- semanticRef: "plan.establishes_intent P1 -> R1",
- x: 230,
- y: laneY.semantic,
- },
- {
- id: "p_intent_established",
- label: "Intent established",
- lane: "semantic",
- description: "Judgment-bearing token: required intent targets are established by the evidence bundle.",
- tokenLabel: "intent ✓",
- x: 630,
- y: laneY.semantic,
- },
- {
- id: "p_completion_accepted",
- label: "Completion accepted",
- lane: "semantic",
- description: "A reviewer or trusted assessment accepts that the plan claim is semantically complete.",
- tokenLabel: "claim ✓",
- x: 820,
- y: laneY.semantic,
- },
- {
- id: "p_plan_done",
- label: "Plan done accepted",
- lane: "semantic",
- description: "Terminal semantic marking. This can support declaring plan execution status done.",
- tokenLabel: "done",
- semanticRef: "plan_node.executionStatus = done candidate",
- x: 1020,
- y: laneY.semantic,
- },
- {
- id: "p_graph_current",
- label: "Graph revision current",
- lane: "revision",
- description: "The evidence and context pack are still aligned with the current workspace graph revision.",
- tokenLabel: "G42 current",
- x: 40,
- y: laneY.revision,
- },
- {
- id: "p_graph_stale",
- label: "Graph stale",
- lane: "revision",
- description: "The context/evidence was produced from an older graph revision and needs reconciliation.",
- tokenLabel: "G42 stale",
- x: 230,
- y: laneY.revision,
- },
- {
- id: "p_risk_resolved",
- label: "Risk resolved / accepted",
- lane: "revision",
- description: "Residual risks are either retired by evidence or explicitly accepted/deferred.",
- tokenLabel: "risk ✓",
- semanticRef: "plan.retires_risk or accepted residual risk",
- x: 820,
- y: laneY.revision,
- },
- ],
- transitions: [
- {
- id: "t_build_context",
- label: "Build context",
- lane: "mechanical",
- description: "Compile the active plan slice and graph neighborhood into an execution context pack.",
- x: 135,
- y: laneY.mechanical,
- },
- {
- id: "t_implement",
- label: "Implement",
- lane: "mechanical",
- description: "Dispatch coding work using the context pack.",
- x: 330,
- y: laneY.mechanical,
- },
- {
- id: "t_verify",
- label: "Run verify",
- lane: "mechanical",
- description: "Run tests/checks that produce mechanical evidence.",
- x: 530,
- y: laneY.mechanical,
- },
- {
- id: "t_collect_oracle_evidence",
- label: "Collect oracle evidence",
- lane: "oracle",
- description: "Route verification output into evidence for the required oracle check.",
- compiledFrom: ["plan.verified_by_oracle P1 -> O1"],
- x: 330,
- y: laneY.oracle,
- },
- {
- id: "t_assess_oracle",
- label: "Assess oracle",
- lane: "oracle",
- description: "Decide whether the evidence satisfies the required oracle check.",
- guard: "Evidence maps to O1, report passes, and graph revision is current.",
- compiledFrom: ["plan.verified_by_oracle P1 -> O1"],
- x: 530,
- y: laneY.oracle,
- },
- {
- id: "t_collect_design_evidence",
- label: "Collect design evidence",
- lane: "design",
- description: "Produce evidence about whether the implementation crossed the intended seam/module.",
- compiledFrom: ["plan.exercises_design P1 -> D2"],
- x: 330,
- y: laneY.design,
- },
- {
- id: "t_assess_design",
- label: "Assess design",
- lane: "design",
- description: "Judge whether the design node was actually introduced or exercised.",
- guard: "Evidence shows the intended design seam D2 was exercised, not bypassed.",
- compiledFrom: ["plan.exercises_design P1 -> D2"],
- x: 530,
- y: laneY.design,
- },
- {
- id: "t_assess_intent",
- label: "Assess intent",
- lane: "semantic",
- description: "Judge whether oracle and design evidence establish the intent target.",
- guard: "Intent target is current; required oracle/design tokens are present.",
- compiledFrom: ["plan.establishes_intent P1 -> R1"],
- x: 530,
- y: laneY.semantic,
- },
- {
- id: "t_review_completion",
- label: "Review completion",
- lane: "semantic",
- description: "Accept the semantic completion claim after evidence and residual risk review.",
- x: 725,
- y: laneY.semantic,
- },
- {
- id: "t_declare_done",
- label: "Declare done",
- lane: "semantic",
- description: "Terminal transition: declare or suggest that the plan node is done.",
- guard: "All gating semantic tokens are present and graph revision remains current.",
- x: 920,
- y: laneY.semantic,
- },
- {
- id: "t_detect_stale",
- label: "Detect stale graph",
- lane: "revision",
- description: "Detect that artifacts/evidence were produced from a stale graph revision.",
- x: 135,
- y: laneY.revision,
- },
- ],
- arcs: [
- { id: "a1", source: "p_slice_selected", target: "t_build_context" },
- { id: "a2", source: "p_graph_current", target: "t_build_context" },
- { id: "a3", source: "t_build_context", target: "p_context_ready" },
- { id: "a4", source: "p_context_ready", target: "t_implement" },
- { id: "a5", source: "t_implement", target: "p_code_ready" },
- { id: "a6", source: "p_code_ready", target: "t_verify" },
- { id: "a7", source: "t_verify", target: "p_verify_passed" },
- { id: "a8", source: "p_oracle_required", target: "t_collect_oracle_evidence" },
- { id: "a9", source: "p_verify_passed", target: "t_collect_oracle_evidence" },
- { id: "a10", source: "t_collect_oracle_evidence", target: "p_oracle_evidence" },
- { id: "a11", source: "p_oracle_evidence", target: "t_assess_oracle" },
- { id: "a12", source: "p_graph_current", target: "t_assess_oracle" },
- { id: "a13", source: "t_assess_oracle", target: "p_oracle_satisfied" },
- { id: "a14", source: "p_design_required", target: "t_collect_design_evidence" },
- { id: "a15", source: "p_code_ready", target: "t_collect_design_evidence" },
- { id: "a16", source: "t_collect_design_evidence", target: "p_design_evidence" },
- { id: "a17", source: "p_design_evidence", target: "t_assess_design" },
- { id: "a18", source: "t_assess_design", target: "p_design_exercised" },
- { id: "a19", source: "p_intent_current", target: "t_assess_intent" },
- { id: "a20", source: "p_oracle_satisfied", target: "t_assess_intent" },
- { id: "a21", source: "p_design_exercised", target: "t_assess_intent" },
- { id: "a22", source: "t_assess_intent", target: "p_intent_established" },
- { id: "a23", source: "p_intent_established", target: "t_review_completion" },
- { id: "a24", source: "p_risk_resolved", target: "t_review_completion" },
- { id: "a25", source: "t_review_completion", target: "p_completion_accepted" },
- { id: "a26", source: "p_verify_passed", target: "t_declare_done" },
- { id: "a27", source: "p_oracle_satisfied", target: "t_declare_done" },
- { id: "a28", source: "p_design_exercised", target: "t_declare_done" },
- { id: "a29", source: "p_intent_established", target: "t_declare_done" },
- { id: "a30", source: "p_completion_accepted", target: "t_declare_done" },
- { id: "a31", source: "p_graph_current", target: "t_declare_done" },
- { id: "a32", source: "t_declare_done", target: "p_plan_done" },
- { id: "a33", source: "p_context_ready", target: "t_detect_stale" },
- { id: "a34", source: "t_detect_stale", target: "p_graph_stale" },
- ],
-}
-
-const baseFired = new Set([
- "t_build_context",
- "t_implement",
- "t_verify",
- "t_collect_oracle_evidence",
- "t_assess_oracle",
- "t_collect_design_evidence",
- "t_assess_design",
- "t_assess_intent",
- "t_review_completion",
-])
-
-export const scenarios: Scenario[] = [
- {
- id: "happy",
- label: "Happy path",
- headline: "Mechanical evidence and semantic gates converge.",
- summary:
- "The slice reaches verification, satisfies its oracle, exercises the design seam, establishes intent, resolves risk, and can declare plan-done.",
- valueProbe:
- "Shows the desirable end state: Petri orchestration is not just task flow, but evidence-gated semantic completion.",
- marking: new Set([
- "p_slice_selected",
- "p_graph_current",
- "p_context_ready",
- "p_code_ready",
- "p_verify_passed",
- "p_oracle_required",
- "p_oracle_evidence",
- "p_oracle_satisfied",
- "p_design_required",
- "p_design_evidence",
- "p_design_exercised",
- "p_intent_current",
- "p_intent_established",
- "p_risk_resolved",
- "p_completion_accepted",
- "p_plan_done",
- ]),
- firedTransitions: new Set([...baseFired, "t_declare_done"]),
- notesById: {},
- },
- {
- id: "missing-oracle",
- label: "Missing oracle",
- headline: "Implementation can finish while semantic completion remains blocked.",
- summary:
- "Code and verify pass, but no oracle requirement/evidence token exists. The plan cannot become semantically done even though mechanical work looks green.",
- valueProbe:
- "Tests whether the UI can explain why green CI is insufficient: the required graph-derived oracle was not satisfied.",
- marking: new Set([
- "p_slice_selected",
- "p_graph_current",
- "p_context_ready",
- "p_code_ready",
- "p_verify_passed",
- "p_design_required",
- "p_design_evidence",
- "p_design_exercised",
- "p_intent_current",
- "p_risk_resolved",
- ]),
- firedTransitions: new Set(["t_build_context", "t_implement", "t_verify", "t_collect_design_evidence", "t_assess_design"]),
- notesById: {
- p_oracle_required: "No accepted oracle edge compiled into this run, or O1 is not implemented yet.",
- t_declare_done: "Blocked despite VerifyPassed: OracleSatisfied is missing.",
- },
- },
- {
- id: "design-bypass",
- label: "Design bypass",
- headline: "Tests pass, but the intended seam was not exercised.",
- summary:
- "Mechanical verification and oracle evidence are present, but design assessment rejects the implementation because it bypassed the design node the slice was meant to prove.",
- valueProbe:
- "Surfaces architectural value: orchestration can protect design intent, not merely run tests.",
- marking: new Set([
- "p_slice_selected",
- "p_graph_current",
- "p_context_ready",
- "p_code_ready",
- "p_verify_passed",
- "p_oracle_required",
- "p_oracle_evidence",
- "p_oracle_satisfied",
- "p_design_required",
- "p_design_evidence",
- "p_intent_current",
- "p_risk_resolved",
- ]),
- firedTransitions: new Set([
- "t_build_context",
- "t_implement",
- "t_verify",
- "t_collect_oracle_evidence",
- "t_assess_oracle",
- "t_collect_design_evidence",
- ]),
- notesById: {
- p_design_exercised: "Assessment rejected: evidence did not show the intended seam D2 was crossed.",
- t_assess_design: "Guard failed: implementation bypassed D2.",
- },
- },
- {
- id: "stale-graph",
- label: "Stale graph",
- headline: "Long-running work cannot complete against an obsolete semantic revision.",
- summary:
- "Context and artifacts exist, but the graph advanced during execution. Semantic transitions that require GraphRevisionCurrent are blocked until reconciliation or rebuild.",
- valueProbe:
- "Tests whether Petri orchestration can make stale-context hazards visible instead of silently accepting outdated work.",
- marking: new Set([
- "p_slice_selected",
- "p_context_ready",
- "p_code_ready",
- "p_verify_passed",
- "p_oracle_required",
- "p_oracle_evidence",
- "p_design_required",
- "p_design_evidence",
- "p_design_exercised",
- "p_intent_current",
- "p_risk_resolved",
- "p_graph_stale",
- ]),
- firedTransitions: new Set([
- "t_build_context",
- "t_implement",
- "t_verify",
- "t_collect_oracle_evidence",
- "t_collect_design_evidence",
- "t_assess_design",
- "t_detect_stale",
- ]),
- notesById: {
- p_graph_current: "Missing because current graph is G43 while context/evidence was produced from G42.",
- t_assess_oracle: "Blocked: oracle assessment requires current graph revision.",
- t_declare_done: "Blocked: PlanDoneAccepted cannot be produced from stale evidence.",
- },
- },
- {
- id: "risk-pending",
- label: "Risk pending",
- headline: "All evidence passes, but residual risk still needs explicit treatment.",
- summary:
- "Intent, oracle, and design gates are satisfied. Completion review is blocked because a residual risk is neither retired nor explicitly accepted/deferred.",
- valueProbe:
- "Tests whether the net can represent judgment work that is not reducible to tests or code artifacts.",
- marking: new Set([
- "p_slice_selected",
- "p_graph_current",
- "p_context_ready",
- "p_code_ready",
- "p_verify_passed",
- "p_oracle_required",
- "p_oracle_evidence",
- "p_oracle_satisfied",
- "p_design_required",
- "p_design_evidence",
- "p_design_exercised",
- "p_intent_current",
- "p_intent_established",
- ]),
- firedTransitions: new Set([
- "t_build_context",
- "t_implement",
- "t_verify",
- "t_collect_oracle_evidence",
- "t_assess_oracle",
- "t_collect_design_evidence",
- "t_assess_design",
- "t_assess_intent",
- ]),
- notesById: {
- p_risk_resolved: "Residual risk is still open. A human/agent must retire it or explicitly accept/defer it.",
- t_review_completion: "Blocked: completion review requires risk treatment.",
- },
- },
-]
diff --git a/archive/docs/next/prototypes/plan-petri-visualization/src/style.css b/archive/docs/next/prototypes/plan-petri-visualization/src/style.css
deleted file mode 100644
index d8091f430..000000000
--- a/archive/docs/next/prototypes/plan-petri-visualization/src/style.css
+++ /dev/null
@@ -1,310 +0,0 @@
-:root {
- color: #172033;
- background: #e5e7eb;
- font-family:
- Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
-}
-
-* {
- box-sizing: border-box;
-}
-
-body {
- margin: 0;
-}
-
-#app {
- min-height: 100vh;
- padding: 24px;
-}
-
-.hero {
- display: grid;
- grid-template-columns: minmax(0, 1fr) auto;
- gap: 24px;
- align-items: end;
- margin: 0 0 18px;
- max-width: none;
-}
-
-.eyebrow {
- margin: 0 0 6px;
- color: #4f46e5;
- font-size: 12px;
- font-weight: 800;
- letter-spacing: 0.12em;
- text-transform: uppercase;
-}
-
-h1,
-h2,
-h3,
-p {
- margin-top: 0;
-}
-
-h1 {
- margin-bottom: 8px;
- font-size: clamp(30px, 4vw, 54px);
- letter-spacing: -0.05em;
- line-height: 0.95;
-}
-
-h2 {
- margin: 0 0 12px;
- color: #334155;
- font-size: 13px;
- letter-spacing: 0.08em;
- text-transform: uppercase;
-}
-
-h3 {
- margin-bottom: 8px;
- font-size: 16px;
-}
-
-.hero p {
- max-width: 780px;
- color: #475569;
- font-size: 17px;
-}
-
-.scenario-picker {
- display: flex;
- flex-wrap: wrap;
- justify-content: flex-end;
- gap: 8px;
- max-width: 560px;
-}
-
-button {
- border: 1px solid #cbd5e1;
- border-radius: 999px;
- background: #fff;
- color: #334155;
- cursor: pointer;
- font: inherit;
- font-size: 13px;
- font-weight: 700;
- padding: 9px 13px;
-}
-
-button:hover,
-button.active {
- border-color: #4f46e5;
- background: #eef2ff;
- color: #312e81;
-}
-
-.workspace {
- display: grid;
- grid-template-columns: 320px minmax(760px, 1fr) 340px;
- gap: 14px;
- height: calc(100vh - 176px);
- min-height: 680px;
- max-width: none;
- margin: 0;
-}
-
-.panel,
-.graph-shell {
- border: 1px solid rgba(15, 23, 42, 0.1);
- border-radius: 24px;
- background: rgba(255, 255, 255, 0.86);
- box-shadow: 0 18px 50px rgba(15, 23, 42, 0.08);
-}
-
-.panel {
- overflow: auto;
- padding: 20px;
-}
-
-.panel h2:not(:first-child) {
- margin-top: 26px;
-}
-
-.graph-shell {
- display: flex;
- flex-direction: column;
- overflow: hidden;
-}
-
-.legend {
- display: flex;
- flex-wrap: wrap;
- gap: 14px;
- align-items: center;
- border-bottom: 1px solid #e2e8f0;
- padding: 12px 18px;
- color: #475569;
- font-size: 12px;
- font-weight: 700;
-}
-
-.dot,
-.token-swatch,
-.transition-swatch {
- display: inline-block;
- width: 10px;
- height: 10px;
- margin-right: 6px;
- border-radius: 999px;
-}
-
-.token-swatch,
-.transition-swatch {
- width: 14px;
- height: 14px;
- vertical-align: -2px;
-}
-
-.transition-swatch {
- border-radius: 2px;
-}
-
-.mechanical {
- background: #60a5fa;
-}
-
-.oracle {
- background: #a78bfa;
-}
-
-.design {
- background: #f59e0b;
-}
-
-.semantic {
- background: #10b981;
-}
-
-.revision {
- background: #ef4444;
-}
-
-.token-swatch.present {
- border: 2px solid #14532d;
- background: #16a34a;
-}
-
-.token-swatch.absent {
- border: 2px dashed #94a3b8;
- background: rgba(255, 255, 255, 0.45);
-}
-
-.transition-swatch.fired {
- border: 2px solid #1e1b4b;
- background: #4338ca;
-}
-
-.transition-swatch.idle {
- border: 2px dashed #94a3b8;
- background: rgba(255, 255, 255, 0.45);
-}
-
-#cy {
- flex: 1;
- min-height: 0;
- background: #f8fafc;
-}
-
-.value-probe,
-.note,
-.blocked-card {
- border-radius: 14px;
- padding: 12px;
-}
-
-.value-probe {
- border: 1px solid #c7d2fe;
- background: #eef2ff;
- color: #3730a3;
-}
-
-.note {
- border: 1px solid #fed7aa;
- background: #fff7ed;
- color: #9a3412;
-}
-
-.blocked-card {
- margin-bottom: 12px;
- border: 1px solid #fecaca;
- background: #fef2f2;
-}
-
-.blocked-card h3 {
- color: #991b1b;
-}
-
-.ok {
- color: #047857;
-}
-
-.empty {
- color: #64748b;
- font-style: italic;
-}
-
-.pill {
- display: inline-flex;
- border-radius: 999px;
- font-size: 12px;
- font-weight: 800;
- padding: 5px 9px;
- text-transform: uppercase;
-}
-
-.pill.good {
- background: #dcfce7;
- color: #166534;
-}
-
-.pill.ready {
- background: #dbeafe;
- color: #1d4ed8;
-}
-
-.pill.bad {
- background: #fee2e2;
- color: #991b1b;
-}
-
-dl {
- display: grid;
- grid-template-columns: 70px 1fr;
- gap: 8px;
-}
-
-dt {
- color: #64748b;
- font-weight: 700;
-}
-
-dd {
- margin: 0;
-}
-
-ul {
- padding-left: 20px;
-}
-
-li + li {
- margin-top: 4px;
-}
-
-@media (max-width: 1180px) {
- .hero,
- .workspace {
- grid-template-columns: 1fr;
- height: auto;
- }
-
- .scenario-picker {
- justify-content: flex-start;
- }
-
- .graph-shell {
- height: 720px;
- }
-}
diff --git a/archive/docs/reference/agent-tracing-self-improvement.md b/archive/docs/reference/agent-tracing-self-improvement.md
deleted file mode 100644
index 735a84b9a..000000000
--- a/archive/docs/reference/agent-tracing-self-improvement.md
+++ /dev/null
@@ -1,284 +0,0 @@
-# Agent tracing for self-improvement feedback
-
-Reference notes on wiring OpenTelemetry (OTel) tracing into a TypeScript agent stack so that (a) humans can read traces and (b) the agent itself can use its past trajectories as feedback for self-improvement.
-
-Compiled from `last30days` research (window: 2026-03-20 → 2026-04-19) plus design synthesis. Where a claim came from research, it is attributed; where it's a design recommendation, that's stated.
-
----
-
-## Landscape findings (from research)
-
-### OTel is the substrate everyone is converging on
-
-The industry signal in the last 30 days is unambiguous: **custom SDK wrappers are being deprecated in favor of standard OTel auto-instrumentation.**
-
-- **PostHog** merged three PRs in a two-week span explicitly moving off their own `@posthog/ai/*` wrappers toward OTel auto-instrumentation:
- - `posthog-js #3349` (Apr-7) — migrate AI examples to OTel
- - `posthog #53668` (Apr-8) — migrate onboarding docs to OTel auto-instrumentation
- - `posthog-js #3415` (Apr-17) — migrate Gemini example to OTel
-- Explicit reasoning in those PRs: *"more portable, follows industry conventions, wrappers kept as last resort."*
-- **Jaeger** has an active LFX project (`jaegertracing/jaeger#8401`, Apr-17) adding GenAI-specialized trace visualization — `invoke_agent` spans, OTel-based.
-
-**Implication:** don't invest in provider-specific tracing wrappers. Emit OTel spans; swap backends behind OTLP.
-
-### Vercel AI SDK — OTel is built in
-
-From Pydantic's Apr-14 article *"OpenTelemetry LLM Tracing with Vercel AI SDK and Pydantic Logfire"* (https://pydantic.dev/articles/vercel-ai-sdk-logfire-otel):
-
-> The AI SDK's `experimental_telemetry` option works identically since it uses the global OTel tracer, regardless of how it was initialized. Three independent teams built the pieces that make this possible: Vercel added OpenTelemetry instrumentation to the AI SDK, emitting spans with `ai.*` and `gen_ai.*` attributes on every LLM call.
-
-Spans are emitted on every `generateText`, `streamText`, `generateObject`, `streamObject` call when `experimental_telemetry.isEnabled` is true. They carry `ai.*` (Vercel-specific) and `gen_ai.*` (OTel GenAI SIG convention) attributes.
-
-**Known bug (PostHog-specific, not OTel-general):** `PostHog/posthog#52442` (Mar-26, still open) — PostHog's OTel ingestion drops custom metadata (`posthog_distinct_id`, `functionId`, custom properties) from Vercel AI SDK spans. Does not affect Langfuse / Logfire / other OTLP backends.
-
-### OpenCode — partial OTel, known gap
-
-Very recent issue `marcusquinn/aidevops#19660` (Apr-18): **OpenCode v1.4.11 `run` mode does not emit per-tool-call `Tool.execute` / `Bash` spans.** Top-level spans work, but you are blind to individual tool calls in non-interactive runs. Check the current version against this thread before relying on OpenCode tracing.
-
-### Claude Agent SDK — no evidence in window
-
-The Claude Agent SDK was not mentioned in any of 62 evidence items across Reddit, GitHub, Web, TikTok. Either (a) its tracing story isn't a topic of community discussion yet, or (b) the query didn't surface it. Check Anthropic's own docs for current state.
-
-### Convenience layer: Traceloop (OpenLLMetry)
-
-Traceloop ships `@traceloop/instrumentation-*` per-provider packages. Referenced explicitly in `PostHog/posthog-js#3415`:
-
-> Traceloop shipped `@traceloop/instrumentation-google-generativeai`, so we can now instrument the official Google Gen AI SDK directly.
-
-TypeScript-native, monkey-patches the provider SDK, emits OTel spans automatically. Covers OpenAI, Anthropic, Google, Cohere, Bedrock, LangChain, LlamaIndex. Use this for SDK calls the Vercel AI SDK doesn't route through (e.g. direct Anthropic calls inside a Claude Agent SDK setup).
-
-### Self-improvement patterns (from research)
-
-Two practical references surfaced:
-
-1. **ChatbotKit "Self-rating Reflection Agent"** (Apr-7, https://chatbotkit.com/examples/self-rating-reflection-agent) — concrete pattern:
- > A rating-aware agent should not spam the system with feedback records after every trivial exchange, and it should never fabricate evidence that is not in the rating log. Instead it should record ratings after meaningful outcomes, use clear reasons, and consult recent ratings before claiming that it has improved.
-
-2. **arXiv "Experiential Reflective Learning for Self-Improving LLM..."** (Mar-24, arxiv.org/pdf/2603.24639) — formal framing:
- > As the agent executes tasks, it accumulates experiences consisting of the task description, the execution trajectory (reasoning steps, tool calls, and outputs), and the outcome signal. After each task, the agent reflects on this...
-
-Together these argue for a **rating-disciplined trajectory log**, not a firehose of every span.
-
-### TypeScript eval layer (DeepEval replacement)
-
-DeepEval is Python-only and not suitable inside a TypeScript codebase without crossing runtimes. TS-native alternatives in the same slot:
-
-- **Evalite** — lightweight, pytest-shaped
-- **Autoevals** (Braintrust) — scoring library, framework-agnostic
-- **Vercel AI SDK Evals** — first-party, integrates with the SDK
-
----
-
-## Architecture: two views, one capture
-
-The core design idea is that **humans and agents need different views of the same data**. Do not try to serve both from one UI.
-
-```
- ┌─────────────────────┐
- │ Vercel AI SDK │
- │ experimental_ │
- │ telemetry = true │
- └──────────┬──────────┘
- │ OTLP spans
- ▼
- ┌─────────────────────┐
- │ OTel SDK │
- │ (global tracer) │
- └──────────┬──────────┘
- │
- ┌─────────────┴─────────────┐
- ▼ ▼
- ┌─────────────────────┐ ┌─────────────────────┐
- │ Langfuse / Logfire │ │ Reflection store │
- │ (human UI) │ │ (agent tool API) │
- │ — read traces │ │ — query past turns │
- │ — run evals │ │ — get ratings │
- │ — curate datasets │ │ — list failures │
- └─────────────────────┘ └─────────────────────┘
-```
-
-If you pick Langfuse, its `observations` / `scores` / `datasets` APIs collapse both views into one backend — the agent queries the same store the human browses. That is the cheapest path.
-
----
-
-## Backend selection
-
-### Recommended: **Langfuse** (self-hosted)
-
-- Open source, Docker-compose install
-- UI purpose-built for LLM traces: nested tree of prompts, completions, tool calls
-- Has sessions, user IDs, evals, datasets, scores — all keyed to traces
-- HTTP API is agent-friendly JSON (the same shape used by the UI)
-- OTLP-native
-
-**Pick this** if you want one tool that covers tracing + eval + dataset curation, and if you want the agent to use the same store as its reflection memory.
-
-### Alternative: **Pydantic Logfire**
-
-- Cleaner general-purpose OTel UI
-- Excellent Vercel AI SDK documentation (Apr-14 article is effectively a Logfire tutorial)
-- Generous free tier, SaaS
-- Less LLM-specific than Langfuse; no built-in dataset/eval management
-
-**Pick this** if you prioritize UI polish and are comfortable building the eval/reflection layer yourself.
-
-### Not recommended for this use case
-
-- **LangSmith** — heaviest hitter in research but tightly coupled to LangChain; skip unless already in that ecosystem.
-- **Datadog / Honeycomb** — great general observability, not shaped for LLM content.
-- **Jaeger (stock)** — not LLM-aware. Wait for the LFX GenAI work (`#8401`) if you want it.
-
----
-
-## Minimal implementation path
-
-### 1. Install the OTel SDK
-
-```ts
-// src/telemetry/otel.ts
-import { NodeSDK } from '@opentelemetry/sdk-node'
-import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
-
-const sdk = new NodeSDK({
- traceExporter: new OTLPTraceExporter({
- url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
- headers: {
- Authorization: `Bearer ${process.env.LANGFUSE_PUBLIC_KEY}:${process.env.LANGFUSE_SECRET_KEY}`,
- },
- }),
-})
-
-sdk.start()
-```
-
-Import this once at process boot, before any Vercel AI SDK calls.
-
-### 2. Enable telemetry on every AI SDK call
-
-```ts
-const result = await generateText({
- model: anthropic('claude-sonnet-4-6'),
- messages,
- experimental_telemetry: {
- isEnabled: true,
- functionId: 'review-accept', // names the span group
- metadata: {
- sessionId,
- taskId,
- userId,
- },
- },
-})
-```
-
-The `functionId` + `metadata.*` fields become queryable facets in Langfuse.
-
-### 3. Run Langfuse locally
-
-```bash
-git clone https://github.com/langfuse/langfuse
-cd langfuse && docker compose up -d
-# UI at http://localhost:3000, OTLP at http://localhost:3000/api/public/otel
-```
-
-Point `OTEL_EXPORTER_OTLP_ENDPOINT` at the Langfuse OTLP endpoint.
-
-### 4. Instrument non-Vercel SDK calls with Traceloop
-
-For any provider call that doesn't go through the Vercel AI SDK (e.g. direct Anthropic Messages API inside a Claude Agent SDK loop):
-
-```ts
-import * as traceloop from '@traceloop/node-server-sdk'
-import Anthropic from '@anthropic-ai/sdk'
-
-traceloop.initialize({ disableBatch: true }) // uses the global OTel tracer
-```
-
-The Anthropic SDK is auto-instrumented; spans land in the same pipeline.
-
-### 5. Expose a reflection tool surface to the agent
-
-Give the agent three tools, backed by Langfuse's API:
-
-```ts
-// Pseudocode shape — exact Langfuse query params depend on their API version.
-const tools = {
- get_recent_trajectories: async ({ functionId, limit }) => {
- // Returns: [{ taskId, outcome, durationMs, toolCalls: [...], rating?, reason? }]
- },
- get_failure_cases: async ({ since, minSeverity }) => {
- // Returns low-rated trajectories with their reason field.
- },
- get_rating: async ({ taskId }) => {
- // Single trajectory's rating + reason, or null.
- },
-}
-```
-
-**Critical design constraints for this surface:**
-
-- Return **flattened summaries**, not raw spans. One trajectory should fit in ~200 tokens.
-- Include the `reason` field every time. A rating without a reason is worse than no rating.
-- Cap the default `limit` low (e.g. 5). The agent should explicitly ask for more.
-
-### 6. Apply rating discipline
-
-From the ChatbotKit pattern:
-
-- **Rate only meaningful outcomes.** Don't rate every LLM call. Rate at task boundaries where a user-visible outcome exists.
-- **Every rating has a reason.** Free-text, one sentence, written by the agent or the human.
-- **Consult ratings before claiming improvement.** If the agent is going to say "I've learned X," it should be required to cite rating evidence from the tool surface.
-
-In Langfuse, ratings live in the `scores` API. Write to it at task boundaries only.
-
-### 7. Layer evals
-
-Pick one:
-
-- **Evalite** for pytest-shaped eval suites run in CI.
-- **Autoevals** (Braintrust) if you want a scoring library without a full framework.
-- **Vercel AI SDK Evals** if you want first-party integration.
-
-Write scores back to Langfuse (via its `scores` API) so rating data and trace data live in one store.
-
----
-
-## Gotchas
-
-- **PostHog-as-backend** currently drops AI SDK custom metadata (`#52442`). Use Langfuse or Logfire if you need `functionId` / session IDs to survive.
-- **OpenCode `run` mode** misses per-tool-call spans (`aidevops#19660`). Check current version before committing.
-- **Claude Agent SDK** tracing story is not publicly discussed in the last 30 days. Verify against Anthropic's docs when you get there.
-- **DeepEval is Python-only.** Don't let its marketing pull you across runtimes.
-- **Do not let the agent read the Langfuse UI JSON directly.** The UI payload is optimized for humans and will blow the context window. Always project into the flat summary shape described in §5.
-
----
-
-## References
-
-### Primary (dated within research window)
-
-- Pydantic, *"OpenTelemetry LLM Tracing with Vercel AI SDK and Pydantic Logfire"*, 2026-04-14 — https://pydantic.dev/articles/vercel-ai-sdk-logfire-otel
-- ChatbotKit, *"Self-rating Reflection Agent"*, 2026-04-07 — https://chatbotkit.com/examples/self-rating-reflection-agent
-- Confident AI, *"Top 7 LLM Observability Tools in 2026"*, 2026-04-07 — https://www.confident-ai.com/knowledge-base/top-7-llm-observability-tools
-- LangChain, *"AI Agent Observability: Tracing, Testing, and Improving Agents"*, 2026-04-02 — https://www.langchain.com/articles/agent-observability
-- arXiv, *"Experiential Reflective Learning for Self-Improving LLM..."*, 2026-03-24 — https://arxiv.org/pdf/2603.24639
-- `PostHog/posthog-js#3349` — migrate AI examples to OTel, 2026-04-07
-- `PostHog/posthog#53668` — migrate onboarding docs to OTel, 2026-04-08
-- `PostHog/posthog-js#3415` — migrate Gemini example to OTel, 2026-04-17
-- `PostHog/posthog#52442` — OTel ingestion drops Vercel AI SDK metadata (open), 2026-03-26
-- `jaegertracing/jaeger#8401` — GenAI trace visualization proposal, 2026-04-17
-- `marcusquinn/aidevops#19660` — OpenCode `run` mode tool-call span gap, 2026-04-18
-- `pydantic/pydantic-ai-harness#120` — agent eval/benchmarking framework, 2026-03-26
-
-### Secondary (product docs)
-
-- Vercel AI SDK telemetry: `experimental_telemetry` option
-- Langfuse: https://langfuse.com (self-hosted via `docker compose`)
-- Traceloop / OpenLLMetry: `@traceloop/node-server-sdk`
-- OTel GenAI semantic conventions: https://opentelemetry.io/docs/specs/semconv/gen-ai/
-
-### Research caveats
-
-- X/Twitter source was unavailable during research (Safari cookie permissions); OTel-GenAI SIG discussion lives there and is not represented.
-- YouTube returned zero results across three query variants.
-- Research window is 30 days; older foundational material (e.g. original OpenLLMetry release) is outside scope.
diff --git a/archive/docs/schema.dbml b/archive/docs/schema.dbml
deleted file mode 100644
index 7e69edac2..000000000
--- a/archive/docs/schema.dbml
+++ /dev/null
@@ -1,89 +0,0 @@
-// Brunch database schema (SQLite via Drizzle)
-// Source of truth: src/server/schema.ts
-
-Project brunch {
- database_type: 'SQLite'
- Note: 'Generated from src/server/schema.ts'
-}
-
-Table specification {
- id integer [pk, increment]
- name text [not null]
- mode text [not null, default: 'greenfield', note: 'enum: greenfield | brownfield']
- active_turn_id integer [note: 'soft ref -> turn.id (no FK in schema)']
- created_at text [not null, default: `datetime('now')`]
- updated_at text [not null, default: `datetime('now')`]
-}
-
-Table turn {
- id integer [pk, increment]
- specification_id integer [not null, ref: > specification.id]
- parent_turn_id integer [ref: > turn.id]
- phase text [not null, note: 'enum: grounding | design | requirements | criteria']
- turn_kind text [not null, default: 'question', note: 'enum: question | kickoff | recovery']
- question text [not null, default: '']
- why text
- impact text [note: 'enum: high | medium | low']
- answer text
- is_resolution integer [not null, default: false, note: 'boolean']
- user_parts text
- assistant_parts text
- created_at text [not null, default: `datetime('now')`]
-}
-
-Table option {
- id integer [pk, increment]
- turn_id integer [not null, ref: > turn.id]
- position integer [not null]
- content text [not null]
- is_recommended integer [not null, default: false, note: 'boolean']
- is_selected integer [not null, default: false, note: 'boolean']
-
- indexes {
- (turn_id, position) [unique, name: 'option_turn_position_unique']
- }
-}
-
-Table phase_outcome {
- id integer [pk, increment]
- specification_id integer [not null, ref: > specification.id]
- phase text [not null, note: 'enum: grounding | design | requirements | criteria']
- proposal_turn_id integer [not null, ref: > turn.id]
- status text [not null, default: 'proposed', note: 'enum: proposed | confirmed | superseded']
- summary text [not null]
- closure_basis text [note: 'enum: interviewer_recommended | user_forced']
- confirmation_turn_id integer [ref: > turn.id]
- confirmed_at text
- superseded_at text
- created_at text [not null, default: `datetime('now')`]
-}
-
-Table knowledge_item {
- id integer [pk, increment]
- specification_id integer [not null, ref: > specification.id]
- kind text [not null, note: 'enum: goal | term | context | constraint | decision | assumption | requirement | criterion']
- subtype text
- content text [not null]
- rationale text
- kind_ordinal integer [not null]
-}
-
-Table turn_knowledge_item {
- turn_id integer [not null, ref: > turn.id]
- item_id integer [not null, ref: > knowledge_item.id]
- relation text [not null, default: 'captured', note: 'enum: captured | confirmed | edited | invalidated | reviewed | rejected']
-
- indexes {
- (turn_id, item_id, relation) [pk]
- }
-}
-
-Table knowledge_edge {
- from_item_id integer [not null, ref: > knowledge_item.id]
- to_item_id integer [not null, ref: > knowledge_item.id]
- relation text [not null, note: 'enum: depends_on | derived_from | constrains | verifies | refines']
-
- indexes {
- (from_item_id, to_item_id, relation) [pk]
- }
-}
diff --git a/docs/architecture/pi-seam-extensions.md b/docs/architecture/pi-seam-extensions.md
index 1ae516c38..26a9cb72f 100644
--- a/docs/architecture/pi-seam-extensions.md
+++ b/docs/architecture/pi-seam-extensions.md
@@ -693,7 +693,17 @@ Kernel-activation gate: behavioral kernels should not engage in earnest before a
#### Edge types
-Additive to the M4 edge-type catalogue:
+> **Retired 2026-05-31.** The named-relation catalogue below is
+> superseded by [`docs/design/GRAPH_MODEL.md`](../design/GRAPH_MODEL.md),
+> which defines a closed set of eight structural edge categories
+> (`dependency`, `proof`, `support`, `realization`, `boundary`,
+> `composition`, `association`, `supersession`) with per-category
+> policy. The named relations below map into that scheme — see
+> GRAPH_MODEL.md §"Worked examples" for the oracle-plane mapping. The
+> oracle-plane *nodes* (`Check`, `ValidationMethod`, `Evidence`,
+> `Obligation`) defined above are not retired by this annotation.
+
+Additive to the M4 edge-type catalogue (now retired — see note above):
- `validates` — Check → Requirement | Invariant | Criterion
- `instance_of` — Check → ValidationMethod
@@ -706,7 +716,7 @@ Additive to the M4 edge-type catalogue:
#### Coherence rule for assumption invalidation cascade
-One new rule in the coherence validator: when an `Assumption` transitions to `invalidated`, every active `Requirement` or `Invariant` with a `depends_on` edge to it must transition to `blocked` or surface a coherence violation. This is the cascade in its minimum form — a coherence rule, not an evolution engine. It composes with the M8 coherence work without inventing a separate lifecycle subsystem.
+One new rule in the coherence validator: when an `Assumption` transitions to `invalidated`, every active `Requirement` or `Invariant` with a `depends_on` edge to it must transition to `blocked` or surface a coherence violation. (Under the retired catalogue this keyed on the `depends_on` named relation; under GRAPH_MODEL.md the equivalent edges are `dependency(assumption → requirement)` and `dependency(assumption → invariant)`. The cascade rule is the same.) This is the cascade in its minimum form — a coherence rule, not an evolution engine. It composes with the M8 coherence work without inventing a separate lifecycle subsystem.
#### What the stub enables and what it does not
diff --git a/docs/archive/PLAN_HISTORY.md b/docs/archive/PLAN_HISTORY.md
index 9c2bfad1b..9cd00e2a7 100644
--- a/docs/archive/PLAN_HISTORY.md
+++ b/docs/archive/PLAN_HISTORY.md
@@ -3,6 +3,40 @@
This file is the active POC-line plan archive for `memory/PLAN.md`.
Legacy pre-`next` history was moved out of the live docs tree with the old archived implementation.
+## 2026-05-20 Origin
+
+- 2026-05-20 — **Pre-POC archive and reseed** — razed pre-POC implementation, archived legacy docs and planning memory under `archive/`, tagged `next-baseline`, and reseeded `memory/SPEC.md` and `memory/PLAN.md` from the three canonical POC architecture docs. Phase 3 infra bootstrap was folded into `walking-skeleton` rather than remaining an independent frontier.
+
+## 2026-05-22 Sync archive
+
+Archived out of `memory/PLAN.md` when `web-shell` closed and the live frontier advanced to `graph-data-plane`.
+
+- 2026-05-22 — **web-shell judo review fixes** — Session projection reads share a canonical Brunch session envelope, prompt-side custom-entry classification uses an explicit allowlist, and the React shell builds transcript query params from a typed session projection target without non-null assertions. Verified: `npm run verify` after each slice.
+- 2026-05-22 — **web-shell tie-off queue** — Explicit session projection rejects ambiguous self-description (`brunch.session_binding` duplicates, missing/duplicate Pi headers, binding/header session-id mismatch); `session.transcriptDisplay` includes displayable transcript-native `brunch.elicitation_prompt` rows; M3 browser-open smoke debt was adjudicated as environment-blocked after direct HTTP/WebSocket postconditions passed.
+- 2026-05-22 — **web-shell hardening slices** — Shared JSON-RPC protocol helpers, `ws`-backed `/rpc` transport, persistent browser RPC multiplexing, traversal-safe static asset serving, stable React runtime ownership, and explicit read-only session projection by durable session id landed without REST product reads or connection-as-session semantics.
+- 2026-05-21 — **web-shell initial slices** — Linear transcript policy hardening landed before browser consumption, transcript readers fail fast on non-linear Pi JSONL, the minimal native web HTTP shell and WebSocket RPC bridge came online, and the React shell rendered `workspace.snapshot` chrome via one WebSocket RPC client.
+- 2026-05-20 — **walking-skeleton** — Brunch launches through a pi-backed TUI boot path with coordinator-first spec gating, project-local `.brunch/` state, self-describing Pi JSONL sessions, same-spec `/new`, persistent chrome through pi's extension widget seam, a bin shim, and the store-only runbook checker. Verified: `npm run verify`, manual TUI smoke, automated TUI/coordinator tests, and runbook oracle.
+
+## 2026-06-01 Sync archive
+
+Archived from `memory/PLAN.md` when `pi-ui-extension-patterns` tied off (FE-744) and the live plan promoted `sealed-pi-profile-runtime-state` to Active with an expanded scope that absorbs graph-model prep before `graph-data-plane` (M4) CRUD begins.
+
+### pi-ui-extension-patterns
+
+- **Name:** Prove Pi extension patterns for Brunch UI affordances
+- **Linear:** [FE-744](https://linear.app/hash/issue/FE-744/pi-ui-extension-patterns)
+- **Branch:** `ln/fe-744-pi-ui-extension-patterns` (off `ln/fe-737-web-shell`, parallel to `ln/fe-741-graph-data-plane`)
+- **Kind:** structural (spike-flavored)
+- **Status:** done — implementation-complete and tied off. All Pi extension seam evidence for M5/M6/M7 has landed: command-containment, dynamic chrome semantics, hierarchical spec/session picker startup + in-session flow, RPC/headless initial-selection contract, pty startup oracle, centered branded overlay reuse, evidence-memo reconciliation, structured-exchange schema/builder, TUI/editor adapters, live Pi RPC editor fallback, response-side projection, option-selection comments, structured-exchange editor fallback, raw Pi RPC structured-exchange evaluator proof, discoverable structured-exchange extension source at `src/tui-client/.pi/extensions/structured-exchange/index.ts`, public Brunch RPC structured-exchange tuple parity through the current deterministic permutation set, parity hardening for distinct exchange ids, terminal non-answered statuses, option content/rationale, no repeated deterministic prompts, committed `.fixtures` public-RPC parity probe artifacts, web real-time observation of RPC-originated structured-exchange transcript updates, private code-composed Brunch prompt-pack topology under `src/tui-client/.pi/context/`, the Zod-authored structured-exchange schema layer under `src/tui-client/.pi/extensions/structured-exchange/schemas/`, shared Brunch identity primitives, branded persistent chrome, and Brunch-host branded startup pty evidence. Strict built-in command suppression remains an A18-L Pi API residue carried forward to the embedded-harness prep envelope.
+- **Objective:** Demonstrate the Pi extension seams and Brunch product RPC seams needed before M5/M6/M7 depend on them: product-named commands routed through Brunch handlers; effect blocking for unsupported branch/session flows; dynamic Brunch-owned chrome through one wrapper; Brunch-owned startup/session selection; structured elicitation where system/assistant-originated questions use Pi transcript truth and TUI/RPC adapters; and a public Brunch JSON-RPC structured-exchange loop where an agent-as-user discovers methods, activates workspace/spec/session, starts/resumes assistant-first elicitation, answers pending structured exchanges through Brunch methods, and leaves transcript/projection evidence for current exchange permutations comparable to a TUI session.
+- **Acceptance:** `docs/architecture/pi-ui-extension-patterns.md` catalogs the evidence with verdicts (`proven` / `feasible-with-cost` / `requires-pi-change` / `not-feasible`), distinguishes strict command suppression from lifecycle effect blocking, records the minimum upstream Pi command/keybinding policy ask, and captures the RPC degradation profile for chrome/custom UI. Brunch code exposes a product-named extension entrypoint plus wrappers for chrome, command policy, session lifecycle binding, and `/brunch`; the centered spec/session picker supports an optional continue-last fast path plus hierarchical create-spec/resume-spec/create-session/resume-session decisions without UI-owned session mutation and is shared by startup plus in-session adapters; TUI startup runs a Brunch-owned pre-Pi gate before `InteractiveMode` so prior transcript rendering is opt-in rather than implicit; creating a new session lands in a binding-only session for the selected spec; chrome receives the activated session id instead of fabricating `unbound`; the startup no-resume pty oracle proves stale transcript text is absent before explicit activation. Public RPC structured-exchange parity is covered: `rpc.discover` describes the supported Brunch JSON-RPC surface with method descriptions, param/result schemas, and examples; `workspace.selectionState` / `workspace.activate` let the driver enter a new workspace→spec→session without invoking TUI picker code; `session.startElicitation`, `session.pendingExchange`, and `elicitation.respond` expose an assistant-first pending-exchange lifecycle over Brunch methods, not raw Pi commands; the deterministic agent-as-user driver answers the current structured-exchange permutations through Brunch JSON-RPC only and reports blockers/frictions; the resulting Pi JSONL plus `session.transcriptDisplay` and `session.elicitationExchanges` projections preserve prompt/question/option content/rationale/answer/comment/mode/status artifacts at TUI-comparable quality. Web clients receive real-time product update notifications for RPC-originated structured-exchange mutations and refetch canonical projection handlers rather than reading from a parallel view store. Branded/themed chrome has been recovered through shared identity primitives, persistent chrome wrapper updates, and a Brunch-host branded startup pty oracle; persistent activated chrome has only qualitative manual-polish debt remaining.
+- **Verification:** Inner — verify gate plus unit tests for extension wrappers; coordinator inventory/activation tests for switch decisions; source/contract tests that switcher UI returns decisions rather than mutating sessions; schema tests for structured question result details and JSON-editor request/response parsing. Middle — probe oracles per affordance category (manual checklist + executable postcondition checker on chrome state, JSONL tool results/custom entries emitted, or command-result discriminants); contract tests for Brunch handler shapes (`rpc.discover`, picker selection, elicitation start/pending/respond relay, transcript projections); pty/ANSI-stripped startup oracle proving no prior transcript appears before an explicit resume/open decision; raw Pi RPC probe demonstrating `ctx.ui.editor` JSON fallback round-trips through the documented extension UI protocol as supporting evidence only; scripted TUI demo covering all supported structured-exchange permutations; deterministic public Brunch RPC agent-as-user parity probe where the evaluator has a mission/intention, critical UX or feature-evaluation focus, permutation-bounded turn budget, and blocker/friction report; parity oracle over the saved Pi JSONL plus transcript/exchange projections, including no repeated deterministic prompts; web real-time update smoke proving browser state changes when selected session/exchange state changes via RPC-originated structured-exchange mutations; TUI-originated observation remains covered only if it reuses the same product invalidation path. Outer — manual TUI walkthrough validating visual quality, full-screen startup feel, interaction feel, and controllability cost between scripted-driver and manual paths.
+- **Cross-cutting obligations:** Preserved the linear-transcript invariant (`I19-L`) — no branch creation, no mid-turn state mutations outside the command layer, no parallel chat/turn store. Preserved the workspace hierarchy and startup invariant (`R19` / `I22-L`): the workspace is the cwd, not a user-created selectable object; `.brunch/state.json` is default acceleration, not implicit resume; no prior transcript or agent loop may run before an explicit spec/session activation decision. Spec/session picker UI remained pure decision rendering; `WorkspaceSessionCoordinator` owns inventory, activation, state writes, session creation/opening, and binding. RPC/headless startup exposes structured initial-selection state/results, not the TUI picker. Structured-exchange affordances use Pi transcript truth first: `toolResult.details` is the canonical structured response payload, including optional user `comment` fields for option-selection exchanges. Slash commands and action buttons route writes through the `CommandExecutor`; the JSON-editor RPC fallback is an adapter over Pi's supported extension UI protocol, not a new public Pi command family. Public agent-as-user probes speak Brunch JSON-RPC (`rpc.discover`, `workspace.*`, `session.*`, `elicitation.*`) and delegate to Pi RPC only behind Brunch adapters. Custom-entry kinds declare `lens` per `I18-L` if elicitor-emitted. Establishment-offer affordances stay orientation-first and user-invoked when expanded. TUI chrome/status affordances call Brunch product wrappers rather than raw Pi `ctx.ui.*` primitives; the chrome wrapper does not publish its own `brunch.chrome` status key.
+- **Why now / unlocks:** Lens/review-set/reviewer UX in M5 and authority gating in M6 both assume Brunch can render rich interactive affordances over Pi without forking it. Proving the affordance set early de-risked those frontiers and let the agent-as-user-driver extension question (controllability vs cost trade flagged in `ln-oracles` pass) be answered with evidence rather than estimation.
+- **Traceability:** R4, R14, R16, R17, R19, R20, R21, R24, R27, R28 / D2-L, D5-L, D11-L, D12-L, D13-L, D17-L, D19-L, D21-L, D22-L, D24-L, D25-L, D26-L, D27-L, D29-L, D32-L, D33-L, D34-L, D35-L, D36-L, D37-L, D38-L, D39-L, D40-L, D41-L, D48-L, D49-L, D50-L / I10-L, I13-L, I18-L, I19-L, I22-L, I23-L, I24-L, I25-L, I26-L, I32-L, I33-L / A14-L, A17-L, A18-L, A19-L
+- **Design docs:** [pi-seam-extensions.md](file:///Users/lunelson/Code/hashintel/brunch-next/docs/architecture/pi-seam-extensions.md), [pi-ui-extension-patterns.md](file:///Users/lunelson/Code/hashintel/brunch-next/docs/architecture/pi-ui-extension-patterns.md), [ELICITATION_LENSES.md](file:///Users/lunelson/Code/hashintel/brunch-next/docs/design/ELICITATION_LENSES.md), [REVIEW_SETS.md](file:///Users/lunelson/Code/hashintel/brunch-next/docs/design/REVIEW_SETS.md)
+- **Final execution pointer:** FE-744 landed the public-RPC structured-exchange parity spine and its hardening end to end (see Status above for the full landed-work catalogue). Strict built-in command suppression (A18-L) was accepted as a residual Pi API risk and carried forward to the `sealed-pi-profile-runtime-state` prep envelope. Future expansions of capture-analysis payloads, shared transcript component subparts, or the runtime migration from tuple details to the new Zod exports require a separate `ln-design` pass before implementation.
+
## 2026-05-28 Sync archive
Archived from `memory/PLAN.md` so the live plan only carries active, next, horizon, and recent-completion state.
@@ -52,7 +86,7 @@ Archived from `memory/PLAN.md` so the live plan only carries active, next, horiz
- **Verification:** Inner — verify gate plus synthetic JSONL projection tests. Middle — JSONL round-trip/property tests for raw payloads, `brunch.session_binding`, structured elicitation entries, defensive branch-shape projection behavior, coordinator-created `/new` sessions, and M1 fixture replay parity. Outer — fixture replay parity across the transcript-first run bundle; no new human review was required because brief content and scripted user notes did not change.
- **Cross-cutting obligations:** This frontier is the transcript-side proof for the shared event substrate that later carries structured elicitation entries, session binding, lens switches, side-task results, mentions, and `worldUpdate` without inventing a parallel channel or canonical chat/turn store. JSONL viability must validate sessions created through the `WorkspaceSessionCoordinator`, including the first-entry binding and `/new` same-spec behavior.
- **Traceability:** R7, R8, R16, R17, R19 / D6-L, D11-L, D12-L, D13-L, D18-L, D24-L / I3-L, I8-L, I10-L, I19-L
-- **Design docs:** archived [jsonl-session-viability-note](file:///Users/lunelson/Code/hashintel/brunch-next/archive/archive/docs/architecture/jsonl-session-viability-note.md)
+- **Design docs:** legacy `next/architecture/jsonl-session-viability-note.md` on the `main` branch of `hashintel/brunch`
- **Current execution pointer:** complete; proceed to `web-shell`.
### web-shell
diff --git a/docs/design/GRAPH_MODEL.md b/docs/design/GRAPH_MODEL.md
new file mode 100644
index 000000000..5a2646407
--- /dev/null
+++ b/docs/design/GRAPH_MODEL.md
@@ -0,0 +1,788 @@
+# Graph Model
+
+Canonical reference for the Brunch graph data plane (M4 and onward).
+Owns both the edge layer and the node layer end-to-end.
+
+This document is the lock for the graph model that supersedes the
+prior "large semantic edge-type catalogue + relation-policy registry"
+direction and the deferred `framing_as` modality. It is also the
+source of truth for the type definitions under
+[`src/graph/`](../../src/graph/) and the per-category policy table
+consumed by snapshot/projection builders and the `CommandExecutor`.
+
+`memory/SPEC.md` and `memory/PLAN.md` are reconciled to this doc;
+if later planning text drifts, treat this document as the canonical
+graph-model contract.
+
+## Status
+
+- **Phase 1:** edges, edge policy, reconciliation-need shape. Locked.
+- **Phase 2:** per-plane node kinds, node shape, detail schemas,
+ kind categories, `source` field, `provenance` retirement. Locked.
+
+## Scope and posture
+
+The primary author of graph nodes and edges in Brunch is an LLM
+agent — through direct elicitation, post-exchange capture, review-set
+proposals, and reviewer findings. Two pressures follow from that:
+
+1. **Authoring burden must be low.** The agent should not be asked
+ to choose among many named relation kinds, each with its own
+ tuple-specific legality and its own projection policy.
+2. **Interpretation burden at snapshot time must be low.** Context
+ builders should derive dependency/dependent/support/realization
+ buckets from the stored edge's category and endpoint roles, not
+ from a per-relation policy registry.
+
+This drives the design move: store a small closed set of
+**structural edge categories with endpoint roles**. Derive
+domain-specific labels later from tuple context (see
+[§Tuple-label lookup](#tuple-label-lookup)). The category drives all
+policy.
+
+## Atoms
+
+```ts
+type NodeId = string
+type EdgeId = string
+type Lsn = number // monotonic, one per commit
+```
+
+## GraphEdge — the single shape
+
+```ts
+type EdgeCategory =
+ | "dependency"
+ | "proof"
+ | "support"
+ | "realization"
+ | "boundary"
+ | "composition"
+ | "association"
+ | "supersession"
+
+type EdgeStance = "for" | "against" // required for proof | support
+type EdgeBasis = "explicit" | "accepted_review_set"
+
+interface GraphEdge {
+ readonly id: EdgeId
+ readonly category: EdgeCategory
+ readonly sourceId: NodeId
+ readonly targetId: NodeId
+ readonly stance?: EdgeStance // REQUIRED for proof, support
+ // INVALID for other categories
+ readonly basis: EdgeBasis
+ readonly rationale?: string
+ readonly createdAtLsn: Lsn
+ readonly updatedAtLsn: Lsn // metadata only;
+ // category/source/target/stance are immutable
+ // (change category = delete + recreate)
+}
+// provenance (sessionId, entryId, proposalEntryId) is retired from
+// the edge shape. The change_log at createdAtLsn owns all audit
+// trail; transcript entry pointers are fragile under compaction.
+```
+
+### What this shape does NOT carry
+
+The prior model carried fields that are dropped here. Each has a
+named successor home; nothing is lost, the substrates are different.
+
+| Dropped field | Successor home |
+| ------------------------- | -------------------------------------------------------------------- |
+| `status: proposed` | Review-set drafts (D27-L) — not a graph edge yet |
+| `status: accepted` | The edge simply exists |
+| `status: rejected` | Edge was never created, or was deleted. Audit lives in `change_log` |
+| `status: stale` | A `ReconciliationNeed` with `target.kind = "edge"` references it |
+| `support: weak_candidate` | Lives in structured-exchange `preface` or `capture_*` analysis (D47-L, D50-L) |
+| `support: strong_inference` | Same — promoted to accepted edge only via review set |
+| `relation: ` | Collapsed into category + stance + endpoint roles |
+| `family` | Implied by category |
+| Per-relation policy axes | Per-category policy table below |
+
+Authority for edge writes lives in the `change_log` keyed by
+`createdAtLsn` / `updatedAtLsn`. Edges do not denormalize authority.
+
+## Edge categories — directional first
+
+Seven directional categories and one symmetric. The first cut for
+the agent is the cardinality question — *directional or peer?* —
+which routes to the right rubric before any subtler discrimination.
+
+| Category | Source role | Target role | Meaning |
+| -------------- | ------------ | ------------ | --------------------------------------------------------- |
+| `dependency` | dependency | dependent | Hard upstream — downstream validity depends on it |
+| `proof` | oracle | claim | Oracle-bearing witness (`for`) or refutation (`against`) |
+| `support` | support | claim | Motivation, rationale, evidence; not load-bearing |
+| `realization` | abstract | concrete | Expression / implementation / establishment / assertion |
+| `boundary` | boundary | subject | Scope rule, constraint, exclusion, limit |
+| `composition` | whole | part | Containment / decomposition (NOT sequencing) |
+| `supersession` | successor | predecessor | Intentional replacement; acyclic |
+| `association` | peer | ↔ peer | Weak relatedness; last resort |
+
+Notes on the categories most likely to be confused:
+
+- **`proof` vs `support`.** Both have stance. `proof` is
+ oracle-bearing — the source is an artifact whose function is to
+ witness (or refute) the claim: a `criterion`, a `check`, an
+ `example` used as positive witness or counterexample, a piece of
+ `evidence`. `support` is rationale-bearing — the source explains
+ why the target exists or motivates it without being load-bearing:
+ a `context`, a goal-shaped motivation, a non-oracle example used
+ for illustration. If invalidating the source should drive a
+ `criteria-help` signal or progressive-checkability rendering, it
+ is `proof`; if it just changes the rationale, it is `support`.
+- **`dependency` vs `support`.** Both run upstream-to-downstream.
+ Use `dependency` only when downstream validity or readiness
+ depends on the upstream. Use `support` when the upstream explains
+ or motivates but the downstream could still stand if the support
+ changed.
+- **`realization` vs `composition`.** Composition is whole/part.
+ Realization is abstract/concrete (same conceptual thing,
+ different level of specification). A milestone *composes* its
+ slices; a requirement is *realized* by a slice.
+- **`boundary` vs `support:against`.** Boundary is when the source
+ is itself a scope rule / constraint / non-goal. `support:against`
+ is when the source is evidence or an example that argues against
+ the claim.
+- **`supersession` vs `realization`.** Supersession is temporal /
+ evolutionary replacement of overlapping scope. Realization is
+ abstract-to-concrete elaboration. A new requirement *supersedes*
+ an old requirement it replaces; a module *realizes* the
+ requirement (no replacement).
+
+## Per-category policy
+
+The category drives all policy. The `CommandExecutor` enforces
+structural legality at write time; snapshot/projection builders use
+this table to bucket edges; coherence triggers use the cascade
+column.
+
+| | cascade on src change | recon_need on src change | criteria-help signal | projection effect |
+| -------------- | :-------------------: | :----------------------: | :------------------: | ---------------------------------------------- |
+| `dependency` | ✓ | ✓ | — | — |
+| `proof` | — | advisory | ✓ | — |
+| `support` | — | advisory | — | — |
+| `realization` | — | advisory | — | — |
+| `boundary` | — | ✓ | — | — |
+| `composition` | — | — | — | — |
+| `association` | — | — | — | — |
+| `supersession` | — | — | — | hide predecessor from active context |
+
+Legend:
+
+- **cascade** — automatic block / mark stale on the dependent
+ (e.g. assumption invalidation cascade).
+- **recon_need on src change** — generate a `ReconciliationNeed`
+ pointing at the edge. *Advisory* = generated only if a coherence
+ rule asks for it; the edge does not auto-cascade.
+- **criteria-help** — used by the interviewer to suggest criteria
+ for the target node ("requirement with no `proof` incoming →
+ suggest criterion").
+- **projection effect** — how snapshot/neighborhood builders treat
+ the edge in active-context views.
+
+Only `dependency` triggers automatic cascades. Other categories
+surface as reconciliation needs at most; they do not auto-block
+downstream items.
+
+## Worked examples — same shape across planes
+
+```text
+# Intent (M4)
+A_local_only : assumption -[dependency]-> D_no_auth : decision
+C_no_cloud : constraint -[boundary]-> D_no_auth : decision
+I_no_network : invariant -[realization]-> R_offline : requirement
+CR_airplane : criterion -[proof:+]-> I_no_network : invariant
+E_typical : example -[proof:+]-> R_offline : requirement
+E_outage : example -[proof:-]-> A_local_only : assumption
+
+# Oracle (M5+ stub) — folds prior validates/instance_of/produces/discharges
+CR_airplane : criterion -[realization]-> CH_airplane : check
+CH_airplane : check -[proof:+]-> I_no_network : invariant
+CH_airplane : check -[realization]-> VM_unit : validation_method
+CH_airplane : check -[realization]-> EV_trace : evidence
+CH_airplane : check -[proof:+]-> OB_no_network : obligation
+OB_no_network : obligation -[realization]-> I_no_network : invariant
+
+# Design (M5+ stub)
+R_offline : requirement -[realization]-> M_sqlite_store : module
+IF_session_store : interface -[realization]-> M_sqlite_store : module
+M_sqlite_store : module -[composition]-> M_sqlite_helper: module
+M_sqlite_store : module -[dependency]-> M_pi_session : module
+
+# Plan (M5+ stub)
+MS_graph : milestone -[composition]-> FE_700 : frontier
+FE_700 : frontier -[composition]-> SL_persist : slice
+R_offline : requirement -[realization]-> SL_persist : slice
+SL_persist : slice -[supersession]-> SL_persist_v0 : slice
+```
+
+No plane-crossing rules. Node `kind` does not constrain edge
+category legality. The categories were chosen so that the
+cross-plane vocabulary stays naturally narrow.
+
+## ReconciliationNeed — separate substrate, NOT a graph edge
+
+```ts
+type ReconciliationNeedKind =
+ | "edge_revalidation" // existing edge needs re-checking
+ | "possible_relation" // two nodes might need an edge
+ | "possible_duplicate" // two nodes might be the same
+ | "semantic_conflict"
+ // open extension
+
+type ReconciliationNeedTarget =
+ | { readonly kind: "edge"; readonly edgeId: EdgeId }
+ | { readonly kind: "node_pair"; readonly aId: NodeId; readonly bId: NodeId }
+
+interface ReconciliationNeed {
+ readonly id: string
+ readonly kind: ReconciliationNeedKind
+ readonly target: ReconciliationNeedTarget
+ readonly rationale?: string
+ readonly createdAtLsn: Lsn
+ readonly resolvedAtLsn?: Lsn
+}
+```
+
+Reconciliation needs reference graph state. They are **not** graph
+edges. They do not appear in projection neighborhoods as edges.
+They surface to the user through next-turn delivery as advisory
+items, per D29-L.
+
+`target.kind = "edge"` is the default — recon_needs describe
+relations whose semantic basis may have changed. `target.kind =
+"node_pair"` covers the cases where no edge exists yet (possible
+duplicate, possible relation). When a `node_pair` need resolves to
+"yes, edge exists," create the edge and close the need; an audit
+choice about whether to rewrite the need's target to `edge` form is
+deferred.
+
+## Tuple-label lookup
+
+Tuple-label lookup is a presentation concern only. It produces
+plain-language phrasing for graph snapshots, UI, and prompt
+context. It does not change category policy; it only renders the
+stored edge readably from one endpoint's perspective.
+
+Examples:
+
+| Stored edge | View from source | View from target |
+| ------------------------------------------ | ----------------------- | -------------------------- |
+| `dependency(assumption → decision)` | "premise for decision" | "depends on assumption" |
+| `dependency(assumption → requirement)` | "required by requirement"| "depends on assumption" |
+| `support(context → requirement, for)` | "motivates requirement" | "motivated by context" |
+| `proof(criterion → invariant, for)` | "witnesses invariant" | "witnessed by criterion" |
+| `proof(example → invariant, against)` | "counterexample for invariant" | "challenged by counterexample" |
+| `realization(invariant → requirement)` | "expressed by requirement" | "expresses invariant" |
+| `realization(requirement → design module)` | "realized by module" | "realizes requirement" |
+| `realization(interface → adapter)` | "implemented by adapter"| "implements interface" |
+| `realization(requirement → plan slice)` | "established by slice" | "establishes requirement" |
+| `boundary(non-goal → requirement)` | "rules out / limits" | "bounded by non-goal" |
+| `composition(milestone → slice)` | "contains slice" | "belongs to milestone" |
+| `supersession(new req → old req)` | "supersedes prior" | "superseded by" |
+| `association(A ↔ B)` | "related to B" | "related to A" |
+
+The lookup is a static table keyed on
+`(category, source.kind, target.kind, perspective[, stance])`. It is
+built by inverting the prior catalogue entries plus the `proof` rows
+above. It lives separately from this document — the canonical
+location for the table is TBD (likely
+`src/graph/projection/labels.ts` when projection builders land).
+
+### Realization sub-types — tuple-implied, not edge-encoded
+
+The prior brainstorm proposed splitting `realization` into
+`implementation`, `establishment`, `assertion`, and `expression`.
+The current bet is that those distinctions emerge from tuple
+context — e.g. `realization(requirement → module)` reads as
+"implementation," `realization(requirement → slice)` reads as
+"establishment" — and therefore live in the label-lookup table
+rather than as an edge-shape field. If probe runs surface three or
+more realization sub-clusters that demand distinct cascade or
+projection policy, split `realization` into siblings (see
+[§Open questions](#open-questions)).
+
+## Snapshot bucketing
+
+Snapshot buckets come from category and endpoint role, not from the
+derived label string. A neighborhood snapshot of an intent node:
+
+```text
+anchor: R_offline : requirement
+
+hard dependencies:
+ A_no_network depends on assumption
+
+support:
+ P_field_users motivated by context
+
+proof:
+ CR_airplane witnessed by criterion
+ E_typical witnessed by example
+
+realized by:
+ M_sqlite_store realized by design module
+ SL_persist established by plan slice
+
+boundaries:
+ C_no_cloud bounded by constraint
+
+supersedes:
+ R_offline_v0 supersedes prior requirement
+```
+
+## Structural invariants
+
+- Edge categories are closed. Agents cannot submit arbitrary
+ relation strings.
+- Every edge has exactly one category.
+- `stance` is required iff `category ∈ { proof, support }`.
+- `association` is symmetric at the product level even if stored
+ with `sourceId` / `targetId` columns.
+- `supersession` chains are acyclic.
+- Accepted graph edges are graph truth. Candidate or low-confidence
+ edges live outside graph truth (preface / capture analysis /
+ review-set drafts) until accepted.
+- Tuple-label lookup cannot change category policy.
+- Snapshot bucket assignment comes from category and endpoint role,
+ not from label strings.
+- `composition` does not imply sequencing or dependency.
+- `support` does not imply blocking / staleness by default.
+- Only `dependency` triggers automatic cascades; other categories
+ surface as `ReconciliationNeed` records when policy says so.
+- Cross-plane freedom: node `kind` does not constrain edge
+ category legality.
+
+## Agent-facing command surface
+
+Prefer category-specific commands over one generic
+`createEdge({ category })` command — the call site documents the
+intended role:
+
+```ts
+linkDependency({ dependency, dependent, basis, rationale })
+linkProof({ oracle, claim, stance, basis, rationale })
+linkSupport({ support, claim, stance, basis, rationale })
+linkRealization({ abstract, concrete, basis, rationale })
+linkBoundary({ boundary, subject, basis, rationale })
+linkComposition({ whole, part, basis, rationale })
+linkAssociation({ a, b, basis, rationale })
+linkSupersession({ successor, predecessor, basis, rationale })
+```
+
+The command layer owns structural validation. If a tuple is
+structurally illegal (missing stance, supersession cycle, etc.) the
+tool returns `structural_illegal`; the agent should not invent a
+narrower category to force the write through.
+
+These commands land in the M5 `agent-graph-integration` extension
+under `src/.pi/extensions/graph/tools/` per D52-L. They are out of
+scope for Phase 1 stubs.
+
+### `commitGraph` — atomic batch mutation (D53-L)
+
+The `propose-graph` strategy's load-bearing tool. One tool call
+creates an entire subgraph — nodes and edges — in a single
+transaction with one LSN.
+
+```ts
+commitGraph({
+ nodes: [
+ { ref: "n1", kind: "requirement", title: "...", body: "..." },
+ { ref: "n2", kind: "constraint", title: "...", body: "..." },
+ { ref: "n5", kind: "invariant", title: "...", body: "..." },
+ { ref: "n3", kind: "decision", title: "...", body: "...",
+ detail: { chosen_option: "...", rejected: ["..."], rationale: "..." } },
+ { ref: "n4", kind: "term", title: "...",
+ detail: { definition: "...", aliases: ["..."] } },
+ ],
+ edges: [
+ { category: "dependency", source: "n1", target: "n2" },
+ { category: "boundary", source: "n2", target: "n1" },
+ { category: "realization", source: "n1", target: "n3" },
+ { category: "support", source: { existing: "A12" }, target: "n1",
+ stance: "for" },
+ ]
+})
+```
+
+Reference modes:
+- **Intra-batch**: `"n1"` — a node defined in the same payload
+- **Existing**: `{ existing: "A12" }` — a node already in the graph
+
+CommandExecutor processing:
+
+```
+commitGraph tool call
+ │
+ ▼
+ 1. Validate all nodes structurally
+ 2. Assign real NodeIds to each batch ref
+ 3. Resolve intra-batch refs on edges
+ 4. Resolve existing-node refs (fail if not found)
+ 5. Validate all edges (closed categories, stance, acyclicity)
+ 6. Allocate ONE Lsn
+ 7. Write all nodes + edges + change-log in one transaction
+ 8. Return success + created ids
+ OR structural_illegal + diagnostics for retry
+```
+
+All-or-nothing (I34-L): if any node or edge fails, the entire batch
+is rejected. The agent may retry within a bounded budget; the user
+does not see intermediate failures.
+
+`commitGraph` and `acceptReviewSet` (D27-L) are parallel paths to the
+same CommandExecutor — one for direct agent-authored commits after
+concept acceptance, one for user-reviewed batch proposals.
+
+## Prompting
+
+System-prompt fragment for graph-writing agents:
+
+```text
+When creating graph edges, choose only from Brunch's structural edge categories:
+dependency, proof, support, realization, boundary, composition, association, supersession.
+
+Do not invent relation names such as depends_on, validates, witnesses, implements,
+expresses, motivated_by, or related_concern. Those are rendering labels derived later
+from the stored category and endpoint node kinds.
+
+Create an accepted graph edge only when the relation is clear enough to become graph truth.
+If the relation is weak, speculative, ambiguous, or merely a possible duplicate / possible
+relation, do not create an accepted edge. Keep it in preface / capture analysis or raise a
+reconciliation_need.
+
+Use one edge for the strongest operational role between two nodes. Do not create multiple
+edges merely because several English paraphrases are possible.
+```
+
+Category-selection rubric (ask in order; stop at first strong match):
+
+```text
+0. Should this be graph truth now?
+ - explicit user statement, accepted review set, or high-confidence extraction -> continue
+ - weak inference, possible relation, possible duplicate, unresolved ambiguity -> no accepted edge
+
+1. Is a newer item intentionally replacing an older item for overlapping scope?
+ -> supersession(successor -> predecessor)
+
+2. Is this a whole/part, parent/child, or decomposition relation?
+ -> composition(whole -> part)
+
+3. Does one item limit, exclude, scope, or constrain another?
+ -> boundary(boundary -> subject)
+
+4. If the upstream item is invalidated, must the downstream be revisited/blocked/stale?
+ -> dependency(dependency -> dependent)
+
+5. Is the source an oracle artifact that witnesses or refutes the claim
+ (criterion, check, example as witness/counterexample, evidence)?
+ -> proof(oracle -> claim, stance: for | against)
+
+6. Is one item a concrete expression, implementation, assertion, or establishment of another?
+ -> realization(abstract -> concrete)
+
+7. Does one item motivate, justify, evidence, or challenge another without being load-bearing
+ and without being an oracle?
+ -> support(support -> claim, stance: for | against)
+
+8. Are the two items usefully related, but no stronger role is safe?
+ -> association(a <-> b)
+
+9. Otherwise, create no edge.
+```
+
+## Naming notes
+
+- **`proof` collides with the prior `proof` checkability tier.** If
+ the progressive-checkability ladder lands as node metadata in
+ Phase 2 or later, rename its tier to `formal_proof` to avoid
+ collision with the edge category. The category name *proof*
+ covers any oracle-bearing witness; *formal_proof* is one rung at
+ the strong end of the checkability ladder.
+
+## GraphNode — the single shape
+
+```ts
+interface GraphNode {
+ readonly id: NodeId
+ readonly plane: NodePlane
+ readonly kind: string // per-plane closed enum (see below)
+ readonly title: string // required, non-empty
+ readonly body?: string // markdown content
+ readonly basis: NodeBasis
+ readonly source?: string // free-form epistemic attribution
+ // convention by prompt, not structural validation
+ // e.g. "stakeholder", "regulatory", "derived"
+ readonly detail?: object // per-kind validated sub-structure (JSON column)
+ readonly createdAtLsn: Lsn
+ readonly updatedAtLsn: Lsn
+}
+
+type NodePlane = "intent" | "oracle" | "design" | "plan"
+type NodeBasis = "explicit" | "accepted_review_set"
+// Same semantics as EdgeBasis — how the node entered graph truth.
+// No "inferred" basis; low-confidence material stays in preface /
+// capture analysis until promoted.
+```
+
+### Fields
+
+- **`plane`** — which graph plane owns this node. Structurally
+ validated; determines which `kind` enum applies.
+- **`kind`** — per-plane closed enum. Structurally validated by
+ the `CommandExecutor`. See [§Per-plane node kinds](#per-plane-node-kinds).
+- **`title`** — required, non-empty. The human-readable name of the
+ node. Used for mentions, snapshot display, and search.
+- **`body`** — optional markdown content. Carries the semantic detail
+ the agent authored. Most kinds put their primary content here.
+- **`basis`** — how the node entered graph truth. Same `explicit` /
+ `accepted_review_set` semantics as edges.
+- **`source`** — free-form string for epistemic attribution.
+ Convention by prompt (e.g. "stakeholder", "regulatory", "derived",
+ "domain expert", "market research", "agent synthesis"), not
+ structural validation. Exists for context-snapshot enrichment —
+ it will be transformed back into sparse text in prompt snapshots,
+ not used for policy or filtering.
+- **`detail`** — optional JSON object with per-kind validated
+ sub-structure. See [§Per-kind detail schemas](#per-kind-detail-schemas).
+- **`provenance`** — retired. The `change_log` at `createdAtLsn`
+ owns all audit trail. Transcript entry pointers (sessionId,
+ entryId, proposalEntryId) are fragile under compaction and
+ redundant with `change_log` + `basis`.
+
+## Per-plane node kinds
+
+### Intent plane
+
+Intent kinds fall into three **derived categories** that map to
+spec-grade progression. Category is a pure function of `kind` — it
+is not stored on the node.
+
+| Category | Kind | Modality of claim | Source question |
+| --- | --- | --- | --- |
+| basic | `goal` | Value or outcome claim | "What outcome are we after?" |
+| basic | `thesis` | Position or bet claim | "What do we believe about who this is for and why?" |
+| basic | `term` | Naming commitment | "What do we mean when we say X?" |
+| basic | `context` | Descriptive claim | "What is true about the world this lives in?" |
+| structural | `requirement` | Obligation claim | "What must the system do?" |
+| structural | `assumption` | Uncertainty claim | "What might be false?" |
+| structural | `constraint` | Boundary claim | "What does this rule out?" |
+| structural | `invariant` | Preservation claim | "What must never be broken?" |
+| reasoning | `decision` | Choice claim | "What did we pick among real alternatives?" |
+| reasoning | `criterion` | Oracle claim | "How will we judge that it holds?" |
+| reasoning | `example` | Witness or disambiguator claim | "What concrete case would settle this?" |
+
+11 intent kinds, 3 derived categories.
+
+The **modality of claim** and **source question** columns are
+agent-facing prompting guidance: they help the agent discriminate
+between kinds when authoring nodes and help the elicitor choose
+which question to ask next. The source question is the abstract
+driver — it is not a literal question to parrot, but a heuristic
+for what kind of material the node captures.
+
+**Category semantics:**
+
+- **`basic`** — grounding material. Establishes what/who/why before
+ structural elicitation can proceed. The spec-grade gate from
+ `grounding_onboarding` toward `elicitation_ready` requires a
+ satisficing threshold of `basic`-category nodes. The gate is
+ LLM-judged with a count floor — the agent assesses readiness,
+ but cannot declare grounding complete with zero basic nodes.
+ Grounding rubric (Walter-style questions: what is it, who is it
+ for, what problem, what value, when used, how measured) lives in
+ the prompt as abstract drivers, not structural enforcement.
+- **`structural`** — core specification material. Requirements,
+ assumptions, and constraints form the structural backbone.
+- **`reasoning`** — decisions, criteria, and evidence. Emerges as
+ the agent and user reason about structural material.
+
+### Oracle plane
+
+| Kind | Description |
+| --- | --- |
+| `check` | A verification action that witnesses or refutes a claim |
+| `validation_method` | How a check is executed (unit test, manual, etc.) |
+| `evidence` | A concrete artifact or observation produced by a check |
+| `obligation` | A verification commitment — what must be checked |
+
+### Design plane
+
+| Kind | Description |
+| --- | --- |
+| `module` | A software component or subsystem |
+| `interface` | A contract between modules |
+
+### Plan plane
+
+| Kind | Description |
+| --- | --- |
+| `milestone` | A bounded phase of work |
+| `frontier` | A named canonical work item within a milestone |
+| `slice` | A thin vertical implementation unit within a frontier |
+
+## Per-kind detail schemas
+
+Most kinds use `title` + `body` only. Two kinds have structured
+`detail` sub-schemas validated by the `CommandExecutor`:
+
+```ts
+// decision: REQUIRED detail
+interface DecisionDetail {
+ readonly chosen_option: string
+ readonly rejected: string[]
+ readonly rationale: string
+}
+
+// term: REQUIRED detail
+interface TermDetail {
+ readonly definition: string
+ readonly aliases?: string[]
+}
+```
+
+**Validation rules:**
+
+- `decision` and `term` nodes REQUIRE `detail`; the CommandExecutor
+ rejects creation without it.
+- All other kinds: `detail` must be absent or null.
+- Unknown fields in `detail` are rejected (closed validation).
+- `detail` is stored as a JSON column in SQLite — one `nodes`
+ table for all planes and kinds.
+
+## Prompting guidance for kind discrimination
+
+The modality-of-claim table (§Intent plane) is the primary agent
+rubric. Additional prompting heuristics for kinds that need them:
+
+- **`requirement` duality.** A requirement may be user-story-shaped
+ (stated directly by a stakeholder, `source: "stakeholder"`,
+ `basis: "explicit"`) or projection-shaped (derived from existing
+ goals/theses/constraints via `project-graph`, `source: "derived"`,
+ `basis: "accepted_review_set"`). Both are obligation claims. The
+ `source` and `basis` fields carry the provenance distinction;
+ strategy prompt packs (`step-wise` vs `project-graph`) guide the
+ agent on which framing to use.
+- **`decision` capture criteria.** A claim should become a
+ `decision` only if all of the following hold:
+ 1. **Plausible alternatives existed** — "we chose A over B"
+ 2. **The choice is durable** — it constrains future work
+ 3. **The choice is explicit** — stated, not implied
+ 4. **Rejected alternatives can be named** — at least one
+ 5. **There is a rationale** — "because X"
+
+ The `CommandExecutor` enforces `rejected.length >= 1` in
+ `DecisionDetail`. If none of these criteria hold, the material
+ is probably `context`, `requirement`, or `assumption` — not a
+ decision.
+- **`invariant` vs `constraint`.** A constraint says "don't go
+ there" — it bounds the solution space. An invariant says "this
+ must always hold" — things break if it's violated. Constraints
+ get `boundary` edges; invariants get `dependency` and `proof`
+ edges. If invalidating the source should cascade downstream
+ breakage, it is an invariant; if it merely narrows what's in
+ scope, it is a constraint.
+- **`thesis` carries the grounding material** that a prose spec
+ invests in: who this is for, what problem it solves, what value
+ it creates, what bet we're making. It is not a requirement (a
+ bet, not a need), not a goal (falsifiable, not aspirational),
+ and not an assumption (a chosen position, not a dependency).
+- **`context` promotion heuristic.** Context is the last-resort
+ descriptive bucket — before filing a node as `context`, check
+ whether it should be promoted:
+
+ | If the context… | Promote to… |
+ | --- | --- |
+ | must be true for success | `requirement` or `invariant` |
+ | limits acceptable solutions | `constraint` |
+ | may be false and matters | `assumption` |
+ | chooses among alternatives | `decision` |
+ | is a bet about users/market/value | `thesis` |
+ | just helps interpretation | keep as `context` |
+
+### Beyond the schema contract
+
+Two categories of agent-facing guidance live outside this document
+because they evolve faster than the schema:
+
+- **Observer classification / translation tables** — phrase-pattern
+ → kind mappings for post-exchange capture. Seeded in
+ [`src/agents/strategies/README.md`](../../src/agents/strategies/README.md);
+ lands as prompt-pack content with M5 `agent-graph-integration`.
+- **Topology-driven question ranking** — graph-shape heuristics
+ for what to ask next (e.g. "requirement with no incoming proof
+ edge → suggest a criterion"). Seeded in
+ [`src/agents/lenses/README.md`](../../src/agents/lenses/README.md);
+ lands as lens prompt-pack content with M5.
+
+Both draw on the archived
+`/brunch/docs/design/INTENT_GRAPH_SEMANTICS.md` as source material.
+
+## `framing_as` — retired
+
+The prior `framing_as` orthogonal modality (problem, persona, JTBD,
+non-goal, etc.) is retired. Its work is absorbed by:
+
+- **`thesis`** — carries "what/who/why" material (problem framing,
+ persona framing, value proposition framing)
+- **`term`** — carries naming commitments
+- **`constraint`** — carries exclusions and boundary claims
+- **`invariant`** — carries preservation claims (was formerly
+ conflated with constraints)
+- **`goal`** — carries aspirational intent
+
+The allowed `framing_as` matrix (I7-L) and the "promote when a
+framing demands unique relation policy" escape hatch are both
+retired. No node carries a `framing_as` field.
+
+## Open questions
+
+- **Tuple-label table location.** Likely
+ `src/graph/projection/labels.ts`; lands with the first
+ projection-builder slice in M4 or M5.
+- **Realization watch criterion.** If probe runs surface three or
+ more realization sub-clusters that demand distinct cascade or
+ projection policy, split `realization` into siblings (probable
+ candidates: `implementation`, `establishment`, `assertion`,
+ `expression`).
+- **Multi-edge between same pair.** The system-prompt discipline
+ says "use one edge, the strongest operational role." Whether to
+ also enforce a structural uniqueness constraint on
+ `(sourceId, targetId)` or `(sourceId, targetId, category)` is
+ deferred — the discipline plus the cost of false positives argue
+ against enforcement.
+- **Recon_need closure on edge deletion.** If an edge is deleted,
+ any `ReconciliationNeed` with `target.kind = "edge"` referencing
+ it needs an explicit resolution rule. Likely: mark resolved with
+ reason `target_removed`. Deferred to the recon_need substrate
+ slice.
+
+## Supersession notes
+
+This document supersedes:
+
+- `docs/architecture/pi-seam-extensions.md` §"Edge types" (the
+ earlier M4 edge-type catalogue: `validates`, `instance_of`,
+ `produces`, `discharges`, `depends_on`, `derived_from`,
+ `counterexample_for`, `witnesses`)
+- `archive/docs/design/INTENT_GRAPH_SEMANTICS.md` §"Relations" and
+ §"Edge schema and epistemic metadata" (already archived prior to
+ this document; the edge-layer content is now canonically here)
+- `archive/docs/design/GRAPH_EDGE_CATEGORIES.md` — the brainstorm
+ that produced this document
+- The `framing_as` orthogonal modality and allowed matrix from
+ `memory/SPEC.md` D7-L, A7-L, I7-L — absorbed by `thesis`,
+ `term`, `constraint.subtype`, and `goal`
+- `EdgeProvenance` / node provenance — retired; `change_log` owns
+ audit trail
+
+Outbound references updated with Phase 2 lock:
+
+- `memory/SPEC.md` — D54-L (node shape), D55-L (provenance
+ retirement), D56-L (intent kind categories), D57-L (grounding
+ gate); A7-L retired; I7-L retired; I36-L, I37-L added
+- `memory/PLAN.md` — `sealed-pi-profile-runtime-state` Phase 2
+ node lock acceptance criteria updated
diff --git a/docs/design/REVIEW_SETS.md b/docs/design/REVIEW_SETS.md
index e32abeb5f..71a2c9a5e 100644
--- a/docs/design/REVIEW_SETS.md
+++ b/docs/design/REVIEW_SETS.md
@@ -22,6 +22,11 @@ This pattern is **reusable across generative lenses**: the same mechanism that h
Generative-lens proposals carry **structured entity-draft payloads** in the proposal custom entry. The proposal contains the graph entities and edges that *would* be created on acceptance, in a form `CommandExecutor` can validate without re-parsing.
+Edge drafts follow the locked graph contract from [GRAPH_MODEL.md](GRAPH_MODEL.md):
+closed `category` values, optional `stance` only for `proof`/`support`, and
+`basis: "accepted_review_set"` for proposal-time edges. Review-set payloads no
+longer carry a free-form `relation` string.
+
Approximate shape (refined during M5 implementation):
```text
@@ -43,7 +48,13 @@ Approximate shape (refined during M5 implementation):
...
],
edge_drafts: [
- { from_draft_id, to_draft_id, relation },
+ {
+ category: "support",
+ source_draft_id: "persona-1",
+ target_draft_id: "requirement-2",
+ stance: "for",
+ basis: "accepted_review_set",
+ },
...
],
rubric: {
diff --git a/memory/PLAN.md b/memory/PLAN.md
index 7c5117b47..015259cdf 100644
--- a/memory/PLAN.md
+++ b/memory/PLAN.md
@@ -14,7 +14,11 @@
## Context
-Brunch-next is proceeding on the razed `next` line (tag `next-baseline`) as a thin product layer over `pi-coding-agent`. M0–M3 proved the basic host, JSONL transcript viability, probe/RPC substrate, and read-only web shell; detailed completed frontier definitions now live in `docs/archive/PLAN_HISTORY.md`. The remaining FE-744 work is Pi-wrapping closeout, not public-RPC substrate doubt: raw Pi RPC editor fallback, public Brunch JSON-RPC assistant-first structured-exchange permutation parity, web real-time observation of RPC-originated structured-exchange updates, private Brunch prompt-pack topology, and the Zod-authored structured-exchange details schema layer have landed. FE-744's visual chrome closeout has landed; strict command containment remains recorded as an A18-L residual Pi API risk requiring a Pi command/keybinding policy seam rather than more Brunch wrapper work. After FE-744, `sealed-pi-profile-runtime-state` must make the embedded Pi harness product-safe. In concrete terms, the sealed-profile/runtime-state frontier prevents ambient user/project `.pi/` settings or resources from shaping Brunch behavior, and persists the active operational mode, role preset/runtime bundle, strategy, and lens in the linear transcript so prompt/tool posture can be reconstructed at turn boundaries. The M4 graph data plane remains structurally next after those harness/control-plane risks are scoped.
+Brunch-next is proceeding on the razed `next` line (tag `next-baseline`) as a thin product layer over `pi-coding-agent`. M0–M3 plus `pi-ui-extension-patterns` (FE-744) proved the basic host, JSONL transcript viability, probe/RPC substrate, read-only web shell, Pi extension seams, and public-RPC structured-exchange parity; detailed completed frontier definitions live in `docs/archive/PLAN_HISTORY.md`. The active frontier is now `sealed-pi-profile-runtime-state`, expanded in place into a **prep envelope before `graph-data-plane` (M4) CRUD**. It carries two strands under one branch (`ln/fe-776-graph-layer-prep-profile`): **(a) Pi harness sealing** — Brunch-owned programmatic settings/resource/tool/prompt/keybinding policy isolates product behavior from ambient user/project `.pi/`, and operational-mode / role-preset / strategy / lens state is appended to Pi JSONL as Brunch custom entries reconstructed at turn boundaries; **(b) graph-model lock-and-materialize** — lock the conceptual edge and node contracts in [`docs/design/GRAPH_MODEL.md`](file:///Users/lunelson/Code/hashintel/brunch-next/docs/design/GRAPH_MODEL.md), stub the type/policy surface under `src/graph/`, and prove the A20-L Drizzle 1.0-beta + `drizzle-orm/typebox` + `better-sqlite3` + Pi `registerTool` round-trip so M4 CRUD lands on settled persistence/schema-derivation foundations. Phase 1 (edges) has landed; Phase 2 (nodes) and the Drizzle spike are the remaining moves before `graph-data-plane` resumes. A18-L strict command containment is still carried as a residual Pi API risk to route as a narrow upstream ask if the embedded-harness strand surfaces a clean seam.
+
+Architecture grill (2026-06-01) locked several decisions that shape graph-data-plane and agent-graph-integration: **(1)** source topology `src/{.pi, agents, db, graph, session, rpc, web}` with directed layer dependencies (D52-L); **(2)** `commitGraph` — a single-tool atomic batch mutation accepting `{ nodes, edges }` with intra-batch and existing-node references, one LSN, all-or-nothing (D53-L, I34-L); **(3)** the `propose-graph` strategy bypasses review-set — user accepts a concept, agent generates and persists the full subgraph through `commitGraph` directly (D26-L updated); **(4)** strategy/lens axis split — strategies are interaction shapes (`step-wise-decision-tree`, `step-wise-disambiguate`, `propose-graph`, `project-graph`), lenses are topical focus (`intent`, `design`, `oracle`) (D25-L updated). The `commitGraph` path under `propose-graph` is the primary A14-L proof target: if LLMs cannot produce structurally-legal multi-node multi-edge batches, the core flow must be re-architected.
+
+Phase 2 node grill (2026-06-01) locked the node layer: **(1)** common flat `GraphNode` shape with `title`, `body`, `basis`, `source` (free-form epistemic attribution), and `detail` JSON column (D54-L); **(2)** `provenance` retired from both nodes and edges — `change_log` owns audit trail (D55-L); **(3)** 11 intent kinds in 3 derived categories: basic (`goal`, `thesis`, `term`, `context`), structural (`requirement`, `assumption`, `constraint`, `invariant`), reasoning (`decision`, `criterion`, `example`) (D56-L); **(4)** `framing_as` retired, absorbed by thesis/term/constraint/invariant/goal (A7-L retired, D7-L retired, I7-L retired); **(5)** spec-grade grounding gate: LLM-judged satisficiency with count floor on basic-category nodes, Walter-style rubric in prompt (D57-L); **(6)** `posture` is spec-level, not a graph node; **(7)** modality-of-claim + source-question rubric and context promotion heuristic for agent prompting.
### POC assumption pressure
@@ -27,18 +31,18 @@ The POC should maximize assumption falsification rather than merely implement mi
| A4-L global LSN adequacy | Replay, staleness, or reconciliation ordering needs per-entity/vector clocks. | `graph-data-plane` establishes one-LSN-per-transaction; `turn-boundary-reconciliation` tries to break it with cross-session traces. |
| A5-L probe/transcript driver quality | Agent-as-user probes fail to catch regressions or cannot produce reviewable transcript evidence for realistic Brunch seams. | FE-744 has proved a deterministic public-RPC structured-exchange permutation driver; future brief-based or generative golden runs must pass through the `.fixtures/runs///` probe/transcript artifact path. |
| A6-L unified `graph.*` namespace | Intent/oracle/design/plan semantics become confusing or unsafe under one umbrella. | `graph-data-plane` and `agent-graph-integration` should start unified but watch for namespace pressure. |
-| A7-L `framing_as` modality | Product framings need relation policies that base kinds cannot express. | M4 schema plus targeted probe scenarios exercise framing; promote only if probe evidence demands it. |
+| A7-L `framing_as` modality | ~~Product framings need a node-shape carrier that `framing_as` cannot express.~~ | **Retired.** Phase 2 node lock absorbed `framing_as` into first-class `thesis`, `term`, `context`, `constraint`, `invariant`, and `goal` kinds (D54-L, D56-L). |
| A8-L reconciliation substrate | Gaps, contradictions, process debt, and conflicts need separate substrates immediately. | `graph-data-plane` builds the shared substrate; `coherence-first-class` and known-bad briefs test subtype pressure. |
| A9-L mention ledger granularity | Session-scoped snapshots miss necessary staleness or create noisy hints. | Defer until `turn-boundary-reconciliation`, after graph ids/LSNs exist. |
| A11-L next-turn delivery | Side-task/reviewer results require mid-turn delivery or another event plane. | Keep deferred until M5/M7 side-task/reviewer paths exist; test at turn-boundary rendezvous. |
| A13-L deferred observer/auditor queue | Async audit/backfill needs canonical chat/turn tables or privileged writes. | Not load-bearing after D18-L; defer until a backstop queue is actually introduced. |
-| A14-L review-set structural legality | LLMs cannot produce dry-run-valid entity/edge drafts reliably enough. | M5 must measure structural-legality rate and retry/fallback behavior before depending on proposal-heavy UX. |
+| A14-L graph-mutation structural legality | LLMs cannot produce structurally-legal `commitGraph` batches (multi-node multi-edge with intra-batch refs) or review-set entity drafts reliably enough. The `propose-graph` → `commitGraph` path (D53-L) is the primary proof target. | `graph-data-plane` must land `commitGraph` batch validation (I34-L); `agent-graph-integration` must test LLM generation against real CommandExecutor. This is the highest-stakes assumption: failure requires re-architecture of the propose-graph flow. |
| A15-L establishment hints | Offers are not reconstructable or useful from transcript entries alone. | M5 establishment-offer probe runs and FE-744 chrome affordances exercise this. |
| A16-L reviewer trigger/scope | Reviewer findings are too slow, noisy, or incomplete under deferred policy. | Do not overbuild early; first accepted review-set probe runs should make reviewer policy empirical. |
| A17-L elicitation temperament preference | Users do not need persistent interrogative/proposal preference. | Outer-loop adoption signal only; do not block POC. |
| A18-L command containment | Hiding suggestions + lifecycle blocking leaves unsafe Pi built-ins reachable. | FE-744 product-shell evidence must name any Pi upstream seam before M5/M6 authority work relies on it. |
| A19-L sealed Pi profile | Ambient `.pi` settings/resources still shape Brunch product behavior. | `sealed-pi-profile-runtime-state` is a gate before graph tools and authority-sensitive agent work. |
-| A20-L Drizzle 1.0 beta | Beta blocks migrations, SQLite fidelity, or TypeBox derivation. | `graph-data-plane` starts with a version/schema spike before broad imports. |
+| A20-L Drizzle line + schema path | The chosen Drizzle line blocks migrations, SQLite fidelity, monotonic counter/change-log mechanics, or runtime-schema derivation. | Prove the persistence seam now inside `sealed-pi-profile-runtime-state`: one representative table plus monotonic counter / change-log skeleton, then let `graph-data-plane` inherit the settled choice. |
| A21-L bounded coherence | Contradiction/gap verdicts cannot represent useful coherence without broader judgment. | Keep implementation late (M8), but design known-bad probe scenarios earlier so the rubric is falsifiable. |
| A22-L synchronous elicitor capture | Elicitor over-captures, misses obvious facts, or cannot use preface to resolve uncertainty. | `agent-graph-integration` needs targeted capture probe runs before async observer backstops are reconsidered. |
@@ -47,13 +51,11 @@ The POC should maximize assumption falsification rather than merely implement mi
### Active
-1. `pi-ui-extension-patterns` — FE-744 Pi-wrapping proof is closing: raw Pi RPC editor fallback, public Brunch JSON-RPC structured-exchange permutation parity, web real-time observation of RPC-originated structured-exchange updates, private prompt-pack topology, structured-exchange schema layer, and branded/themed chrome recovery have landed. Remaining work is PR/branch tie-off plus carrying A18-L strict command-containment risk forward to the sealed-profile/runtime-state or upstream-Pi seam, not more FE-744 implementation.
+(none — `graph-data-plane` just completed; `agent-graph-integration` is next)
### Next
-1. `sealed-pi-profile-runtime-state` — Seal Brunch's embedded Pi profile and transcript-backed runtime-bundle state before future agent-loop work depends on ambient-safe settings, prompt composition, or tool gating.
-2. `graph-data-plane` — M4 remains structurally next after FE-744 chrome closeout and the sealed-profile/runtime-state follow-up are scoped; the public-RPC elicitation input loop is no longer the blocker.
-3. `agent-graph-integration` — M5. Graph tools, synchronous elicitor capture, review-set acceptance, and reviewer advisory writes through pi extension seams; all writes via the shared command layer.
+1. `agent-graph-integration` — M5. Graph tools, synchronous elicitor capture, review-set acceptance, and reviewer advisory writes through pi extension seams; all writes via the shared command layer.
### Parallel / Low-conflict
@@ -75,42 +77,48 @@ The POC should maximize assumption falsification rather than merely implement mi
### sealed-pi-profile-runtime-state
-- **Name:** Sealed Pi profile and transcript-backed runtime state
-- **Linear:** unassigned
-- **Kind:** structural hardening
-- **Status:** not-started
-- **Objective:** Turn the discussion-locked Brunch Pi Profile and runtime-bundle model into code/tests by porting the useful `.pi/` probe extensions into explicit Brunch-owned product modules under `src/tui-client/.pi/extensions/*` plus aggregate `src/tui-client/pi-extension-shell.ts`: Brunch-owned programmatic settings/resource/tool/prompt/keybinding policy isolates product behavior from ambient user/project `.pi/`; operational mode / role preset / strategy / lens state is appended to Pi JSONL as Brunch custom entries and reconstructed at turn boundaries.
-- **Why now / unlocks:** FE-744 proved multiple Pi extension seams and exposed the exact weak point: ambient resource discovery is mostly disabled, but `SettingsManager.create(cwd, agentDir)` can still leak behavior-shaping settings, and future `elicit` vs `execute` work needs prompt/tool posture to be stateful without hidden extension memory. This frontier de-risks M5/M6/M7 before graph tools, capture/reviewer jobs, and authority gating depend on the embedded harness.
-- **Acceptance:** A `BrunchPiProfile` (or equivalent module boundary) owns settings policy, resource-loader options, extension factories, keybinding/command policy, tool policy, and prompt policy; tests prove ambient context files/extensions/skills/prompt templates/themes do not load while explicit Brunch-owned extension-discovered resources can load intentionally through Pi `resources_discover`; settings that affect product behavior are overridden/sealed or documented as a Pi upstream seam; runtime extension factories now load explicitly from `src/tui-client/pi-extension-shell.ts` / `src/tui-client/.pi/extensions/*` and reusable TUI components under `src/tui-client/.pi/components/*`, with no root project-local Pi discovery path as product runtime. Full selected-state transcript entries under `brunch.agent_runtime_state` can be appended by Brunch helpers and replayed to reconstruct active operational mode, role preset/runtime bundle, strategy, and lens; turn prep composes prompt packs from base Brunch prompt + operational mode + role preset + strategy + lens + spec readiness grade + elicitation posture + current graph/coherence/world state + pending structured-interaction rules; `elicit` suppresses execute/dangerous tools such as raw `bash`/`write` unless explicitly allowed by the active bundle.
-- **Verification:** Inner — profile/runtimestate unit tests, prompt-composition snapshot tests, and tool-policy contract tests. Middle — ambient `.pi/` fixture/audit tests proving disabled discovery and sealed settings; explicit Brunch resource-injection test proving extension factories may inject Brunch-owned skills/prompts despite ambient `noSkills`/`noPromptTemplates`; JSONL reload/projection tests for runtime init/switch entries; before-agent-start/tool-call policy tests for `elicit`. Outer — manual TUI/RPC smoke that active role/lens/strategy changes are inspectable in transcript and reflected in prompt/tool posture rather than hidden UI state.
-- **Cross-cutting obligations:** Do not expose Pi's generic extension/skill/prompt/theme configuration to Brunch users; do not make Pi skills the primary authority for core operational prompts; keep raw Pi RPC behind Brunch adapters; keep runtime state linear-transcript-backed and compatible with compaction/session-boundary lifecycle hooks (`session_start`, `resources_discover`, `before_agent_start`, `context`, `tool_call`, `session_before_switch`, `session_before_compact`, `session_shutdown`).
-- **Traceability:** R25, R26 / D2-L, D23-L, D39-L, D40-L / I24-L, I25-L / A19-L
-- **Design docs:** [pi-seam-extensions.md](file:///Users/lunelson/Code/hashintel/brunch-next/docs/architecture/pi-seam-extensions.md), [pi-ui-extension-patterns.md](file:///Users/lunelson/Code/hashintel/brunch-next/docs/architecture/pi-ui-extension-patterns.md)
-- **Current execution pointer:** do not start this frontier until FE-744 closes the remaining product-surface relay and chrome-recovery seams. Then scope the profile audit first: preserve current `noContextFiles`/`noExtensions`/`noPromptTemplates`/`noSkills`/`noThemes` posture, prove extension-factory resource injection is intentional, and seal or document the remaining `SettingsManager` leakage. Follow-up slices should add any best-effort lifecycle-generated session display names over Pi `session_info` and tighten prompt/tool policy around transcript-backed runtime bundles.
+- **Name:** Sealed Pi profile, transcript-backed runtime state, and graph-model prep (M4 prep envelope)
+- **Linear:** [FE-776](https://linear.app/hash/issue/FE-776)
+- **Branch:** `ln/fe-776-graph-layer-prep-profile`
+- **Kind:** structural hardening (prep envelope before M4 CRUD)
+- **Status:** done
+- **Objective:** Broader prep envelope before `graph-data-plane` (M4) CRUD work begins. Two strands under one frontier/branch: **(a) Pi harness sealing** — Brunch-owned programmatic settings/resource/tool/prompt/keybinding policy isolates product behavior from ambient user/project `.pi/`; operational mode / role preset / strategy / lens state is appended to Pi JSONL as Brunch custom entries and reconstructed at turn boundaries. **(b) Graph-model lock-and-materialize** — lock the conceptual edge and node contracts in [`docs/design/GRAPH_MODEL.md`](file:///Users/lunelson/Code/hashintel/brunch-next/docs/design/GRAPH_MODEL.md), stub the type/policy surface under `src/graph/`, and prove the A20-L persistence seam now: the Drizzle line, row-schema derivation path, monotonic counter allocation, change-log shape, and Pi `registerTool` round-trip so M4 CRUD lands on settled persistence/schema-derivation foundations.
+- **Why now / unlocks:** FE-744 proved multiple Pi extension seams and exposed the exact weak point: ambient resource discovery and settings policy had to be sealed before future `elicit` vs `execute` work could depend on product-owned prompt/tool posture. Once sealing is in code, the cheapest remaining moves before M4 CRUD are conceptual rather than persistence-mechanical: lock the graph model so review-set drafts, reconciliation needs, snapshot bucketing, and CommandExecutor result discriminants share a stable contract; retire the speculative named-relation catalogue and brainstormed edge taxonomy; settle the persistence/schema-derivation toolchain (A20-L). De-risks M5/M6/M7 before graph tools, capture/reviewer jobs, and authority gating depend on the embedded harness or the graph data plane.
+- **Acceptance:**
+ - **Sealing strand:** A `BrunchPiProfile` (or equivalent module boundary) owns settings policy, resource-loader options, extension factories, keybinding/command policy, tool policy, and prompt policy; tests prove ambient context files/extensions/skills/prompt templates/themes do not load while explicit Brunch-owned extension-discovered resources can load intentionally through Pi `resources_discover`; settings that affect product behavior are overridden/sealed or documented as a Pi upstream seam; runtime extension factories load explicitly from `src/tui-client/pi-extension-shell.ts` / `src/tui-client/.pi/extensions/*` and reusable TUI components under `src/tui-client/.pi/components/*`, with no root project-local Pi discovery path as product runtime. Full selected-state transcript entries under `brunch.agent_runtime_state` can be appended by Brunch helpers and replayed to reconstruct active operational mode, role preset/runtime bundle, strategy, and lens; turn prep composes prompt packs from base Brunch prompt + operational mode + role preset + strategy + lens + spec readiness grade + elicitation posture + current graph/coherence/world state + pending structured-interaction rules; `elicit` suppresses execute/dangerous tools such as raw `bash`/`write` unless explicitly allowed by the active bundle.
+ - **Graph-model strand:** Phase 1 edge contract locked in `docs/design/GRAPH_MODEL.md` and materialized as type/policy stubs under `src/graph/` (✓ landed at commit `100585a1`). Phase 2 node contract locked (✓ landed at commits `b6ecec1e`–`8346e23d`): 11 intent kinds in 3 derived categories (basic/structural/reasoning), common `GraphNode` shape with `detail` JSON column for `decision`/`term`, `provenance` retired from both nodes and edges, `framing_as` retired, `source` as free-form epistemic attribution, modality-of-claim + source-question agent rubric, context promotion heuristic. Materialized as `src/graph/schema/nodes.ts` and reflected in SPEC via D54-L/D55-L/D56-L/D57-L plus I36-L/I37-L. A20-L spike produces a verdict on the persistence seam over one representative intent-plane slice: Drizzle line choice, row-schema derivation path (`drizzle-zod`, `drizzle-orm/typebox`, or equivalent), monotonic counter allocation, change-log writes, and Pi `registerTool` round-trip.
+- **Verification:** Inner — profile/runtimestate unit tests, prompt-composition snapshot tests, tool-policy contract tests, edge/node schema unit tests, category-policy unit tests. Middle — ambient `.pi/` fixture/audit tests proving disabled discovery and sealed settings; explicit Brunch resource-injection test proving extension factories may inject Brunch-owned skills/prompts despite ambient `noSkills`/`noPromptTemplates`; JSONL reload/projection tests for runtime init/switch entries; before-agent-start/tool-call policy tests for `elicit`; persistence spike tests covering one representative table, one insert/select cycle, monotonic counter allocation, change-log append shape, and one Pi `registerTool` parameter binding; I26-L grep-based architectural test wired alongside the first Drizzle import so the single-schema-vocabulary boundary stays enforced. Outer — manual TUI/RPC smoke that active role/lens/strategy changes are inspectable in transcript and reflected in prompt/tool posture rather than hidden UI state.
+- **Cross-cutting obligations:** Do not expose Pi's generic extension/skill/prompt/theme configuration to Brunch users; do not make Pi skills the primary authority for core operational prompts; keep raw Pi RPC behind Brunch adapters; keep runtime state linear-transcript-backed and compatible with compaction/session-boundary lifecycle hooks (`session_start`, `resources_discover`, `before_agent_start`, `context`, `tool_call`, `session_before_switch`, `session_before_compact`, `session_shutdown`). Graph-model lock work must trace to `docs/design/GRAPH_MODEL.md`; node lock must preserve the closed-edge-set invariants (immutable accepted-edge identity, `dependency`-only auto-cascade, separate `ReconciliationNeed` substrate with `{kind:'edge'|'node_pair'}` target). The persistence spike is throwaway scope — one representative slice, no broad imports until the verdict lands; if the current beta line blocks (migrations, SQLite fidelity, schema-derivation bugs, or ergonomics), pick the simpler working adapter/line and continue without re-opening M4 design.
+- **Traceability:** R25, R26 / D2-L, D16-L, D23-L, D39-L, D40-L, D41-L, D51-L / I24-L, I25-L, I26-L / A19-L, A20-L
+- **Design docs:** [GRAPH_MODEL.md](file:///Users/lunelson/Code/hashintel/brunch-next/docs/design/GRAPH_MODEL.md) (canonical graph contract; Phase 1 + Phase 2 locked), [pi-seam-extensions.md](file:///Users/lunelson/Code/hashintel/brunch-next/docs/architecture/pi-seam-extensions.md), [pi-ui-extension-patterns.md](file:///Users/lunelson/Code/hashintel/brunch-next/docs/architecture/pi-ui-extension-patterns.md)
+- **Current execution pointer:**
+ - **Sealing strand:** ✓ Complete. Profile resource/settings/runtime slices landed. Session display names generated from spec title + ordinal, persisted via Pi `session_info`, rendered in TUI chrome.
+ - **Graph-model strand:** ✓ Complete. Phase 1 (edges) + Phase 2 (nodes) locked. A20-L persistence spike validated (`drizzle-orm@0.45.2` + `drizzle-typebox@0.3.3` + `better-sqlite3@12.8.0`).
+ - **Tie-off:** ✓ Both strands at acceptance. `graph-data-plane` (M4 CRUD) is unblocked.
### graph-data-plane
-- **Name:** Graph data plane (intent-first, workspace-graph-ready) (M4)
+- **Name:** Graph data plane (intent-first, workspace-graph-ready) (M4 CRUD)
- **Linear:** [FE-741](https://linear.app/hash/issue/FE-741/graph-data-plane-intent-first-workspace-graph-ready-m4)
-- **Branch:** `ln/fe-741-graph-data-plane` (stacked on `ln/fe-737-web-shell`)
+- **Branch:** `ln/fe-741-graph-data-plane` (stacked on `ln/fe-737-web-shell`; will re-base above `ln/fe-776-graph-layer-prep-profile` once the prep envelope ties off)
- **Kind:** structural
-- **Status:** next / unblocked by FE-744 chrome recovery; paused until the sealed-profile/runtime-state follow-up is scoped
-- **Objective:** Stand up SQLite-backed graph persistence; durable intent-plane nodes and edges; a single global LSN per commit; the change log; the reconciliation-need substrate; named homes for coherence state (verdicts and violations) — all forward-compatible with oracle, design, and plan planes.
-- **Why now / unlocks:** Pins I1-L, I6-L. Unlocks all agent ↔ graph work (M5+) and lets oracle / design / plan planes be added later without re-foundation.
-- **Acceptance:** Graph CRUD + change-log replay tests pass through the `CommandExecutor` public mutation boundary; command results already include success, `needs_human`, `policy_blocked`, `version_conflict`, and `structural_illegal` shapes even if pre-M6 policy classification is minimal; reconciliation-need substrate accepts inserts/updates/resolutions with LSN invariants enforced; oracle-plane stub tables exist (Check, Validation Method, Evidence, Obligation) even if unused; the persistence layer proves the one-transaction protocol that couples authority/result classification, version checks, structural validation, LSN allocation, change-log append, and any coherence updates.
-- **Verification:** Inner gate plus command/result schema/type tests. Middle — property/model-based tests on LSN monotonicity, graph replay, reconciliation invariants, framing matrix, and `CommandExecutor` transaction/result behavior; architectural no-bypass tests. Outer — fixture property invariants on reconciliation-substrate begin running.
-- **Cross-cutting obligations:** Establish the Drizzle + `better-sqlite3` persistence shape, `CommandExecutor` result contract, and no-bypass transaction rule as shared infrastructure for later direct-agent, elicitor-capture, deferred observer/auditor, side-task, migration, and UI-attributed writes. Derive row/insert/update runtime schemas from Drizzle table definitions via TypeBox (`drizzle-orm/typebox` if A20-L resolves to the Drizzle 1.0 beta line; standalone `drizzle-typebox` + `drizzle-orm/typebox-legacy` otherwise) — do not hand-author parallel row schemas. Land the I26-L grep-based architectural test alongside the first Drizzle import so the single-schema-vocabulary boundary stays enforced.
-- **Traceability:** R7, R9, R13 / D3-L, D4-L, D6-L, D8-L, D9-L, D16-L, D20-L, D41-L / I1-L, I6-L, I7-L, I11-L, I26-L / A3-L, A4-L, A20-L
-- **Design docs:** [pi-seam-extensions.md §1 Async side-chain sub-agents](file:///Users/lunelson/Code/hashintel/brunch-next/docs/architecture/pi-seam-extensions.md#1-async-side-chain-sub-agents), [pi-seam-extensions.md §Graph clock, §Reconciliation-need substrate, §Oracle plane](file:///Users/lunelson/Code/hashintel/brunch-next/docs/architecture/pi-seam-extensions.md)
-- **Current execution pointer:** start by scoping the narrow `CommandExecutor` result contract and one-transaction LSN/change-log skeleton before widening CRUD or coherence homes. Pair the first slice with an A20-L spike (Drizzle 1.0 beta + `drizzle-orm/typebox` + `better-sqlite3` + Pi `registerTool` round-trip) so the version pin and schema-derivation path are settled before later slices import them broadly. Keep M4 thin enough to falsify A3-L/A4-L/A6-L/A8-L/A20-L before widening CRUD or coherence homes.
+- **Status:** done (all 6 execution steps complete 2026-06-01)
+- **Objective:** Stand up SQLite-backed CRUD over the prep-envelope-locked graph model: durable intent-plane nodes and edges per [`docs/design/GRAPH_MODEL.md`](file:///Users/lunelson/Code/hashintel/brunch-next/docs/design/GRAPH_MODEL.md); a single global LSN per commit; the change log; the reconciliation-need substrate; named homes for coherence state (verdicts and violations); and the `commitGraph` atomic batch mutation (D53-L) that accepts `{ nodes, edges }` with intra-batch and existing-node references — all forward-compatible with oracle, design, and plan planes. Source topology follows D52-L: `db/` owns Drizzle schema and migrations; `graph/` owns the CommandExecutor, readers, policy, validators, snapshot bucketing, change-log replay, and recon-need substrate; `graph/` imports from `db/`; no other layer imports `db/` directly.
+- **Why now / unlocks:** Pins I1-L, I6-L. Unlocks all agent ↔ graph work (M5+) and lets oracle / design / plan planes be added later without re-foundation. The graph contract and persistence toolchain are settled by the prep envelope, so M4 is pure CRUD/transaction/CommandExecutor work rather than mixed design-and-mechanics. Landing `commitGraph` here (not deferring to M5) means the A14-L proof can run as soon as agent tools are wired.
+- **Acceptance:** Graph CRUD + change-log replay tests pass through the `CommandExecutor` public mutation boundary; command results already include success, `needs_human`, `policy_blocked`, `version_conflict`, and `structural_illegal` shapes even if pre-M6 policy classification is minimal; `commitGraph` batch validation is all-or-nothing (I34-L) — if any node or edge fails structural checks, the entire batch is rejected with diagnostics sufficient for agent self-correction; reconciliation-need substrate accepts inserts/updates/resolutions with LSN invariants enforced; oracle-plane stub tables exist (Check, Validation Method, Evidence, Obligation) even if unused; graph snapshot readers support at least two detail levels (cursory full-graph overview, node-neighborhood with hops) per I35-L; the persistence layer proves the one-transaction protocol that couples authority/result classification, version checks, structural validation (including the closed edge-category set, immutable accepted-edge identity per D51-L, and intra-batch reference resolution per D53-L), LSN allocation, change-log append, and any coherence updates.
+- **Verification:** Inner gate plus command/result schema/type tests. Middle — property/model-based tests on LSN monotonicity, graph replay, reconciliation invariants (target shape `{kind:'edge'|'node_pair'}`), framing matrix, edge structural legality (closed category set, stance scoping, supersession acyclicity), `commitGraph` batch all-or-nothing property (I34-L), intra-batch reference resolution correctness, existing-node reference validation, and `CommandExecutor` transaction/result behavior; architectural no-bypass tests. Outer — fixture property invariants on reconciliation-substrate begin running.
+- **Cross-cutting obligations:** Reuse the Drizzle + `better-sqlite3` persistence shape settled by the prep-envelope A20-L spike; do not re-open the line/adapter choice in M4 unless the spike itself falsifies it. `CommandExecutor` result contract and no-bypass transaction rule become shared infrastructure for later direct-agent, elicitor-capture, deferred observer/auditor, side-task, migration, and UI-attributed writes. Derive row/insert/update runtime schemas from Drizzle table definitions via the schema path chosen during the spike — do not hand-author parallel row schemas. The I26-L grep-based architectural test should already be live from the prep envelope; M4 widens its coverage as new Drizzle imports land. `commitGraph` and `acceptReviewSet` are parallel paths to the same CommandExecutor — both must share the same validation, LSN, and change-log mechanics.
+- **Traceability:** R7, R9, R13 / D3-L, D4-L, D6-L, D8-L, D9-L, D16-L, D20-L, D41-L, D51-L, D52-L, D53-L / I1-L, I6-L, I7-L, I11-L, I26-L, I34-L, I35-L / A3-L, A4-L, A14-L
+- **Design docs:** [GRAPH_MODEL.md](file:///Users/lunelson/Code/hashintel/brunch-next/docs/design/GRAPH_MODEL.md) (canonical graph contract), [pi-seam-extensions.md §1 Async side-chain sub-agents](file:///Users/lunelson/Code/hashintel/brunch-next/docs/architecture/pi-seam-extensions.md#1-async-side-chain-sub-agents), [pi-seam-extensions.md §Graph clock, §Reconciliation-need substrate, §Oracle plane](file:///Users/lunelson/Code/hashintel/brunch-next/docs/architecture/pi-seam-extensions.md) (note: §"Edge types" in pi-seam-extensions.md is retired and superseded by `docs/design/GRAPH_MODEL.md`)
+- **Current execution pointer:** **(1)** ✓ `src/db/` Drizzle schema + `initSchema` DDL push + graph_clock seed. **(2)** ✓ `CommandExecutor` result contract and one-transaction LSN/change-log skeleton with `createNode` proof-of-life, I26-L architectural boundary test, NodeId/EdgeId corrected to `number`. **(3)** skipped — subsumed by (4). **(4)** ✓ `commitGraph` atomic batch mutation with intra-batch + existing-node ref resolution, edge structural validation (closed category set, stance scoping, self-loop rejection), I34-L all-or-nothing (edge failure rolls back nodes), one LSN per batch, one change_log entry per batch. **(5)** ✓ graph snapshot readers (`getGraphOverview`, `getNodeNeighborhood`) with superseded-predecessor exclusion, configurable hop depth, typed domain returns (I35-L); **(6)** ✓ reconciliation-need substrate (`createReconciliationNeed`, `resolveReconciliationNeed`, `getOpenReconciliationNeeds`) with target validation, LSN invariants, and change_log; oracle-plane stub acceptance met by existing `createNode` + `ORACLE_KINDS`. **All M4 graph-data-plane steps complete.**
### agent-graph-integration
- **Name:** Agent ↔ graph integration through the shared command layer (M5)
-- **Linear:** unassigned
+- **Linear:** [FE-785](https://linear.app/hash/issue/FE-785)
- **Kind:** structural
- **Status:** not-started
-- **Objective:** Brunch installs graph tools through pi's extension seams; agent graph operations, elicitor post-exchange capture writes, reviewer-attributed advisory writes, review-set batch acceptances, spec readiness grade/posture updates, and the transcript-native establishment/intent-hint surfaces all route exclusively through the Brunch-owned command layer and shared event substrate; web, TUI, and agent all observe the same changes.
+- **Objective:** Brunch installs graph tools through pi's extension seams; agent graph operations — including `commitGraph` batch mutations for the `propose-graph` direct-commit path (D53-L, D26-L) — elicitor post-exchange capture writes, reviewer-attributed advisory writes, review-set batch acceptances for `project-graph`, spec readiness grade/posture updates, and the transcript-native establishment/intent-hint surfaces all route exclusively through the Brunch-owned command layer and shared event substrate; web, TUI, and agent all observe the same changes. **The primary A14-L proof runs here:** test whether the LLM can produce structurally-legal `commitGraph` batches against the real CommandExecutor with bounded retry.
- **Acceptance:**
```text
@@ -143,7 +151,7 @@ The POC should maximize assumption falsification rather than merely implement mi
└── async substrate (conditional)
└── if observer/auditor queues land → backstops only, not primary capture freshness path
```
-- **Implementation layout:** Put the Pi-facing adapter in one explicit product extension directory, `src/tui-client/.pi/extensions/graph/`, imported by `src/tui-client/pi-extension-shell.ts` as `registerBrunchGraph` rather than discovered dynamically. Use `graph/index.ts` only to register Pi tools, message renderers, and event hooks. Keep tool definitions in `graph/tools/*` (`read-graph`, `create-intent-node`, `update-intent-node`, `link-intent-nodes`, `accept-review-set`), boundary schemas in `graph/schemas/*` (`tool-inputs`, `tool-results`, `custom-entries`), transcript helpers in `graph/transcript/*` (`entries`, `projections`, `renderers`), synchronous capture in `graph/capture/post-exchange-capture.ts`, reviewer target enforcement in `graph/reviewer/reviewer-writes.ts`, and the Pi→CommandExecutor translation seam in `graph/command-adapter.ts`. The extension directory must not own SQLite/Drizzle persistence, LSN allocation, structural graph validators, reviewer-agent implementation, or capture model/prompt machinery; those are Brunch product/core modules passed into the extension through explicit shell options such as `{ graph: { commandExecutor, capturePostExchange?, reviewerWrites? } }`.
+- **Implementation layout:** Per D52-L, graph domain logic lives in `src/graph/` (CommandExecutor, readers, policy, validators, snapshot functions) and persistence in `src/db/`. The Pi-facing adapter goes in one explicit product extension directory, `src/.pi/extensions/graph/`, imported by `src/.pi/pi-extension-shell.ts` as `registerBrunchGraph` rather than discovered dynamically. Use `graph/index.ts` only to register Pi tools, message renderers, and event hooks. Keep tool definitions in `graph/tools/*` (`read-graph`, `commit-graph`, `create-intent-node`, `update-intent-node`, `link-intent-nodes`, `accept-review-set`), boundary schemas in `graph/schemas/*` (`tool-inputs`, `tool-results`, `custom-entries`), transcript helpers in `graph/transcript/*` (`entries`, `projections`, `renderers`), synchronous capture in `graph/capture/post-exchange-capture.ts`, reviewer target enforcement in `graph/reviewer/reviewer-writes.ts`, and the Pi→CommandExecutor translation seam in `graph/command-adapter.ts`. The extension directory must not own SQLite/Drizzle persistence, LSN allocation, structural graph validators, reviewer-agent implementation, or capture model/prompt machinery; those are Brunch product/core modules passed into the extension through explicit shell options such as `{ graph: { commandExecutor, capturePostExchange?, reviewerWrites? } }`. Agent prompts, strategy definitions (including `propose-graph` and `project-graph`), lens definitions, and context builders live in `src/agents/` per D52-L.
- **Verification:** Inner — verify gate plus graph-tool/capture/reviewer command shape tests, proposal-entry schema validation (`brunch.review_set_proposal` must declare `epistemic_status` and support/grounding coverage), establishment-offer / elicitor-intent-hint schema validation (must declare `lens`), structured-exchange `preface` contract tests, and projection-helper tests for latest-offer lookup. Middle — `CommandExecutor` contract tests including `acceptReviewSet` discriminants and the rule that only dry-run-valid proposals become reviewable review sets, direct-DB no-bypass checks, extension-layout/import-boundary tests proving `src/tui-client/.pi/extensions/graph/**` reaches graph mutation only through `command-adapter.ts` and never imports Drizzle/SQLite directly, post-exchange capture fixtures distinguishing committed facts from preface-only implications, reviewer-job restart/idempotence tests keyed by batch-acceptance entry id, reviewer-write-target architectural boundary test (rejects non-`reconciliation_need` targets), `acceptReviewSet` batch-atomicity property tests (one LSN / one change-log entry; partial-batch impossible under mid-batch validation failure), `supersedes`-chain acyclicity property tests, lens-routing correctness property tests, differential test comparing dry-run validation at proposal time vs real-run validation at acceptance, and cross-surface projection checks. Outer — kernel-card-output coverage assertions begin landing through targeted probe runs; first batch-proposal probe (e.g. `propose-scenarios-with-tradeoffs`) replays through review cycle + acceptance; A14-L proposal structural-legality rate captured in probe metadata as POC-phase fitness (not merge gate); 1–2 known-bad coherence-problem probe scenarios exercise reviewer precision; side-task / elicitor-capture / reviewer-attributed writes remain indistinguishable from other writes at the command-layer boundary except for attribution and reviewer's narrow target.
- **Cross-cutting obligations:** Preserve the single-authority mutation rule for primary-agent, elicitor-capture, reviewer, side-task, and batch-acceptance flows by making the `CommandExecutor` the only mutation entry; deferred observer/auditor jobs, if introduced, are operational backstops keyed to transcript anchors, not a revived chat/turn store or privileged primary extraction path; reviewer is advisory and writes only to `reconciliation_need`; lens metadata on elicitor-emitted entries routes capture/reviewer/future-auditor consumption; establishment offers remain orientation artifacts for chrome/web surfaces rather than a default exhaustive lens picker.
- **Traceability:** R10, R13, R17, R21, R22, R23 / D4-L, D13-L, D15-L, D18-L, D20-L, D25-L, D26-L, D27-L, D28-L, D29-L, D30-L, D32-L, D45-L, D46-L, D47-L, D50-L / I2-L, I11-L, I14-L, I15-L, I16-L, I17-L, I18-L, I20-L, I30-L, I31-L, I33-L / A3-L, A11-L, A13-L, A14-L, A16-L, A22-L
@@ -227,22 +235,6 @@ The POC should maximize assumption falsification rather than merely implement mi
- **Traceability:** A5-L
- **Design docs:** [probes-and-transcripts.md](file:///Users/lunelson/Code/hashintel/brunch-next/docs/architecture/probes-and-transcripts.md)
-### pi-ui-extension-patterns
-
-- **Name:** Prove Pi extension patterns for Brunch UI affordances
-- **Linear:** [FE-744](https://linear.app/hash/issue/FE-744/pi-ui-extension-patterns)
-- **Branch:** `ln/fe-744-pi-ui-extension-patterns` (off `ln/fe-737-web-shell`, parallel to `ln/fe-741-graph-data-plane`)
-- **Kind:** structural (spike-flavored)
-- **Status:** implementation-complete / tie-off (command-containment, dynamic chrome semantics, hierarchical spec/session picker startup + in-session flow, RPC/headless initial-selection contract, pty startup oracle, centered branded overlay reuse, evidence-memo reconciliation, structured-exchange schema/builder, TUI/editor adapters, live Pi RPC editor fallback, response-side projection, option-selection comments, structured-exchange editor fallback, raw Pi RPC structured-exchange evaluator proof, discoverable structured-exchange extension source at `src/tui-client/.pi/extensions/structured-exchange/index.ts`, public Brunch RPC structured-exchange tuple parity through the current deterministic permutation set, parity hardening for distinct exchange ids, terminal non-answered statuses, option content/rationale, no repeated deterministic prompts, committed `.fixtures` public-RPC parity probe artifacts, web real-time observation of RPC-originated structured-exchange transcript updates, private code-composed Brunch prompt-pack topology under `src/tui-client/.pi/context/`, the Zod-authored structured-exchange schema layer under `src/tui-client/.pi/extensions/structured-exchange/schemas/`, shared Brunch identity primitives, branded persistent chrome, and Brunch-host branded startup pty evidence have landed. Strict built-in command suppression remains an A18-L Pi API residue.)
-- **Objective:** Demonstrate the Pi extension seams and Brunch product RPC seams needed before M5/M6/M7 depend on them: product-named commands routed through Brunch handlers; effect blocking for unsupported branch/session flows; dynamic Brunch-owned chrome through one wrapper; Brunch-owned startup/session selection; structured elicitation where system/assistant-originated questions use Pi transcript truth and TUI/RPC adapters; and, now active, a public Brunch JSON-RPC structured-exchange loop where an agent-as-user discovers methods, activates workspace/spec/session, starts/resumes assistant-first elicitation, answers pending structured exchanges through Brunch methods, and leaves transcript/projection evidence for current exchange permutations comparable to a TUI session.
-- **Acceptance:** `docs/architecture/pi-ui-extension-patterns.md` catalogs the evidence with verdicts (`proven` / `feasible-with-cost` / `requires-pi-change` / `not-feasible`), distinguishes strict command suppression from lifecycle effect blocking, records the minimum upstream Pi command/keybinding policy ask, and captures the RPC degradation profile for chrome/custom UI. Brunch code exposes a product-named extension entrypoint plus wrappers for chrome, command policy, session lifecycle binding, and `/brunch`; the centered spec/session picker supports an optional continue-last fast path plus hierarchical create-spec/resume-spec/create-session/resume-session decisions without UI-owned session mutation and is shared by startup plus in-session adapters; TUI startup runs a Brunch-owned pre-Pi gate before `InteractiveMode` so prior transcript rendering is opt-in rather than implicit; creating a new session lands in a binding-only session for the selected spec; chrome receives the activated session id instead of fabricating `unbound`; the startup no-resume pty oracle proves stale transcript text is absent before explicit activation. Public RPC structured-exchange parity is now covered: `rpc.discover` describes the supported Brunch JSON-RPC surface with method descriptions, param/result schemas, and examples; `workspace.selectionState` / `workspace.activate` let the driver enter a new workspace→spec→session without invoking TUI picker code; `session.startElicitation`, `session.pendingExchange`, and `elicitation.respond` expose an assistant-first pending-exchange lifecycle over Brunch methods, not raw Pi commands; the deterministic agent-as-user driver answers the current structured-exchange permutations through Brunch JSON-RPC only and reports blockers/frictions; the resulting Pi JSONL plus `session.transcriptDisplay` and `session.elicitationExchanges` projections preserve prompt/question/option content/rationale/answer/comment/mode/status artifacts at TUI-comparable quality. Web clients now receive real-time product update notifications for RPC-originated structured-exchange mutations and refetch canonical projection handlers rather than reading from a parallel view store. Branded/themed chrome has been recovered through shared identity primitives, persistent chrome wrapper updates, and a Brunch-host branded startup pty oracle; persistent activated chrome has only qualitative manual-polish debt remaining.
-- **Verification:** Inner — verify gate plus unit tests for any extension wrappers added; coordinator inventory/activation tests for switch decisions; source/contract tests that switcher UI returns decisions rather than mutating sessions; schema tests for structured question result details and JSON-editor request/response parsing. Middle — probe oracles per affordance category (manual checklist + executable postcondition checker on chrome state, JSONL tool results/custom entries emitted, or command-result discriminants); contract tests for Brunch handler shapes (`rpc.discover`, picker selection, elicitation start/pending/respond relay, transcript projections); pty/ANSI-stripped startup oracle proving no prior transcript appears before an explicit resume/open decision; raw Pi RPC probe demonstrating `ctx.ui.editor` JSON fallback round-trips through the documented extension UI protocol as supporting evidence only; scripted TUI demo covering all supported structured-exchange permutations; deterministic public Brunch RPC agent-as-user parity probe where the evaluator has a mission/intention, critical UX or feature-evaluation focus, permutation-bounded turn budget, and blocker/friction report; parity oracle over the saved Pi JSONL plus transcript/exchange projections, including no repeated deterministic prompts; web real-time update smoke proving browser state changes when selected session/exchange state changes via RPC-originated structured-exchange mutations; TUI-originated observation remains covered only if it reuses the same product invalidation path. Outer — manual TUI walkthrough validating visual quality, full-screen startup feel, interaction feel, and controllability cost between scripted-driver and manual paths.
-- **Cross-cutting obligations:** Preserve the linear-transcript invariant (`I19-L`) — affordance prototypes must not introduce branch creation, mid-turn state mutations outside the command layer, or a parallel chat/turn store. Preserve the workspace hierarchy and startup invariant (`R19` / `I22-L`): the workspace is the cwd, not a user-created selectable object; `.brunch/state.json` is default acceleration, not implicit resume; no prior transcript or agent loop may run before an explicit spec/session activation decision. Spec/session picker UI must remain pure decision rendering; `WorkspaceSessionCoordinator` owns inventory, activation, state writes, session creation/opening, and binding. RPC/headless startup must expose structured initial-selection state/results, not invoke the TUI picker. Structured-exchange affordances must use Pi transcript truth first: `toolResult.details` may be the canonical structured response payload, including optional user `comment` fields for option-selection exchanges, while assistant tool-call args are positional/causal context. Slash commands and action buttons must route writes through the `CommandExecutor`; the JSON-editor RPC fallback is an adapter over Pi's supported extension UI protocol, not a new public Pi command family and not a bypass around Brunch's product RPC surface. Public agent-as-user probes must speak Brunch JSON-RPC (`rpc.discover`, `workspace.*`, `session.*`, `elicitation.*`) and may delegate to Pi RPC only behind Brunch adapters. Any new custom-entry kinds must declare `lens` per `I18-L` if elicitor-emitted. Establishment-offer affordances must stay orientation-first and user-invoked when expanded, rather than turning the full offer tree into a default next-action menu. TUI chrome/status affordances should call Brunch product wrappers rather than raw Pi `ctx.ui.*` primitives; the chrome wrapper must not publish its own `brunch.chrome` status key, and RPC fixtures should assert only chrome events that Pi actually emits for the current wrapper (diagnostic string-array `setWidget`, `setTitle`, notifications, and any future explicit status adapter rather than TUI-only header/footer).
-- **Why now / unlocks:** Lens/review-set/reviewer UX in M5 and authority gating in M6 both assume Brunch can render rich interactive affordances over Pi without forking it. Proving the affordance set early de-risks those frontiers and lets the agent-as-user-driver extension question (controllability vs cost trade flagged in `ln-oracles` pass) be answered with evidence rather than estimation. Can run in parallel with `graph-data-plane` because TUI seams are independent of graph persistence.
-- **Traceability:** R4, R14, R16, R17, R19, R20, R21, R24, R27, R28 / D2-L, D5-L, D11-L, D12-L, D13-L, D17-L, D19-L, D21-L, D22-L, D24-L, D25-L, D26-L, D27-L, D29-L, D32-L, D33-L, D34-L, D35-L, D36-L, D37-L, D38-L, D39-L, D40-L, D41-L, D48-L, D49-L, D50-L / I10-L, I13-L, I18-L, I19-L, I22-L, I23-L, I24-L, I25-L, I26-L, I32-L, I33-L / A14-L, A17-L, A18-L, A19-L
-- **Design docs:** [pi-seam-extensions.md](file:///Users/lunelson/Code/hashintel/brunch-next/docs/architecture/pi-seam-extensions.md), [pi-ui-extension-patterns.md](file:///Users/lunelson/Code/hashintel/brunch-next/docs/architecture/pi-ui-extension-patterns.md), [ELICITATION_LENSES.md](file:///Users/lunelson/Code/hashintel/brunch-next/docs/design/ELICITATION_LENSES.md), [REVIEW_SETS.md](file:///Users/lunelson/Code/hashintel/brunch-next/docs/design/REVIEW_SETS.md).
-- **Current execution pointer:** FE-744 has landed the public-RPC structured-exchange parity spine and its hardening: `rpc.discover` lists the current Brunch methods; activated sessions can start/resume deterministic `present_*` pending exchanges; `elicitation.respond` appends matching `request_answer`, `request_choice`, or `request_choices` toolResult evidence; `session.pendingExchange`, `session.elicitationExchanges`, and `session.transcriptDisplay` project tuple-shaped Pi JSONL; `src/probes/public-rpc-parity-proof.ts` drives the deterministic permutation set through public Brunch JSON-RPC only; and the committed run under `.fixtures/runs/public-rpc-parity/2026-05-29-public-rpc-parity/` carries `session.jsonl`, rendered `transcript.md`, and `report.json`. The structured-exchange UI extension has been remodeled into sequential `present_*` / `request_*` tools under `src/tui-client/.pi/extensions/structured-exchange/`: `present_question`, `present_options`, `request_answer`, `request_choice`, and `request_choices` are registered; review/candidate tools remain named stubs and intentionally unregistered, while future `capture_*` tools are specified as transcript-native ANALYSIS toolResults. The Zod-authored schema layer under `src/tui-client/.pi/extensions/structured-exchange/schemas/` now captures the target present/request/capture details contract (`schema` + `v`, `exchange_id`, `tool_meta`, `comment`/`message`, candidate rubrics/graph refs, and minimal no-graph capture details) with parse and JSON Schema export tests; runtime tools and projections still use the existing tuple details model until a later migration slice deliberately rewires them to those exports. Pi can auto-discover the extension when launched from `src/tui-client` for `/reload`-based iteration, while production imports it explicitly through `src/tui-client/pi-extension-shell.ts`; keep tests under `src/tui-client/.pi/__tests__/`, not in auto-discovered `.pi/extensions` or `.pi/components` resource directories. The Brunch extension shell is explicit again, and Brunch product prompting now has a private prompt-pack/context topology at `src/tui-client/.pi/context/`; do not expose these packs through Pi `resources_discover`/`promptPaths`. Next build: scope `sealed-pi-profile-runtime-state`; do not return to `graph-data-plane` until the sealed-profile/runtime-state follow-up is scoped. A18-L strict command-containment is accepted as a residual Pi API risk unless that follow-up explicitly routes a narrow upstream/API ask. Run a separate `ln-design` pass before expanding capture-analysis payloads, shared transcript component subparts, or the runtime migration from tuple details to the new Zod exports.
-
### flue-pattern-adoption
- **Name:** Adopt selected Flue patterns post-POC
@@ -292,29 +284,27 @@ The POC should maximize assumption falsification rather than merely implement mi
## Recently Completed
-- 2026-05-22 `web-shell` — Done: M3 now serves the native React web shell over one persistent WebSocket RPC client, blocks/adjudicates branchy transcript shapes for session-consuming reads, serves only static HTTP assets (no REST product reads), projects explicit durable sessions through a canonical Brunch session-envelope reader, renders assistant/user/prompt transcript rows, and keeps browser state as a read-only client attachment rather than a durable session. Verified: `npm run verify` after each slice plus direct host/WebSocket smoke for static HTML, missing REST product reads, explicit `{ sessionId, specId }` projections, transcript display, and exchange projection. Accepted deferral: qualitative browser-open smoke remains environment-blocked by the current macOS sandbox.
-- 2026-05-21 `jsonl-session-viability` — Done: Pi JSONL reload preserves coordinator-created binding-only sessions, first assistant/user flushes without duplicate prefixes, `/new` same-spec bindings, raw user/assistant payloads, representative Brunch custom entries, context-participating custom messages, continuity/compaction metadata, structured elicitation entries, defensive active-branch projection behavior, and M1 bundle-local replay parity for the now-retired scripted brief captures. Verified at the time with `npm run verify` after each slice; scripted captures were later deleted when tuple-shaped structured-exchange probes superseded the lightweight prompt/response fixture shape. Watch: M2 validates JSONL as sufficient for Brunch-supported linear sessions on current POC terms; branch-aware Brunch sessions are intentionally unsupported per D24-L, and later side-task, mention, and continuity frontiers still own their final payload semantics.
-- 2026-05-21 `mode-shell-and-fixture-driver` — Done: print and RPC transport modes boot through the Brunch host; named `workspace.snapshot` and `session.elicitationExchanges` handlers project coordinator-selected session state; the first capture path copied the same selected Pi JSONL session projected by RPC and produced scripted brief captures. Those M1 artifacts and their milestone probe script are now retired: FE-744 tuple-shaped public-RPC probe artifacts are the current probe/transcript evidence. Historical verification included `npm run verify`, RPC/print parity smoke, exchange projection tests, fixture replay/projection parity tests, and human inspection.
+- 2026-06-01 `graph-data-plane` (FE-741) — Done: all 6 execution steps complete. **(1)** Drizzle schema + `initSchema` DDL push + graph_clock seed. **(2)** `CommandExecutor` result contract, one-transaction LSN/change-log skeleton, `createNode` proof-of-life, I26-L architectural boundary test. **(3)** skipped (subsumed by 4). **(4)** `commitGraph` atomic batch mutation with intra-batch + existing-node ref resolution, edge structural validation, I34-L all-or-nothing. **(5)** graph snapshot readers (`getGraphOverview`, `getNodeNeighborhood`) with superseded-predecessor exclusion, configurable hop depth, typed domain returns (I35-L). **(6)** reconciliation-need substrate (`createReconciliationNeed`, `resolveReconciliationNeed`, `getOpenReconciliationNeeds`) with target validation + LSN invariants; oracle-plane stub acceptance met by existing node kinds. Verified: `npm run verify` after each slice. `agent-graph-integration` (M5) is now unblocked.
+- 2026-06-01 `sealed-pi-profile-runtime-state` (FE-776) — Done: prep envelope tied off. Both strands complete: **(a)** Pi harness sealing including sealed profile, runtime-state transcript projection, session display names via Pi `session_info`; **(b)** graph-model lock-and-materialize with Phase 1 (edges) + Phase 2 (nodes) locked in `docs/design/GRAPH_MODEL.md`, code stubs under `src/graph/`, and A20-L persistence spike validating `drizzle-orm@0.45.2` + `drizzle-typebox@0.3.3` + `better-sqlite3@12.8.0`. `graph-data-plane` (M4 CRUD) is now unblocked. Verified: `npm run verify` after each slice.
+- 2026-06-01 `pi-ui-extension-patterns` (FE-744) — Done. All Pi extension seam evidence for M5/M6/M7 landed. Detailed frontier definition archived to [docs/archive/PLAN_HISTORY.md §2026-06-01 Sync archive](file:///Users/lunelson/Code/hashintel/brunch-next/docs/archive/PLAN_HISTORY.md).
-Older history: `docs/archive/PLAN_HISTORY.md`
+Older history (including `web-shell`, `graph-data-plane` Phase 1 edge lock, `jsonl-session-viability`, `mode-shell-and-fixture-driver`, `walking-skeleton`): `docs/archive/PLAN_HISTORY.md`
## Dependencies
```text
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]
+ sealed-pi-profile-runtime-state [done] (M4 prep envelope: sealing + graph-model lock)
+ graph-data-plane [done] (M4 CRUD proper)
+ agent-graph-integration [not-started] (M5)
+ subagents-for-proposal-diversity [deferred · optional]
+ authority-model [not-started] (M6)
+ turn-boundary-reconciliation [not-started] (M7)
+ coherence-first-class [not-started] (M8)
+ compaction-and-conflict-widening [not-started] (M9)
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
@@ -335,4 +325,5 @@ notes:
- probes-and-transcripts-evolution runs in parallel across all frontiers; not a spine edge.
- unconnected items are horizon work; surfaced for acknowledgment, not active dependency.
- the m5 -> subagents edge is `optional` — subagents is never a blocker for the spine.
+ - `pi-ui-extension-patterns` (FE-744) tied off 2026-06-01; see docs/archive/PLAN_HISTORY.md.
```
diff --git a/memory/SPEC.md b/memory/SPEC.md
index c5eb1360e..1dc43b9da 100644
--- a/memory/SPEC.md
+++ b/memory/SPEC.md
@@ -104,18 +104,18 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c
| A4-L | A monotonic global LSN per commit (one-LSN-per-transaction) is adequate for change-log replay, reconciliation-need ordering, and mention staleness without per-row vector clocks. | high | open | I1-L, I4-L | M4 + M7: replay fidelity and `worldUpdate` ordering tests. |
| A5-L | Agent-as-user probes over the public Brunch RPC surface can produce regression-quality transcript artifacts without depending on a parallel brief-library subsystem. | medium | partially validated | D5-L, D48-L, D49-L | FE-744 public-RPC parity proves the deterministic transport/projection substrate for current structured-exchange permutations; future brief-based or generative golden-fixture work must enter through the probe/transcript artifact path. |
| A6-L | The graph-native vocabulary can be deferred from explicit per-plane namespacing (`intent.*`, `oracle.*`, etc.) and start unified under `graph.*` without painful rework later. | medium | open | D3-L | M4–M5: if intent-plane plus oracle-plane stubs both fit under one namespace cleanly, the assumption holds. |
-| A7-L | `framing_as` as an orthogonal modality on existing node kinds is sufficient for product-intent ontology (problem, persona, JTBD, etc.) and does not need to become first-class node kinds in the POC. | medium | open | D7-L | Targeted probe runs that exercise framing pressure: if a framing repeatedly demands unique relation policy, promote per the seam-extensions Open Question #8. |
+| A7-L | ~~`framing_as` as an orthogonal modality on existing node kinds is sufficient for product-intent ontology.~~ | — | **retired** | D7-L, D54-L, D56-L | Validated and retired by Phase 2 node lock: `framing_as` is absorbed by first-class `thesis`, `term`, and `constraint.subtype` kinds plus `goal`. The modality, allowed matrix (I7-L), and "promote on relation-policy pressure" escape hatch are all retired. See [`docs/design/GRAPH_MODEL.md` §framing_as — retired](file:///Users/lunelson/Code/hashintel/brunch-next/docs/design/GRAPH_MODEL.md#framing_as--retired). |
| A8-L | One reconciliation-need substrate, sharing the same global LSN as the change log, can absorb impasses, conflicts, gaps, and process debt without needing finer kind subtypes in the POC. | medium | open | D8-L | M8 + adversarial fixtures ("contradictory requirements") exercise the substrate; subtype split deferred per Open Question #10. |
| A9-L | A session-scoped mention ledger of (`entity_id`, `snapshotted_lsn`) is the right granularity for staleness hints; transcript-scoped or graph-scoped ledgers are not needed for the POC. | low | open | I7-L | M7 — turn-boundary reconciliation slice; observed via fixture runs that stress re-read decisions. |
| A11-L | Pi's `prepareNextTurn` plus custom-message delivery are sufficient to express side-task result delivery without inventing a second event plane or forking pi. | medium | open | D15-L | M5 + M7: side-task registry wiring and next-turn delivery proof. |
| A13-L | If Brunch later adds deferred observer/auditor jobs, a durable queue keyed by session id and elicitation-exchange entry range can recover async audit/backfill after process interruption without reintroducing canonical chat/turn tables; whether this shares storage with a generalized work-item/reconciliation table can be deferred. | medium | open | D18-L, I14-L | Deferred until async audit/backfill lands: restart/idempotence tests exercise exchange-keyed jobs once graph writes exist. |
-| A14-L | LLM elicitor agents can reliably produce graph-structurally-legal review-set proposals (well-formed entity drafts and semantic edges that pass `CommandExecutor` structural validation). | medium | open | D27-L | Probe runs that exercise batch-proposal and commitment review-set flows; dry-run `CommandExecutor` validation at proposal time before user review. Fallback (constrained generation, retry-with-feedback, or NL-parse-at-accept) preserves the user-facing review-cycle if reliability is insufficient. |
+| A14-L | LLM elicitor agents can reliably produce graph-structurally-legal graph mutations — both `commitGraph` batches (D53-L) and review-set proposals (D27-L) — as well-formed entity drafts and category-typed edge drafts per [`docs/design/GRAPH_MODEL.md`](file:///Users/lunelson/Code/hashintel/brunch-next/docs/design/GRAPH_MODEL.md) that pass `CommandExecutor` structural validation. The `commitGraph` path under `propose-graph` strategy (D26-L) is the primary proof target: the agent must produce valid multi-node multi-edge batches with intra-batch references from a graph-vocabulary prompt after concept-level user acceptance. | medium | open | D27-L, D51-L, D53-L | Build graph layer (db/ + graph/ CommandExecutor) and commitGraph tool, then test LLM generation against real CommandExecutor validation. Bounded retry on structural_illegal. Fallback (constrained generation, retry-with-feedback, or NL-parse-at-accept) preserves the user-facing flow if reliability is insufficient. |
| A15-L | Establishment hints as transcript-native custom entries (`brunch.establishment_offer`) provide sufficient inspectability, fixture-ability, and ambient-affordance source without a separate establishment-needs graph substrate; whether such a substrate ever shares storage with reconciliation needs can be deferred. | medium | open | D25-L, D30-L | M5+: fixture inspection confirms lens offers are reconstructable from transcript; chrome region renders ambient affordances from the latest such entry. |
| A16-L | Reviewer triggering policy (always-on vs lens-keyed) and reviewer scope (batch + how-far-neighborhood) can be deferred to per-lens decisions without architectural commitment now. | low | open | D29-L | M5+: empirical — reviewer integration reveals which policy avoids unacceptable next-turn latency without losing relevant findings. |
| A17-L | A user-level temperamental preference for interrogative vs proposal-based elicitation meaningfully affects adoption and eventually warrants expression as a user-level setting. | low | open | D25-L, D26-L | Deferred; surfaces from outer-loop walkthroughs and adversarial fixtures once both single-exchange and batch-proposal flows exist in product. |
| A18-L | Hiding unsupported Pi built-ins from autocomplete plus blocking dangerous session effects is sufficient for the POC product shell even though exact interactive built-ins remain callable until Pi exposes command policy. | medium | open | D2-L, D24-L, D34-L, D35-L | `pi-ui-extension-patterns` product-shell review after command-containment and dynamic Brunch chrome evidence; strict suppression requires a Pi upstream/API change if residual exposure is unacceptable. |
| A19-L | Pi's current settings/resource lifecycle can be made product-safe through a sealed Brunch Pi Profile without forking Pi: ambient discovery remains disabled, Brunch-owned extension factories may inject explicit resources, and remaining settings/keybinding leakage can be eliminated through programmatic policy or a narrow upstream seam. | medium | open | D39-L | FE-744/profile audit: source-backed resource-loader/settings audit, tests proving no ambient `.pi/` skills/prompts/themes/extensions/context files affect Brunch, and product-owned resources still load when intentionally injected. |
-| A20-L | The Drizzle 1.0 beta line (specifically `drizzle-orm@^1.0.0-beta.15` or later, with the built-in `drizzle-orm/typebox` path that consumes the new `typebox` package) is stable enough for Brunch to depend on for M4 graph persistence and beyond. | medium | open | D16-L, D41-L | M4 scoping spike: round-trip `drizzle-orm@1.0.0-beta.*` + `drizzle-orm/typebox` + `better-sqlite3` + Pi `registerTool` over a representative intent-plane table; if beta blocks land (migrations, SQLite type fidelity, or schema-derivation bugs), fall back to Drizzle 0.x + standalone `drizzle-typebox` + `drizzle-orm/typebox-legacy` and re-evaluate per release. |
+| A20-L | The chosen Drizzle line and row-schema derivation path can be settled during the prep envelope without forcing later M4 rework: Brunch can prove migrations, SQLite fidelity, monotonic counter allocation, change-log writes, and runtime-schema derivation on one representative persistence slice before CRUD proper starts. | high | **validated** | D16-L, D41-L | **Validated by A20-L spike (2026-06-01).** Stack: `drizzle-orm@0.45.2` + `drizzle-kit@0.31.10` + `better-sqlite3@12.8.0` + `drizzle-typebox@0.3.3` + `@sinclair/typebox@0.34.14`. Proved: (1) `drizzle-typebox` derives valid TypeBox insert/select schemas from Drizzle tables; `Value.Check` validates/rejects correctly. (2) Batch `commitGraph`-shaped transaction (multi-node → intra-batch ref resolution → multi-edge → LSN allocation → change-log append) works atomically; full rollback on FK violation or domain-validation throw. (3) `update().returning()` works for atomic monotonic counter increment; `insert().returning()` gives auto-increment IDs for ref resolution; JSON detail column round-trips cleanly. (4) Pi tool parameters (`typebox` v1.x) and Drizzle row schemas (`@sinclair/typebox` v0.34 via `drizzle-typebox`) serve different roles and never cross — shared enum `const` arrays bridge both. |
| A21-L | The POC can treat coherence as a bounded product verdict over structural legality plus explicitly detected contradictions, gaps, and unresolved reconciliation needs, without solving a general theory of “spec coherence.” | low | open | D8-L | M8 must sharpen the coherence rubric before implementation: known-bad adversarial briefs should show what counts as incoherent, what is merely immature/underspecified, and what should become a reconciliation need. |
| A22-L | The elicitor can perform synchronous post-exchange capture well enough for the POC: high-confidence extractive facts and readiness/posture updates can be committed immediately, while low-confidence implications can be kept out of graph truth and used as disambiguation material. | medium | open | D18-L, D26-L, D45-L, I30-L | M5 agent-graph-integration fixtures and review: compare elicitor-captured graph updates against transcript evidence; track over-capture, missed obvious facts, and whether preface-led disambiguation resolves low-confidence material without an async observer owning primary extraction. |
@@ -125,23 +125,30 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c
- **D1-L — Depend on `pi-coding-agent`, not only `pi-agent-core`.** The POC reuses the coding-agent service bundle, TUI/print adapters, RPC machinery, session logging, and tool plumbing. Dropping down to `pi-agent-core` is a fallback if Brunch proves too different. Depends on: A1-L. Supersedes: —.
- **D2-L — Brunch is an opinionated product, not a pi platform shell.** The POC hardcodes its toolset, system prompt, and policy doctrine; scopes state to `.brunch/`; and hides pi's generic extension surface from end users. Depends on: A1-L. Supersedes: —.
-- **D39-L — Brunch owns a sealed Pi Profile around the embedded harness.** Product behavior must come from Brunch-owned programmatic policy, not ambient Pi discovery. The profile includes settings policy, resource-loader policy, extension factories, keybinding/command policy, tool policy, and prompt policy. Current known posture disables ambient context files, extensions, prompt templates, skills, and themes while loading Brunch's inline extension shell; Pi source confirms extension `resources_discover` can still inject explicit Brunch-owned skill/prompt/theme paths even when `noSkills`/`noPromptTemplates`/`noThemes` disable ambient discovery. Brunch-owned Pi extensions are loaded by an explicit product shell (`src/tui-client/pi-extension-shell.ts`) rather than ambient discovery; *explicit* means the shell statically imports its product extensions and registers them from a fixed ordered list — it must not filesystem-discover or dynamically `import()` extension modules at runtime, because a Brunch-internal discovery layer is itself the discovery this decision rejects. Each product extension exposes one registrar taking explicit dependencies, and the shell wires those dependencies at the call site; the `default` exports under `src/tui-client/.pi/extensions/*` exist only for dev `/reload` iteration, not as a product load path. Product extension modules live under `src/tui-client/.pi/extensions/*`, and reusable Pi TUI components live under `src/tui-client/.pi/components/*`, so they can also be iterated by launching Pi from `src/tui-client` and using `/reload`; the root project-local `.pi/` probe runtime files are retired and must not be treated as product configuration. Test files must not live directly under auto-discovered `.pi/extensions` or `.pi/components` resource directories; TUI-client extension/component tests live under `src/tui-client/.pi/__tests__/`. The remaining weak point is settings leakage through `SettingsManager.create(cwd, agentDir)`, currently only overriding quiet startup; Brunch must audit and either override/seal settings that affect product behavior (shell path/prefix, compaction/retry, image handling, keybindings if exposed) or request a narrow Pi seam. Depends on: D1-L, D2-L, A19-L. Supersedes: treating `noSkills: true` as full profile isolation, relying on user/project `.pi/` defaults to be harmless, nesting Brunch's product extension modules under `src/tui-client/.pi/extensions/brunch/`, or replacing the explicit static shell list with a Brunch-internal filesystem-discovery / `brunchExtensionMeta` / `loadOrder` mechanism as the product runtime load path.
+- **D39-L — Brunch owns a sealed Pi Profile around the embedded harness.** Product behavior must come from Brunch-owned programmatic policy, not ambient Pi discovery. The profile includes settings policy, resource-loader policy, extension factories, keybinding/command policy, tool policy, and prompt policy. Current known posture routes TUI launch policy through `src/brunch-pi-profile.ts`, creates an in-memory Brunch-owned `SettingsManager` policy instead of reading ambient global/project `.pi/settings.json`, disables ambient context files, extensions, prompt templates, skills, and themes while loading Brunch's inline extension shell, and defaults Brunch-launched Pi to offline mode; Pi source confirms extension `resources_discover` can still inject explicit Brunch-owned skill/prompt/theme paths even when `noSkills`/`noPromptTemplates`/`noThemes` disable ambient discovery. Brunch-owned Pi extensions are loaded by an explicit product shell (`src/tui-client/pi-extension-shell.ts`) rather than ambient discovery; *explicit* means the shell statically imports its product extensions and registers them from a fixed ordered list — it must not filesystem-discover or dynamically `import()` extension modules at runtime, because a Brunch-internal discovery layer is itself the discovery this decision rejects. Each product extension exposes one registrar taking explicit dependencies, and the shell wires those dependencies at the call site; the `default` exports under `src/tui-client/.pi/extensions/*` exist only for dev `/reload` iteration, not as a product load path. Product extension modules live under `src/tui-client/.pi/extensions/*`, and reusable Pi TUI components live under `src/tui-client/.pi/components/*`, so they can also be iterated by launching Pi from `src/tui-client` and using `/reload`; the root project-local `.pi/` probe runtime files are retired and must not be treated as product configuration. Test files must not live directly under auto-discovered `.pi/extensions` or `.pi/components` resource directories; TUI-client extension/component tests live under `src/tui-client/.pi/__tests__/`. The profile boundary now owns the audited behavior-shaping settings list in code (`BRUNCH_SETTINGS_POLICY` / `BRUNCH_SETTINGS_AUDITED_GETTERS`), with hostile ambient settings and reload-resilience tests covering shell path/prefix, npm command, ambient resources, skill commands, double-escape behavior, compaction/retry, image/terminal/UI, transport/theme/changelog, and telemetry settings. Remaining profile work is runtime-state/prompt/tool posture, not ambient settings file leakage. Depends on: D1-L, D2-L, A19-L. Supersedes: treating `noSkills: true` as full profile isolation, relying on user/project `.pi/` defaults to be harmless, nesting Brunch's product extension modules under `src/tui-client/.pi/extensions/brunch/`, or replacing the explicit static shell list with a Brunch-internal filesystem-discovery / `brunchExtensionMeta` / `loadOrder` mechanism as the product runtime load path.
- **D40-L — Runtime posture is a transcript-backed Brunch state machine, not hidden extension memory.** Brunch distinguishes operational modes (`elicit`, future `execute`) from agent roles (`elicitor`, `reviewer`, `reconciler`, future `executor/orchestrator`, `scout`, `researcher`, and any deferred observer/auditor roles) and from strategies/lenses. The active top-level role is selected through a role preset/runtime bundle that derives model, thinking level, prompt packs, allowed strategies/lenses, and tool policy rather than storing each knob independently. Brunch runtime helpers append full selected-state product custom entries under `brunch.agent_runtime_state` with `reason: "init" | "switch"`; turn preparation projects the latest valid linear transcript snapshot into prompt and tool posture. Brunch product prompt packs are private code-composed assets under `src/tui-client/.pi/context/prompt-packs/*`, composed only through `src/tui-client/.pi/context/compose-brunch-prompt.ts` and appended by the explicit `src/tui-client/.pi/extensions/prompting.ts` product extension; they are not Pi prompt templates, skills, context-file discovery, or user-invoked slash-command resources. The current `elicit` tool policy is a denylist over side-effecting tools (`bash`, `edit`, `write`) plus user-shell interception, so new safe Brunch extension tools are not hidden by a stale allowlist. The Pi extension module that owns tool policy is `src/tui-client/.pi/extensions/operational-mode.ts`, while product prompting is owned separately by `src/tui-client/.pi/extensions/prompting.ts`; neither should duplicate the other's control-plane responsibility. Depends on: D17-L, D23-L, D25-L, D39-L. Supersedes: mode-only vocabulary, extension-local mutable state as authority for agent behavior, modeling read-only posture as a volatile allowlist of every safe tool, or exposing Brunch prompt packs through Pi resource discovery.
- **D34-L — Command containment separates visibility suppression from effect blocking.** Current Pi extension seams can hide unsupported slash suggestions with autocomplete wrapping and can cancel branch/session effects through lifecycle hooks, but they cannot strictly suppress exact interactive built-in commands before `InteractiveMode` dispatches them. Brunch-owned commands must use product-specific names and route writes through Brunch handlers/`CommandExecutor`; extension command collisions are not an override mechanism. Strict built-in command/keybinding policy is a Pi upstream/API ask, while POC safety relies on hiding generic affordances, blocking dangerous effects (`/fork`, `/clone`, `/tree`, raw session replacement), and failing fast on branched transcripts. Brunch's command-policy code should live in `src/tui-client/.pi/extensions/command-policy.ts`, merging branch/session-effect blocking with any product command allow/deny behavior instead of preserving a branch-only module. Depends on: D2-L, D24-L, A18-L. Supersedes: treating extension `input` handlers or command-name collisions as built-in command allowlisting.
- **D35-L — Dynamic TUI chrome is a Brunch projection wrapper over Pi UI primitives.** Downstream TUI affordances should call a Brunch-owned renderer (`renderBrunchChrome` or its successor) with one activated product-state snapshot rather than scattering raw `ctx.ui.setHeader`, `setFooter`, `setWidget`, title, or working-indicator calls. The wrapper is stateless projection over canonical workspace/session/graph facts, including the real activated session id, while its TUI footer compositor may read Pi footer telemetry (`getGitBranch`, foreign `getExtensionStatuses`) at render time. Brunch chrome does not publish a `brunch.chrome` status key; `ctx.ui.setStatus(key, text)` remains a lateral contribution channel for other extensions and future dynamic Brunch state. RPC clients should rely only on surfaces Pi actually emits for the wrapper (currently diagnostic widget/title, plus any future explicit status adapter) because header/footer/working-indicator are TUI-only in current Pi RPC mode. Session display names are likewise product projections over Pi session metadata: Brunch may append Pi `session_info` entries, but generated names must characterize the selected spec/session transcript rather than replace spec identity or graph truth. Depends on: D2-L, D21-L, D34-L, A18-L. Supersedes: treating Pi UI methods as direct downstream affordance APIs, rendering placeholder session state such as `unbound` after a session is activated, or consuming the status-key namespace for chrome's own static summary.
+- **D52-L — Source topology is `src/{.pi, agents, db, graph, session, rpc, web}` with directed layer dependencies.** `graph/` is the domain layer: CommandExecutor, readers, policy, validators, snapshot bucketing, change-log replay, reconciliation-need substrate; it imports from `db/` (Drizzle schema, migrations, connection lifecycle) and no other layer imports `db/` directly. `session/` owns transcript projection, exchange extraction, workspace coordination, session binding, and LSN staleness tracking over Pi JSONL. `agents/` is organized by axis (`modes/`, `strategies/`, `lenses/`, `contexts/`) and imports snapshot functions from `graph/` and `session/`; it owns prompt composition, context building, and the state definitions that drive mode/role/strategy/lens selection. `.pi/extensions/` houses Pi adapter registrars (agent tools, TUI commands, TUI enhancements); `.pi/components/` houses reusable TUI components. `rpc/` owns Brunch JSON-RPC handlers. `web/` owns the React client. Dependency direction: `.pi/extensions/` and `rpc/` may import from `graph/`, `session/`, and `agents/`; `agents/` imports from `graph/` and `session/`; `graph/` imports from `db/`; `web/` is a standalone build target. Depends on: D2-L, D4-L, D39-L, D40-L. Supersedes: scattering session domain files at `src/` root; nesting prompt composition exclusively under `src/tui-client/.pi/context/`.
#### Data model & vocabulary
- **D3-L — Graph-native, session-native vocabulary; no generic `records.*` surface.** Commands converge on `graph.*` / `session.*` (with per-plane families `intent.*`, `oracle.*`, `design.*`, `plan.*` available when sharper semantics are useful). Depends on: A6-L. Supersedes: —.
-- **D7-L — `framing_as` modality, not first-class kinds, for product-intent framings.** Product framings (problem, persona, JTBD, non-goal, etc.) are an orthogonal modality on existing intent/constraint node kinds, gated by an allowed matrix. Depends on: A7-L. Supersedes: —.
-- **D8-L — Reconciliation needs are a first-class substrate alongside graph truth, change log, and a bounded coherence verdict.** Needs (impasses, gaps, contradictions, process debt) share the same global LSN as the change log and follow the same mutation invariant. For the POC, coherence is not an unbounded aesthetic or philosophical judgment; it is the product-visible verdict produced from structural legality plus surfaced contradictions/gaps/unresolved needs, with the exact rubric still open under A21-L until M8. Depends on: A8-L, A21-L. Supersedes: —.
-- **D9-L — Reasoning records split by shape.** `decision` is graph-native; `impasse` is a reconciliation need, not a graph node; `justification` stays compact (rendered text on the decision) until forced otherwise. Depends on: D8-L. Supersedes: —.
+- **D7-L — ~~`framing_as` modality, not first-class kinds.~~ Retired.** `framing_as` is absorbed by first-class `thesis`, `term`, `constraint.subtype`, and `goal` kinds per the Phase 2 node lock. No node carries a `framing_as` field. See [`docs/design/GRAPH_MODEL.md` §framing_as — retired](file:///Users/lunelson/Code/hashintel/brunch-next/docs/design/GRAPH_MODEL.md#framing_as--retired). Depends on: A7-L (retired). Superseded by: D54-L, D56-L.
+- **D8-L — Reconciliation needs are a first-class substrate alongside graph truth, change log, and a bounded coherence verdict.** Needs (impasses, gaps, contradictions, process debt) share the same global LSN as the change log and follow the same mutation invariant. Per [`docs/design/GRAPH_MODEL.md`](file:///Users/lunelson/Code/hashintel/brunch-next/docs/design/GRAPH_MODEL.md#reconciliationneed--separate-substrate-not-a-graph-edge), each need targets exactly one of `{kind: 'edge', edgeId}` or `{kind: 'node_pair', aId, bId}` and is not itself a graph edge. For the POC, coherence is not an unbounded aesthetic or philosophical judgment; it is the product-visible verdict produced from structural legality plus surfaced contradictions/gaps/unresolved needs, with the exact rubric still open under A21-L until M8. Depends on: A8-L, A21-L. Refined by: D51-L. Supersedes: any `concerns`-edge wiring from reconciliation needs to graph nodes.
+- **D9-L — Reasoning records split by shape.** `decision` is graph-native; `impasse` is a reconciliation need, not a graph node; `justification` stays compact (rendered text on the decision) until forced otherwise. Phase 2 (per `docs/design/GRAPH_MODEL.md`) keeps `decision` as a plain node rather than a hyper-edge / hub-node for the POC. Depends on: D8-L. Supersedes: —.
+- **D54-L — Graph node shape is a common flat interface with `title`, `body`, `basis`, `source`, and a per-kind `detail` JSON column; canonical contract is [`docs/design/GRAPH_MODEL.md` §GraphNode](file:///Users/lunelson/Code/hashintel/brunch-next/docs/design/GRAPH_MODEL.md#graphnode--the-single-shape).** All planes and kinds share one `nodes` table. `plane` determines which closed `kind` enum applies; `kind` is structurally validated. `basis ∈ explicit | accepted_review_set` (same semantics as edges). `source` is a free-form string for epistemic attribution (e.g. "stakeholder", "regulatory", "derived") — convention by prompt, not structural validation; it exists for context-snapshot enrichment and will be rendered back into sparse text, not used for policy or filtering. `detail` is an optional JSON column with per-kind validated sub-structures: `decision` requires `{ chosen_option, rejected, rationale }`, `term` requires `{ definition, aliases? }`; all other kinds must omit `detail`. `provenance` is retired from the node shape — `change_log` at `createdAtLsn` owns all audit trail. The intent kind rubric (modality of claim + source question per kind) is agent-facing prompting guidance in GRAPH_MODEL.md §"Prompting guidance for kind discrimination", not structural enforcement. Depends on: D4-L, D16-L, D52-L, D56-L. Supersedes: D7-L (`framing_as` modality), the deferred Phase 2 node placeholder in prior GRAPH_MODEL.md.
+- **D55-L — `provenance` retired from both edges and nodes; `change_log` owns all audit trail.** Transcript entry pointers (`sessionId`, `entryId`, `proposalEntryId`) are fragile under compaction and redundant with `change_log` + `basis`. `basis` tells you the authority path; `change_log[createdAtLsn]` tells you the durable audit context. Edges retain `basis` and `rationale`. Nodes have `basis` and `source` (epistemic attribution). Depends on: D16-L, D51-L, D54-L. Supersedes: `EdgeProvenance` from Phase 1 edge lock, the planned node-side `provenance` symmetry with edges.
+- **D56-L — Intent node kinds: 11 kinds in 3 derived categories (basic / structural / reasoning); canonical contract is [`docs/design/GRAPH_MODEL.md` §Per-plane node kinds](file:///Users/lunelson/Code/hashintel/brunch-next/docs/design/GRAPH_MODEL.md#per-plane-node-kinds).** `basic` (goal, thesis, term, context) carries grounding material; `structural` (requirement, assumption, constraint, invariant) carries core specification; `reasoning` (decision, criterion, example) carries decisions and evidence. Category is a pure function of `kind` — not stored on the node. The `basic` category maps to spec-grade progression: grounding-gate readiness depends on satisficing threshold of basic-category nodes (D57-L). `thesis` carries "what/who/why/for whom" material (La Carte Blanche style). `term` carries canonical naming commitments (ubiquitous language). `invariant` is first-class (not a constraint subtype) because its operational role differs: invariants get `dependency` and `proof` edges, constraints get `boundary` edges. Each intent kind has a modality-of-claim and source-question rubric for agent prompting (GRAPH_MODEL.md §"Prompting guidance"). Oracle (check, validation_method, evidence, obligation), design (module, interface), and plan (milestone, frontier, slice) kinds are stable from worked examples. Depends on: D54-L. Supersedes: D7-L (`framing_as`), A7-L.
+- **D57-L — Spec-grade grounding gate is LLM-judged satisficiency with a count floor on basic-category nodes.** The gate from `grounding_onboarding` toward `elicitation_ready` is not structurally enforced by rubric coverage checks. The agent judges readiness using prompt-embedded abstract drivers (Walter-style: what is it, who is it for, what problem, what value, when used, how measured) but cannot declare grounding complete with zero `basic`-category nodes. Grounding elicitation interweaves basic intent nodes with spec-level posture establishment. `posture` is a spec-level property set, not a graph node kind. Depends on: D45-L, D56-L. Supersedes: D30-L grounding-bundle anchor vocabulary as the sole readiness gate description. Refines: D30-L, D45-L.
+- **D51-L — Graph edge model is a closed structural-category set with a separate ReconciliationNeed substrate; canonical contract is [`docs/design/GRAPH_MODEL.md`](file:///Users/lunelson/Code/hashintel/brunch-next/docs/design/GRAPH_MODEL.md).** Every accepted edge is one of eight closed categories (`dependency`, `proof`, `support`, `realization`, `boundary`, `composition`, `association`, `supersession`); `stance: for | against` is valid only on `proof` and `support`; `basis ∈ explicit | accepted_review_set` (no `inferred`). Accepted edges have no mutable `status` field — `proposed` lives in review-set drafts, `rejected` is absent + change-log audit, `stale` is represented by a `ReconciliationNeed`. Identity fields (`category`, `sourceId`, `targetId`, `stance`) are immutable on an accepted edge; a "category change" is delete + recreate. Only `dependency` cascades automatically; other categories surface advisory recon-needs rather than auto-blocking. Cross-plane edges are unrestricted at the POC stage; `realization` subtypes (implementation/establishment/assertion/etc.) may be derived from node-tuple lookup later rather than encoded on the edge. `ReconciliationNeed` is a separate substrate whose target is exactly `{kind:'edge', edgeId}` or `{kind:'node_pair', aId, bId}` — it is not itself a graph edge. Depends on: D4-L, D8-L, D16-L, D27-L, A14-L. Supersedes: the named-relation catalogue in `docs/architecture/pi-seam-extensions.md` §"Edge types" (`validates`, `instance_of`, `produces`, `discharges`, `depends_on`, `derived_from`, `counterexample_for`, `witnesses`), the per-relation policy registry / lookup, the brainstormed expanded edge taxonomy in `archive/docs/design/GRAPH_EDGE_CATEGORIES.md`, and any `concerns`-edge wiring from reconciliation needs to graph nodes.
#### Authority & mutation
- **D4-L — One shared mutation surface owns graph truth.** Every semantic graph mutation routes through Brunch-owned typed command handlers responsible for validation, structural legality, optimistic concurrency, event emission, audit attribution, and coherence triggering. Agents and adapters must not touch the ORM or SQLite directly. Depends on: A3-L. Supersedes: —.
- **D20-L — Command execution owns the pre-M6 authority seam.** Callers submit product commands to a Brunch `CommandExecutor` and receive a structured result; they do not call a standalone authority service or graph persistence directly. The executor is the public mutation boundary that hides attribution, optimistic concurrency, structural validation, the minimal pre-M6 policy classifier, transaction execution, LSN allocation, change-log append, and coherence-trigger hooks. Before M6, the policy logic may be deliberately small, but the result shape must already include `needs_human`, `policy_blocked`, `version_conflict`, and `structural_illegal` so early RPC, print, agent-tool, deferred observer/auditor, and side-task code cannot bake in permissive mode-specific shortcuts. Depends on: D4-L, D16-L. Supersedes: the separate optional `AuthorityGate` / generic policy-service mental model.
-- **D27-L — Review-set proposals are structured entity-draft payloads; batch acceptance is one atomic `CommandExecutor` call.** The elicitor's proposal custom entry (`brunch.review_set_proposal`) contains the graph entities and edges that *would* be created on acceptance, in a form `CommandExecutor` can dry-run-validate at proposal time so `structural_illegal` / `policy_blocked` discriminants surface before the user reviews. Only proposals that pass this dry-run validation are surfaced as user-reviewable review sets; invalid generations stay internal to retry/regeneration paths rather than becoming review UI state. Acceptance is one `acceptReviewSet` command that consumes one LSN, writes the entire batch in one transaction, appends one change-log entry attributed to the user, triggers coherence updates, and enqueues any reviewer job. "Accept with edits" does not exist as a primitive: the cycle is approve / request changes (triggers regeneration of a successor proposal) / reject. Applies to batch-proposal flows and commitment review sets. Depends on: A14-L, D4-L, D20-L, D26-L. Supersedes: any caller-side multi-step "patch then commit" mental model.
+- **D27-L — Review-set proposals are structured entity-draft payloads; batch acceptance is one atomic `CommandExecutor` call.** The elicitor's proposal custom entry (`brunch.review_set_proposal`) contains the graph entities and edges that *would* be created on acceptance — edge drafts follow the locked contract in [`docs/design/GRAPH_MODEL.md`](file:///Users/lunelson/Code/hashintel/brunch-next/docs/design/GRAPH_MODEL.md) (`{category, sourceId, targetId, stance?, basis: 'accepted_review_set'}`) rather than a free-form `relation` string — in a form `CommandExecutor` can dry-run-validate at proposal time so `structural_illegal` / `policy_blocked` discriminants surface before the user reviews. Only proposals that pass this dry-run validation are surfaced as user-reviewable review sets; invalid generations stay internal to retry/regeneration paths rather than becoming review UI state. Acceptance is one `acceptReviewSet` command that consumes one LSN, writes the entire batch in one transaction, appends one change-log entry attributed to the user, triggers coherence updates, and enqueues any reviewer job. "Accept with edits" does not exist as a primitive: the cycle is approve / request changes (triggers regeneration of a successor proposal) / reject. Applies to batch-proposal flows and commitment review sets. Depends on: A14-L, D4-L, D20-L, D26-L. Supersedes: any caller-side multi-step "patch then commit" mental model.
+- **D53-L — `commitGraph` is a single-tool atomic batch mutation accepting `{ nodes, edges }` with intra-batch and existing-node references.** The propose-graph strategy's load-bearing tool for direct graph commitment after concept-level user acceptance (D26-L). The agent produces one tool call with a `nodes` array (each carrying a temporary batch `ref`, `kind`, and content fields) and an `edges` array (each carrying `category`, source/target as either an intra-batch `ref` or an existing node id, optional `stance`, and `rationale`). The CommandExecutor validates all nodes structurally, assigns real NodeIds to each batch ref, resolves intra-batch and existing-node references, validates all edges per the closed category set and structural invariants in [`docs/design/GRAPH_MODEL.md`](file:///Users/lunelson/Code/hashintel/brunch-next/docs/design/GRAPH_MODEL.md), allocates one LSN, writes all nodes + edges + change-log in one SQLite transaction, and returns success with created ids or `structural_illegal` with diagnostics sufficient for agent self-correction. On validation failure the agent may retry within a bounded budget; the user does not see intermediate failures. `commitGraph` and `acceptReviewSet` (D27-L) are parallel paths to the same CommandExecutor — one for direct agent-authored commits after concept acceptance, one for user-reviewed batch proposals. Depends on: D4-L, D20-L, D51-L, D52-L. Supersedes: —.
#### Transport & client
@@ -198,7 +205,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c
- **D6-L — JSONL-first transcript persistence in `.brunch/sessions/`; SQLite-backed graph persistence in `.brunch/`.** Two durability surfaces with distinct responsibilities. Transcript starts on pi `SessionManager` redirected to the project-local directory; graph plane is SQLite from M4. Brunch does not recreate canonical `chat` or `turn` tables while Pi JSONL remains viable for Brunch-supported linear sessions. Validated by M2. Supersedes: —.
- **D15-L — Side tasks are a first-class Brunch subsystem delivered through the same transcript/event substrate.** Side tasks are main-agent-invoked, non-blocking work items: the main agent fires them and continues without awaiting a return value. A Brunch-owned `SideTaskRegistry` tracks status; the only path a side task influences the main agent is by appending a custom-message status update to the session log that arrives at the next-turn boundary through the existing `prepareNextTurn` path — never mid-turn. Side-task writes remain subject to the same command-layer authority as primary-agent writes. This is distinct from D44-L Subagent (main-agent-invoked **blocking** tool call whose result is returned directly as tool content). Depends on: A11-L, D4-L. Supersedes: —.
-- **D16-L — Graph persistence uses Drizzle over `better-sqlite3`, with one-LSN-per-commit and no bypass paths.** The command layer owns precondition checks, structural validation, entity writes, LSN allocation, change-log append, and any coherence updates inside one transaction. This rule applies equally to migrations and maintenance code; there is no privileged write path outside the command-executor protocol. Runtime row/insert/update schemas are derived from Drizzle table definitions through a D41-L-compatible adapter (`drizzle-zod`, `drizzle-orm/typebox`, or equivalent chosen during the A20-L graph-data-plane spike) rather than hand-authored alongside the table. The Drizzle version pin is open per A20-L. Depends on: A3-L, A4-L. Refined by: D41-L. Supersedes: —.
+- **D16-L — Graph persistence uses Drizzle over `better-sqlite3`, with one-LSN-per-commit and no bypass paths.** The command layer owns precondition checks, structural validation, entity writes, LSN allocation, change-log append, and any coherence updates inside one transaction. This rule applies equally to migrations and maintenance code; there is no privileged write path outside the command-executor protocol. Runtime row/insert/update schemas are derived from Drizzle table definitions through `drizzle-typebox` (`createInsertSchema`, `createSelectSchema`) rather than hand-authored alongside the table. **Settled by A20-L spike (2026-06-01):** `drizzle-orm@0.45.2` + `drizzle-kit@0.31.10` + `better-sqlite3@12.8.0` + `drizzle-typebox@0.3.3` + `@sinclair/typebox@0.34.14`. Pi tool parameter schemas use `typebox` v1.x (Pi's package) separately; Drizzle-derived row schemas stay internal to `db/`→`graph/`; shared enum `const` arrays bridge both. Depends on: A3-L, A4-L, A20-L (validated). Refined by: D41-L. Supersedes: —.
- **D18-L — Post-exchange capture is synchronous elicitor work for the POC; observer/auditor queues are deferred backstops, not primary extraction authority.** After a user response closes an elicitation exchange, the elicitor may run a post-exchange capture step in the same turn-boundary flow: commit high-confidence extractive facts, concrete reconciliation needs, and spec readiness/posture updates through the `CommandExecutor`; fold low-confidence implications into later questions rather than graph truth. Brunch may still introduce durable observer/auditor jobs keyed by session id plus exchange entry ids for restartable audit, quality checks, or later backfill, but those jobs are not the load-bearing path for keeping the next turn's world fresh. Any async job writes still route through the command layer and remain operational queue state unless they surface semantic work as reconciliation needs. Depends on: A13-L, A22-L, D4-L, D13-L, D16-L. Supersedes: the old DB-backed `chat` / `turn` mental model and the earlier observer-owned primary extraction path.
- **D28-L — Regenerated review-set proposals are appended as successor entries in the linear Pi JSONL session; projection helpers filter to the accepted set for context economy.** When the user requests changes, the agent appends a successor proposal entry that references its predecessor via `supersedes`; prior proposals are *not* deleted from JSONL but remain visible as raw transcript history. This stays within Brunch's linear transcript policy — no Pi branching is created. Pi JSONL is treated as a "capture everything" store for replay and audit. Projection helpers used to drive the agent (context injection, summarization) walk the `supersedes` chain and surface only the latest (or ultimately accepted) proposal — the agent does not re-process every superseded proposal as live context. The reviewer likewise sees only the accepted set, not the regeneration history. Depends on: D6-L, D12-L, D17-L, D24-L, D27-L. Supersedes: any "in-place edit" or "fork-on-regenerate" mental model.
- **D29-L — Reviewer is an async advisory role with narrow write authority.** After a batch acceptance closes, Brunch may enqueue a reviewer job keyed by session id plus the batch-acceptance entry id; the job survives process restart and analyzes the accepted batch plus its graph neighborhood for coherence, completeness, and gaps. **Reviewer writes only `reconciliation_need` records via the `CommandExecutor`**; it never writes graph entities, edges, change-log entries directly, or any other record class. Findings reach the user through next-turn delivery as advisory items on the reconciliation-need surface — the batch acceptance remains the user's atomic commitment and the reviewer cannot amend it. (Suggestion-shaped findings may later route to candidate-artefacts when that substrate exists; the POC routes everything to reconciliation needs.) Depends on: A16-L, D4-L, D8-L, D15-L, D17-L, D18-L, D20-L, D27-L. Supersedes: any "reviewer may quietly amend the graph" mental model.
@@ -207,7 +214,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c
#### Schema & validation
-- **D41-L — Boundary schemas are runtime-validated and JSON-Schema-exportable; Zod v4 may be the product/protocol schema source.** Brunch boundary shapes must have one runtime schema source of truth, derived static TypeScript types, and JSON Schema output wherever a public protocol or Pi tool boundary needs discoverability. Zod v4 is permitted — and preferred for structured-exchange product/protocol schemas — when the schema stays inside Zod's JSON-representable subset and tests prove `z.toJSONSchema(..., { unrepresentable: "throw" })` succeeds for the exported boundary. TypeBox remains valid for Pi tool parameter objects, small config/frontmatter contracts, and any seam where the direct JSON-Schema-shaped authoring style is cheaper. Do not hand-author parallel Zod and TypeBox definitions for the same boundary; if a Pi API requires a JSON-Schema-shaped object, generate or adapt it from the chosen source schema and test the adapter. Drizzle table definitions remain canonical for persisted shapes; row/insert/update validation must be derived from Drizzle through a single adapter path (`drizzle-zod`, `drizzle-orm/typebox`, or equivalent selected during A20-L) rather than hand-authored alongside the table. Boundary Zod schemas must avoid transforms, `Date`, `Map`, `Set`, `bigint`, and other unrepresentable constructs unless an explicit adapter owns the input/output split and JSON Schema export tests cover it; refinements are allowed only for runtime constraints that stay inside JSON-representable input/output shapes and are covered by parse tests plus export tests. Static TS types come from the schema source (`z.infer` for Zod, `Static` for TypeBox); runtime parsing uses the matching library (`zExample.parse`/`safeParse` for Zod, `Value.Parse`/`Value.Check` for TypeBox). Depends on: D4-L, D5-L, D16-L. Supersedes: TypeBox as Brunch's single runtime schema vocabulary, the ban on Zod outside downstream adapters, and an implicit "any runtime schema library is fine" posture.
+- **D41-L — Boundary schemas are runtime-validated and JSON-Schema-exportable; Zod v4 may be the product/protocol schema source.** Brunch boundary shapes must have one runtime schema source of truth, derived static TypeScript types, and JSON Schema output wherever a public protocol or Pi tool boundary needs discoverability. Zod v4 is permitted — and preferred for structured-exchange product/protocol schemas — when the schema stays inside Zod's JSON-representable subset and tests prove `z.toJSONSchema(..., { unrepresentable: "throw" })` succeeds for the exported boundary. TypeBox remains valid for Pi tool parameter objects, small config/frontmatter contracts, and any seam where the direct JSON-Schema-shaped authoring style is cheaper. Do not hand-author parallel Zod and TypeBox definitions for the same boundary; if a Pi API requires a JSON-Schema-shaped object, generate or adapt it from the chosen source schema and test the adapter. Drizzle table definitions remain canonical for persisted shapes; row/insert/update validation must be derived from Drizzle through a single adapter path (`drizzle-zod`, `drizzle-orm/typebox`, or equivalent selected during the A20-L prep-envelope spike) rather than hand-authored alongside the table. Boundary Zod schemas must avoid transforms, `Date`, `Map`, `Set`, `bigint`, and other unrepresentable constructs unless an explicit adapter owns the input/output split and JSON Schema export tests cover it; refinements are allowed only for runtime constraints that stay inside JSON-representable input/output shapes and are covered by parse tests plus export tests. Static TS types come from the schema source (`z.infer` for Zod, `Static` for TypeBox); runtime parsing uses the matching library (`zExample.parse`/`safeParse` for Zod, `Value.Parse`/`Value.Check` for TypeBox). Depends on: D4-L, D5-L, D16-L. Supersedes: TypeBox as Brunch's single runtime schema vocabulary, the ban on Zod outside downstream adapters, and an implicit "any runtime schema library is fine" posture.
#### Interaction & UI shape
@@ -219,8 +226,8 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c
- **D38-L — JSON-over-editor is the Pi-RPC compatibility seam for complex extension UI, not a second product API.** Pi RPC supports `ctx.ui.select`, `confirm`, `input`, and `editor`, but not `ctx.ui.custom()`. When a structured-exchange tool needs a complex shape (multi-select, review-style response, or a deferred multi-question/questionnaire shape) over raw Pi RPC, the tool may call `ctx.ui.editor()` with schema-tagged JSON prefill and validate the returned JSON before producing normal `toolResult.content` plus self-contained `toolResult.details`. A Brunch-aware adapter may render that JSON as a native product form and translate the user response back into Pi's documented `extension_ui_response`; public clients still speak Brunch RPC methods/events, not ad hoc raw Pi RPC extensions. Depends on: D5-L, D19-L, D33-L, D37-L. Supersedes: inventing unsupported Pi RPC command types for Brunch interactions or exposing raw editor JSON as the product UX.
- **D13-L — Capture-aware elicitation exchange projection.** Post-exchange capture consumes derived elicitation exchanges: a prompt-side span (system/assistant/tool-side entries since the previous response, including structured/internal prompt content) plus a response-side span (user text, linked structured response entries, and/or terminal structured-exchange toolResults whose `details` encode the answer). Role/span alternation is the default projection in Brunch-supported linear sessions, but typed structured-exchange results override the naive "all toolResults are prompt side" rule where needed for deterministic replay. Depends on: D12-L, D24-L, D37-L. Supersedes: treating Pi message role alone as sufficient to classify structured elicitation response spans.
- **D14-L — `#`-mentions are stable-handle text references resolved by Brunch, with a session-scoped mention ledger.** Pi autocomplete persists only the inserted `AutocompleteItem.value` as ordinary transcript text; popup labels/descriptions are UI-only. Brunch autocomplete may search by title/description, but insertion must rewrite to a stable handle (`#A12`, `#I7`, or equivalent node handle) that Brunch can resolve to the graph entity id through a read-only lookup/re-read tool when the agent needs detail. Brunch prompt injection (`before_agent_start`) teaches agents how to interpret the handles; Brunch-owned parsing/indexing, not Pi autocomplete, creates mention-ledger state. Per-session `(entity_id, snapshotted_lsn)` ledger drives discretionary `brunch.mention_staleness_hint` entries in `prepareNextTurn`. Depends on: A9-L, I4-L. Supersedes: assuming Pi autocomplete persists hidden mention metadata.
-- **D25-L — Elicitation strategies are *lenses* within the `elicitor` agent role, not separate roles or operational modes.** Lens is metadata on elicitor-emitted custom transcript entries (`brunch.elicitor_intent_hint`, `brunch.establishment_offer`, `brunch.review_set_proposal`, etc.); roles (`elicitor`, `reviewer`, `reconciler`, and any deferred observer/auditor roles) remain orthogonal. The known starter lens set is `step-by-step`, `disambiguate-via-examples`, `propose-scenarios-with-tradeoffs`, `propose-design-shapes`, `propose-oracle-ensembles`, and `project-requirements-from-upstream`; the catalogue is expected to grow. Capture, review, and future audit routing may filter on lens. Depends on: D12-L, D17-L, D23-L. Supersedes: collapsing strategy and agent role into one vocabulary axis.
-- **D26-L — Elicitation flows split by capture and commitment mechanism, not by a hard extractive/generative phase boundary.** Single-exchange flows (`step-by-step`, many `disambiguate-via-examples` prompts, and ordinary structured questions) are captured synchronously by the elicitor post-exchange per D18-L. Batch-proposal flows (`propose-scenarios-with-tradeoffs`, `propose-design-shapes`, `propose-oracle-ensembles`, `project-requirements-from-upstream`) carry structured entity-draft payloads at proposal time and become durable only through review-set approval. Design/oracle lenses may appear during ordinary elicitation before any commitment posture; later commitment posture changes what can be pinned, not what topics may be explored. Depends on: D18-L, D25-L, D45-L. Supersedes: a single uniform "agent asks questions" mental model and the observer-owned extractive vs elicitor-owned generative split as the primary architecture.
+- **D25-L — Elicitation strategies are *lenses* within the `elicitor` agent role, not separate roles or operational modes.** Lens is metadata on elicitor-emitted custom transcript entries (`brunch.elicitor_intent_hint`, `brunch.establishment_offer`, `brunch.review_set_proposal`, etc.); roles (`elicitor`, `reviewer`, `reconciler`, and any deferred observer/auditor roles) remain orthogonal. The catalogue is organized along two axes: *strategies* describe the interaction shape (`step-wise-decision-tree`, `step-wise-disambiguate`, `propose-graph`, `project-graph`) while *lenses* describe the topical focus (`intent`, `design`, `oracle`, and future execute-mode lenses `plan`, `sync`, `scope`). The prior lens-catalogue names map to strategy+lens combinations: `propose-scenarios-with-tradeoffs` = propose-graph strategy under intent lens; `propose-design-shapes` = propose-graph under design lens; `propose-oracle-ensembles` = propose-graph under oracle lens; `project-requirements-from-upstream` = project-graph under intent lens; `step-by-step` and `disambiguate-via-examples` = extractive strategies applicable across lenses. The catalogue is expected to grow. Capture, review, and future audit routing may filter on lens. Depends on: D12-L, D17-L, D23-L. Supersedes: collapsing strategy and agent role into one vocabulary axis.
+- **D26-L — Elicitation flows split by capture and commitment mechanism, not by a hard extractive/generative phase boundary.** Three commitment mechanisms: (1) Single-exchange flows (`step-wise-decision-tree`, `step-wise-disambiguate`, and ordinary structured questions) are captured synchronously by the elicitor post-exchange per D18-L. (2) Review-set flows (`project-graph` strategy) carry structured entity-draft payloads at proposal time and become durable only through review-set approval (D27-L). (3) Direct-commit flows (`propose-graph` strategy) present a concept to the user via structured exchange with rubric axes, choices, and a recommendation; when the user accepts a concept, the agent autonomously generates and persists the full subgraph through `commitGraph` (D53-L) without intermediate entity-level user review — the user accepts a concept, not a graph shape. Design/oracle lenses may appear during ordinary elicitation before any commitment posture; later commitment posture changes what can be pinned, not what topics may be explored. Depends on: D18-L, D25-L, D45-L, D53-L. Supersedes: a single uniform "agent asks questions" mental model, the observer-owned extractive vs elicitor-owned generative split as the primary architecture, and assuming all batch-graph writes require review-set approval.
- **D30-L — Grounding advances readiness for main elicitation; strategies remain available with honest epistemic signaling.** A minimum grounding bundle — *domain anchor*, *protagonist anchor*, *pain/pull anchor*, *constraint anchor* — establishes the frame required to move the spec from `grounding_onboarding` toward `elicitation_ready`. Lenses and strategies are not refused merely because grounding is thin, but their output resolution and epistemic load must honestly reflect what grounding supports: speculative outputs are visibly hedged and lower-authority, while grounded outputs may drive capture and later review-set projection. Grounding coverage should be explicit in offers/proposals where it affects confidence or gate transitions. Depends on: D26-L, D45-L. Supersedes: gating-by-refusal as a UX move and over-focusing readiness on generative lenses alone.
- **D32-L — Establishment offers are orientation artifacts, not a default next-action menu.** `brunch.establishment_offer` records the agent's current offer tree and recommended next move as durable transcript state. Ambient chrome or web affordances may render the latest offer, and Brunch may expose a user-invoked orientation view summarizing what is established vs open, but Brunch does not surface an exhaustive lens/offer chooser by default; the agent still owns next-move selection unless the user explicitly asks to inspect alternatives. Depends on: D25-L, D30-L, A15-L. Supersedes: UI interpretations that turn establishment offers into a persistent strategy menu.
- **D31-L — A four-axis meta-rubric is a soft heuristic for fan-out comparison rubrics across all three flows; not architecturally enforced.** When generating comparison rubrics for fan-out alternatives across candidate-spec, technical-design, and verification-design flows, the elicitor attempts to express each axis in terms of (*legibility / cost-of-knowing*, *failure modes*, *coverage / range*, *commitment*). Project-specific axes are allowed alongside; the meta-frame is dropped when it doesn't fit. The hypothesis (uniform comparison UI across all three flows is more useful than per-flow improvisation) is testable via fixture comparison; promote to schema/UI only if it holds up. Depends on: D25-L, D26-L. Supersedes: a hardcoded per-flow rubric.
@@ -244,8 +251,8 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c
| I3-L | Transcript reload reproduces raw assistant/user payloads plus Brunch session binding, structured elicitation entries, and other custom transcript entries byte-equivalently (modulo timestamps). | covered (M2 JSONL viability round-trip tests) | D6-L |
| I4-L | For every `worldUpdate` entry, all named graph items have LSNs strictly greater than the session's pre-update `lastSeenLsn`. | planned (M7 property test) | D6-L, I1-L |
| I5-L | For every `brunch.lens_switch` entry and every session/spec binding transition, the session interest set is recomputed before the next agent turn. | planned (M7 property test) | D11-L |
-| I6-L | Every reconciliation need has `created_at_lsn ≤` current global LSN; `kind='impasse'` needs reference at least two graph nodes; resolved needs carry a strictly later `resolved_at_lsn`. | planned (M8 property test) | D8-L, I1-L |
-| I7-L | Every `framing_as` value belongs to the allowed matrix for that node's base kind. | planned (fixture property check) | D7-L |
+| I6-L | Every reconciliation need has `created_at_lsn ≤` current global LSN; its target is exactly one of `{kind:'edge', edgeId}` or `{kind:'node_pair', aId, bId}` per [`docs/design/GRAPH_MODEL.md`](file:///Users/lunelson/Code/hashintel/brunch-next/docs/design/GRAPH_MODEL.md#reconciliationneed--separate-substrate-not-a-graph-edge); resolved needs carry a strictly later `resolved_at_lsn`. | planned (M8 property test) | D8-L, D51-L, I1-L |
+| I7-L | ~~Every `framing_as` value belongs to the allowed matrix for that node's base kind.~~ **Retired.** `framing_as` absorbed by D54-L/D56-L node kinds; no node carries a `framing_as` field. | — | D7-L (retired) |
| I8-L | Spec selection persists across pi `switchSession` (i.e. `/new`); the selected session file is reopened consistently by headless projection/capture paths; each session has exactly one `brunch.session_binding`, and a session's bound spec never changes. | partially covered (M0 coordinator/TUI boot integration tests + store-only probe checker; M1 no-injected-coordinator capture regression; M2 coordinator-created JSONL reload tests; manual TUI smoke still planned) | D11-L, D21-L |
| I9-L | Every `brunch.mention` payload resolves a transcript `#` handle to a stable graph entity id; the ledger never stores title-anchored references or relies on autocomplete popup metadata. | planned (M7 invariant) | D14-L |
| I10-L | Structured elicitation prompts/responses live in the Pi transcript when structure is needed; Brunch-supported elicitation exchanges are projected only from linear coordinator-bound sessions, and no parallel canonical chat/turn table carries elicitation state. | covered for projection shape and current read surfaces (M1 exchange projection tests, M2 JSONL/RPC projection tests, M3 canonical Brunch session-envelope validation and explicit custom-entry classifiers) | D12-L, D13-L, D18-L, D24-L |
@@ -262,16 +269,20 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c
| I21-L | WebSocket/stdio/TUI client attachment state never becomes the canonical spec/session binding: every session-consuming projection validates the durable `brunch.session_binding`, and write-capable session operations must target an explicit session or future write lease rather than whichever transport connection happens to be open. | partially covered (M3 RPC/WebSocket explicit session projection tests validate durable `brunch.session_binding` for read paths; FE-744 web live-update tests prove WebSocket notifications only invalidate/refetch canonical projection handlers after RPC-originated structured-exchange mutations; future write-lease tests remain planned when web input lands) | D10-L, D19-L, D21-L, D33-L |
| I22-L | Brunch TUI startup must not render prior session transcript entries or enter an agent loop until the user has explicitly activated a spec/session decision; creating a new spec implicitly creates its first session, creating a new session for an existing spec lands in a binding-only session, resuming a prior transcript is opt-in, and RPC/headless startup exposes structured initial-selection state rather than invoking TUI picker code. | covered (FE-744 coordinator tests; hierarchical spec/session picker model + component tests; `workspace.selectionState` / `workspace.activate` JSON-RPC contract tests with source assertion that RPC does not import TUI picker code; `src/probes/scripts/verify-startup-no-resume.sh` pty/ANSI-stripped TUI probe oracle proving stale transcript text is absent before explicit activation) | D11-L, D21-L, D22-L, D36-L |
| I23-L | Every structured elicitation interaction that owns the response surface persists durable semantic display only through Pi `toolResult` rows rendered by `renderResult`; `renderCall` and live `ctx.ui.*` surfaces are transient. A structured-exchange tuple has a recoverable `present_*` result and, when required, exactly one matching terminal `request_*` result before the next agent turn consumes it. The target details model is checked by `schema` + `v`, `exchange_id`, and `tool_meta`; request outcomes are an exactly-one property-presence union; user-authored text is `comment` and runtime-authored text is `message`; present-side status/kind/expected-request aliases and capture graph payloads are invalid in the Zod-authored schema layer. `toolResult.content` is rich markdown suitable for both TUI transcript display and model context; `toolResult.details` carries structured projection/recovery data. | covered for current FE-744 structured-exchange tools (registered sequential `present_question`, `present_options`, `request_answer`, `request_choice`, and `request_choices`; tests cover non-semantic `renderCall`, markdown `renderResult`, present/request details, unmatched-present recovery, active-vs-stub registry, JSON-editor fallback for multi-choice, terminal `answered`/`cancelled`/`unavailable` projection closure, option content/rationale parity, and same-assistant-message `present_options → request_choice` ordering over a real Pi RPC run. The Zod-authored schema layer is covered by JSON Schema export and drift-rejection tests for present/request/capture details; runtime tools still need a deliberate migration to those exports. `present_review_set`, `present_candidates`, and `request_review` remain named stubs and intentionally unregistered.) | D12-L, D13-L, D17-L, D37-L, D38-L, D41-L |
-| I24-L | A Brunch-launched Pi runtime does not load ambient user/project Pi context files, extensions, skills, prompt templates, themes, or behavior-shaping settings unless the Brunch Pi Profile explicitly allows them; Brunch-owned extension-discovered resources are identified as intentional product resources. | planned (sealed-profile audit and resource/settings isolation tests) | D2-L, D39-L |
-| I25-L | The active operational mode, role preset/runtime bundle, strategy, and lens are reconstructable from linear transcript entries at turn start; tool gating follows the reconstructed operational mode so `elicit` cannot use execute/dangerous tools such as raw `bash`/`write` unless explicitly permitted by the bundle. | planned (runtime-state projection tests plus before-agent-start/tool-policy contract tests) | D17-L, D23-L, D40-L |
+| I24-L | A Brunch-launched Pi runtime does not load ambient user/project Pi context files, extensions, skills, prompt templates, themes, or behavior-shaping settings unless the Brunch Pi Profile explicitly allows them; Brunch-owned extension-discovered resources are identified as intentional product resources. | covered for TUI-launch profile boundary by contract tests: ambient resource flags and explicit extension factories are preserved; hostile ambient global/project settings are ignored by the in-memory Brunch settings policy before and after reload; audited Pi settings getters are tracked in `src/brunch-pi-profile.ts`. Subagent subprocess inheritance remains future coverage under I29-L. | D2-L, D39-L |
+| I25-L | The active operational mode, role preset/runtime bundle, strategy, and lens are reconstructable from linear transcript entries at turn start; tool gating follows the reconstructed operational mode so `elicit` cannot use execute/dangerous tools such as raw `bash`/`write` unless explicitly permitted by the bundle. | covered for the current POC runtime bundle: runtime-state append/project/switch helpers write full `brunch.agent_runtime_state` snapshots to Pi custom entries, init is idempotent, switches carry previous state, malformed/impossible mode-role-strategy-lens entries are ignored or rejected, real SessionManager JSONL reload projects the latest valid state, and session-start/before-agent-start prompt/tool policy derives from the transcript-projected state without extension-local memory. | D17-L, D23-L, D40-L |
| I27-L | Session-name generation is best-effort presentation metadata only: lifecycle hooks may append Pi `session_info` entries, but naming failures never block shutdown/session replacement and generated names never mutate spec identity, session binding, or graph truth. | planned (session-lifecycle naming tests with empty transcript/auth failure/success paths; picker projection tests read session names when present) | D6-L, D21-L, D35-L, D42-L |
-| I26-L | Runtime schema-library imports stay deliberately scoped: Zod may appear only in D41-L-acknowledged product/protocol schema seams such as `src/tui-client/.pi/extensions/structured-exchange/schemas/`; TypeBox remains valid for Pi tool parameters, small config/frontmatter contracts, and future Drizzle-derived row schemas; no boundary may hand-author parallel Zod and TypeBox sources for the same shape. Drizzle row/insert/update schemas are not hand-authored alongside their target tables. | partially covered (structured-exchange schema tests prove Zod parse/export for the acknowledged seam; a grep-based architectural test should still land with M4 for broader schema import boundaries and Drizzle derivation) | D41-L |
+| I26-L | Runtime schema-library imports stay deliberately scoped: Zod may appear only in D41-L-acknowledged product/protocol schema seams such as `src/tui-client/.pi/extensions/structured-exchange/schemas/`; TypeBox remains valid for Pi tool parameters, small config/frontmatter contracts, and future Drizzle-derived row schemas; no boundary may hand-author parallel Zod and TypeBox sources for the same shape. Drizzle row/insert/update schemas are not hand-authored alongside their target tables. | covered (structured-exchange schema tests prove Zod parse/export; grep-based architectural boundary test in `architecture.test.ts` enforces no direct `db/` imports outside `graph/`; Drizzle derivation via `drizzle-typebox` in `row-schemas.ts`) | D41-L |
| I28-L | Auto-compaction output preserves the configured anchor set byte-stable: every entry kind listed in [src/tui-client/.pi/extensions/auto-compaction-anchors.json](file:///Users/lunelson/Code/hashintel/brunch-next/src/tui-client/.pi/extensions/auto-compaction-anchors.json) is reconstructable post-compaction according to its `select` rule (`first | latest | active-leaves | all-unresolved`); LLM-generated narrative summary never replaces or rephrases preserved-anchor content; extension failure falls through to Pi default compaction rather than dropping anchors silently. | planned (compaction round-trip property tests at M9 plus inner-loop anchor-rendering unit tests and TypeBox schema validation of the anchor config) | D43-L; R15, R13; I3-L, I4-L, I8-L, I12-L |
| I29-L | Subagent subprocesses inherit Brunch Pi Profile sealing: every `subagent` tool invocation spawns `pi --mode json -p --no-session --no-skills --no-extensions` with an explicit per-agent tool allowlist and per-agent model; subagents never load ambient user/project `.pi/` skills, prompts, themes, extensions, context files, or behavior-shaping settings; subagents never gain direct access to the parent's `CommandExecutor`, Brunch RPC handlers, or graph persistence; subagent results return to the main agent only as tool result content (no side-effect transcript writes). | planned (subagent subprocess argv tests; isolation audit asserting absent ambient-resource leakage; tool-allowlist conformance test per starter agent) | D2-L, D39-L, D44-L; I2-L, I11-L, I24-L |
| I30-L | Elicitor post-exchange capture only commits high-confidence extractive facts, concrete reconciliation needs, and justified spec grade/posture updates; low-confidence implications remain in structured-exchange preface/question material and do not become graph truth until clarified, accepted, or explicitly escalated. | planned (M5 capture fixtures comparing committed graph facts and preface-only interpretations against transcript evidence) | D18-L, D47-L; A22-L |
| I31-L | `readiness_grade` is a forward gate, not a workflow location: higher grades unlock later strategies/commitments/export paths but do not make earlier gathering/refinement invalid or unavailable; all grade/posture mutations route through `CommandExecutor` and carry provenance. | planned (M4 schema/command tests for spec row updates; M5 prompt/tool-policy tests for grade-gated availability) | D20-L, D45-L |
| I32-L | Public RPC structured-exchange driving never requires a client to speak raw Pi RPC: after Brunch method discovery and workspace/spec/session activation, each pending assistant-originated exchange is answered exactly once through `elicitation.respond`, and the deterministic permutation run produces linear Pi JSONL whose transcript display and elicitation-exchange projections preserve the same prompt/answer/status/comment artifacts as the equivalent TUI structured-exchange path. | covered for deterministic FE-744 parity (`rpc.discover` contract tests, pending/respond lifecycle tests, current public-RPC structured-exchange permutations, terminal non-answered status handling, option content/rationale parity, no repeated deterministic prompts, and transcript/projection oracle in `src/probes/public-rpc-parity-proof.ts`) | R11, R16, R17, R24, R27, R28; D5-L, D12-L, D37-L, D48-L, D49-L |
| I33-L | `capture_*` analysis entries are transcript evidence only: they persist as Brunch structured-exchange `toolResult` rows, are included by Brunch-semantic transcript renderers, are hidden or collapsed in TUI display, and never mutate graph truth or bypass `CommandExecutor`. | partially covered (minimum capture details schemas parse/export and reject graph payload fields; future runtime capture-analysis schema/rendering tests plus transcript renderer fixtures still need to prove persisted result rendering and TUI hide/collapse behavior; later graph-capture fixtures compare analysis candidates against committed graph mutations) | D17-L, D18-L, D37-L, D47-L, D50-L; I2-L, I11-L, I23-L, I30-L |
+| I34-L | `commitGraph` batch validation is all-or-nothing: if any node or edge in the batch is structurally illegal, the entire batch is rejected and no partial state is persisted; the agent receives diagnostics sufficient for bounded self-correction retry. | covered (22 tests in `command-executor.test.ts` — edge failure rolls back nodes, mixed-batch rejection, diagnostic sufficiency) | D53-L; I1-L, I11-L |
+| I35-L | Graph context snapshots support multiple detail levels: a cursory/compact full-graph overview for orientation, and detailed node-neighborhood snapshots with configurable hop depth for focused work. Context builders in `agents/contexts/` orchestrate which level to inject based on mode/role/strategy/lens/grade. | partially covered (`getGraphOverview` + `getNodeNeighborhood` in `snapshot.ts` with 10 tests; context-builder integration deferred to M5) | D52-L, D53-L |
+| I36-L | Node `kind` is drawn from a per-plane closed enum structurally validated by the `CommandExecutor`; the intent kind category (basic / structural / reasoning) is a pure function of `kind` and is never stored on the node. | covered (CommandExecutor rejects invalid kind-for-plane; `intentKindCategory` is pure derivation with exhaustive switch; tests in `command-executor.test.ts`) | D54-L, D56-L |
+| I37-L | `detail` is per-kind validated by the `CommandExecutor`: `decision` and `term` nodes REQUIRE `detail` with their respective sub-schemas; all other kinds must omit `detail`; unknown fields in `detail` are rejected. | covered (detail-required/prohibited/shape tests in `command-executor.test.ts`) | D54-L |
## Future Direction Register
@@ -295,7 +306,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c
### Prompt/runtime profile architecture
-- Brunch prompt composition is explicit and layered: base Brunch product prompt + operational-mode prompt pack + top-level role preset + strategy prompt pack + lens prompt pack + spec readiness grade + elicitation posture + current graph/coherence/world state + pending structured-interaction rules. The initial private prompt-pack topology lives under `src/tui-client/.pi/context/`, with deterministic composition through `compose-brunch-prompt.ts` and future dynamic context renderers under `context/builders/`.
+- Brunch prompt composition is explicit and layered: base Brunch product prompt + operational-mode prompt pack + top-level role preset + strategy prompt pack + lens prompt pack + spec readiness grade + elicitation posture + current graph/coherence/world state + pending structured-interaction rules. The target topology per D52-L is `src/agents/` organized by axis: `modes/` (operational mode prompts and rules), `strategies/` (interaction-shape prompts per strategy), `lenses/` (topical-focus prompts per lens), and `contexts/` (snapshot orchestration functions that import from `graph/` and `session/`). Prompt composition lives in `agents/compose.ts`; state definitions (valid mode/role/strategy/lens combinations) in `agents/state.ts`. The current `src/tui-client/.pi/context/` layout migrates to this structure.
- Readiness is an internal forward gate, not a user-facing workflow stepper or session-local phase. `readiness_grade` and `elicitation_posture` live on the spec row per D45-L; validators may warn when graph/transcript evidence and assigned grade/posture diverge. Before these fields drive hard tool/agent authority beyond the POC, Brunch needs explicit rubrics for what evidence advances, blocks, or regresses grade/posture.
- Core role/lens prompting should usually be product prompt packs rather than Pi skills. Pi skills remain available as Brunch-owned explicit resources when progressive disclosure is the right mechanism, but they are not the primary authority for operational mode/tool policy.
@@ -307,7 +318,8 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c
### Vocabulary evolution
- Whether public graph commands eventually split from one `graph.*` umbrella into `intent.*` / `oracle.*` / `design.*` / `plan.*` namespaces is deferred; current posture is unified `graph.*` for the POC.
-- Whether `framing_as` values graduate to first-class node kinds (seam-extensions Open Question #8) is deferred until fixture pressure shows the need.
+- ~~Whether `framing_as` values graduate to first-class node kinds~~ — resolved: `framing_as` retired, absorbed by `thesis`, `term`, `constraint.subtype`, and `goal` (D54-L, D56-L).
+- `posture` is a spec-level property set for now; whether it earns a graph node kind is deferred until product pressure shows that per-subsystem or per-phase posture declarations need graph-native representation.
### Thin transport/read posture
@@ -344,15 +356,15 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c
| **Operational mode** | A top-level Brunch authority/tooling posture such as `elicit` or future `execute`. It determines what kind of work is allowed and which tools/prompt posture are available. Distinct from Pi's transport mode concept. |
| **Agent role** | A worker identity within an operational mode. Top-level roles drive the main turn (`elicitor`, future `executor/orchestrator`); side roles run async/advisory or delegated work (`reviewer`, `reconciler`, deferred observer/auditor, future `scout` / `researcher`). |
| **Runtime bundle / role preset** | The transcript-backed Brunch selection that derives active operational mode, top-level role, model, thinking level, prompt packs, allowed strategies/lenses, and tool policy. Commands switch bundles instead of mutating hidden extension memory. |
-| **Strategy** | A conversation or work tactic selected within the active runtime bundle. Strategies control interaction plan; lenses control interpretive/extraction/review framing. |
-| **Lens** | A narrower interpretive, extraction, or review framing applied within a role/strategy, such as technical-design, verification-design, or disambiguation. Lenses may eventually be driven by Brunch-owned prompt packs or skills. |
+| **Strategy** | An interaction-shape tactic within the active runtime bundle: `step-wise-decision-tree` (single-exchange Q&A), `step-wise-disambiguate` (contrastive examples), `propose-graph` (novel coherent subgraph via direct commit), `project-graph` (derived nodes/edges from existing graph via review-set). Strategies determine the commitment mechanism (D26-L). |
+| **Lens** | A topical-focus framing applied within a role/strategy: `intent`, `design`, `oracle` for elicitation mode; `plan`, `sync`, `scope` for future execute mode. Strategy+lens pairs map to the prior lens-catalogue names (D25-L). |
| **Brunch Pi Profile** | The sealed programmatic wrapper around embedded Pi: settings policy, resource-loader policy, extension factories, keybinding/command policy, tool policy, and prompt policy. It allows Brunch-owned resources while suppressing ambient `.pi/` behavior. |
| **Prompt pack** | A Brunch-owned prompt fragment selected by operational mode, role preset, strategy, lens, readiness grade, or elicitation posture. Prompt packs compose at turn boundaries; they are product control-plane state, not ambient Pi prompt templates. |
| **Readiness grade** | Spec-owned forward gate stored on the spec row: `grounding_onboarding | elicitation_ready | commitments_ready | planning_ready`. It unlocks later strategies, commitment review sets, and eventual export/plan/execute posture, but never forbids earlier gathering or refinement. |
| **Elicitation posture** | Spec-owned current stance inside `elicit`: `gathering | refining | pinning`. Semi-independent from readiness grade; a high-grade spec may still return to gathering/refining when new ambiguity appears. |
| **Commitment focus** | Optional/deferred target for `pinning` posture (`design | oracle`) if active review-set state and missing-commitment analysis cannot make the focus obvious. Not required as canonical state now. |
| **Coherence** | Bounded product-visible verdict over whether the current spec graph is structurally legal and free of known unresolved contradictions/gaps at the current maturity. It is backed by reconciliation needs and remains intentionally narrower than a general judgment that the whole idea is good or complete. |
-| **Structural legality** | Synchronous schema/ontology validity of graph mutations: allowed node/edge shapes, required fields, framing matrix, and transaction invariants. Structural legality can fail even before semantic coherence is evaluated. |
+| **Structural legality** | Synchronous schema/ontology validity of graph mutations: edge categories from the closed set in `docs/design/GRAPH_MODEL.md`, per-category stance/cardinality/acyclicity rules, immutable accepted-edge identity (`category`, `sourceId`, `targetId`, `stance`), per-plane closed node `kind` enums, required `detail` sub-schemas for `decision`/`term`, `constraint.subtype` enum, and transaction invariants. Structural legality can fail even before semantic coherence is evaluated. |
| **Print snapshot** | The M1 meaning of the print transport mode: boot the Brunch host, resolve workspace/spec/session state through the coordinator, render product-shaped state, and exit without running an agent turn. |
| **Workspace** | The current working directory where the Brunch CLI was invoked. It scopes `.brunch/` state for the launch context. It is not user-created, not selectable within the dialog, and there is only one active workspace per Brunch process. The UI may display a project identity/name derived from cwd-local manifests or directory basename, but that name labels the cwd; it does not create a separate workspace object. |
| **Spec / specification** | The user-created specification container within a workspace, identified by its intent-graph root. Multiple specs may coexist under one workspace. A spec contains sessions and the graph data gathered through those sessions (intent nodes, design nodes, oracle/plan data as they land). Future plan-execution mode operates on a selected spec. |
@@ -370,10 +382,13 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c
| **Plan graph** | Milestone/frontier/slice delivery claims accountable to intent, oracle, and design. Stubbed in POC. |
| **LSN** | Log Sequence Number. A single monotonic counter, one-LSN-per-commit, shared by the change log, graph-node versions, and reconciliation needs. |
| **Change log** | The audit trail of graph mutations. Authoritative for replay, `worldUpdate` synthesis, and reconciliation-need ordering. |
-| **Reconciliation need** | First-class record of an open impasse, gap, contradiction, or process debt; carries `created_at_lsn`, optional `resolved_at_lsn`, `concerns` edges to graph nodes. Routine async jobs are not reconciliation needs unless they surface semantic work to resolve. |
+| **Reconciliation need** | First-class record of an open impasse, gap, contradiction, or process debt; carries `created_at_lsn`, optional `resolved_at_lsn`, and a target that is exactly one of `{kind:'edge', edgeId}` or `{kind:'node_pair', aId, bId}` per `docs/design/GRAPH_MODEL.md`. Recon-needs are a separate substrate, not graph edges (no `concerns`-edge wiring). Routine async jobs are not reconciliation needs unless they surface semantic work to resolve. |
| **Coherence verdict** | Per-spec product state (`coherent` / `incoherent`) emitted by validators and visible to both UI and agent. |
| **Command layer** | The single Brunch-owned mutation surface. Validates, gates concurrency, audits, emits events, triggers coherence. Its public mutation entry point is the `CommandExecutor`, not direct ORM calls or caller-side authority gates. |
| **Command executor** | The deep module that accepts Brunch product commands plus execution context and returns structured command results (`ok`, `needs_human`, `policy_blocked`, `version_conflict`, `structural_illegal`). It hides attribution, minimal pre-M6 authority classification, validation, transaction, LSN, change-log, and coherence-trigger mechanics from callers. |
+| **commitGraph** | Single-tool atomic batch mutation accepting `{ nodes, edges }` with intra-batch and existing-node references. One tool call, one LSN, all-or-nothing (I34-L). The load-bearing tool for the `propose-graph` strategy's direct-commit path (D53-L). |
+| **propose-graph** | Elicitor strategy for generative lenses where the agent proposes a novel coherent subgraph. The concept is presented to the user with rubric axes, choices, and recommendation via structured exchange; upon acceptance the agent generates and persists the full subgraph through `commitGraph` without intermediate entity-level review (D26-L, D53-L). The hardest thing to get structurally legal and the primary proof target for A14-L. |
+| **project-graph** | Elicitor strategy for deriving nodes and edges from existing graph truth (e.g. projecting requirements from upstream goals/constraints). Uses review-set commitment (D27-L). Extractive rather than inventive; lower structural-legality risk than propose-graph. |
| **Brunch public RPC surface** | The one product-facing JSON-RPC surface exposed over stdio, WebSocket, and in-process handlers. Product clients use this surface for workspace, session, graph, coherence, command, agent, and elicitation behavior; raw Pi RPC is hidden behind adapters when needed. |
| **RPC discovery** | Brunch-owned `rpc.discover` method output: public method names, descriptions, parameter/result schemas, and examples for the current Brunch host. It is distinct from Pi `get_commands`, which only lists slash commands/prompt templates/skills invokable through Pi's `prompt` command. |
| **RPC method family** | A named group of Brunch JSON-RPC methods (`rpc.*`, `workspace.*`, `session.*`, `elicitation.*`, `graph.*`, `coherence.*`, `command.*`) that exposes product behavior through stdio, WebSocket, or in-process handler calls without creating a second public API surface. |
@@ -414,7 +429,14 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c
| **Mention ledger** | Per-session `(entity_id, snapshotted_lsn)` record driving discretionary staleness hints when an entity has changed since the agent last saw it. |
| **Authority** | Source of a node's claim: `stakeholder | technical | external | derived`. |
| **Epistemic status** | Confidence basis: `observed | asserted | assumed | inferred`. Like `authority`, this is a context-shaping label for attention, grouping, and compression rather than a complete theory of truth. |
-| **Framing-as** | Orthogonal modality classifying a node's product role (e.g. `problem`, `persona`, `non_goal`) within an allowed matrix. |
+| **Framing-as** | ~~Orthogonal modality classifying a node's product role.~~ **Retired.** Absorbed by `thesis`, `term`, `constraint.subtype`, and `goal` (D54-L, D56-L). |
+| **Thesis** | A first-class intent node kind (`kind: "thesis"`). A chosen position or bet about the product — falsifiable, carries "what/who/why/for whom" material (La Carte Blanche style). Not a requirement (it's a bet, not a need), not a goal (it's falsifiable, not aspirational), not an assumption (it's a chosen position, not a dependency). Natural edge relationships: criteria and evidence witness for/against a thesis via `proof` edges. |
+| **Term** | A first-class intent node kind (`kind: "term"`). A canonical naming commitment for ubiquitous language and conceptual consistency. Requires `detail: { definition, aliases? }`. Participates in graph edges: downstream nodes may `dependency`-depend on the term's definition; a term may `boundary`-scope what counts as X; a newer term may `supersession`-replace a prior term. |
+| **Node source** | Free-form string on `GraphNode.source` for epistemic attribution (e.g. "stakeholder", "regulatory", "derived"). Convention by prompt, not structural validation. Exists for context-snapshot enrichment — rendered back into sparse text in prompt snapshots, not used for policy or filtering. Not applicable to edges. |
+| **Node detail** | Optional JSON column on `GraphNode.detail` with per-kind validated sub-structures. `decision` requires `{ chosen_option, rejected, rationale }`; `term` requires `{ definition, aliases? }`; `constraint` may carry `{ subtype }`. All other kinds omit `detail`. |
+| **Context (node kind)** | A first-class intent node kind (`kind: "context"`). A descriptive claim about the environment — observed facts that color interpretation without driving decisions directly. Last-resort basic bucket: before filing as context, check the promotion heuristic (must be true for success → requirement/invariant; limits solutions → constraint; may be false → assumption; chooses among alternatives → decision; bet about users/market → thesis). |
+| **Intent kind category** | Derived grouping of intent node kinds: `basic` (goal, thesis, term, context), `structural` (requirement, assumption, constraint, invariant), `reasoning` (decision, criterion, example). A pure function of `kind`, not stored. Maps to spec-grade progression — grounding gate requires satisficing threshold of basic-category nodes. |
+| **Posture** | A spec-level property set declaring project epistemic/strategic stance (certainty, stakes, audience, horizon, migration, sourcing). Not a graph node kind in the POC; spec-level rather than graph-level. Grounding elicitation interweaves basic intent nodes with posture establishment. |
| **Kernel** | A behavioural elicitation pattern from `docs/design/BEHAVIORAL_KERNELS.md` (state/lifecycle, containment, concurrency, etc.). |
| **Probe run** | A scripted or executable check of a Brunch seam that drives the public product surface and persists reviewable artifacts under `.fixtures/runs///`. |
| **Transcript artifact** | The durable transcript evidence for a probe run, usually `session.jsonl` plus a Brunch-semantic `transcript.md`; reports explain the oracle, but transcript artifacts remain the evidence. |
@@ -524,7 +546,7 @@ The first required probe is M0: after manual TUI interaction, a checker proves `
| I4-L | M7 generated LSN/change traces and paired-session fixture assertions. |
| I5-L | M7 property tests over binding/lens transitions and interest-set recomputation. |
| I6-L | M4/M8 reconciliation-need property tests and contradictory-requirements fixture. |
-| I7-L | M4+ schema/property tests over framing matrix plus brief fixture assertions. |
+| I7-L | ~~M4+ framing matrix tests.~~ **Retired** with `framing_as` (D54-L, D56-L). |
| I8-L | M0 probe oracle plus M2 coordinator-created JSONL reload tests. |
| I9-L | M7 mention parser/ledger unit tests and staleness property tests. |
| I10-L | M1/M2 exchange projection tests, linear transcript validation, and no chat/turn architectural test. |
@@ -550,6 +572,8 @@ The first required probe is M0: after manual TUI interaction, a checker proves `
| I31-L | M4/M5 spec-row command tests for grade/posture updates plus prompt/tool-policy tests proving grade gates unlock later actions without disabling gathering/refinement. |
| I32-L | FE-744 public-RPC structured-exchange parity proof: `rpc.discover` contract tests, pending/respond lifecycle tests, deterministic permutation run over Brunch JSON-RPC only, no repeated deterministic prompts, and parity assertions over the resulting Pi JSONL, transcript display, and elicitation-exchange projections. |
| I33-L | Current schema tests cover minimum no-graph `capture_*` details and reject graph payload fields. Future capture-analysis runtime tests must still cover persisted result rendering, no graph-write side effects, Brunch-semantic transcript inclusion, and hidden/collapsed TUI rendering fallback. |
+| I36-L | M4 per-plane kind enum validation tests in CommandExecutor; kind-to-category derivation unit tests proving pure function parity with GRAPH_MODEL.md table. |
+| I37-L | M4 node-creation tests: decision/term rejected without detail; constraint accepted with or without detail; other kinds rejected with detail; unknown detail fields rejected. |
### Design Notes
diff --git a/package-lock.json b/package-lock.json
index 146e1a327..475df253b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -21,13 +21,19 @@
"brunch-next": "bin/brunch.js"
},
"devDependencies": {
+ "@sinclair/typebox": "^0.34.14",
"@testing-library/dom": "^10.4.1",
"@testing-library/react": "^16.3.2",
+ "@types/better-sqlite3": "^7.6.13",
"@types/node": "^22.10.0",
"@types/react": "^19.2.15",
"@types/react-dom": "^19.2.3",
"@types/ws": "^8.18.1",
"@vitejs/plugin-react": "^4.7.0",
+ "better-sqlite3": "^12.8.0",
+ "drizzle-kit": "^0.31.10",
+ "drizzle-orm": "^0.45.2",
+ "drizzle-typebox": "^0.3.3",
"jsdom": "^29.1.1",
"oxfmt": "^0.2.0",
"oxlint": "^0.18.0",
@@ -988,6 +994,13 @@
"node": ">=20.19.0"
}
},
+ "node_modules/@drizzle-team/brocli": {
+ "version": "0.10.2",
+ "resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz",
+ "integrity": "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
"node_modules/@earendil-works/pi-agent-core": {
"version": "0.75.3",
"resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.75.3.tgz",
@@ -1076,27 +1089,22 @@
"koffi": "2.16.2"
}
},
- "node_modules/@esbuild/aix-ppc64": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz",
- "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==",
- "cpu": [
- "ppc64"
- ],
+ "node_modules/@esbuild-kit/core-utils": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz",
+ "integrity": "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==",
+ "deprecated": "Merged into tsx: https://tsx.is",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "aix"
- ],
- "engines": {
- "node": ">=18"
+ "dependencies": {
+ "esbuild": "~0.18.20",
+ "source-map-support": "^0.5.21"
}
},
- "node_modules/@esbuild/android-arm": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz",
- "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==",
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
+ "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
"cpu": [
"arm"
],
@@ -1107,13 +1115,13 @@
"android"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
- "node_modules/@esbuild/android-arm64": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz",
- "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==",
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
+ "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
"cpu": [
"arm64"
],
@@ -1124,13 +1132,13 @@
"android"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
- "node_modules/@esbuild/android-x64": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz",
- "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==",
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
+ "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
"cpu": [
"x64"
],
@@ -1141,13 +1149,13 @@
"android"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
- "node_modules/@esbuild/darwin-arm64": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz",
- "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==",
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
+ "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
"cpu": [
"arm64"
],
@@ -1158,13 +1166,13 @@
"darwin"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
- "node_modules/@esbuild/darwin-x64": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz",
- "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==",
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
+ "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
"cpu": [
"x64"
],
@@ -1175,13 +1183,13 @@
"darwin"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
- "node_modules/@esbuild/freebsd-arm64": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz",
- "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==",
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
+ "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
"cpu": [
"arm64"
],
@@ -1192,13 +1200,13 @@
"freebsd"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
- "node_modules/@esbuild/freebsd-x64": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz",
- "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==",
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
+ "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
"cpu": [
"x64"
],
@@ -1209,13 +1217,13 @@
"freebsd"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
- "node_modules/@esbuild/linux-arm": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz",
- "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==",
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
+ "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
"cpu": [
"arm"
],
@@ -1226,13 +1234,13 @@
"linux"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
- "node_modules/@esbuild/linux-arm64": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz",
- "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==",
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
+ "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
"cpu": [
"arm64"
],
@@ -1243,13 +1251,13 @@
"linux"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
- "node_modules/@esbuild/linux-ia32": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz",
- "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==",
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ia32": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
+ "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
"cpu": [
"ia32"
],
@@ -1260,13 +1268,13 @@
"linux"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
- "node_modules/@esbuild/linux-loong64": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz",
- "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==",
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-loong64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
+ "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
"cpu": [
"loong64"
],
@@ -1277,13 +1285,13 @@
"linux"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
- "node_modules/@esbuild/linux-mips64el": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz",
- "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==",
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-mips64el": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
+ "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
"cpu": [
"mips64el"
],
@@ -1294,13 +1302,13 @@
"linux"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
- "node_modules/@esbuild/linux-ppc64": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz",
- "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==",
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ppc64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
+ "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
"cpu": [
"ppc64"
],
@@ -1311,13 +1319,13 @@
"linux"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
- "node_modules/@esbuild/linux-riscv64": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz",
- "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==",
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-riscv64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
+ "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
"cpu": [
"riscv64"
],
@@ -1328,13 +1336,13 @@
"linux"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
- "node_modules/@esbuild/linux-s390x": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz",
- "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==",
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-s390x": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
+ "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
"cpu": [
"s390x"
],
@@ -1345,13 +1353,13 @@
"linux"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
- "node_modules/@esbuild/linux-x64": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz",
- "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==",
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
+ "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
"cpu": [
"x64"
],
@@ -1362,15 +1370,15 @@
"linux"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
- "node_modules/@esbuild/netbsd-arm64": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz",
- "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==",
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/netbsd-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
+ "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
"cpu": [
- "arm64"
+ "x64"
],
"dev": true,
"license": "MIT",
@@ -1379,13 +1387,13 @@
"netbsd"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
- "node_modules/@esbuild/netbsd-x64": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz",
- "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==",
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/openbsd-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
+ "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
"cpu": [
"x64"
],
@@ -1393,16 +1401,33 @@
"license": "MIT",
"optional": true,
"os": [
- "netbsd"
+ "openbsd"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
- "node_modules/@esbuild/openbsd-arm64": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz",
- "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==",
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/sunos-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
+ "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
+ "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
"cpu": [
"arm64"
],
@@ -1410,16 +1435,33 @@
"license": "MIT",
"optional": true,
"os": [
- "openbsd"
+ "win32"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
- "node_modules/@esbuild/openbsd-x64": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz",
- "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==",
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-ia32": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
+ "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
+ "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
"cpu": [
"x64"
],
@@ -1427,50 +1469,100 @@
"license": "MIT",
"optional": true,
"os": [
- "openbsd"
+ "win32"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
- "node_modules/@esbuild/openharmony-arm64": {
+ "node_modules/@esbuild-kit/core-utils/node_modules/esbuild": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
+ "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/android-arm": "0.18.20",
+ "@esbuild/android-arm64": "0.18.20",
+ "@esbuild/android-x64": "0.18.20",
+ "@esbuild/darwin-arm64": "0.18.20",
+ "@esbuild/darwin-x64": "0.18.20",
+ "@esbuild/freebsd-arm64": "0.18.20",
+ "@esbuild/freebsd-x64": "0.18.20",
+ "@esbuild/linux-arm": "0.18.20",
+ "@esbuild/linux-arm64": "0.18.20",
+ "@esbuild/linux-ia32": "0.18.20",
+ "@esbuild/linux-loong64": "0.18.20",
+ "@esbuild/linux-mips64el": "0.18.20",
+ "@esbuild/linux-ppc64": "0.18.20",
+ "@esbuild/linux-riscv64": "0.18.20",
+ "@esbuild/linux-s390x": "0.18.20",
+ "@esbuild/linux-x64": "0.18.20",
+ "@esbuild/netbsd-x64": "0.18.20",
+ "@esbuild/openbsd-x64": "0.18.20",
+ "@esbuild/sunos-x64": "0.18.20",
+ "@esbuild/win32-arm64": "0.18.20",
+ "@esbuild/win32-ia32": "0.18.20",
+ "@esbuild/win32-x64": "0.18.20"
+ }
+ },
+ "node_modules/@esbuild-kit/esm-loader": {
+ "version": "2.6.5",
+ "resolved": "https://registry.npmjs.org/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz",
+ "integrity": "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==",
+ "deprecated": "Merged into tsx: https://tsx.is",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@esbuild-kit/core-utils": "^3.3.2",
+ "get-tsconfig": "^4.7.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
"version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz",
- "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz",
+ "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==",
"cpu": [
- "arm64"
+ "ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
- "openharmony"
+ "aix"
],
"engines": {
"node": ">=18"
}
},
- "node_modules/@esbuild/sunos-x64": {
+ "node_modules/@esbuild/android-arm": {
"version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz",
- "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz",
+ "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==",
"cpu": [
- "x64"
+ "arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
- "sunos"
+ "android"
],
"engines": {
"node": ">=18"
}
},
- "node_modules/@esbuild/win32-arm64": {
+ "node_modules/@esbuild/android-arm64": {
"version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz",
- "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz",
+ "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==",
"cpu": [
"arm64"
],
@@ -1478,359 +1570,288 @@
"license": "MIT",
"optional": true,
"os": [
- "win32"
+ "android"
],
"engines": {
"node": ">=18"
}
},
- "node_modules/@esbuild/win32-ia32": {
+ "node_modules/@esbuild/android-x64": {
"version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz",
- "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz",
+ "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==",
"cpu": [
- "ia32"
+ "x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
- "win32"
+ "android"
],
"engines": {
"node": ">=18"
}
},
- "node_modules/@esbuild/win32-x64": {
+ "node_modules/@esbuild/darwin-arm64": {
"version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz",
- "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz",
+ "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==",
"cpu": [
- "x64"
+ "arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
- "win32"
+ "darwin"
],
"engines": {
"node": ">=18"
}
},
- "node_modules/@exodus/bytes": {
- "version": "1.15.1",
- "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz",
- "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==",
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz",
+ "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
"engines": {
- "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
- },
- "peerDependencies": {
- "@noble/hashes": "^1.8.0 || ^2.0.0"
- },
- "peerDependenciesMeta": {
- "@noble/hashes": {
- "optional": true
- }
- }
- },
- "node_modules/@google/genai": {
- "version": "1.52.0",
- "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.52.0.tgz",
- "integrity": "sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q==",
- "hasInstallScript": true,
- "license": "Apache-2.0",
- "dependencies": {
- "google-auth-library": "^10.3.0",
- "p-retry": "^4.6.2",
- "protobufjs": "^7.5.4",
- "ws": "^8.18.0"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "@modelcontextprotocol/sdk": "^1.25.2"
- },
- "peerDependenciesMeta": {
- "@modelcontextprotocol/sdk": {
- "optional": true
- }
- }
- },
- "node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.13",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
- "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/sourcemap-codec": "^1.5.0",
- "@jridgewell/trace-mapping": "^0.3.24"
+ "node": ">=18"
}
},
- "node_modules/@jridgewell/remapping": {
- "version": "2.3.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
- "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz",
+ "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.24"
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
}
},
- "node_modules/@jridgewell/resolve-uri": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
- "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz",
+ "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
"engines": {
- "node": ">=6.0.0"
+ "node": ">=18"
}
},
- "node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
- "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.31",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
- "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz",
+ "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==",
+ "cpu": [
+ "arm"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@jridgewell/resolve-uri": "^3.1.0",
- "@jridgewell/sourcemap-codec": "^1.4.14"
- }
- },
- "node_modules/@mariozechner/clipboard": {
- "version": "0.3.6",
- "resolved": "https://registry.npmjs.org/@mariozechner/clipboard/-/clipboard-0.3.6.tgz",
- "integrity": "sha512-MXdtr+6+ntlIVHdrZYuZNQydu6o8yZswFJ2Ln81j2O/Y9B/LDHvEaIm95xWNPkjGTWriSOeLnQJRFs6dYb60bg==",
- "license": "MIT",
"optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">= 10"
- },
- "optionalDependencies": {
- "@mariozechner/clipboard-darwin-arm64": "0.3.6",
- "@mariozechner/clipboard-darwin-universal": "0.3.6",
- "@mariozechner/clipboard-darwin-x64": "0.3.6",
- "@mariozechner/clipboard-linux-arm64-gnu": "0.3.6",
- "@mariozechner/clipboard-linux-arm64-musl": "0.3.6",
- "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.6",
- "@mariozechner/clipboard-linux-x64-gnu": "0.3.6",
- "@mariozechner/clipboard-linux-x64-musl": "0.3.6",
- "@mariozechner/clipboard-win32-arm64-msvc": "0.3.6",
- "@mariozechner/clipboard-win32-x64-msvc": "0.3.6"
+ "node": ">=18"
}
},
- "node_modules/@mariozechner/clipboard-darwin-arm64": {
- "version": "0.3.6",
- "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-arm64/-/clipboard-darwin-arm64-0.3.6.tgz",
- "integrity": "sha512-HjaisYCAbHi/1+N1yDAQHc8ZXGffufIUT5NSOSVR3f3AuMDusxTtnbK8tZ7JFDkShua1oNGZoNwQHsc8MPtE0Q==",
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz",
+ "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==",
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
- "darwin"
+ "linux"
],
"engines": {
- "node": ">= 10"
+ "node": ">=18"
}
},
- "node_modules/@mariozechner/clipboard-darwin-universal": {
- "version": "0.3.6",
- "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-universal/-/clipboard-darwin-universal-0.3.6.tgz",
- "integrity": "sha512-8BWtPjOtJOJoykml3w0fx0zRrfWP31mXrJwfoA7xzNprkZw1uolCNfgmjDiVBseoKjp16EGITz7bN+61qn8dWA==",
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz",
+ "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
- "darwin"
+ "linux"
],
"engines": {
- "node": ">= 10"
+ "node": ">=18"
}
},
- "node_modules/@mariozechner/clipboard-darwin-x64": {
- "version": "0.3.6",
- "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-x64/-/clipboard-darwin-x64-0.3.6.tgz",
- "integrity": "sha512-p9syiZD1kU4I+1ya7f7g+zD1GiUvR8fdlRlNmgsZNWlyjtc8rlV2EjTLd/35x1LsdBq020GVvtzp0ZmPgBI09Q==",
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz",
+ "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==",
"cpu": [
- "x64"
+ "loong64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
- "darwin"
+ "linux"
],
"engines": {
- "node": ">= 10"
+ "node": ">=18"
}
},
- "node_modules/@mariozechner/clipboard-linux-arm64-gnu": {
- "version": "0.3.6",
- "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-gnu/-/clipboard-linux-arm64-gnu-0.3.6.tgz",
- "integrity": "sha512-5JFf5rGofrm+V29HNF+wLthXphHdQpMbKDUYJ5tML6/Z5DLlLOV/9Ak4kDPtYyZ+Dzf+kAusE0VsFg4+tfP1IA==",
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz",
+ "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==",
"cpu": [
- "arm64"
- ],
- "libc": [
- "glibc"
+ "mips64el"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
- "node": ">= 10"
+ "node": ">=18"
}
},
- "node_modules/@mariozechner/clipboard-linux-arm64-musl": {
- "version": "0.3.6",
- "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-musl/-/clipboard-linux-arm64-musl-0.3.6.tgz",
- "integrity": "sha512-JlVjxxw0GbGC0djXYWRIqyteO3J1KZ/QG3udlEFaOD5TLOM1FnmXXAPDQBqr+aBVr720ef9K00dirYnJ0LDCtw==",
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz",
+ "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==",
"cpu": [
- "arm64"
- ],
- "libc": [
- "musl"
+ "ppc64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
- "node": ">= 10"
+ "node": ">=18"
}
},
- "node_modules/@mariozechner/clipboard-linux-riscv64-gnu": {
- "version": "0.3.6",
- "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-riscv64-gnu/-/clipboard-linux-riscv64-gnu-0.3.6.tgz",
- "integrity": "sha512-4t8BUi5zZ+L77otFQVnVSlaTyAX4TVk9EqQm4syMrEQp96trFEHEwwNHcNEBGzYv5+K7mxay50TthYkz47OWzQ==",
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz",
+ "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==",
"cpu": [
"riscv64"
],
- "libc": [
- "glibc"
- ],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
- "node": ">= 10"
+ "node": ">=18"
}
},
- "node_modules/@mariozechner/clipboard-linux-x64-gnu": {
- "version": "0.3.6",
- "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-gnu/-/clipboard-linux-x64-gnu-0.3.6.tgz",
- "integrity": "sha512-trtPwcNLW37irwQCJLtCxLw757jjJZk3TSnY/MU9bhtWtA3K9b/eLW0e4RGhUXDoFRds9opNWWaUDuFLa8dm0w==",
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz",
+ "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==",
"cpu": [
- "x64"
- ],
- "libc": [
- "glibc"
+ "s390x"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
- "node": ">= 10"
+ "node": ">=18"
}
},
- "node_modules/@mariozechner/clipboard-linux-x64-musl": {
- "version": "0.3.6",
- "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-musl/-/clipboard-linux-x64-musl-0.3.6.tgz",
- "integrity": "sha512-WfnzIvOCCWQiN0MmltCEo6cLceUDbYe+I7xyFZjaps5A+2Op/M2CY7Rey+C4ucQhrvmpoHmTSFgY9ODWk7snoA==",
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz",
+ "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==",
"cpu": [
"x64"
],
- "libc": [
- "musl"
- ],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
- "node": ">= 10"
+ "node": ">=18"
}
},
- "node_modules/@mariozechner/clipboard-win32-arm64-msvc": {
- "version": "0.3.6",
- "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-arm64-msvc/-/clipboard-win32-arm64-msvc-0.3.6.tgz",
- "integrity": "sha512-+8+1aHYsBPUjmW3otmWlg+Hijt0iJvoBBs5e0mxFeUd4gDaKMB8Bn6x7c6KVtscg7E5j5NFXnwQqNSIAO4p8zQ==",
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz",
+ "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==",
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
- "win32"
+ "netbsd"
],
"engines": {
- "node": ">= 10"
+ "node": ">=18"
}
},
- "node_modules/@mariozechner/clipboard-win32-x64-msvc": {
- "version": "0.3.6",
- "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-x64-msvc/-/clipboard-win32-x64-msvc-0.3.6.tgz",
- "integrity": "sha512-S4xfPmERC8ZkiLHe3vekZCjdDwNEETCuvCgQK2kP6/TnvmUkq1y2Pk+DjM4t8uh9KMX9bH4zs5ePcKa8GTXmfg==",
- "cpu": [
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz",
+ "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==",
+ "cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
- "win32"
+ "netbsd"
],
"engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@mistralai/mistralai": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-2.2.1.tgz",
- "integrity": "sha512-uKU8CZmL2RzYKmplsU01hii4p3pe4HqJefpWNRWXm1Tcm0Sm4xXfwSLIy4k7ZCPlbETCGcp69E7hZs+WOJ5itQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "ws": "^8.18.0",
- "zod": "^3.25.0 || ^4.0.0",
- "zod-to-json-schema": "^3.25.0"
+ "node": ">=18"
}
},
- "node_modules/@nodable/entities": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz",
- "integrity": "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/nodable"
- }
- ],
- "license": "MIT"
- },
- "node_modules/@oxfmt/darwin-arm64": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/@oxfmt/darwin-arm64/-/darwin-arm64-0.2.0.tgz",
- "integrity": "sha512-NK7iEPqRovUvKac+4dn2ui8v5Y5q6UJ9v4z5Zjr5lmEzTlBwXToP3TwY75IAaCYeu0g8Es7ToJpS7qCQuxUhuA==",
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz",
+ "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==",
"cpu": [
"arm64"
],
@@ -1838,13 +1859,16 @@
"license": "MIT",
"optional": true,
"os": [
- "darwin"
- ]
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
},
- "node_modules/@oxfmt/darwin-x64": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/@oxfmt/darwin-x64/-/darwin-x64-0.2.0.tgz",
- "integrity": "sha512-eXDgT+DbIMnA3sWE+w38rOvXaxkP4RvHi41rPpWb9XoRGbM+tOo0c8RYqCIX39n7PizOlbF/xUBh9nMhiW01wQ==",
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz",
+ "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==",
"cpu": [
"x64"
],
@@ -1852,95 +1876,84 @@
"license": "MIT",
"optional": true,
"os": [
- "darwin"
- ]
- },
- "node_modules/@oxfmt/linux-arm64-gnu": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/@oxfmt/linux-arm64-gnu/-/linux-arm64-gnu-0.2.0.tgz",
- "integrity": "sha512-h4mw9/Lck5X/SU1RPE26VraoBJgn9SOoLl8GnaYvtmRBFHK0v+2KpnBSwMWeiaIxCdJSsrVc9mpKSOcPsen7Cg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "libc": [
- "glibc"
+ "openbsd"
],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
+ "engines": {
+ "node": ">=18"
+ }
},
- "node_modules/@oxfmt/linux-arm64-musl": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/@oxfmt/linux-arm64-musl/-/linux-arm64-musl-0.2.0.tgz",
- "integrity": "sha512-kDZ/kVCgh9kSczMH2gPXZs+rsY/VcCQ1BoQnND8D3v1Le91LceJ5s/a4XBBA3ADI3NiPdqNmK2ssaxY4BfgXDg==",
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz",
+ "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==",
"cpu": [
"arm64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
- "linux"
- ]
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
},
- "node_modules/@oxfmt/linux-x64-gnu": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/@oxfmt/linux-x64-gnu/-/linux-x64-gnu-0.2.0.tgz",
- "integrity": "sha512-qvf6cNuf4z3yjzjSvg4Jwr88jDyeFaYPU+4dBWVr6fdWhAA+BIuIvurJWYQGvWNNSkobVWH3A7m+wp+h5fNsSg==",
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz",
+ "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==",
"cpu": [
"x64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
- "linux"
- ]
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
},
- "node_modules/@oxfmt/linux-x64-musl": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/@oxfmt/linux-x64-musl/-/linux-x64-musl-0.2.0.tgz",
- "integrity": "sha512-D0rw4BVLHEbtMB6p9e+Ph7Y/56oD1DRP4Qcbbv1B1w0kJanWBB3vMH+v2rqgeYsErqJOtLpYRkPFmJSU9In2HA==",
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz",
+ "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==",
"cpu": [
- "x64"
+ "arm64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
- "linux"
- ]
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
},
- "node_modules/@oxfmt/win32-arm64": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/@oxfmt/win32-arm64/-/win32-arm64-0.2.0.tgz",
- "integrity": "sha512-QM0fP8YUvBNyuNa6d+jNonV34A6zGvvQ2mX93wnERpX25jIbFKljKiKDiWo2M1nUsW8zL2z4ESEeaLO9bx8djg==",
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz",
+ "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==",
"cpu": [
- "arm64"
+ "ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
- ]
+ ],
+ "engines": {
+ "node": ">=18"
+ }
},
- "node_modules/@oxfmt/win32-x64": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/@oxfmt/win32-x64/-/win32-x64-0.2.0.tgz",
- "integrity": "sha512-WkFXliDbwFiziBSfvWXeM0Y3x07Kxxrl39kgsp+N3n5QgzsM8q6qe094+OW+hItDQ+9SSd2y9u231sMCsKxlog==",
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz",
+ "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==",
"cpu": [
"x64"
],
@@ -1949,61 +1962,196 @@
"optional": true,
"os": [
"win32"
- ]
- },
- "node_modules/@oxlint/darwin-arm64": {
- "version": "0.18.1",
- "resolved": "https://registry.npmjs.org/@oxlint/darwin-arm64/-/darwin-arm64-0.18.1.tgz",
- "integrity": "sha512-FqDrcQJmEGNkgmZgI4wbCrGyJl1tiRZa3udxvyYaXag8W80A0zLFNCyWVvHIgUJ0DHlZjRc7O72xUGjiyvQrqQ==",
- "cpu": [
- "arm64"
],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@exodus/bytes": {
+ "version": "1.15.1",
+ "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz",
+ "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "@noble/hashes": "^1.8.0 || ^2.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@noble/hashes": {
+ "optional": true
+ }
+ }
},
- "node_modules/@oxlint/darwin-x64": {
- "version": "0.18.1",
- "resolved": "https://registry.npmjs.org/@oxlint/darwin-x64/-/darwin-x64-0.18.1.tgz",
- "integrity": "sha512-YUcyWBJdNuMcJxAwdV/i25/kvnKrVsA+vLn7SsL87cAwiD//rqGdOixk0r8sKUYa71Kx3h0Fg2ToUOjdE6ddYw==",
- "cpu": [
- "x64"
- ],
+ "node_modules/@google/genai": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.52.0.tgz",
+ "integrity": "sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q==",
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "google-auth-library": "^10.3.0",
+ "p-retry": "^4.6.2",
+ "protobufjs": "^7.5.4",
+ "ws": "^8.18.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "@modelcontextprotocol/sdk": "^1.25.2"
+ },
+ "peerDependenciesMeta": {
+ "@modelcontextprotocol/sdk": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
},
- "node_modules/@oxlint/linux-arm64-gnu": {
- "version": "0.18.1",
- "resolved": "https://registry.npmjs.org/@oxlint/linux-arm64-gnu/-/linux-arm64-gnu-0.18.1.tgz",
- "integrity": "sha512-ol3jhmUv5VI/omMrt6DkwY/jVTSVJlflFyU1SnSb/BuVVf3TyBiCHmZ4wVtcrcT5k3sWjrvYWw2kSozvmuE4tg==",
- "cpu": [
- "arm64"
- ],
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
},
- "node_modules/@oxlint/linux-arm64-musl": {
- "version": "0.18.1",
- "resolved": "https://registry.npmjs.org/@oxlint/linux-arm64-musl/-/linux-arm64-musl-0.18.1.tgz",
- "integrity": "sha512-iKDj1ZwlU4KpXuIL1qkVP6NJzri2VSJreqXCIAe1Bf5RZXMAGSO3xjldgiX+HBvFOKSBIarLcqONYDbYco9uaQ==",
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@mariozechner/clipboard": {
+ "version": "0.3.6",
+ "resolved": "https://registry.npmjs.org/@mariozechner/clipboard/-/clipboard-0.3.6.tgz",
+ "integrity": "sha512-MXdtr+6+ntlIVHdrZYuZNQydu6o8yZswFJ2Ln81j2O/Y9B/LDHvEaIm95xWNPkjGTWriSOeLnQJRFs6dYb60bg==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">= 10"
+ },
+ "optionalDependencies": {
+ "@mariozechner/clipboard-darwin-arm64": "0.3.6",
+ "@mariozechner/clipboard-darwin-universal": "0.3.6",
+ "@mariozechner/clipboard-darwin-x64": "0.3.6",
+ "@mariozechner/clipboard-linux-arm64-gnu": "0.3.6",
+ "@mariozechner/clipboard-linux-arm64-musl": "0.3.6",
+ "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.6",
+ "@mariozechner/clipboard-linux-x64-gnu": "0.3.6",
+ "@mariozechner/clipboard-linux-x64-musl": "0.3.6",
+ "@mariozechner/clipboard-win32-arm64-msvc": "0.3.6",
+ "@mariozechner/clipboard-win32-x64-msvc": "0.3.6"
+ }
+ },
+ "node_modules/@mariozechner/clipboard-darwin-arm64": {
+ "version": "0.3.6",
+ "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-arm64/-/clipboard-darwin-arm64-0.3.6.tgz",
+ "integrity": "sha512-HjaisYCAbHi/1+N1yDAQHc8ZXGffufIUT5NSOSVR3f3AuMDusxTtnbK8tZ7JFDkShua1oNGZoNwQHsc8MPtE0Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@mariozechner/clipboard-darwin-universal": {
+ "version": "0.3.6",
+ "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-universal/-/clipboard-darwin-universal-0.3.6.tgz",
+ "integrity": "sha512-8BWtPjOtJOJoykml3w0fx0zRrfWP31mXrJwfoA7xzNprkZw1uolCNfgmjDiVBseoKjp16EGITz7bN+61qn8dWA==",
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@mariozechner/clipboard-darwin-x64": {
+ "version": "0.3.6",
+ "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-x64/-/clipboard-darwin-x64-0.3.6.tgz",
+ "integrity": "sha512-p9syiZD1kU4I+1ya7f7g+zD1GiUvR8fdlRlNmgsZNWlyjtc8rlV2EjTLd/35x1LsdBq020GVvtzp0ZmPgBI09Q==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@mariozechner/clipboard-linux-arm64-gnu": {
+ "version": "0.3.6",
+ "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-gnu/-/clipboard-linux-arm64-gnu-0.3.6.tgz",
+ "integrity": "sha512-5JFf5rGofrm+V29HNF+wLthXphHdQpMbKDUYJ5tML6/Z5DLlLOV/9Ak4kDPtYyZ+Dzf+kAusE0VsFg4+tfP1IA==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@mariozechner/clipboard-linux-arm64-musl": {
+ "version": "0.3.6",
+ "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-musl/-/clipboard-linux-arm64-musl-0.3.6.tgz",
+ "integrity": "sha512-JlVjxxw0GbGC0djXYWRIqyteO3J1KZ/QG3udlEFaOD5TLOM1FnmXXAPDQBqr+aBVr720ef9K00dirYnJ0LDCtw==",
"cpu": [
"arm64"
],
- "dev": true,
"libc": [
"musl"
],
@@ -2011,16 +2159,37 @@
"optional": true,
"os": [
"linux"
- ]
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
},
- "node_modules/@oxlint/linux-x64-gnu": {
- "version": "0.18.1",
- "resolved": "https://registry.npmjs.org/@oxlint/linux-x64-gnu/-/linux-x64-gnu-0.18.1.tgz",
- "integrity": "sha512-A3g+fZhlOivUdK7xU/IrbhBcMHig5GLrfMX0HYjXL1fiSqKYu9n1o1p42WpT6KfPL3L2uncSg/iyg7hspcN6qA==",
+ "node_modules/@mariozechner/clipboard-linux-riscv64-gnu": {
+ "version": "0.3.6",
+ "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-riscv64-gnu/-/clipboard-linux-riscv64-gnu-0.3.6.tgz",
+ "integrity": "sha512-4t8BUi5zZ+L77otFQVnVSlaTyAX4TVk9EqQm4syMrEQp96trFEHEwwNHcNEBGzYv5+K7mxay50TthYkz47OWzQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@mariozechner/clipboard-linux-x64-gnu": {
+ "version": "0.3.6",
+ "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-gnu/-/clipboard-linux-x64-gnu-0.3.6.tgz",
+ "integrity": "sha512-trtPwcNLW37irwQCJLtCxLw757jjJZk3TSnY/MU9bhtWtA3K9b/eLW0e4RGhUXDoFRds9opNWWaUDuFLa8dm0w==",
"cpu": [
"x64"
],
- "dev": true,
"libc": [
"glibc"
],
@@ -2028,16 +2197,18 @@
"optional": true,
"os": [
"linux"
- ]
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
},
- "node_modules/@oxlint/linux-x64-musl": {
- "version": "0.18.1",
- "resolved": "https://registry.npmjs.org/@oxlint/linux-x64-musl/-/linux-x64-musl-0.18.1.tgz",
- "integrity": "sha512-LA02SdATWZEZBy8ZZpR2GlUbDg7+Jq1/WKkywMXqxdClkcoyyFozj8aQD2iTMKELSra4OSyqqZpOYroqjSSKmw==",
+ "node_modules/@mariozechner/clipboard-linux-x64-musl": {
+ "version": "0.3.6",
+ "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-musl/-/clipboard-linux-x64-musl-0.3.6.tgz",
+ "integrity": "sha512-WfnzIvOCCWQiN0MmltCEo6cLceUDbYe+I7xyFZjaps5A+2Op/M2CY7Rey+C4ucQhrvmpoHmTSFgY9ODWk7snoA==",
"cpu": [
"x64"
],
- "dev": true,
"libc": [
"musl"
],
@@ -2045,138 +2216,70 @@
"optional": true,
"os": [
"linux"
- ]
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
},
- "node_modules/@oxlint/win32-arm64": {
- "version": "0.18.1",
- "resolved": "https://registry.npmjs.org/@oxlint/win32-arm64/-/win32-arm64-0.18.1.tgz",
- "integrity": "sha512-FNL+OxDflqLGXRgLxfBM/X4RnLYgtOKTsb1mNSqsjSCEfUi1Oqivh7KvZ09IfAMZeJ85/fL6EI6hSOyY7nNYUg==",
+ "node_modules/@mariozechner/clipboard-win32-arm64-msvc": {
+ "version": "0.3.6",
+ "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-arm64-msvc/-/clipboard-win32-arm64-msvc-0.3.6.tgz",
+ "integrity": "sha512-+8+1aHYsBPUjmW3otmWlg+Hijt0iJvoBBs5e0mxFeUd4gDaKMB8Bn6x7c6KVtscg7E5j5NFXnwQqNSIAO4p8zQ==",
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
- ]
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
},
- "node_modules/@oxlint/win32-x64": {
- "version": "0.18.1",
- "resolved": "https://registry.npmjs.org/@oxlint/win32-x64/-/win32-x64-0.18.1.tgz",
- "integrity": "sha512-W+aVE9Siqs6Oe3NDaDOTTOYsN9X3znl+whfqWK1EcLpqJXX1kdB8Hf45HkGjqnHoFoP96GRgUnXQHQvmUybjvg==",
+ "node_modules/@mariozechner/clipboard-win32-x64-msvc": {
+ "version": "0.3.6",
+ "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-x64-msvc/-/clipboard-win32-x64-msvc-0.3.6.tgz",
+ "integrity": "sha512-S4xfPmERC8ZkiLHe3vekZCjdDwNEETCuvCgQK2kP6/TnvmUkq1y2Pk+DjM4t8uh9KMX9bH4zs5ePcKa8GTXmfg==",
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
- ]
- },
- "node_modules/@protobufjs/aspromise": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
- "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@protobufjs/base64": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
- "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@protobufjs/codegen": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz",
- "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@protobufjs/eventemitter": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
- "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
- "license": "BSD-3-Clause"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
},
- "node_modules/@protobufjs/fetch": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz",
- "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==",
- "license": "BSD-3-Clause",
+ "node_modules/@mistralai/mistralai": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-2.2.1.tgz",
+ "integrity": "sha512-uKU8CZmL2RzYKmplsU01hii4p3pe4HqJefpWNRWXm1Tcm0Sm4xXfwSLIy4k7ZCPlbETCGcp69E7hZs+WOJ5itQ==",
+ "license": "Apache-2.0",
"dependencies": {
- "@protobufjs/aspromise": "^1.1.1"
+ "ws": "^8.18.0",
+ "zod": "^3.25.0 || ^4.0.0",
+ "zod-to-json-schema": "^3.25.0"
}
},
- "node_modules/@protobufjs/float": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
- "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
- "license": "BSD-3-Clause"
+ "node_modules/@nodable/entities": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz",
+ "integrity": "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/nodable"
+ }
+ ],
+ "license": "MIT"
},
- "node_modules/@protobufjs/inquire": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.2.tgz",
- "integrity": "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@protobufjs/path": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
- "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@protobufjs/pool": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
- "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@protobufjs/utf8": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz",
- "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@rolldown/pluginutils": {
- "version": "1.0.0-beta.27",
- "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
- "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz",
- "integrity": "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-android-arm64": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz",
- "integrity": "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz",
- "integrity": "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==",
+ "node_modules/@oxfmt/darwin-arm64": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/darwin-arm64/-/darwin-arm64-0.2.0.tgz",
+ "integrity": "sha512-NK7iEPqRovUvKac+4dn2ui8v5Y5q6UJ9v4z5Zjr5lmEzTlBwXToP3TwY75IAaCYeu0g8Es7ToJpS7qCQuxUhuA==",
"cpu": [
"arm64"
],
@@ -2187,10 +2290,10 @@
"darwin"
]
},
- "node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz",
- "integrity": "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==",
+ "node_modules/@oxfmt/darwin-x64": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/darwin-x64/-/darwin-x64-0.2.0.tgz",
+ "integrity": "sha512-eXDgT+DbIMnA3sWE+w38rOvXaxkP4RvHi41rPpWb9XoRGbM+tOo0c8RYqCIX39n7PizOlbF/xUBh9nMhiW01wQ==",
"cpu": [
"x64"
],
@@ -2201,40 +2304,46 @@
"darwin"
]
},
- "node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.4.tgz",
- "integrity": "sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==",
+ "node_modules/@oxfmt/linux-arm64-gnu": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/linux-arm64-gnu/-/linux-arm64-gnu-0.2.0.tgz",
+ "integrity": "sha512-h4mw9/Lck5X/SU1RPE26VraoBJgn9SOoLl8GnaYvtmRBFHK0v+2KpnBSwMWeiaIxCdJSsrVc9mpKSOcPsen7Cg==",
"cpu": [
"arm64"
],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
"optional": true,
"os": [
- "freebsd"
+ "linux"
]
},
- "node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.4.tgz",
- "integrity": "sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==",
+ "node_modules/@oxfmt/linux-arm64-musl": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/linux-arm64-musl/-/linux-arm64-musl-0.2.0.tgz",
+ "integrity": "sha512-kDZ/kVCgh9kSczMH2gPXZs+rsY/VcCQ1BoQnND8D3v1Le91LceJ5s/a4XBBA3ADI3NiPdqNmK2ssaxY4BfgXDg==",
"cpu": [
- "x64"
+ "arm64"
],
"dev": true,
+ "libc": [
+ "musl"
+ ],
"license": "MIT",
"optional": true,
"os": [
- "freebsd"
+ "linux"
]
},
- "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.4.tgz",
- "integrity": "sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==",
+ "node_modules/@oxfmt/linux-x64-gnu": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/linux-x64-gnu/-/linux-x64-gnu-0.2.0.tgz",
+ "integrity": "sha512-qvf6cNuf4z3yjzjSvg4Jwr88jDyeFaYPU+4dBWVr6fdWhAA+BIuIvurJWYQGvWNNSkobVWH3A7m+wp+h5fNsSg==",
"cpu": [
- "arm"
+ "x64"
],
"dev": true,
"libc": [
@@ -2246,12 +2355,12 @@
"linux"
]
},
- "node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.4.tgz",
- "integrity": "sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==",
+ "node_modules/@oxfmt/linux-x64-musl": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/linux-x64-musl/-/linux-x64-musl-0.2.0.tgz",
+ "integrity": "sha512-D0rw4BVLHEbtMB6p9e+Ph7Y/56oD1DRP4Qcbbv1B1w0kJanWBB3vMH+v2rqgeYsErqJOtLpYRkPFmJSU9In2HA==",
"cpu": [
- "arm"
+ "x64"
],
"dev": true,
"libc": [
@@ -2263,80 +2372,68 @@
"linux"
]
},
- "node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.4.tgz",
- "integrity": "sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==",
+ "node_modules/@oxfmt/win32-arm64": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/win32-arm64/-/win32-arm64-0.2.0.tgz",
+ "integrity": "sha512-QM0fP8YUvBNyuNa6d+jNonV34A6zGvvQ2mX93wnERpX25jIbFKljKiKDiWo2M1nUsW8zL2z4ESEeaLO9bx8djg==",
"cpu": [
"arm64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
- "linux"
+ "win32"
]
},
- "node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.4.tgz",
- "integrity": "sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==",
+ "node_modules/@oxfmt/win32-x64": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/win32-x64/-/win32-x64-0.2.0.tgz",
+ "integrity": "sha512-WkFXliDbwFiziBSfvWXeM0Y3x07Kxxrl39kgsp+N3n5QgzsM8q6qe094+OW+hItDQ+9SSd2y9u231sMCsKxlog==",
"cpu": [
- "arm64"
+ "x64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
- "linux"
+ "win32"
]
},
- "node_modules/@rollup/rollup-linux-loong64-gnu": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.4.tgz",
- "integrity": "sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==",
+ "node_modules/@oxlint/darwin-arm64": {
+ "version": "0.18.1",
+ "resolved": "https://registry.npmjs.org/@oxlint/darwin-arm64/-/darwin-arm64-0.18.1.tgz",
+ "integrity": "sha512-FqDrcQJmEGNkgmZgI4wbCrGyJl1tiRZa3udxvyYaXag8W80A0zLFNCyWVvHIgUJ0DHlZjRc7O72xUGjiyvQrqQ==",
"cpu": [
- "loong64"
+ "arm64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
- "linux"
+ "darwin"
]
},
- "node_modules/@rollup/rollup-linux-loong64-musl": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.4.tgz",
- "integrity": "sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==",
+ "node_modules/@oxlint/darwin-x64": {
+ "version": "0.18.1",
+ "resolved": "https://registry.npmjs.org/@oxlint/darwin-x64/-/darwin-x64-0.18.1.tgz",
+ "integrity": "sha512-YUcyWBJdNuMcJxAwdV/i25/kvnKrVsA+vLn7SsL87cAwiD//rqGdOixk0r8sKUYa71Kx3h0Fg2ToUOjdE6ddYw==",
"cpu": [
- "loong64"
+ "x64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
- "linux"
+ "darwin"
]
},
- "node_modules/@rollup/rollup-linux-ppc64-gnu": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.4.tgz",
- "integrity": "sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==",
+ "node_modules/@oxlint/linux-arm64-gnu": {
+ "version": "0.18.1",
+ "resolved": "https://registry.npmjs.org/@oxlint/linux-arm64-gnu/-/linux-arm64-gnu-0.18.1.tgz",
+ "integrity": "sha512-ol3jhmUv5VI/omMrt6DkwY/jVTSVJlflFyU1SnSb/BuVVf3TyBiCHmZ4wVtcrcT5k3sWjrvYWw2kSozvmuE4tg==",
"cpu": [
- "ppc64"
+ "arm64"
],
"dev": true,
"libc": [
@@ -2348,12 +2445,12 @@
"linux"
]
},
- "node_modules/@rollup/rollup-linux-ppc64-musl": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.4.tgz",
- "integrity": "sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==",
+ "node_modules/@oxlint/linux-arm64-musl": {
+ "version": "0.18.1",
+ "resolved": "https://registry.npmjs.org/@oxlint/linux-arm64-musl/-/linux-arm64-musl-0.18.1.tgz",
+ "integrity": "sha512-iKDj1ZwlU4KpXuIL1qkVP6NJzri2VSJreqXCIAe1Bf5RZXMAGSO3xjldgiX+HBvFOKSBIarLcqONYDbYco9uaQ==",
"cpu": [
- "ppc64"
+ "arm64"
],
"dev": true,
"libc": [
@@ -2365,12 +2462,12 @@
"linux"
]
},
- "node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.4.tgz",
- "integrity": "sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==",
+ "node_modules/@oxlint/linux-x64-gnu": {
+ "version": "0.18.1",
+ "resolved": "https://registry.npmjs.org/@oxlint/linux-x64-gnu/-/linux-x64-gnu-0.18.1.tgz",
+ "integrity": "sha512-A3g+fZhlOivUdK7xU/IrbhBcMHig5GLrfMX0HYjXL1fiSqKYu9n1o1p42WpT6KfPL3L2uncSg/iyg7hspcN6qA==",
"cpu": [
- "riscv64"
+ "x64"
],
"dev": true,
"libc": [
@@ -2382,12 +2479,12 @@
"linux"
]
},
- "node_modules/@rollup/rollup-linux-riscv64-musl": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.4.tgz",
- "integrity": "sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==",
+ "node_modules/@oxlint/linux-x64-musl": {
+ "version": "0.18.1",
+ "resolved": "https://registry.npmjs.org/@oxlint/linux-x64-musl/-/linux-x64-musl-0.18.1.tgz",
+ "integrity": "sha512-LA02SdATWZEZBy8ZZpR2GlUbDg7+Jq1/WKkywMXqxdClkcoyyFozj8aQD2iTMKELSra4OSyqqZpOYroqjSSKmw==",
"cpu": [
- "riscv64"
+ "x64"
],
"dev": true,
"libc": [
@@ -2399,75 +2496,136 @@
"linux"
]
},
- "node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.4.tgz",
- "integrity": "sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==",
+ "node_modules/@oxlint/win32-arm64": {
+ "version": "0.18.1",
+ "resolved": "https://registry.npmjs.org/@oxlint/win32-arm64/-/win32-arm64-0.18.1.tgz",
+ "integrity": "sha512-FNL+OxDflqLGXRgLxfBM/X4RnLYgtOKTsb1mNSqsjSCEfUi1Oqivh7KvZ09IfAMZeJ85/fL6EI6hSOyY7nNYUg==",
"cpu": [
- "s390x"
+ "arm64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
- "linux"
+ "win32"
]
},
- "node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.4.tgz",
- "integrity": "sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==",
+ "node_modules/@oxlint/win32-x64": {
+ "version": "0.18.1",
+ "resolved": "https://registry.npmjs.org/@oxlint/win32-x64/-/win32-x64-0.18.1.tgz",
+ "integrity": "sha512-W+aVE9Siqs6Oe3NDaDOTTOYsN9X3znl+whfqWK1EcLpqJXX1kdB8Hf45HkGjqnHoFoP96GRgUnXQHQvmUybjvg==",
"cpu": [
"x64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
- "linux"
+ "win32"
]
},
- "node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.4.tgz",
- "integrity": "sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "libc": [
- "musl"
+ "node_modules/@protobufjs/aspromise": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
+ "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/base64": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
+ "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/codegen": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz",
+ "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/eventemitter": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
+ "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/fetch": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz",
+ "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@protobufjs/aspromise": "^1.1.1"
+ }
+ },
+ "node_modules/@protobufjs/float": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
+ "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/inquire": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.2.tgz",
+ "integrity": "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/path": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
+ "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/pool": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
+ "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/utf8": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz",
+ "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.27",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz",
+ "integrity": "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==",
+ "cpu": [
+ "arm"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
- "linux"
+ "android"
]
},
- "node_modules/@rollup/rollup-openbsd-x64": {
+ "node_modules/@rollup/rollup-android-arm64": {
"version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.4.tgz",
- "integrity": "sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz",
+ "integrity": "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==",
"cpu": [
- "x64"
+ "arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
- "openbsd"
+ "android"
]
},
- "node_modules/@rollup/rollup-openharmony-arm64": {
+ "node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.4.tgz",
- "integrity": "sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz",
+ "integrity": "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==",
"cpu": [
"arm64"
],
@@ -2475,41 +2633,41 @@
"license": "MIT",
"optional": true,
"os": [
- "openharmony"
+ "darwin"
]
},
- "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "node_modules/@rollup/rollup-darwin-x64": {
"version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.4.tgz",
- "integrity": "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz",
+ "integrity": "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==",
"cpu": [
- "arm64"
+ "x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
- "win32"
+ "darwin"
]
},
- "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz",
- "integrity": "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.4.tgz",
+ "integrity": "sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==",
"cpu": [
- "ia32"
+ "arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
- "win32"
+ "freebsd"
]
},
- "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz",
- "integrity": "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.4.tgz",
+ "integrity": "sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==",
"cpu": [
"x64"
],
@@ -2517,916 +2675,1973 @@
"license": "MIT",
"optional": true,
"os": [
- "win32"
+ "freebsd"
]
},
- "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.4.tgz",
- "integrity": "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.4.tgz",
+ "integrity": "sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==",
"cpu": [
- "x64"
+ "arm"
],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
"optional": true,
"os": [
- "win32"
+ "linux"
]
},
- "node_modules/@silvia-odwyer/photon-node": {
- "version": "0.3.4",
- "resolved": "https://registry.npmjs.org/@silvia-odwyer/photon-node/-/photon-node-0.3.4.tgz",
- "integrity": "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==",
- "license": "Apache-2.0"
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.4.tgz",
+ "integrity": "sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
},
- "node_modules/@smithy/core": {
- "version": "3.24.3",
- "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.24.3.tgz",
- "integrity": "sha512-Ep/7tPamGY8mgESE3LyLKtxJyy6U52WWAqr/3wial47Sj4u3PiIF73AOGI27UyLy9duTkhZbgzodOfLV4TduZg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-crypto/crc32": "5.2.0",
- "@smithy/types": "^4.14.2",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.4.tgz",
+ "integrity": "sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
},
- "node_modules/@smithy/credential-provider-imds": {
- "version": "4.3.3",
- "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.3.3.tgz",
- "integrity": "sha512-I2Bti0DKFo2IJyN28ijCsx51BAumEYR4/1yZ1FXyBygy9MqbnMqCev4JPth/MbpRfBSRAX35hITSnAdJRo1u5w==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/core": "^3.24.3",
- "@smithy/types": "^4.14.2",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.4.tgz",
+ "integrity": "sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
},
- "node_modules/@smithy/fetch-http-handler": {
- "version": "5.4.3",
- "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.4.3.tgz",
- "integrity": "sha512-F+DRf8IJazRJgYog2A/yJK7eYVc0rqTlRzO+5ZxjJd4WkZoKz0IJRncf7G6t1pdVT3kryJcwuTFhN1c5m6N47A==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/core": "^3.24.3",
- "@smithy/types": "^4.14.2",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.4.tgz",
+ "integrity": "sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
},
- "node_modules/@smithy/is-array-buffer": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz",
- "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==",
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.6.2"
- },
- "engines": {
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.4.tgz",
+ "integrity": "sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.4.tgz",
+ "integrity": "sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.4.tgz",
+ "integrity": "sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.4.tgz",
+ "integrity": "sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.4.tgz",
+ "integrity": "sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.4.tgz",
+ "integrity": "sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.4.tgz",
+ "integrity": "sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.4.tgz",
+ "integrity": "sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.4.tgz",
+ "integrity": "sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.4.tgz",
+ "integrity": "sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.4.tgz",
+ "integrity": "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz",
+ "integrity": "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz",
+ "integrity": "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.4.tgz",
+ "integrity": "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@silvia-odwyer/photon-node": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/@silvia-odwyer/photon-node/-/photon-node-0.3.4.tgz",
+ "integrity": "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@sinclair/typebox": {
+ "version": "0.34.14",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.14.tgz",
+ "integrity": "sha512-TJ7Al17j3+by5y2QkTLcF/oBVMbgXBhILVgi9PuwpxQVZZvGh5BFRzWbJPmZVNKpbRLjuMzFuRwR+tdFPqCkvA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@smithy/core": {
+ "version": "3.24.3",
+ "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.24.3.tgz",
+ "integrity": "sha512-Ep/7tPamGY8mgESE3LyLKtxJyy6U52WWAqr/3wial47Sj4u3PiIF73AOGI27UyLy9duTkhZbgzodOfLV4TduZg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-crypto/crc32": "5.2.0",
+ "@smithy/types": "^4.14.2",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/credential-provider-imds": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.3.3.tgz",
+ "integrity": "sha512-I2Bti0DKFo2IJyN28ijCsx51BAumEYR4/1yZ1FXyBygy9MqbnMqCev4JPth/MbpRfBSRAX35hITSnAdJRo1u5w==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/core": "^3.24.3",
+ "@smithy/types": "^4.14.2",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/fetch-http-handler": {
+ "version": "5.4.3",
+ "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.4.3.tgz",
+ "integrity": "sha512-F+DRf8IJazRJgYog2A/yJK7eYVc0rqTlRzO+5ZxjJd4WkZoKz0IJRncf7G6t1pdVT3kryJcwuTFhN1c5m6N47A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/core": "^3.24.3",
+ "@smithy/types": "^4.14.2",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/is-array-buffer": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz",
+ "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/node-http-handler": {
+ "version": "4.7.3",
+ "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.7.3.tgz",
+ "integrity": "sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/core": "^3.24.3",
+ "@smithy/types": "^4.14.2",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/signature-v4": {
+ "version": "5.4.3",
+ "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.4.3.tgz",
+ "integrity": "sha512-53+75QuPl6DL+ct6vVEB51FDO5oulXr20TPV46VvJZg76lIlXNWfxi8j+G2V/t0I2qxCBOa3vX/8bmjrpFVo9g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/core": "^3.24.3",
+ "@smithy/types": "^4.14.2",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/types": {
+ "version": "4.14.2",
+ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.2.tgz",
+ "integrity": "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/util-buffer-from": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz",
+ "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/is-array-buffer": "^2.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
"node": ">=14.0.0"
}
},
- "node_modules/@smithy/node-http-handler": {
- "version": "4.7.3",
- "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.7.3.tgz",
- "integrity": "sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA==",
- "license": "Apache-2.0",
+ "node_modules/@smithy/util-utf8": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz",
+ "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/util-buffer-from": "^2.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@tanstack/history": {
+ "version": "1.162.0",
+ "resolved": "https://registry.npmjs.org/@tanstack/history/-/history-1.162.0.tgz",
+ "integrity": "sha512-79pf/RkhteYZTRgcR4F9kbk84P2N8rugQJswxfIqovlbRiT3yI7eBE+5QorIrZaOKktsgzRlXh1l/du/xpl4iA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.19"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/query-core": {
+ "version": "5.100.11",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.100.11.tgz",
+ "integrity": "sha512-lmE0994apShXPj8CUxgx4ch5yUJhE9k/+tVwihBvPOyerACWdBocfFg24t8+0RhtlTd7tEgchDkhlCxNssvDxw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/react-query": {
+ "version": "5.100.11",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.100.11.tgz",
+ "integrity": "sha512-J0f9s5x3LE1450nNNfYx+e/n0DMa0uOBdFJUy5r0RvmsXd4nB/n0rbHtHI1vYXhikNFan+wf51p6Tmp4c8ucrg==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/query-core": "5.100.11"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19"
+ }
+ },
+ "node_modules/@tanstack/react-router": {
+ "version": "1.170.6",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.170.6.tgz",
+ "integrity": "sha512-tGQkOjcMESBbfw+iz9zE/ivcuw4D2zdhW8PA4wMmpOyA2bLiqpc6bg5MnJPxamdXoO3GZBiHYOOoEwi5qxpPgA==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/history": "1.162.0",
+ "@tanstack/react-store": "^0.9.3",
+ "@tanstack/router-core": "1.171.4",
+ "isbot": "^5.1.22"
+ },
+ "engines": {
+ "node": ">=20.19"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0 || >=19.0.0",
+ "react-dom": ">=18.0.0 || >=19.0.0"
+ }
+ },
+ "node_modules/@tanstack/react-store": {
+ "version": "0.9.3",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.9.3.tgz",
+ "integrity": "sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/store": "0.9.3",
+ "use-sync-external-store": "^1.6.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/@tanstack/router-core": {
+ "version": "1.171.4",
+ "resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.171.4.tgz",
+ "integrity": "sha512-LU9YuVdgaP+h4MEXRvokyjIEelulylgsromHMfYwVfgo1PF9oJP3NHyy7qtjxGLJq6zoZMCfoa1frDJlPo7I8g==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/history": "1.162.0",
+ "cookie-es": "^3.0.0",
+ "seroval": "^1.5.4",
+ "seroval-plugins": "^1.5.4"
+ },
+ "engines": {
+ "node": ">=20.19"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/store": {
+ "version": "0.9.3",
+ "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.9.3.tgz",
+ "integrity": "sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@testing-library/dom": {
+ "version": "10.4.1",
+ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
+ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.10.4",
+ "@babel/runtime": "^7.12.5",
+ "@types/aria-query": "^5.0.1",
+ "aria-query": "5.3.0",
+ "dom-accessibility-api": "^0.5.9",
+ "lz-string": "^1.5.0",
+ "picocolors": "1.1.1",
+ "pretty-format": "^27.0.2"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@testing-library/react": {
+ "version": "16.3.2",
+ "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz",
+ "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@testing-library/dom": "^10.0.0",
+ "@types/react": "^18.0.0 || ^19.0.0",
+ "@types/react-dom": "^18.0.0 || ^19.0.0",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@types/aria-query": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
+ "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/better-sqlite3": {
+ "version": "7.6.13",
+ "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz",
+ "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
+ "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "22.19.19",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz",
+ "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@types/react": {
+ "version": "19.2.15",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.15.tgz",
+ "integrity": "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.2.3",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
+ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^19.2.0"
+ }
+ },
+ "node_modules/@types/retry": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
+ "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/ws": {
+ "version": "8.18.1",
+ "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
+ "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.28.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-beta.27",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.17.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/@vitest/expect": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz",
+ "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "2.1.9",
+ "@vitest/utils": "2.1.9",
+ "chai": "^5.1.2",
+ "tinyrainbow": "^1.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/mocker": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz",
+ "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "2.1.9",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.12"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vitest/pretty-format": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz",
+ "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==",
+ "dev": true,
+ "license": "MIT",
"dependencies": {
- "@smithy/core": "^3.24.3",
- "@smithy/types": "^4.14.2",
- "tslib": "^2.6.2"
+ "tinyrainbow": "^1.2.0"
},
- "engines": {
- "node": ">=18.0.0"
+ "funding": {
+ "url": "https://opencollective.com/vitest"
}
},
- "node_modules/@smithy/signature-v4": {
- "version": "5.4.3",
- "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.4.3.tgz",
- "integrity": "sha512-53+75QuPl6DL+ct6vVEB51FDO5oulXr20TPV46VvJZg76lIlXNWfxi8j+G2V/t0I2qxCBOa3vX/8bmjrpFVo9g==",
- "license": "Apache-2.0",
+ "node_modules/@vitest/runner": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz",
+ "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==",
+ "dev": true,
+ "license": "MIT",
"dependencies": {
- "@smithy/core": "^3.24.3",
- "@smithy/types": "^4.14.2",
- "tslib": "^2.6.2"
+ "@vitest/utils": "2.1.9",
+ "pathe": "^1.1.2"
},
- "engines": {
- "node": ">=18.0.0"
+ "funding": {
+ "url": "https://opencollective.com/vitest"
}
},
- "node_modules/@smithy/types": {
- "version": "4.14.2",
- "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.2.tgz",
- "integrity": "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw==",
- "license": "Apache-2.0",
+ "node_modules/@vitest/snapshot": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz",
+ "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==",
+ "dev": true,
+ "license": "MIT",
"dependencies": {
- "tslib": "^2.6.2"
+ "@vitest/pretty-format": "2.1.9",
+ "magic-string": "^0.30.12",
+ "pathe": "^1.1.2"
},
- "engines": {
- "node": ">=18.0.0"
+ "funding": {
+ "url": "https://opencollective.com/vitest"
}
},
- "node_modules/@smithy/util-buffer-from": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz",
- "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==",
- "license": "Apache-2.0",
+ "node_modules/@vitest/spy": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz",
+ "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==",
+ "dev": true,
+ "license": "MIT",
"dependencies": {
- "@smithy/is-array-buffer": "^2.2.0",
- "tslib": "^2.6.2"
+ "tinyspy": "^3.0.2"
},
- "engines": {
- "node": ">=14.0.0"
+ "funding": {
+ "url": "https://opencollective.com/vitest"
}
},
- "node_modules/@smithy/util-utf8": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz",
- "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==",
- "license": "Apache-2.0",
+ "node_modules/@vitest/utils": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz",
+ "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==",
+ "dev": true,
+ "license": "MIT",
"dependencies": {
- "@smithy/util-buffer-from": "^2.2.0",
- "tslib": "^2.6.2"
+ "@vitest/pretty-format": "2.1.9",
+ "loupe": "^3.1.2",
+ "tinyrainbow": "^1.2.0"
},
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+ "license": "MIT",
"engines": {
- "node": ">=14.0.0"
+ "node": ">= 14"
}
},
- "node_modules/@tanstack/history": {
- "version": "1.162.0",
- "resolved": "https://registry.npmjs.org/@tanstack/history/-/history-1.162.0.tgz",
- "integrity": "sha512-79pf/RkhteYZTRgcR4F9kbk84P2N8rugQJswxfIqovlbRiT3yI7eBE+5QorIrZaOKktsgzRlXh1l/du/xpl4iA==",
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
"license": "MIT",
"engines": {
- "node": ">=20.19"
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
},
"funding": {
- "type": "github",
- "url": "https://github.com/sponsors/tannerlinsley"
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
- "node_modules/@tanstack/query-core": {
- "version": "5.100.11",
- "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.100.11.tgz",
- "integrity": "sha512-lmE0994apShXPj8CUxgx4ch5yUJhE9k/+tVwihBvPOyerACWdBocfFg24t8+0RhtlTd7tEgchDkhlCxNssvDxw==",
+ "node_modules/aria-query": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
+ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "dequal": "^2.0.3"
+ }
+ },
+ "node_modules/assertion-error": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+ "dev": true,
"license": "MIT",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/tannerlinsley"
+ "engines": {
+ "node": ">=12"
}
},
- "node_modules/@tanstack/react-query": {
- "version": "5.100.11",
- "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.100.11.tgz",
- "integrity": "sha512-J0f9s5x3LE1450nNNfYx+e/n0DMa0uOBdFJUy5r0RvmsXd4nB/n0rbHtHI1vYXhikNFan+wf51p6Tmp4c8ucrg==",
+ "node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
"license": "MIT",
- "dependencies": {
- "@tanstack/query-core": "5.100.11"
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.10.31",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.31.tgz",
+ "integrity": "sha512-MujYO3eP72uvmSE0i4wltsodRfIpZATP3jvzRNRGGxgzId7aVocVJJV3nf01qnzzKFGxQVC9bpWxl5cjxTr/7Q==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
},
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/tannerlinsley"
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/better-sqlite3": {
+ "version": "12.8.0",
+ "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.8.0.tgz",
+ "integrity": "sha512-RxD2Vd96sQDjQr20kdP+F+dK/1OUNiVOl200vKBZY8u0vTwysfolF6Hq+3ZK2+h8My9YvZhHsF+RSGZW2VYrPQ==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "bindings": "^1.5.0",
+ "prebuild-install": "^7.1.1"
},
- "peerDependencies": {
- "react": "^18 || ^19"
+ "engines": {
+ "node": "20.x || 22.x || 23.x || 24.x || 25.x"
+ }
+ },
+ "node_modules/bidi-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
+ "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "require-from-string": "^2.0.2"
+ }
+ },
+ "node_modules/bignumber.js": {
+ "version": "9.3.1",
+ "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz",
+ "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==",
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/bindings": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
+ "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "file-uri-to-path": "1.0.0"
}
},
- "node_modules/@tanstack/react-router": {
- "version": "1.170.6",
- "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.170.6.tgz",
- "integrity": "sha512-tGQkOjcMESBbfw+iz9zE/ivcuw4D2zdhW8PA4wMmpOyA2bLiqpc6bg5MnJPxamdXoO3GZBiHYOOoEwi5qxpPgA==",
+ "node_modules/bl": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
+ "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "@tanstack/history": "1.162.0",
- "@tanstack/react-store": "^0.9.3",
- "@tanstack/router-core": "1.171.4",
- "isbot": "^5.1.22"
- },
- "engines": {
- "node": ">=20.19"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/tannerlinsley"
- },
- "peerDependencies": {
- "react": ">=18.0.0 || >=19.0.0",
- "react-dom": ">=18.0.0 || >=19.0.0"
+ "buffer": "^5.5.0",
+ "inherits": "^2.0.4",
+ "readable-stream": "^3.4.0"
}
},
- "node_modules/@tanstack/react-store": {
- "version": "0.9.3",
- "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.9.3.tgz",
- "integrity": "sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg==",
+ "node_modules/bowser": {
+ "version": "2.14.1",
+ "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz",
+ "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==",
+ "license": "MIT"
+ },
+ "node_modules/brace-expansion": {
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
+ "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
"license": "MIT",
"dependencies": {
- "@tanstack/store": "0.9.3",
- "use-sync-external-store": "^1.6.0"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/tannerlinsley"
+ "balanced-match": "^4.0.2"
},
- "peerDependencies": {
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
- "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ "engines": {
+ "node": "18 || 20 || >=22"
}
},
- "node_modules/@tanstack/router-core": {
- "version": "1.171.4",
- "resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.171.4.tgz",
- "integrity": "sha512-LU9YuVdgaP+h4MEXRvokyjIEelulylgsromHMfYwVfgo1PF9oJP3NHyy7qtjxGLJq6zoZMCfoa1frDJlPo7I8g==",
+ "node_modules/browserslist": {
+ "version": "4.28.2",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
+ "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
"license": "MIT",
"dependencies": {
- "@tanstack/history": "1.162.0",
- "cookie-es": "^3.0.0",
- "seroval": "^1.5.4",
- "seroval-plugins": "^1.5.4"
+ "baseline-browser-mapping": "^2.10.12",
+ "caniuse-lite": "^1.0.30001782",
+ "electron-to-chromium": "^1.5.328",
+ "node-releases": "^2.0.36",
+ "update-browserslist-db": "^1.2.3"
},
- "engines": {
- "node": ">=20.19"
+ "bin": {
+ "browserslist": "cli.js"
},
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/tannerlinsley"
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
- "node_modules/@tanstack/store": {
- "version": "0.9.3",
- "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.9.3.tgz",
- "integrity": "sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw==",
+ "node_modules/buffer": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
+ "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
"license": "MIT",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/tannerlinsley"
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.1.13"
}
},
- "node_modules/@testing-library/dom": {
- "version": "10.4.1",
- "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
- "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
+ "node_modules/buffer-equal-constant-time": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/buffer-from": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cac": {
+ "version": "6.7.14",
+ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
+ "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.10.4",
- "@babel/runtime": "^7.12.5",
- "@types/aria-query": "^5.0.1",
- "aria-query": "5.3.0",
- "dom-accessibility-api": "^0.5.9",
- "lz-string": "^1.5.0",
- "picocolors": "1.1.1",
- "pretty-format": "^27.0.2"
- },
"engines": {
- "node": ">=18"
+ "node": ">=8"
}
},
- "node_modules/@testing-library/react": {
- "version": "16.3.2",
- "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz",
- "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==",
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001793",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz",
+ "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/chai": {
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz",
+ "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/runtime": "^7.12.5"
+ "assertion-error": "^2.0.1",
+ "check-error": "^2.1.1",
+ "deep-eql": "^5.0.1",
+ "loupe": "^3.1.0",
+ "pathval": "^2.0.0"
},
"engines": {
"node": ">=18"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "5.6.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
+ "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
+ "license": "MIT",
+ "engines": {
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
},
- "peerDependencies": {
- "@testing-library/dom": "^10.0.0",
- "@types/react": "^18.0.0 || ^19.0.0",
- "@types/react-dom": "^18.0.0 || ^19.0.0",
- "react": "^18.0.0 || ^19.0.0",
- "react-dom": "^18.0.0 || ^19.0.0"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- },
- "@types/react-dom": {
- "optional": true
- }
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
}
},
- "node_modules/@types/aria-query": {
- "version": "5.0.4",
- "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
- "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
+ "node_modules/check-error": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz",
+ "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "engines": {
+ "node": ">= 16"
+ }
},
- "node_modules/@types/babel__core": {
- "version": "7.20.5",
- "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
- "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "node_modules/chownr": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
+ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
"dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cookie-es": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-3.1.1.tgz",
+ "integrity": "sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==",
+ "license": "MIT"
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.20.7",
- "@babel/types": "^7.20.7",
- "@types/babel__generator": "*",
- "@types/babel__template": "*",
- "@types/babel__traverse": "*"
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/css-tree": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz",
+ "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mdn-data": "2.27.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
}
},
- "node_modules/@types/babel__generator": {
- "version": "7.27.0",
- "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
- "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"dev": true,
+ "license": "MIT"
+ },
+ "node_modules/data-uri-to-buffer": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
+ "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
"license": "MIT",
- "dependencies": {
- "@babel/types": "^7.0.0"
+ "engines": {
+ "node": ">= 12"
}
},
- "node_modules/@types/babel__template": {
- "version": "7.4.4",
- "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
- "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "node_modules/data-urls": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz",
+ "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.1.0",
- "@babel/types": "^7.0.0"
+ "whatwg-mimetype": "^5.0.0",
+ "whatwg-url": "^16.0.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
}
},
- "node_modules/@types/babel__traverse": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
- "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
- "dev": true,
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.28.2"
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
}
},
- "node_modules/@types/estree": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
- "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
+ "node_modules/decimal.js": {
+ "version": "10.6.0",
+ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
+ "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
"dev": true,
"license": "MIT"
},
- "node_modules/@types/node": {
- "version": "22.19.19",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz",
- "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==",
+ "node_modules/decompress-response": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
+ "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "undici-types": "~6.21.0"
+ "mimic-response": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/@types/react": {
- "version": "19.2.15",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.15.tgz",
- "integrity": "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==",
+ "node_modules/deep-eql": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
+ "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "csstype": "^3.2.2"
+ "engines": {
+ "node": ">=6"
}
},
- "node_modules/@types/react-dom": {
- "version": "19.2.3",
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
- "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
+ "node_modules/deep-extend": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
+ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
"dev": true,
"license": "MIT",
- "peerDependencies": {
- "@types/react": "^19.2.0"
+ "engines": {
+ "node": ">=4.0.0"
}
},
- "node_modules/@types/retry": {
- "version": "0.12.0",
- "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
- "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==",
- "license": "MIT"
- },
- "node_modules/@types/ws": {
- "version": "8.18.1",
- "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
- "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "@types/node": "*"
+ "engines": {
+ "node": ">=6"
}
},
- "node_modules/@vitejs/plugin-react": {
- "version": "4.7.0",
- "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
- "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/core": "^7.28.0",
- "@babel/plugin-transform-react-jsx-self": "^7.27.1",
- "@babel/plugin-transform-react-jsx-source": "^7.27.1",
- "@rolldown/pluginutils": "1.0.0-beta.27",
- "@types/babel__core": "^7.20.5",
- "react-refresh": "^0.17.0"
- },
+ "license": "Apache-2.0",
"engines": {
- "node": "^14.18.0 || >=16.0.0"
- },
- "peerDependencies": {
- "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ "node": ">=8"
}
},
- "node_modules/@vitest/expect": {
- "version": "2.1.9",
- "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz",
- "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==",
+ "node_modules/diff": {
+ "version": "8.0.4",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz",
+ "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
+ "node_modules/dom-accessibility-api": {
+ "version": "0.5.16",
+ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
+ "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/drizzle-kit": {
+ "version": "0.31.10",
+ "resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.31.10.tgz",
+ "integrity": "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/spy": "2.1.9",
- "@vitest/utils": "2.1.9",
- "chai": "^5.1.2",
- "tinyrainbow": "^1.2.0"
+ "@drizzle-team/brocli": "^0.10.2",
+ "@esbuild-kit/esm-loader": "^2.5.5",
+ "esbuild": "^0.25.4",
+ "tsx": "^4.21.0"
},
- "funding": {
- "url": "https://opencollective.com/vitest"
+ "bin": {
+ "drizzle-kit": "bin.cjs"
}
},
- "node_modules/@vitest/mocker": {
- "version": "2.1.9",
- "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz",
- "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==",
+ "node_modules/drizzle-kit/node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
+ "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
+ "cpu": [
+ "ppc64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@vitest/spy": "2.1.9",
- "estree-walker": "^3.0.3",
- "magic-string": "^0.30.12"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- },
- "peerDependencies": {
- "msw": "^2.4.9",
- "vite": "^5.0.0"
- },
- "peerDependenciesMeta": {
- "msw": {
- "optional": true
- },
- "vite": {
- "optional": true
- }
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
}
},
- "node_modules/@vitest/pretty-format": {
- "version": "2.1.9",
- "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz",
- "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==",
+ "node_modules/drizzle-kit/node_modules/@esbuild/android-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
+ "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
+ "cpu": [
+ "arm"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "tinyrainbow": "^1.2.0"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
}
},
- "node_modules/@vitest/runner": {
- "version": "2.1.9",
- "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz",
- "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==",
+ "node_modules/drizzle-kit/node_modules/@esbuild/android-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
+ "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@vitest/utils": "2.1.9",
- "pathe": "^1.1.2"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
}
},
- "node_modules/@vitest/snapshot": {
- "version": "2.1.9",
- "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz",
- "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==",
+ "node_modules/drizzle-kit/node_modules/@esbuild/android-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
+ "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@vitest/pretty-format": "2.1.9",
- "magic-string": "^0.30.12",
- "pathe": "^1.1.2"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
}
},
- "node_modules/@vitest/spy": {
- "version": "2.1.9",
- "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz",
- "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==",
+ "node_modules/drizzle-kit/node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
+ "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "tinyspy": "^3.0.2"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
}
},
- "node_modules/@vitest/utils": {
- "version": "2.1.9",
- "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz",
- "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==",
+ "node_modules/drizzle-kit/node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
+ "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@vitest/pretty-format": "2.1.9",
- "loupe": "^3.1.2",
- "tinyrainbow": "^1.2.0"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
}
},
- "node_modules/agent-base": {
- "version": "7.1.4",
- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
- "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+ "node_modules/drizzle-kit/node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
"engines": {
- "node": ">= 14"
+ "node": ">=18"
}
},
- "node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "node_modules/drizzle-kit/node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
+ "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
"engines": {
- "node": ">=8"
+ "node": ">=18"
}
},
- "node_modules/ansi-styles": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
- "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "node_modules/drizzle-kit/node_modules/@esbuild/linux-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
+ "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
+ "cpu": [
+ "arm"
+ ],
"dev": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "node": ">=18"
}
},
- "node_modules/aria-query": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
- "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
+ "node_modules/drizzle-kit/node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
+ "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "dequal": "^2.0.3"
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
}
},
- "node_modules/assertion-error": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
- "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+ "node_modules/drizzle-kit/node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
+ "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
+ "cpu": [
+ "ia32"
+ ],
"dev": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
- "node_modules/balanced-match": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
- "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "node_modules/drizzle-kit/node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
+ "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": "18 || 20 || >=22"
+ "node": ">=18"
}
},
- "node_modules/base64-js": {
- "version": "1.5.1",
- "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
- "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
+ "node_modules/drizzle-kit/node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
+ "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
+ "cpu": [
+ "mips64el"
],
- "license": "MIT"
- },
- "node_modules/baseline-browser-mapping": {
- "version": "2.10.31",
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.31.tgz",
- "integrity": "sha512-MujYO3eP72uvmSE0i4wltsodRfIpZATP3jvzRNRGGxgzId7aVocVJJV3nf01qnzzKFGxQVC9bpWxl5cjxTr/7Q==",
"dev": true,
- "license": "Apache-2.0",
- "bin": {
- "baseline-browser-mapping": "dist/cli.cjs"
- },
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=6.0.0"
+ "node": ">=18"
}
},
- "node_modules/bidi-js": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
- "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
+ "node_modules/drizzle-kit/node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
+ "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
+ "cpu": [
+ "ppc64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "require-from-string": "^2.0.2"
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
}
},
- "node_modules/bignumber.js": {
- "version": "9.3.1",
- "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz",
- "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==",
+ "node_modules/drizzle-kit/node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
+ "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": "*"
+ "node": ">=18"
}
},
- "node_modules/bowser": {
- "version": "2.14.1",
- "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz",
- "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==",
- "license": "MIT"
- },
- "node_modules/brace-expansion": {
- "version": "5.0.6",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
- "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
+ "node_modules/drizzle-kit/node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
+ "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
"license": "MIT",
- "dependencies": {
- "balanced-match": "^4.0.2"
- },
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": "18 || 20 || >=22"
+ "node": ">=18"
}
},
- "node_modules/browserslist": {
- "version": "4.28.2",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
- "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/browserslist"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
+ "node_modules/drizzle-kit/node_modules/@esbuild/linux-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
+ "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
+ "cpu": [
+ "x64"
],
+ "dev": true,
"license": "MIT",
- "dependencies": {
- "baseline-browser-mapping": "^2.10.12",
- "caniuse-lite": "^1.0.30001782",
- "electron-to-chromium": "^1.5.328",
- "node-releases": "^2.0.36",
- "update-browserslist-db": "^1.2.3"
- },
- "bin": {
- "browserslist": "cli.js"
- },
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ "node": ">=18"
}
},
- "node_modules/buffer-equal-constant-time": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
- "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
- "license": "BSD-3-Clause"
- },
- "node_modules/cac": {
- "version": "6.7.14",
- "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
- "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
+ "node_modules/drizzle-kit/node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
"engines": {
- "node": ">=8"
+ "node": ">=18"
}
},
- "node_modules/caniuse-lite": {
- "version": "1.0.30001793",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz",
- "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==",
+ "node_modules/drizzle-kit/node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
],
- "license": "CC-BY-4.0"
+ "engines": {
+ "node": ">=18"
+ }
},
- "node_modules/chai": {
- "version": "5.3.3",
- "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz",
- "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==",
+ "node_modules/drizzle-kit/node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "assertion-error": "^2.0.1",
- "check-error": "^2.1.1",
- "deep-eql": "^5.0.1",
- "loupe": "^3.1.0",
- "pathval": "^2.0.0"
- },
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
"engines": {
"node": ">=18"
}
},
- "node_modules/chalk": {
- "version": "5.6.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
- "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
+ "node_modules/drizzle-kit/node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
"engines": {
- "node": "^12.17.0 || ^14.13 || >=16.0.0"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
+ "node": ">=18"
}
},
- "node_modules/check-error": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz",
- "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==",
+ "node_modules/drizzle-kit/node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
+ "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
"engines": {
- "node": ">= 16"
+ "node": ">=18"
}
},
- "node_modules/convert-source-map": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
- "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "node_modules/drizzle-kit/node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
+ "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
- "license": "MIT"
- },
- "node_modules/cookie-es": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-3.1.1.tgz",
- "integrity": "sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==",
- "license": "MIT"
- },
- "node_modules/cross-spawn": {
- "version": "7.0.6",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
- "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"license": "MIT",
- "dependencies": {
- "path-key": "^3.1.0",
- "shebang-command": "^2.0.0",
- "which": "^2.0.1"
- },
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
"engines": {
- "node": ">= 8"
+ "node": ">=18"
}
},
- "node_modules/css-tree": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz",
- "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==",
+ "node_modules/drizzle-kit/node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
+ "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "mdn-data": "2.27.1",
- "source-map-js": "^1.2.1"
- },
+ "optional": true,
+ "os": [
+ "win32"
+ ],
"engines": {
- "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
+ "node": ">=18"
}
},
- "node_modules/csstype": {
- "version": "3.2.3",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
- "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "node_modules/drizzle-kit/node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
+ "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
+ "cpu": [
+ "ia32"
+ ],
"dev": true,
- "license": "MIT"
- },
- "node_modules/data-uri-to-buffer": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
- "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
"license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
"engines": {
- "node": ">= 12"
+ "node": ">=18"
}
},
- "node_modules/data-urls": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz",
- "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==",
+ "node_modules/drizzle-kit/node_modules/@esbuild/win32-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
+ "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "whatwg-mimetype": "^5.0.0",
- "whatwg-url": "^16.0.0"
- },
+ "optional": true,
+ "os": [
+ "win32"
+ ],
"engines": {
- "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ "node": ">=18"
}
},
- "node_modules/debug": {
- "version": "4.4.3",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
- "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "node_modules/drizzle-kit/node_modules/esbuild": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
+ "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
+ "dev": true,
+ "hasInstallScript": true,
"license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
+ "bin": {
+ "esbuild": "bin/esbuild"
},
"engines": {
- "node": ">=6.0"
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.12",
+ "@esbuild/android-arm": "0.25.12",
+ "@esbuild/android-arm64": "0.25.12",
+ "@esbuild/android-x64": "0.25.12",
+ "@esbuild/darwin-arm64": "0.25.12",
+ "@esbuild/darwin-x64": "0.25.12",
+ "@esbuild/freebsd-arm64": "0.25.12",
+ "@esbuild/freebsd-x64": "0.25.12",
+ "@esbuild/linux-arm": "0.25.12",
+ "@esbuild/linux-arm64": "0.25.12",
+ "@esbuild/linux-ia32": "0.25.12",
+ "@esbuild/linux-loong64": "0.25.12",
+ "@esbuild/linux-mips64el": "0.25.12",
+ "@esbuild/linux-ppc64": "0.25.12",
+ "@esbuild/linux-riscv64": "0.25.12",
+ "@esbuild/linux-s390x": "0.25.12",
+ "@esbuild/linux-x64": "0.25.12",
+ "@esbuild/netbsd-arm64": "0.25.12",
+ "@esbuild/netbsd-x64": "0.25.12",
+ "@esbuild/openbsd-arm64": "0.25.12",
+ "@esbuild/openbsd-x64": "0.25.12",
+ "@esbuild/openharmony-arm64": "0.25.12",
+ "@esbuild/sunos-x64": "0.25.12",
+ "@esbuild/win32-arm64": "0.25.12",
+ "@esbuild/win32-ia32": "0.25.12",
+ "@esbuild/win32-x64": "0.25.12"
+ }
+ },
+ "node_modules/drizzle-orm": {
+ "version": "0.45.2",
+ "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.45.2.tgz",
+ "integrity": "sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "@aws-sdk/client-rds-data": ">=3",
+ "@cloudflare/workers-types": ">=4",
+ "@electric-sql/pglite": ">=0.2.0",
+ "@libsql/client": ">=0.10.0",
+ "@libsql/client-wasm": ">=0.10.0",
+ "@neondatabase/serverless": ">=0.10.0",
+ "@op-engineering/op-sqlite": ">=2",
+ "@opentelemetry/api": "^1.4.1",
+ "@planetscale/database": ">=1.13",
+ "@prisma/client": "*",
+ "@tidbcloud/serverless": "*",
+ "@types/better-sqlite3": "*",
+ "@types/pg": "*",
+ "@types/sql.js": "*",
+ "@upstash/redis": ">=1.34.7",
+ "@vercel/postgres": ">=0.8.0",
+ "@xata.io/client": "*",
+ "better-sqlite3": ">=7",
+ "bun-types": "*",
+ "expo-sqlite": ">=14.0.0",
+ "gel": ">=2",
+ "knex": "*",
+ "kysely": "*",
+ "mysql2": ">=2",
+ "pg": ">=8",
+ "postgres": ">=3",
+ "sql.js": ">=1",
+ "sqlite3": ">=5"
},
"peerDependenciesMeta": {
- "supports-color": {
+ "@aws-sdk/client-rds-data": {
"optional": true
- }
- }
- },
- "node_modules/decimal.js": {
- "version": "10.6.0",
- "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
- "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/deep-eql": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
- "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
+ },
+ "@cloudflare/workers-types": {
+ "optional": true
+ },
+ "@electric-sql/pglite": {
+ "optional": true
+ },
+ "@libsql/client": {
+ "optional": true
+ },
+ "@libsql/client-wasm": {
+ "optional": true
+ },
+ "@neondatabase/serverless": {
+ "optional": true
+ },
+ "@op-engineering/op-sqlite": {
+ "optional": true
+ },
+ "@opentelemetry/api": {
+ "optional": true
+ },
+ "@planetscale/database": {
+ "optional": true
+ },
+ "@prisma/client": {
+ "optional": true
+ },
+ "@tidbcloud/serverless": {
+ "optional": true
+ },
+ "@types/better-sqlite3": {
+ "optional": true
+ },
+ "@types/pg": {
+ "optional": true
+ },
+ "@types/sql.js": {
+ "optional": true
+ },
+ "@upstash/redis": {
+ "optional": true
+ },
+ "@vercel/postgres": {
+ "optional": true
+ },
+ "@xata.io/client": {
+ "optional": true
+ },
+ "better-sqlite3": {
+ "optional": true
+ },
+ "bun-types": {
+ "optional": true
+ },
+ "expo-sqlite": {
+ "optional": true
+ },
+ "gel": {
+ "optional": true
+ },
+ "knex": {
+ "optional": true
+ },
+ "kysely": {
+ "optional": true
+ },
+ "mysql2": {
+ "optional": true
+ },
+ "pg": {
+ "optional": true
+ },
+ "postgres": {
+ "optional": true
+ },
+ "prisma": {
+ "optional": true
+ },
+ "sql.js": {
+ "optional": true
+ },
+ "sqlite3": {
+ "optional": true
+ }
}
},
- "node_modules/dequal": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
- "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "node_modules/drizzle-typebox": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/drizzle-typebox/-/drizzle-typebox-0.3.3.tgz",
+ "integrity": "sha512-iJpW9K+BaP8+s/ImHxOFVjoZk9G5N/KXFTOpWcFdz9SugAOWv2fyGaH7FmqgdPo+bVNYQW0OOI3U9dkFIVY41w==",
"dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/diff": {
- "version": "8.0.4",
- "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz",
- "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==",
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.3.1"
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "@sinclair/typebox": ">=0.34.8",
+ "drizzle-orm": ">=0.36.0"
}
},
- "node_modules/dom-accessibility-api": {
- "version": "0.5.16",
- "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
- "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
@@ -3443,6 +4658,16 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/end-of-stream": {
+ "version": "1.4.5",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
+ "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "once": "^1.4.0"
+ }
+ },
"node_modules/entities": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz",
@@ -3525,6 +4750,16 @@
"@types/estree": "^1.0.0"
}
},
+ "node_modules/expand-template": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
+ "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
+ "dev": true,
+ "license": "(MIT OR WTFPL)",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/expect-type": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz",
@@ -3601,6 +4836,13 @@
"node": "^12.20 || >= 14.13"
}
},
+ "node_modules/file-uri-to-path": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
+ "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
@@ -3613,6 +4855,13 @@
"node": ">=12.20.0"
}
},
+ "node_modules/fs-constants": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
+ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -3678,6 +4927,26 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/get-tsconfig": {
+ "version": "4.14.0",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz",
+ "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "resolve-pkg-maps": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+ }
+ },
+ "node_modules/github-from-package": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
+ "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/glob": {
"version": "13.0.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz",
@@ -3787,6 +5056,27 @@
"node": ">= 14"
}
},
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
"node_modules/ignore": {
"version": "7.0.5",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
@@ -3796,6 +5086,20 @@
"node": ">= 4"
}
},
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/ini": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/is-potential-custom-element-name": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
@@ -4026,6 +5330,19 @@
"dev": true,
"license": "CC0-1.0"
},
+ "node_modules/mimic-response": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
+ "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/minimatch": {
"version": "10.2.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
@@ -4041,6 +5358,16 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/minipass": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
@@ -4050,6 +5377,13 @@
"node": ">=16 || 14 >=14.17"
}
},
+ "node_modules/mkdirp-classic": {
+ "version": "0.5.3",
+ "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
+ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -4075,6 +5409,39 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
+ "node_modules/napi-build-utils": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
+ "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/node-abi": {
+ "version": "3.92.0",
+ "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.92.0.tgz",
+ "integrity": "sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.3.5"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/node-abi/node_modules/semver": {
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz",
+ "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
@@ -4123,6 +5490,16 @@
"node": ">=18"
}
},
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
"node_modules/openai": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/openai/-/openai-6.26.0.tgz",
@@ -4322,6 +5699,34 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/prebuild-install": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
+ "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
+ "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "detect-libc": "^2.0.0",
+ "expand-template": "^2.0.3",
+ "github-from-package": "0.0.0",
+ "minimist": "^1.2.3",
+ "mkdirp-classic": "^0.5.3",
+ "napi-build-utils": "^2.0.0",
+ "node-abi": "^3.3.0",
+ "pump": "^3.0.0",
+ "rc": "^1.2.7",
+ "simple-get": "^4.0.0",
+ "tar-fs": "^2.0.0",
+ "tunnel-agent": "^0.6.0"
+ },
+ "bin": {
+ "prebuild-install": "bin.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/pretty-format": {
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
@@ -4381,6 +5786,17 @@
"node": ">=12.0.0"
}
},
+ "node_modules/pump": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
+ "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -4391,6 +5807,22 @@
"node": ">=6"
}
},
+ "node_modules/rc": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
+ "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
+ "dev": true,
+ "license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
+ "dependencies": {
+ "deep-extend": "^0.6.0",
+ "ini": "~1.3.0",
+ "minimist": "^1.2.0",
+ "strip-json-comments": "~2.0.1"
+ },
+ "bin": {
+ "rc": "cli.js"
+ }
+ },
"node_modules/react": {
"version": "19.2.6",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz",
@@ -4429,6 +5861,21 @@
"node": ">=0.10.0"
}
},
+ "node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
@@ -4439,6 +5886,16 @@
"node": ">=0.10.0"
}
},
+ "node_modules/resolve-pkg-maps": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
+ }
+ },
"node_modules/retry": {
"version": "0.13.1",
"resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
@@ -4604,6 +6061,63 @@
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"license": "ISC"
},
+ "node_modules/simple-concat": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
+ "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/simple-get": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
+ "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "decompress-response": "^6.0.0",
+ "once": "^1.3.1",
+ "simple-concat": "^1.0.0"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -4614,6 +6128,17 @@
"node": ">=0.10.0"
}
},
+ "node_modules/source-map-support": {
+ "version": "0.5.21",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
+ "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
"node_modules/stackback": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
@@ -4628,6 +6153,26 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+ "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/strnum": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/strnum/-/strnum-2.3.0.tgz",
@@ -4647,6 +6192,36 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/tar-fs": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
+ "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chownr": "^1.1.1",
+ "mkdirp-classic": "^0.5.2",
+ "pump": "^3.0.0",
+ "tar-stream": "^2.1.4"
+ }
+ },
+ "node_modules/tar-stream": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
+ "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bl": "^4.0.3",
+ "end-of-stream": "^1.4.1",
+ "fs-constants": "^1.0.0",
+ "inherits": "^2.0.3",
+ "readable-stream": "^3.1.1"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/tinybench": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
@@ -4768,6 +6343,19 @@
"fsevents": "~2.3.3"
}
},
+ "node_modules/tunnel-agent": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+ "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/typebox": {
"version": "1.1.38",
"resolved": "https://registry.npmjs.org/typebox/-/typebox-1.1.38.tgz",
@@ -4843,6 +6431,13 @@
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/vite": {
"version": "5.4.21",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
@@ -5511,6 +7106,13 @@
"node": ">=8"
}
},
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/ws": {
"version": "8.20.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz",
diff --git a/package.json b/package.json
index 2aa14cf60..784c61716 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "brunch-next",
"version": "0.0.0",
- "description": "Brunch \u2014 opinionated specification-workspace product over pi-coding-agent.",
+ "description": "Brunch — opinionated specification-workspace product over pi-coding-agent.",
"private": true,
"type": "module",
"main": "./dist/brunch.js",
@@ -42,13 +42,19 @@
"zod": "^4.4.3"
},
"devDependencies": {
+ "@sinclair/typebox": "^0.34.14",
"@testing-library/dom": "^10.4.1",
"@testing-library/react": "^16.3.2",
+ "@types/better-sqlite3": "^7.6.13",
"@types/node": "^22.10.0",
"@types/react": "^19.2.15",
"@types/react-dom": "^19.2.3",
"@types/ws": "^8.18.1",
"@vitejs/plugin-react": "^4.7.0",
+ "better-sqlite3": "^12.8.0",
+ "drizzle-kit": "^0.31.10",
+ "drizzle-orm": "^0.45.2",
+ "drizzle-typebox": "^0.3.3",
"jsdom": "^29.1.1",
"oxfmt": "^0.2.0",
"oxlint": "^0.18.0",
@@ -59,5 +65,8 @@
},
"engines": {
"node": ">=20"
+ },
+ "allowScripts": {
+ "better-sqlite3@12.8.0": true
}
}
diff --git a/src/README.md b/src/README.md
new file mode 100644
index 000000000..276653644
--- /dev/null
+++ b/src/README.md
@@ -0,0 +1,63 @@
+# src/ — Brunch source topology
+
+Decision D52-L in `memory/SPEC.md` locks this layout.
+
+```
+src/
+├── .pi/ Pi adapter layer (TUI)
+│ ├── components/ reusable TUI components
+│ └── extensions/ Pi registrars: agent tools, TUI commands, enhancements
+│
+├── agents/ Agent intelligence layer
+│ ├── modes/ operational mode prompts and rules
+│ ├── strategies/ interaction-shape prompts (propose-graph, project-graph, etc.)
+│ ├── lenses/ topical-focus prompts (intent, design, oracle, etc.)
+│ └── contexts/ snapshot orchestration (calls graph/ and session/)
+│
+├── db/ Persistence substrate
+│ Drizzle schema, migrations, connection lifecycle
+│
+├── graph/ Graph domain layer
+│ CommandExecutor, readers, policy, validators,
+│ snapshot bucketing, change-log replay, recon-need substrate
+│
+├── session/ Session domain layer
+│ transcript projection, exchange extraction,
+│ workspace coordination, session binding, LSN staleness
+│
+├── rpc/ Brunch JSON-RPC handlers
+│ protocol, method handlers, WebSocket adapter
+│
+└── web/ React client (standalone build target)
+ routes, hooks, RPC client
+```
+
+## Dependency direction
+
+```
+.pi/extensions/ ──┐
+ ├──▶ graph/ ──▶ db/
+rpc/ ────────────┤
+ ├──▶ session/
+agents/ ─────────┘
+ (Pi JSONL — not Brunch-owned storage)
+
+web/ ── standalone build, imports from rpc/ types only
+```
+
+Rules:
+- `graph/` imports from `db/`. No other layer imports `db/` directly.
+- `agents/` imports snapshot functions from `graph/` and `session/`.
+- `.pi/extensions/` and `rpc/` may import from `graph/`, `session/`, and `agents/`.
+- `web/` is a separate Vite build target.
+
+## Migration notes
+
+Some files currently at `src/` root belong in `src/session/` per this layout
+(workspace-session-coordinator, session-binding, session-projection-reader,
+brunch-session-envelope, session-transcript, elicitation-exchange,
+structured-exchange). Move incrementally as each file is touched.
+
+Prompt composition currently under `src/tui-client/.pi/context/` migrates
+to `src/agents/` per D52-L. The `.pi/context/` README describes the
+current interim layout.
diff --git a/src/agents/README.md b/src/agents/README.md
new file mode 100644
index 000000000..dc9d64123
--- /dev/null
+++ b/src/agents/README.md
@@ -0,0 +1,97 @@
+# agents/ — Agent intelligence layer
+
+SPEC decisions: D25-L, D40-L, D52-L
+
+## Owns
+
+Everything that shapes what the LLM sees and does: state definitions,
+prompt composition, strategy/lens content, and context snapshot orchestration.
+
+### Agent state hierarchy
+
+```
+spec.grade
+ grounding → elicitation I,II → commitment → export
+
+session.mode = elicitation | execution (future) | reconciliation (deferred)
+session.agent = elicitor | planner (future) | reconciler (deferred)
+session.strategy = per-agent interaction shape
+session.lens = per-mode topical focus
+session.sub-agents = research, explore, design, oracle, review, reconcile
+```
+
+### Strategy × lens (D25-L)
+
+Strategies describe the interaction shape. Lenses describe topical focus.
+The combination maps to the prior "lens catalogue" names:
+
+| Strategy | Commitment path | Example lens combinations |
+|--------------------------|--------------------|------------------------------------|
+| `step-wise-decision-tree`| single-exchange | any lens |
+| `step-wise-disambiguate` | single-exchange | any lens |
+| `propose-graph` | direct commit | intent, design, oracle |
+| `project-graph` | review-set | intent |
+
+### Context building
+
+Snapshot functions live in `contexts/`. They orchestrate *which* snapshots
+to inject based on mode/role/strategy/lens/grade, by calling into:
+
+```
+agents/contexts/
+ │
+ ├──▶ graph/ → snapshotGraph(detail), snapshotNode(id, hops)
+ │
+ └──▶ session/ → workspace/spec envelope
+```
+
+Graph snapshots support multiple detail levels (I35-L):
+- **Cursory** — compact full-graph overview for orientation
+- **Neighborhood** — detailed node + N-hop expansion for focused work
+
+## Directory layout
+
+```
+agents/
+├── README.md
+├── state.ts mode/role/strategy/lens type defs + valid combos
+├── compose.ts prompt orchestrator: reads state, picks packs, calls snapshots
+├── modes/
+│ └── elicit.md elicitation mode rules, tool authority
+├── strategies/
+│ ├── step-wise-decision-tree.md
+│ ├── step-wise-disambiguate.md
+│ ├── propose-graph.md ← graph vocabulary, category rubric, batch format
+│ └── project-graph.md
+├── lenses/
+│ ├── intent.md
+│ ├── design.md
+│ └── oracle.md
+└── contexts/
+ ├── graph-context.ts calls graph/ snapshot fns, formats for prompt
+ └── readiness-context.ts
+```
+
+## Does NOT own
+
+- Pi extension registration, tool definitions — those live in `.pi/extensions/`.
+- Graph domain logic, CommandExecutor — those live in `graph/`.
+- Session projection, transcript reading — those live in `session/`.
+
+## Imported by
+
+- `.pi/extensions/prompting.ts` — calls compose.ts at turn boundaries
+- `.pi/extensions/operational-mode.ts` — reads state definitions
+
+## Migration from .pi/context/
+
+The current `src/tui-client/.pi/context/` layout migrates here:
+
+| Current location | Target |
+|-------------------------------------------|-------------------------------|
+| `.pi/context/compose-brunch-prompt.ts` | `agents/compose.ts` |
+| `.pi/context/prompt-packs/*.md` | `agents/modes/`, `strategies/`, `lenses/` |
+| `.pi/context/builders/graph-context.ts` | `agents/contexts/graph-context.ts` |
+| `.pi/context/builders/readiness-context.ts`| `agents/contexts/readiness-context.ts` |
+
+Move incrementally as prompt composition is refactored.
diff --git a/src/agents/lenses/README.md b/src/agents/lenses/README.md
new file mode 100644
index 000000000..a00b86ca2
--- /dev/null
+++ b/src/agents/lenses/README.md
@@ -0,0 +1,43 @@
+# lenses/ — Topical-focus prompt packs
+
+SPEC decisions: D25-L, D56-L
+
+Each lens describes a topical focus — what domain of the spec the
+agent is currently exploring or proposing into.
+
+## Lenses
+
+| Lens | Plane focus | Notes |
+|-----------|-------------|-----------------------------------------|
+| `intent` | intent | Requirements, goals, constraints, etc. |
+| `design` | design | Modules, interfaces, architecture |
+| `oracle` | oracle | Checks, criteria, evidence, obligations |
+
+Future execute-mode lenses (`plan`, `sync`, `scope`) are deferred.
+
+## Topology-driven question ranking (M5 input)
+
+When `agent-graph-integration` lands prompt packs, each lens
+should include topology-driven heuristics for what to ask next.
+These heuristics read graph shape, not templates:
+
+| Signal | Suggested question shape |
+|----------------------------------------------|---------------------------------------------------|
+| `assumption` with high fanout + low confidence | "We depend on X. Want to validate it?" |
+| `requirement` with no incoming `proof` edge | "How will we know this holds?" |
+| `criterion` with no outgoing `proof` target | "What does this criterion check?" |
+| `decision` with empty `rejected` | "What did we consider and rule out?" |
+| Conflicting `boundary` edges into same target | "These constraints disagree. Which wins?" |
+| `goal` with no derived requirements | "Nothing ties to this goal. What would satisfy it?" |
+| `requirement` with no examples + high uncertainty | "What's a concrete case where this matters?" |
+
+These complement behavioral-kernel signal-phrase routing: kernels
+suggest *what kind* of question; topology heuristics suggest *which
+item* to ask about next.
+
+## Source reference
+
+Rich topology-driven ranking heuristics from the earlier design
+are in the archived
+`/brunch/docs/design/INTENT_GRAPH_SEMANTICS.md` §Topology-driven
+question ranking. Treat as a prompt engineering input.
diff --git a/src/agents/strategies/README.md b/src/agents/strategies/README.md
new file mode 100644
index 000000000..1859e8ca6
--- /dev/null
+++ b/src/agents/strategies/README.md
@@ -0,0 +1,56 @@
+# strategies/ — Interaction-shape prompt packs
+
+SPEC decisions: D25-L, D26-L, D53-L
+
+Each strategy describes an interaction shape — how the agent
+structures its turns, what commitment mechanism it uses, and what
+the user experiences.
+
+## Strategies
+
+| Strategy | Commitment path | Notes |
+|---------------------------|-----------------|------------------------------------|
+| `step-wise-decision-tree` | single-exchange | Q&A one claim at a time |
+| `step-wise-disambiguate` | single-exchange | contrastive examples |
+| `propose-graph` | direct commit | concept → user accepts → commitGraph |
+| `project-graph` | review-set | derive from existing graph |
+
+## Prompt pack contents
+
+Each `.md` file in this directory is a prompt pack injected when
+the strategy is active. It should contain:
+
+- What the agent is doing in this strategy
+- How to structure the turn
+- What commitment mechanism to use
+- What graph operations are available
+- Category-selection rubric (for graph-writing strategies)
+
+## Observer classification guide (M5 input)
+
+When `agent-graph-integration` lands prompt packs, seed each
+strategy's prompt with the observer classification rules from
+the earlier `INTENT_GRAPH_SEMANTICS.md` translation table:
+
+| User phrase pattern | Most likely kind |
+|----------------------------------|-----------------------|
+| "always true that…" | `invariant` |
+| "should never…" | `invariant` |
+| "for example, when…" | `example` |
+| "we wouldn't want…" | `example` (negative) or `constraint` |
+| "we don't care about X" | `constraint` |
+| "we picked Y over Z because…" | `decision` |
+| "we think" / "probably" | `assumption` |
+| "the system shall" / "must do" | `requirement` |
+| "what outcome are we after?" | `goal` |
+
+The observer should **abstain** rather than guess when
+classification support is weak.
+
+## Source reference
+
+Rich classification and translation tables from the earlier
+design are in the archived
+`/brunch/docs/design/INTENT_GRAPH_SEMANTICS.md` §Observer-prompt
+classification guide and §Translation table. Treat as a prompt
+engineering input, not a schema target.
diff --git a/src/brunch-pi-profile.ts b/src/brunch-pi-profile.ts
new file mode 100644
index 000000000..831f66951
--- /dev/null
+++ b/src/brunch-pi-profile.ts
@@ -0,0 +1,162 @@
+import process from "node:process"
+
+import {
+ SettingsManager,
+ type ExtensionFactory,
+} from "@earendil-works/pi-coding-agent"
+
+export const BRUNCH_SETTINGS_POLICY = {
+ quietStartup: true,
+ packages: [],
+ extensions: [],
+ skills: [],
+ prompts: [],
+ themes: [],
+ enableSkillCommands: false,
+ doubleEscapeAction: "none",
+ compaction: {
+ enabled: true,
+ reserveTokens: 16384,
+ keepRecentTokens: 20000,
+ },
+ branchSummary: {
+ reserveTokens: 16384,
+ skipPrompt: false,
+ },
+ retry: {
+ enabled: true,
+ maxRetries: 3,
+ baseDelayMs: 2000,
+ provider: {
+ maxRetryDelayMs: 60000,
+ },
+ },
+ terminal: {
+ showImages: true,
+ imageWidthCells: 60,
+ clearOnShrink: false,
+ showTerminalProgress: false,
+ },
+ images: {
+ autoResize: true,
+ blockImages: false,
+ },
+ transport: "auto",
+ collapseChangelog: false,
+ enableInstallTelemetry: false,
+ showHardwareCursor: false,
+ editorPaddingX: 0,
+ autocompleteMaxVisible: 5,
+ markdown: {
+ codeBlockIndent: " ",
+ },
+ warnings: {},
+} satisfies Parameters[0]
+
+export const BRUNCH_SETTINGS_AUDITED_GETTERS = [
+ "getGlobalSettings",
+ "getProjectSettings",
+ "getLastChangelogVersion",
+ "getSessionDir",
+ "getDefaultProvider",
+ "getDefaultModel",
+ "getSteeringMode",
+ "getFollowUpMode",
+ "getTheme",
+ "getDefaultThinkingLevel",
+ "getTransport",
+ "getCompactionEnabled",
+ "getCompactionReserveTokens",
+ "getCompactionKeepRecentTokens",
+ "getCompactionSettings",
+ "getBranchSummarySettings",
+ "getBranchSummarySkipPrompt",
+ "getRetryEnabled",
+ "getRetrySettings",
+ "getProviderRetrySettings",
+ "getHideThinkingBlock",
+ "getShellPath",
+ "getQuietStartup",
+ "getShellCommandPrefix",
+ "getNpmCommand",
+ "getCollapseChangelog",
+ "getEnableInstallTelemetry",
+ "getPackages",
+ "getExtensionPaths",
+ "getSkillPaths",
+ "getPromptTemplatePaths",
+ "getThemePaths",
+ "getEnableSkillCommands",
+ "getThinkingBudgets",
+ "getShowImages",
+ "getImageWidthCells",
+ "getClearOnShrink",
+ "getShowTerminalProgress",
+ "getImageAutoResize",
+ "getBlockImages",
+ "getEnabledModels",
+ "getDoubleEscapeAction",
+ "getTreeFilterMode",
+ "getShowHardwareCursor",
+ "getEditorPaddingX",
+ "getAutocompleteMaxVisible",
+ "getCodeBlockIndent",
+ "getWarnings",
+] as const
+
+export interface BrunchPiProfileOptions {
+ cwd: string
+ agentDir: string
+ extensionFactories: ExtensionFactory[]
+}
+
+export interface BrunchPiProfile {
+ settingsManager: SettingsManager
+ resourceLoaderOptions: BrunchResourceLoaderOptions
+}
+
+export interface BrunchResourceLoaderOptions {
+ noContextFiles: true
+ noExtensions: true
+ noPromptTemplates: true
+ noSkills: true
+ noThemes: true
+ extensionFactories: ExtensionFactory[]
+}
+
+export function createBrunchPiProfile({
+ cwd,
+ agentDir,
+ extensionFactories,
+}: BrunchPiProfileOptions): BrunchPiProfile {
+ return {
+ settingsManager: createBrunchSettingsManager(cwd, agentDir),
+ resourceLoaderOptions: brunchResourceLoaderOptions(extensionFactories),
+ }
+}
+
+export function brunchResourceLoaderOptions(
+ extensionFactories: ExtensionFactory[],
+): BrunchResourceLoaderOptions {
+ return {
+ noContextFiles: true,
+ noExtensions: true,
+ noPromptTemplates: true,
+ noSkills: true,
+ noThemes: true,
+ extensionFactories,
+ }
+}
+
+export function applyBrunchOfflineDefault(
+ env: { PI_OFFLINE?: string } = process.env,
+): void {
+ env.PI_OFFLINE ??= "1"
+}
+
+export function createBrunchSettingsManager(
+ _cwd: string,
+ _agentDir: string,
+): SettingsManager {
+ return SettingsManager.inMemory(BRUNCH_SETTINGS_POLICY)
+}
diff --git a/src/brunch-tui.test.ts b/src/brunch-tui.test.ts
index efcbf1acc..ae660c1f9 100644
--- a/src/brunch-tui.test.ts
+++ b/src/brunch-tui.test.ts
@@ -1,5 +1,5 @@
import { userMessage } from "./test-helpers.js"
-import { mkdtemp, readFile } from "node:fs/promises"
+import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises"
import { tmpdir } from "node:os"
import { join } from "node:path"
@@ -14,11 +14,14 @@ import {
} from "@earendil-works/pi-coding-agent"
import {
+ BRUNCH_SETTINGS_AUDITED_GETTERS,
+ BRUNCH_SETTINGS_POLICY,
applyBrunchOfflineDefault,
brunchResourceLoaderOptions,
createBrunchSettingsManager,
runBrunchTui,
} from "./brunch-tui.js"
+import { createBrunchPiProfile } from "./brunch-pi-profile.js"
import {
BRUNCH_WORKSPACE_COMMAND,
BRUNCH_WORKSPACE_SHORTCUT,
@@ -762,8 +765,227 @@ describe("Brunch TUI boot", () => {
})
expect(env.PI_OFFLINE).toBe("1")
})
+
+ it("ignores hostile ambient Pi settings for behavior-shaping profile policy", async () => {
+ const cwd = await mkdtemp(join(tmpdir(), "brunch-tui-"))
+ const agentDir = join(cwd, "home-pi")
+ await writeHostilePiSettings(cwd, agentDir)
+
+ const settingsManager = createBrunchSettingsManager(cwd, agentDir)
+
+ expect(settingsManager.getShellPath()).toBeUndefined()
+ expect(settingsManager.getShellCommandPrefix()).toBeUndefined()
+ expect(settingsManager.getNpmCommand()).toBeUndefined()
+ expect(settingsManager.getPackages()).toEqual([])
+ expect(settingsManager.getExtensionPaths()).toEqual([])
+ expect(settingsManager.getSkillPaths()).toEqual([])
+ expect(settingsManager.getPromptTemplatePaths()).toEqual([])
+ expect(settingsManager.getThemePaths()).toEqual([])
+ expect(settingsManager.getEnableSkillCommands()).toBe(false)
+ expect(settingsManager.getDoubleEscapeAction()).toBe("none")
+ expect(settingsManager.getCompactionSettings()).toEqual({
+ enabled: true,
+ reserveTokens: 16384,
+ keepRecentTokens: 20000,
+ })
+ expect(settingsManager.getRetrySettings()).toEqual({
+ enabled: true,
+ maxRetries: 3,
+ baseDelayMs: 2000,
+ })
+ expect(settingsManager.getProviderRetrySettings()).toEqual({
+ timeoutMs: undefined,
+ maxRetries: undefined,
+ maxRetryDelayMs: 60000,
+ })
+ expect(settingsManager.getShowImages()).toBe(true)
+ expect(settingsManager.getImageWidthCells()).toBe(60)
+ expect(settingsManager.getClearOnShrink()).toBe(false)
+ expect(settingsManager.getShowTerminalProgress()).toBe(false)
+ expect(settingsManager.getImageAutoResize()).toBe(true)
+ expect(settingsManager.getBlockImages()).toBe(false)
+ expect(settingsManager.getTransport()).toBe("auto")
+ expect(settingsManager.getTheme()).toBeUndefined()
+ expect(settingsManager.getLastChangelogVersion()).toBeUndefined()
+ expect(settingsManager.getCollapseChangelog()).toBe(false)
+ expect(settingsManager.getEnableInstallTelemetry()).toBe(false)
+ expect(settingsManager.getShowHardwareCursor()).toBe(false)
+ expect(settingsManager.getEditorPaddingX()).toBe(0)
+ expect(settingsManager.getAutocompleteMaxVisible()).toBe(5)
+ })
+
+ it("keeps sealed Brunch settings after Pi settings reload", async () => {
+ const cwd = await mkdtemp(join(tmpdir(), "brunch-tui-"))
+ const agentDir = join(cwd, "home-pi")
+ await writeHostilePiSettings(cwd, agentDir)
+ const settingsManager = createBrunchSettingsManager(cwd, agentDir)
+
+ await settingsManager.reload()
+
+ expect(settingsManager.getQuietStartup()).toBe(true)
+ expect(settingsManager.getPackages()).toEqual([])
+ expect(settingsManager.getExtensionPaths()).toEqual([])
+ expect(settingsManager.getSkillPaths()).toEqual([])
+ expect(settingsManager.getPromptTemplatePaths()).toEqual([])
+ expect(settingsManager.getThemePaths()).toEqual([])
+ expect(settingsManager.getEnableSkillCommands()).toBe(false)
+ expect(settingsManager.getDoubleEscapeAction()).toBe("none")
+ expect(settingsManager.getShellPath()).toBeUndefined()
+ expect(settingsManager.getNpmCommand()).toBeUndefined()
+ })
+
+ it("keeps ambient resource suppression and explicit product extensions behind one profile boundary", async () => {
+ const cwd = await mkdtemp(join(tmpdir(), "brunch-tui-"))
+ const extension = () => {}
+ const profile = createBrunchPiProfile({
+ cwd,
+ agentDir: cwd,
+ extensionFactories: [extension],
+ })
+
+ expect(profile.settingsManager.getQuietStartup()).toBe(true)
+ expect(profile.resourceLoaderOptions).toEqual({
+ noContextFiles: true,
+ noExtensions: true,
+ noPromptTemplates: true,
+ noSkills: true,
+ noThemes: true,
+ extensionFactories: [extension],
+ })
+ })
+
+ it("keeps Pi settings/resource policy out of the TUI launcher", async () => {
+ const launcherSource = await readFile(
+ join(import.meta.dirname, "brunch-tui.ts"),
+ "utf8",
+ )
+ const profileSource = await readFile(
+ join(import.meta.dirname, "brunch-pi-profile.ts"),
+ "utf8",
+ )
+
+ expect(launcherSource).toContain("createBrunchPiProfile")
+ expect(launcherSource).not.toContain("SettingsManager.create")
+ expect(launcherSource).not.toContain("noContextFiles")
+ expect(profileSource).toContain("SettingsManager.inMemory")
+ expect(profileSource).toContain("noContextFiles: true")
+ })
+
+ it("keeps the Brunch settings override and audit list in the profile boundary", async () => {
+ const launcherSource = await readFile(
+ join(import.meta.dirname, "brunch-tui.ts"),
+ "utf8",
+ )
+ const profileSource = await readFile(
+ join(import.meta.dirname, "brunch-pi-profile.ts"),
+ "utf8",
+ )
+ const settingsManagerTypes = await readFile(
+ join(
+ import.meta.dirname,
+ "..",
+ "node_modules",
+ "@earendil-works",
+ "pi-coding-agent",
+ "dist",
+ "core",
+ "settings-manager.d.ts",
+ ),
+ "utf8",
+ )
+ const getterNames = Array.from(
+ settingsManagerTypes.matchAll(/\n (get[A-Z][A-Za-z0-9]+)\(/g),
+ (match) => match[1]!,
+ )
+
+ expect(BRUNCH_SETTINGS_POLICY).toMatchObject({
+ quietStartup: true,
+ packages: [],
+ extensions: [],
+ skills: [],
+ prompts: [],
+ themes: [],
+ enableSkillCommands: false,
+ doubleEscapeAction: "none",
+ })
+ expect(getterNames.sort()).toEqual(
+ [...BRUNCH_SETTINGS_AUDITED_GETTERS].sort(),
+ )
+ expect(launcherSource).not.toContain("SettingsManager.inMemory")
+ expect(profileSource).toContain("BRUNCH_SETTINGS_POLICY")
+ expect(profileSource).toContain("SettingsManager.inMemory")
+ })
})
+async function writeHostilePiSettings(
+ cwd: string,
+ agentDir: string,
+): Promise {
+ const hostileSettings = {
+ lastChangelogVersion: "999.0.0-hostile",
+ defaultProvider: "hostile-provider",
+ defaultModel: "hostile-model",
+ transport: "websocket",
+ theme: "hostile-theme",
+ compaction: {
+ enabled: false,
+ reserveTokens: 1,
+ keepRecentTokens: 2,
+ },
+ branchSummary: {
+ reserveTokens: 3,
+ skipPrompt: true,
+ },
+ retry: {
+ enabled: false,
+ maxRetries: 99,
+ baseDelayMs: 1,
+ provider: {
+ timeoutMs: 1,
+ maxRetries: 99,
+ maxRetryDelayMs: 2,
+ },
+ },
+ shellPath: "/tmp/hostile-shell",
+ quietStartup: false,
+ shellCommandPrefix: "hostile-prefix",
+ npmCommand: ["hostile-npm"],
+ collapseChangelog: true,
+ enableInstallTelemetry: true,
+ packages: ["hostile-package"],
+ extensions: ["hostile-extension"],
+ skills: ["hostile-skill"],
+ prompts: ["hostile-prompt"],
+ themes: ["hostile-theme-path"],
+ enableSkillCommands: true,
+ terminal: {
+ showImages: false,
+ imageWidthCells: 1,
+ clearOnShrink: true,
+ showTerminalProgress: true,
+ },
+ images: {
+ autoResize: false,
+ blockImages: true,
+ },
+ doubleEscapeAction: "tree",
+ showHardwareCursor: true,
+ editorPaddingX: 3,
+ autocompleteMaxVisible: 20,
+ }
+
+ await mkdir(agentDir, { recursive: true })
+ await mkdir(join(cwd, ".pi"), { recursive: true })
+ await writeFile(
+ join(agentDir, "settings.json"),
+ JSON.stringify(hostileSettings, null, 2),
+ )
+ await writeFile(
+ join(cwd, ".pi", "settings.json"),
+ JSON.stringify(hostileSettings, null, 2),
+ )
+}
+
function readyWorkspace(
cwd: string,
sessionId: string,
diff --git a/src/brunch-tui.ts b/src/brunch-tui.ts
index e66b71efb..21f3e21eb 100644
--- a/src/brunch-tui.ts
+++ b/src/brunch-tui.ts
@@ -6,9 +6,7 @@ import {
createAgentSessionServices,
getAgentDir,
InteractiveMode,
- SettingsManager,
type CreateAgentSessionRuntimeFactory,
- type ExtensionFactory,
} from "@earendil-works/pi-coding-agent"
import {
@@ -24,6 +22,20 @@ import {
createBrunchPiExtensionShell,
} from "./tui-client/pi-extension-shell.js"
import { runWorkspaceDialogPreflight } from "./tui-client/.pi/components/workspace-dialog.js"
+import {
+ applyBrunchOfflineDefault,
+ brunchResourceLoaderOptions,
+ createBrunchPiProfile,
+ createBrunchSettingsManager,
+} from "./brunch-pi-profile.js"
+export {
+ BRUNCH_SETTINGS_AUDITED_GETTERS,
+ BRUNCH_SETTINGS_POLICY,
+ applyBrunchOfflineDefault,
+ brunchResourceLoaderOptions,
+ createBrunchPiProfile,
+ createBrunchSettingsManager,
+} from "./brunch-pi-profile.js"
export {
BRUNCH_BRANCH_FLOW_BLOCKED_MESSAGE,
chromeStateForWorkspace,
@@ -103,12 +115,10 @@ async function launchPiInteractive({
agentDir: runtimeAgentDir,
sessionManager,
}) => {
- const settingsManager = createBrunchSettingsManager(cwd, runtimeAgentDir)
- const services = await createAgentSessionServices({
+ const profile = createBrunchPiProfile({
cwd,
agentDir: runtimeAgentDir,
- settingsManager,
- resourceLoaderOptions: brunchResourceLoaderOptions([
+ extensionFactories: [
createBrunchPiExtensionShell(
chromeStateForWorkspace(workspace),
async (sessionManager) => {
@@ -118,7 +128,13 @@ async function launchPiInteractive({
},
{ coordinator },
),
- ]),
+ ],
+ })
+ const services = await createAgentSessionServices({
+ cwd,
+ agentDir: runtimeAgentDir,
+ settingsManager: profile.settingsManager,
+ resourceLoaderOptions: profile.resourceLoaderOptions,
})
const created = await createAgentSessionFromServices({
services,
@@ -140,31 +156,3 @@ async function launchPiInteractive({
applyBrunchOfflineDefault()
await new InteractiveMode(runtime).run()
}
-
-export function brunchResourceLoaderOptions(
- extensionFactories: ExtensionFactory[],
-) {
- return {
- noContextFiles: true,
- noExtensions: true,
- noPromptTemplates: true,
- noSkills: true,
- noThemes: true,
- extensionFactories,
- }
-}
-
-export function applyBrunchOfflineDefault(
- env: { PI_OFFLINE?: string } = process.env,
-): void {
- env.PI_OFFLINE ??= "1"
-}
-
-export function createBrunchSettingsManager(
- cwd: string,
- agentDir: string,
-): SettingsManager {
- const settingsManager = SettingsManager.create(cwd, agentDir)
- settingsManager.getQuietStartup = () => true
- return settingsManager
-}
diff --git a/src/db/README.md b/src/db/README.md
new file mode 100644
index 000000000..b45565ce9
--- /dev/null
+++ b/src/db/README.md
@@ -0,0 +1,57 @@
+# db/ — Persistence substrate
+
+SPEC decisions: D16-L, D41-L, D52-L
+
+## Owns
+
+- **Drizzle table definitions** (`schema.ts`) — nodes, edges, change_log,
+ graph_clock, reconciliation_need. Canonical column-level source of truth
+ for persisted shapes. Exports shared enum `const` arrays (`INTENT_KINDS`,
+ `EDGE_CATEGORIES`, etc.) reused by `graph/` domain types and Pi tool
+ parameter schemas.
+
+- **Row schema derivation** (`row-schemas.ts`) — runtime insert/select
+ schemas derived from Drizzle tables via `drizzle-typebox`. Do not
+ hand-author parallel row schemas alongside table definitions.
+
+- **Connection lifecycle** (`connection.ts`) — `better-sqlite3` connection
+ creation, WAL mode, pragmas, migration runner.
+
+- **Migrations** — Drizzle-managed schema migrations for `.brunch/data.db`.
+ Wired when the first `drizzle-kit generate` run lands.
+
+## Does NOT own
+
+- Domain logic, validation, policy, CommandExecutor, readers, change-log
+ replay — all of that lives in `graph/`.
+- Query construction beyond simple helpers — domain queries live in `graph/`.
+
+## Imported by
+
+- `graph/` — the only layer that imports `db/` directly.
+ No other layer should import from this directory.
+
+## Enum flow
+
+`db/schema.ts` owns the single `const` arrays (`INTENT_KINDS`,
+`EDGE_CATEGORIES`, `NODE_BASES`, etc.). Other layers derive from them:
+
+```
+db/schema.ts const arrays + Drizzle tables
+ │ (single source of truth)
+ ├──► db/row-schemas.ts drizzle-typebox insert/select
+ │ (@sinclair/typebox 0.34)
+ ├──► graph/schema/nodes.ts type IntentKind = (typeof INTENT_KINDS)[number]
+ │ graph/schema/edges.ts type EdgeCategory = (typeof EDGE_CATEGORIES)[number]
+ │
+ └──► Pi tool parameter schemas Type.Union(INTENT_KINDS.map(Type.Literal))
+ (typebox v1.x — Pi's package)
+```
+
+Do not redeclare enum literals in `graph/` or tool definitions.
+Import the `const` array from `db/schema.ts` and derive.
+
+## Stack (settled by A20-L spike)
+
+`drizzle-orm@0.45.2` + `drizzle-kit@0.31.10` + `better-sqlite3@12.8.0`
++ `drizzle-typebox@0.3.3` + `@sinclair/typebox@0.34.14`
diff --git a/src/db/connection.ts b/src/db/connection.ts
new file mode 100644
index 000000000..2e9022879
--- /dev/null
+++ b/src/db/connection.ts
@@ -0,0 +1,94 @@
+/**
+ * better-sqlite3 connection lifecycle.
+ *
+ * SPEC decisions: D16-L (settled by A20-L spike)
+ * Stack: drizzle-orm@0.45.2 + better-sqlite3@12.8.0
+ */
+
+import Database from "better-sqlite3"
+import { drizzle } from "drizzle-orm/better-sqlite3"
+
+import * as schema from "./schema.js"
+
+export type BrunchDb = ReturnType>
+
+/**
+ * Create a Brunch database connection with schema initialized.
+ *
+ * Creates all tables if they don't exist and seeds the graph_clock
+ * with lsn=0. For tests, pass `":memory:"` for an in-memory database.
+ *
+ * When real migrations are needed (existing data to transform),
+ * replace `initSchema` with `drizzle-kit`-managed migrations.
+ */
+export function createDb(path: string): BrunchDb {
+ const sqlite = new Database(path)
+ sqlite.pragma("journal_mode = WAL")
+ sqlite.pragma("foreign_keys = ON")
+ initSchema(sqlite)
+ return drizzle(sqlite, { schema })
+}
+
+/**
+ * Push schema DDL and seed initial data.
+ *
+ * This replaces drizzle-kit migrations for the initial M4 slice.
+ * Pre-release posture: no existing data to preserve, so CREATE IF
+ * NOT EXISTS is sufficient. Add migration files when schema evolution
+ * needs data transformation.
+ */
+function initSchema(sqlite: Database.Database): void {
+ sqlite.exec(`
+ CREATE TABLE IF NOT EXISTS nodes (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ plane TEXT NOT NULL,
+ kind TEXT NOT NULL,
+ title TEXT NOT NULL,
+ body TEXT,
+ basis TEXT NOT NULL DEFAULT 'explicit',
+ source TEXT,
+ detail TEXT,
+ created_at_lsn INTEGER NOT NULL,
+ updated_at_lsn INTEGER NOT NULL
+ );
+
+ CREATE TABLE IF NOT EXISTS edges (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ category TEXT NOT NULL,
+ source_id INTEGER NOT NULL REFERENCES nodes(id),
+ target_id INTEGER NOT NULL REFERENCES nodes(id),
+ stance TEXT,
+ basis TEXT NOT NULL DEFAULT 'explicit',
+ rationale TEXT,
+ created_at_lsn INTEGER NOT NULL,
+ updated_at_lsn INTEGER NOT NULL
+ );
+
+ CREATE TABLE IF NOT EXISTS graph_clock (
+ id INTEGER PRIMARY KEY,
+ lsn INTEGER NOT NULL DEFAULT 0
+ );
+
+ CREATE TABLE IF NOT EXISTS change_log (
+ lsn INTEGER PRIMARY KEY,
+ operation TEXT NOT NULL,
+ payload TEXT NOT NULL,
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
+ );
+
+ CREATE TABLE IF NOT EXISTS reconciliation_need (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ target_kind TEXT NOT NULL,
+ target_edge_id INTEGER REFERENCES edges(id),
+ target_a_id INTEGER REFERENCES nodes(id),
+ target_b_id INTEGER REFERENCES nodes(id),
+ kind TEXT NOT NULL,
+ status TEXT NOT NULL DEFAULT 'open',
+ reason TEXT,
+ created_at_lsn INTEGER NOT NULL,
+ resolved_at_lsn INTEGER
+ );
+
+ INSERT OR IGNORE INTO graph_clock (id, lsn) VALUES (1, 0);
+ `)
+}
diff --git a/src/db/row-schemas.ts b/src/db/row-schemas.ts
new file mode 100644
index 000000000..ae5bd15a0
--- /dev/null
+++ b/src/db/row-schemas.ts
@@ -0,0 +1,40 @@
+/**
+ * Runtime row schemas derived from Drizzle table definitions.
+ *
+ * SPEC decisions: D16-L, D41-L (settled by A20-L spike)
+ * Stack: drizzle-typebox@0.3.3 + @sinclair/typebox@0.34.14
+ *
+ * Do not hand-author parallel row schemas. These are the single
+ * derived source for insert/select validation inside db/ and graph/.
+ */
+
+import { createInsertSchema, createSelectSchema } from "drizzle-typebox"
+
+import {
+ changeLog,
+ edges,
+ graphClock,
+ nodes,
+ reconciliationNeed,
+} from "./schema.js"
+
+// --- Node schemas ---
+export const insertNodeSchema = createInsertSchema(nodes)
+export const selectNodeSchema = createSelectSchema(nodes)
+
+// --- Edge schemas ---
+export const insertEdgeSchema = createInsertSchema(edges)
+export const selectEdgeSchema = createSelectSchema(edges)
+
+// --- Change log schemas ---
+export const insertChangeLogSchema = createInsertSchema(changeLog)
+export const selectChangeLogSchema = createSelectSchema(changeLog)
+
+// --- Graph clock schemas ---
+export const insertGraphClockSchema = createInsertSchema(graphClock)
+
+// --- Reconciliation need schemas ---
+export const insertReconciliationNeedSchema =
+ createInsertSchema(reconciliationNeed)
+export const selectReconciliationNeedSchema =
+ createSelectSchema(reconciliationNeed)
diff --git a/src/db/schema.ts b/src/db/schema.ts
new file mode 100644
index 000000000..16622d4e7
--- /dev/null
+++ b/src/db/schema.ts
@@ -0,0 +1,118 @@
+/**
+ * Drizzle table definitions — canonical column-level source of truth.
+ *
+ * SPEC decisions: D16-L, D51-L, D54-L, D56-L
+ * Canonical reference: docs/design/GRAPH_MODEL.md
+ *
+ * Enum const arrays are exported for reuse by graph/ domain types
+ * and by Pi tool parameter schemas (via typebox v1.x).
+ */
+
+import { sql } from "drizzle-orm"
+import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"
+
+// ---------------------------------------------------------------------------
+// Shared enum arrays — the single source for text enum columns,
+// graph/ domain types, and Pi tool parameter schemas.
+// ---------------------------------------------------------------------------
+
+export const INTENT_KINDS = [
+ "goal",
+ "thesis",
+ "term",
+ "context",
+ "requirement",
+ "assumption",
+ "constraint",
+ "invariant",
+ "decision",
+ "criterion",
+ "example",
+] as const
+
+export const ORACLE_KINDS = [
+ "check",
+ "validation_method",
+ "evidence",
+ "obligation",
+] as const
+
+export const DESIGN_KINDS = ["module", "interface"] as const
+
+export const PLAN_KINDS = ["milestone", "frontier", "slice"] as const
+
+export const NODE_BASES = ["explicit", "accepted_review_set"] as const
+
+export const EDGE_CATEGORIES = [
+ "dependency",
+ "proof",
+ "support",
+ "realization",
+ "boundary",
+ "composition",
+ "association",
+ "supersession",
+] as const
+
+export const EDGE_STANCES = ["for", "against"] as const
+
+// ---------------------------------------------------------------------------
+// Tables
+// ---------------------------------------------------------------------------
+
+export const nodes = sqliteTable("nodes", {
+ id: integer().primaryKey({ autoIncrement: true }),
+ plane: text({ enum: ["intent", "oracle", "design", "plan"] }).notNull(),
+ kind: text().notNull(), // validated at domain layer against plane-specific enum
+ title: text().notNull(),
+ body: text(),
+ basis: text({ enum: NODE_BASES }).notNull().default("explicit"),
+ source: text(),
+ detail: text(), // JSON column: decision → {chosen_option, rejected, rationale}, term → {definition, aliases?}
+ created_at_lsn: integer().notNull(),
+ updated_at_lsn: integer().notNull(),
+})
+
+export const edges = sqliteTable("edges", {
+ id: integer().primaryKey({ autoIncrement: true }),
+ category: text({ enum: EDGE_CATEGORIES }).notNull(),
+ source_id: integer()
+ .notNull()
+ .references(() => nodes.id),
+ target_id: integer()
+ .notNull()
+ .references(() => nodes.id),
+ stance: text({ enum: EDGE_STANCES }),
+ basis: text({ enum: NODE_BASES }).notNull().default("explicit"),
+ rationale: text(),
+ created_at_lsn: integer().notNull(),
+ updated_at_lsn: integer().notNull(),
+})
+
+export const graphClock = sqliteTable("graph_clock", {
+ id: integer().primaryKey(), // always row 1
+ lsn: integer().notNull().default(0),
+})
+
+export const changeLog = sqliteTable("change_log", {
+ lsn: integer().primaryKey(),
+ operation: text().notNull(),
+ payload: text().notNull(), // JSON summary of the mutation
+ created_at: text().notNull().default(sql`(datetime('now'))`),
+})
+
+export const reconciliationNeed = sqliteTable("reconciliation_need", {
+ id: integer().primaryKey({ autoIncrement: true }),
+ // target is {kind:'edge', edgeId} or {kind:'node_pair', aId, bId}
+ target_kind: text({ enum: ["edge", "node_pair"] }).notNull(),
+ target_edge_id: integer().references(() => edges.id),
+ target_a_id: integer().references(() => nodes.id),
+ target_b_id: integer().references(() => nodes.id),
+ kind: text().notNull(), // substantive taxonomy deferred per A8-L
+ status: text({ enum: ["open", "resolved"] })
+ .notNull()
+ .default("open"),
+ reason: text(),
+ created_at_lsn: integer().notNull(),
+ resolved_at_lsn: integer(),
+})
diff --git a/src/graph/README.md b/src/graph/README.md
new file mode 100644
index 000000000..82851e931
--- /dev/null
+++ b/src/graph/README.md
@@ -0,0 +1,73 @@
+# graph/ — Graph domain layer
+
+Canonical reference: `docs/design/GRAPH_MODEL.md`
+SPEC decisions: D4-L, D20-L, D51-L, D52-L, D53-L
+
+## Owns
+
+- **CommandExecutor** — the single mutation boundary for all graph writes.
+ Hides validation, LSN allocation, change-log append, transaction mechanics.
+ Returns structured results: `ok`, `needs_human`, `policy_blocked`,
+ `version_conflict`, `structural_illegal`.
+
+- **commitGraph** (D53-L) — atomic batch mutation accepting `{ nodes, edges }`
+ with intra-batch refs (`"n1"`) and existing-node refs. One tool call,
+ one LSN, all-or-nothing (I34-L). The load-bearing tool for propose-graph.
+
+- **Readers / snapshot functions** — graph queries at multiple detail levels:
+ cursory full-graph overview, node-neighborhood with configurable hops (I35-L).
+ Called by `agents/contexts/` for prompt injection.
+
+- **Policy** — per-category edge policy (cascade, recon-need triggers,
+ criteria-help signals, projection effects).
+
+- **Validators** — structural legality checks: closed edge-category set,
+ stance rules, supersession acyclicity, framing matrix, intra-batch
+ reference resolution.
+
+- **Change-log replay** — ordered mutation history keyed by LSN.
+
+- **Reconciliation-need substrate** — separate from graph edges;
+ target is `{kind:'edge', edgeId}` or `{kind:'node_pair', aId, bId}`.
+
+## Imports from
+
+- `db/` — Drizzle table definitions, connection handle.
+ This is the only layer that touches `db/` directly.
+
+## Imported by
+
+- `.pi/extensions/graph/` — Pi tool adapters call CommandExecutor
+- `rpc/` — graph.* RPC handlers call readers and CommandExecutor
+- `agents/contexts/` — snapshot functions for prompt context
+
+## Current state (Phase 1 stubs)
+
+```
+graph/
+├── atoms.ts NodeId, EdgeId, Lsn type aliases
+├── index.ts public re-exports
+├── schema/
+│ ├── edges.ts GraphEdge, EdgeCategory, EdgeStance, EdgeBasis
+│ └── reconciliation-need.ts ReconciliationNeed types
+└── policy/
+ └── category-policy.ts CATEGORY_POLICY table
+```
+
+## Target state (after M4)
+
+```
+graph/
+├── atoms.ts
+├── index.ts
+├── command-executor.ts CommandExecutor + result types
+├── commit-graph.ts batch validation + intra-batch ref resolution
+├── readers.ts snapshot queries (cursory, neighborhood)
+├── change-log.ts replay, changesSince
+├── schema/
+│ ├── edges.ts
+│ ├── nodes.ts Phase 2 — per-plane node kinds
+│ └── reconciliation-need.ts
+└── policy/
+ └── category-policy.ts
+```
diff --git a/src/graph/architecture.test.ts b/src/graph/architecture.test.ts
new file mode 100644
index 000000000..5aab75c37
--- /dev/null
+++ b/src/graph/architecture.test.ts
@@ -0,0 +1,30 @@
+/**
+ * I26-L architectural boundary test.
+ *
+ * Enforces: only `graph/` imports from `db/` directly.
+ * No other `src/` layer may import `db/` modules.
+ *
+ * SPEC: D52-L, I26-L
+ */
+
+import { execSync } from "node:child_process"
+import { describe, expect, it } from "vitest"
+
+describe("I26-L architectural boundary", () => {
+ it("no src/ module outside graph/ imports from db/", () => {
+ // Find all .ts files importing from db/ (excluding graph/, db/ itself,
+ // and test files within graph/)
+ const result = execSync(
+ `rg --files-with-matches "from ['\\"]\\.\\./db/|from ['\\"]\\.\\./\\.\\./db/|from ['\\"]\\./db/" src/ --glob '*.ts' --glob '!*.test.*' || true`,
+ { cwd: process.cwd(), encoding: "utf-8" },
+ )
+
+ const importingFiles = result
+ .trim()
+ .split("\n")
+ .filter(Boolean)
+ .filter((f) => !f.startsWith("src/graph/") && !f.startsWith("src/db/"))
+
+ expect(importingFiles).toEqual([])
+ })
+})
diff --git a/src/graph/atoms.ts b/src/graph/atoms.ts
new file mode 100644
index 000000000..a933d8c12
--- /dev/null
+++ b/src/graph/atoms.ts
@@ -0,0 +1,18 @@
+/**
+ * Graph atoms — id and clock primitives shared across the graph layer.
+ *
+ * Canonical reference: docs/design/GRAPH_MODEL.md §"Atoms"
+ *
+ * Phase 1 lock-and-materialize: type definitions only.
+ * Persistence (Drizzle + better-sqlite3 tables, LSN allocation, change_log)
+ * lands with the M4 A20-L spike slice.
+ */
+
+/** Stable id for a graph node (SQLite auto-increment integer). */
+export type NodeId = number
+
+/** Stable id for a graph edge (SQLite auto-increment integer). */
+export type EdgeId = number
+
+/** Monotonic logical sequence number; one per CommandExecutor commit. */
+export type Lsn = number
diff --git a/src/graph/command-executor.test.ts b/src/graph/command-executor.test.ts
new file mode 100644
index 000000000..29b9a7985
--- /dev/null
+++ b/src/graph/command-executor.test.ts
@@ -0,0 +1,922 @@
+/**
+ * CommandExecutor tests — acceptance criteria for the M4 skeleton slice.
+ *
+ * SPEC: D4-L, D20-L, D16-L, D52-L
+ * Scope card: CommandExecutor skeleton with single-node proof-of-life
+ */
+
+import { describe, expect, it, beforeEach } from "vitest"
+
+import { createDb, type BrunchDb } from "../db/connection.js"
+import { graphClock, changeLog, edges, nodes } from "../db/schema.js"
+import { CommandExecutor } from "./command-executor.js"
+import type { CommitGraphInput } from "./command-executor.js"
+
+function createTestDb(): BrunchDb {
+ return createDb(":memory:")
+}
+
+describe("CommandExecutor", () => {
+ let db: BrunchDb
+ let executor: CommandExecutor
+
+ beforeEach(() => {
+ db = createTestDb()
+ executor = new CommandExecutor(db)
+ })
+
+ // --- graph_clock initialization ---
+
+ it("initializes graph_clock with lsn=0", () => {
+ const rows = db.select().from(graphClock).all()
+ expect(rows).toHaveLength(1)
+ expect(rows[0]!.lsn).toBe(0)
+ })
+
+ // --- createNode: success path ---
+
+ it("creates a valid intent node and returns success with nodeId and lsn", () => {
+ const result = executor.createNode({
+ plane: "intent",
+ kind: "requirement",
+ title: "System must be offline-capable",
+ body: "Works without network connectivity",
+ })
+
+ expect(result.status).toBe("success")
+ if (result.status !== "success") throw new Error("unreachable")
+ expect(result.nodeId).toBeTypeOf("number")
+ expect(result.lsn).toBe(1)
+ })
+
+ it("defaults basis to 'explicit' when omitted", () => {
+ executor.createNode({
+ plane: "intent",
+ kind: "goal",
+ title: "Some goal",
+ })
+
+ const row = db.select().from(nodes).all()[0]
+ expect(row!.basis).toBe("explicit")
+ })
+
+ it("stores optional body and source fields", () => {
+ executor.createNode({
+ plane: "intent",
+ kind: "context",
+ title: "Target market",
+ body: "Enterprise B2B SaaS",
+ source: "stakeholder",
+ })
+
+ const row = db.select().from(nodes).all()[0]
+ expect(row!.body).toBe("Enterprise B2B SaaS")
+ expect(row!.source).toBe("stakeholder")
+ })
+
+ it("creates a decision node with required detail", () => {
+ const result = executor.createNode({
+ plane: "intent",
+ kind: "decision",
+ title: "Use SQLite for persistence",
+ detail: {
+ chosen_option: "SQLite via better-sqlite3",
+ rejected: ["PostgreSQL", "In-memory only"],
+ rationale: "Local-first single-process, no server needed",
+ },
+ })
+
+ expect(result.status).toBe("success")
+ const row = db.select().from(nodes).all()[0]
+ expect(row!.detail).not.toBeNull()
+ const detail = JSON.parse(row!.detail!)
+ expect(detail.chosen_option).toBe("SQLite via better-sqlite3")
+ expect(detail.rejected).toEqual(["PostgreSQL", "In-memory only"])
+ })
+
+ it("creates a term node with required detail", () => {
+ const result = executor.createNode({
+ plane: "intent",
+ kind: "term",
+ title: "Reconciliation Need",
+ detail: {
+ definition: "A record of an open impasse over graph state",
+ aliases: ["recon need", "impasse"],
+ },
+ })
+
+ expect(result.status).toBe("success")
+ const row = db.select().from(nodes).all()[0]
+ const detail = JSON.parse(row!.detail!)
+ expect(detail.definition).toBe(
+ "A record of an open impasse over graph state",
+ )
+ expect(detail.aliases).toEqual(["recon need", "impasse"])
+ })
+
+ // --- createNode: structural_illegal rejections ---
+
+ it("rejects invalid kind for plane", () => {
+ const result = executor.createNode({
+ plane: "intent",
+ kind: "check", // oracle-plane kind, not intent
+ title: "Wrong plane",
+ })
+
+ expect(result.status).toBe("structural_illegal")
+ if (result.status !== "structural_illegal") throw new Error("unreachable")
+ expect(result.diagnostics.some((d) => d.field === "kind")).toBe(true)
+ })
+
+ it("rejects decision without detail", () => {
+ const result = executor.createNode({
+ plane: "intent",
+ kind: "decision",
+ title: "Some decision",
+ })
+
+ expect(result.status).toBe("structural_illegal")
+ if (result.status !== "structural_illegal") throw new Error("unreachable")
+ expect(result.diagnostics.some((d) => d.field === "detail")).toBe(true)
+ })
+
+ it("rejects term without detail", () => {
+ const result = executor.createNode({
+ plane: "intent",
+ kind: "term",
+ title: "Some term",
+ })
+
+ expect(result.status).toBe("structural_illegal")
+ if (result.status !== "structural_illegal") throw new Error("unreachable")
+ expect(result.diagnostics.some((d) => d.field === "detail")).toBe(true)
+ })
+
+ it("rejects non-decision/term node with detail present", () => {
+ const result = executor.createNode({
+ plane: "intent",
+ kind: "requirement",
+ title: "Some requirement",
+ detail: { definition: "should not be here" },
+ })
+
+ expect(result.status).toBe("structural_illegal")
+ if (result.status !== "structural_illegal") throw new Error("unreachable")
+ expect(result.diagnostics.some((d) => d.field === "detail")).toBe(true)
+ })
+
+ it("rejects decision with empty rejected array", () => {
+ const result = executor.createNode({
+ plane: "intent",
+ kind: "decision",
+ title: "Bad decision",
+ detail: {
+ chosen_option: "A",
+ rejected: [],
+ rationale: "because",
+ },
+ })
+
+ expect(result.status).toBe("structural_illegal")
+ if (result.status !== "structural_illegal") throw new Error("unreachable")
+ expect(result.diagnostics.some((d) => d.field === "detail.rejected")).toBe(
+ true,
+ )
+ })
+
+ it("rejects decision detail with unknown fields", () => {
+ const result = executor.createNode({
+ plane: "intent",
+ kind: "decision",
+ title: "Leaky decision",
+ detail: {
+ chosen_option: "A",
+ rejected: ["B"],
+ rationale: "because",
+ extra_field: "should not be here",
+ },
+ })
+
+ expect(result.status).toBe("structural_illegal")
+ if (result.status !== "structural_illegal") throw new Error("unreachable")
+ expect(
+ result.diagnostics.some((d) => d.field === "detail.extra_field"),
+ ).toBe(true)
+ })
+
+ // --- LSN / graph_clock ---
+
+ it("increments graph_clock atomically per command", () => {
+ executor.createNode({
+ plane: "intent",
+ kind: "goal",
+ title: "First",
+ })
+ executor.createNode({
+ plane: "intent",
+ kind: "goal",
+ title: "Second",
+ })
+
+ const [clock] = db.select().from(graphClock).all()
+ expect(clock!.lsn).toBe(2)
+ })
+
+ it("assigns matching created_at_lsn and updated_at_lsn on new nodes", () => {
+ const result = executor.createNode({
+ plane: "intent",
+ kind: "assumption",
+ title: "Pi exposes enough seams",
+ })
+
+ if (result.status !== "success") throw new Error("unreachable")
+ const row = db.select().from(nodes).all()[0]
+ expect(row!.created_at_lsn).toBe(result.lsn)
+ expect(row!.updated_at_lsn).toBe(result.lsn)
+ })
+
+ it("LSN is strictly monotonic across multiple creates", () => {
+ const lsns: number[] = []
+ for (let i = 0; i < 10; i++) {
+ const result = executor.createNode({
+ plane: "intent",
+ kind: "context",
+ title: `Context ${i}`,
+ })
+ if (result.status !== "success") throw new Error("unreachable")
+ lsns.push(result.lsn)
+ }
+
+ for (let i = 1; i < lsns.length; i++) {
+ expect(lsns[i]).toBe(lsns[i - 1]! + 1)
+ }
+ })
+
+ // --- change_log ---
+
+ it("appends exactly one change_log entry per successful command", () => {
+ executor.createNode({
+ plane: "intent",
+ kind: "requirement",
+ title: "Must persist",
+ })
+
+ const logs = db.select().from(changeLog).all()
+ expect(logs).toHaveLength(1)
+ expect(logs[0]!.operation).toBe("create_node")
+ })
+
+ it("change_log payload contains nodeId, plane, and kind", () => {
+ const result = executor.createNode({
+ plane: "intent",
+ kind: "invariant",
+ title: "LSN monotonicity",
+ })
+
+ if (result.status !== "success") throw new Error("unreachable")
+ const [log] = db.select().from(changeLog).all()
+ const payload = JSON.parse(log!.payload)
+ expect(payload.nodeId).toBe(result.nodeId)
+ expect(payload.plane).toBe("intent")
+ expect(payload.kind).toBe("invariant")
+ })
+
+ it("change_log.lsn matches the command's allocated LSN", () => {
+ const result = executor.createNode({
+ plane: "intent",
+ kind: "goal",
+ title: "Test",
+ })
+
+ if (result.status !== "success") throw new Error("unreachable")
+ const [log] = db.select().from(changeLog).all()
+ expect(log!.lsn).toBe(result.lsn)
+ })
+
+ // --- Transaction integrity ---
+
+ it("writes nothing on validation failure (no LSN bump, no change_log)", () => {
+ executor.createNode({
+ plane: "intent",
+ kind: "check", // invalid kind for intent plane
+ title: "Should fail",
+ })
+
+ const [clock] = db.select().from(graphClock).all()
+ expect(clock!.lsn).toBe(0)
+ expect(db.select().from(nodes).all()).toHaveLength(0)
+ expect(db.select().from(changeLog).all()).toHaveLength(0)
+ })
+
+ // --- Oracle/design/plan plane nodes ---
+
+ it("creates oracle-plane nodes", () => {
+ const result = executor.createNode({
+ plane: "oracle",
+ kind: "check",
+ title: "Verify LSN monotonicity",
+ })
+
+ expect(result.status).toBe("success")
+ })
+
+ it("creates design-plane nodes", () => {
+ const result = executor.createNode({
+ plane: "design",
+ kind: "module",
+ title: "CommandExecutor",
+ })
+
+ expect(result.status).toBe("success")
+ })
+
+ it("creates plan-plane nodes", () => {
+ const result = executor.createNode({
+ plane: "plan",
+ kind: "slice",
+ title: "M4 skeleton",
+ })
+
+ expect(result.status).toBe("success")
+ })
+
+ // ==========================================================================
+ // commitGraph
+ // ==========================================================================
+
+ describe("commitGraph", () => {
+ // --- success path ---
+
+ it("creates multiple nodes + edges in one transaction with one LSN", () => {
+ const input: CommitGraphInput = {
+ nodes: [
+ { ref: "n1", plane: "intent", kind: "requirement", title: "Req A" },
+ { ref: "n2", plane: "intent", kind: "constraint", title: "Con B" },
+ ],
+ edges: [{ category: "boundary", source: "n2", target: "n1" }],
+ }
+
+ const result = executor.commitGraph(input)
+ expect(result.status).toBe("success")
+ if (result.status !== "success") throw new Error("unreachable")
+
+ expect(result.lsn).toBe(1)
+ expect(Object.keys(result.nodes)).toHaveLength(2)
+ expect(result.edges).toHaveLength(1)
+
+ // Verify DB state
+ expect(db.select().from(nodes).all()).toHaveLength(2)
+ expect(db.select().from(edges).all()).toHaveLength(1)
+ })
+
+ it("resolves intra-batch refs to real NodeIds", () => {
+ const result = executor.commitGraph({
+ nodes: [
+ { ref: "a", plane: "intent", kind: "assumption", title: "A1" },
+ {
+ ref: "b",
+ plane: "intent",
+ kind: "decision",
+ title: "D1",
+ detail: {
+ chosen_option: "X",
+ rejected: ["Y"],
+ rationale: "because",
+ },
+ },
+ ],
+ edges: [{ category: "dependency", source: "a", target: "b" }],
+ })
+
+ if (result.status !== "success") throw new Error("unreachable")
+ const edgeRow = db.select().from(edges).all()[0]!
+ expect(edgeRow.source_id).toBe(result.nodes["a"])
+ expect(edgeRow.target_id).toBe(result.nodes["b"])
+ })
+
+ it("resolves existing-node refs to verified NodeIds", () => {
+ // Pre-create a node
+ const pre = executor.createNode({
+ plane: "intent",
+ kind: "goal",
+ title: "Existing goal",
+ })
+ if (pre.status !== "success") throw new Error("unreachable")
+
+ const result = executor.commitGraph({
+ nodes: [
+ { ref: "n1", plane: "intent", kind: "requirement", title: "New req" },
+ ],
+ edges: [
+ {
+ category: "support",
+ source: { existing: pre.nodeId },
+ target: "n1",
+ stance: "for",
+ },
+ ],
+ })
+
+ expect(result.status).toBe("success")
+ if (result.status !== "success") throw new Error("unreachable")
+ const edgeRow = db.select().from(edges).all()[0]!
+ expect(edgeRow.source_id).toBe(pre.nodeId)
+ expect(edgeRow.target_id).toBe(result.nodes["n1"])
+ })
+
+ it("returns nodes mapping and edges array in success result", () => {
+ const result = executor.commitGraph({
+ nodes: [
+ { ref: "x", plane: "intent", kind: "context", title: "Ctx" },
+ { ref: "y", plane: "intent", kind: "thesis", title: "Thesis" },
+ ],
+ edges: [],
+ })
+
+ if (result.status !== "success") throw new Error("unreachable")
+ expect(result.nodes["x"]).toBeTypeOf("number")
+ expect(result.nodes["y"]).toBeTypeOf("number")
+ expect(result.nodes["x"]).not.toBe(result.nodes["y"])
+ expect(result.edges).toEqual([])
+ })
+
+ it("appends one change_log entry for the entire batch", () => {
+ executor.commitGraph({
+ nodes: [
+ { ref: "n1", plane: "intent", kind: "goal", title: "G1" },
+ { ref: "n2", plane: "intent", kind: "goal", title: "G2" },
+ ],
+ edges: [{ category: "association", source: "n1", target: "n2" }],
+ })
+
+ const logs = db.select().from(changeLog).all()
+ expect(logs).toHaveLength(1)
+ expect(logs[0]!.operation).toBe("commit_graph")
+ const payload = JSON.parse(logs[0]!.payload)
+ expect(Object.keys(payload.nodes)).toHaveLength(2)
+ expect(payload.edges).toHaveLength(1)
+ })
+
+ // --- edge structural validation ---
+
+ it("rejects edge with invalid category", () => {
+ const result = executor.commitGraph({
+ nodes: [
+ { ref: "n1", plane: "intent", kind: "goal", title: "G" },
+ { ref: "n2", plane: "intent", kind: "goal", title: "G2" },
+ ],
+ edges: [{ category: "invented_relation", source: "n1", target: "n2" }],
+ })
+
+ expect(result.status).toBe("structural_illegal")
+ if (result.status !== "structural_illegal") throw new Error("unreachable")
+ expect(result.diagnostics.some((d) => d.field.includes("category"))).toBe(
+ true,
+ )
+ })
+
+ it("rejects proof edge without stance", () => {
+ const result = executor.commitGraph({
+ nodes: [
+ { ref: "n1", plane: "intent", kind: "criterion", title: "Cr" },
+ { ref: "n2", plane: "intent", kind: "invariant", title: "Inv" },
+ ],
+ edges: [{ category: "proof", source: "n1", target: "n2" }],
+ })
+
+ expect(result.status).toBe("structural_illegal")
+ if (result.status !== "structural_illegal") throw new Error("unreachable")
+ expect(result.diagnostics.some((d) => d.field.includes("stance"))).toBe(
+ true,
+ )
+ })
+
+ it("rejects support edge without stance", () => {
+ const result = executor.commitGraph({
+ nodes: [
+ { ref: "n1", plane: "intent", kind: "context", title: "Ctx" },
+ { ref: "n2", plane: "intent", kind: "requirement", title: "Req" },
+ ],
+ edges: [{ category: "support", source: "n1", target: "n2" }],
+ })
+
+ expect(result.status).toBe("structural_illegal")
+ })
+
+ it("rejects non-proof/non-support edge with stance", () => {
+ const result = executor.commitGraph({
+ nodes: [
+ { ref: "n1", plane: "intent", kind: "assumption", title: "A" },
+ { ref: "n2", plane: "intent", kind: "requirement", title: "R" },
+ ],
+ edges: [
+ { category: "dependency", source: "n1", target: "n2", stance: "for" },
+ ],
+ })
+
+ expect(result.status).toBe("structural_illegal")
+ if (result.status !== "structural_illegal") throw new Error("unreachable")
+ expect(result.diagnostics.some((d) => d.field.includes("stance"))).toBe(
+ true,
+ )
+ })
+
+ it("rejects edge referencing non-existent existing node", () => {
+ const result = executor.commitGraph({
+ nodes: [{ ref: "n1", plane: "intent", kind: "goal", title: "G" }],
+ edges: [
+ { category: "dependency", source: { existing: 9999 }, target: "n1" },
+ ],
+ })
+
+ expect(result.status).toBe("structural_illegal")
+ if (result.status !== "structural_illegal") throw new Error("unreachable")
+ expect(result.diagnostics.some((d) => d.field.includes("source"))).toBe(
+ true,
+ )
+ })
+
+ it("rejects edge with unresolvable intra-batch ref", () => {
+ const result = executor.commitGraph({
+ nodes: [{ ref: "n1", plane: "intent", kind: "goal", title: "G" }],
+ edges: [
+ { category: "dependency", source: "n1", target: "missing_ref" },
+ ],
+ })
+
+ expect(result.status).toBe("structural_illegal")
+ if (result.status !== "structural_illegal") throw new Error("unreachable")
+ expect(result.diagnostics.some((d) => d.field.includes("target"))).toBe(
+ true,
+ )
+ })
+
+ it("rejects self-loop edge", () => {
+ const result = executor.commitGraph({
+ nodes: [{ ref: "n1", plane: "intent", kind: "goal", title: "G" }],
+ edges: [{ category: "association", source: "n1", target: "n1" }],
+ })
+
+ expect(result.status).toBe("structural_illegal")
+ if (result.status !== "structural_illegal") throw new Error("unreachable")
+ expect(
+ result.diagnostics.some((d) => d.message.includes("self-loop")),
+ ).toBe(true)
+ })
+
+ // --- node validation reuse ---
+
+ it("rejects batch node with invalid kind-for-plane", () => {
+ const result = executor.commitGraph({
+ nodes: [{ ref: "n1", plane: "intent", kind: "check", title: "Wrong" }],
+ edges: [],
+ })
+
+ expect(result.status).toBe("structural_illegal")
+ if (result.status !== "structural_illegal") throw new Error("unreachable")
+ expect(result.diagnostics.some((d) => d.field.includes("nodes[0]"))).toBe(
+ true,
+ )
+ })
+
+ it("rejects batch decision without detail", () => {
+ const result = executor.commitGraph({
+ nodes: [{ ref: "n1", plane: "intent", kind: "decision", title: "D" }],
+ edges: [],
+ })
+
+ expect(result.status).toBe("structural_illegal")
+ })
+
+ // --- all-or-nothing (I34-L) ---
+
+ it("if any node fails validation, entire batch rejected — nothing written", () => {
+ const result = executor.commitGraph({
+ nodes: [
+ { ref: "n1", plane: "intent", kind: "goal", title: "Valid" },
+ { ref: "n2", plane: "intent", kind: "check", title: "Invalid kind" },
+ ],
+ edges: [],
+ })
+
+ expect(result.status).toBe("structural_illegal")
+ expect(db.select().from(nodes).all()).toHaveLength(0)
+ const [clock] = db.select().from(graphClock).all()
+ expect(clock!.lsn).toBe(0)
+ })
+
+ it("if any edge fails validation, no nodes written", () => {
+ const result = executor.commitGraph({
+ nodes: [
+ { ref: "n1", plane: "intent", kind: "goal", title: "Valid goal" },
+ { ref: "n2", plane: "intent", kind: "context", title: "Valid ctx" },
+ ],
+ edges: [
+ { category: "proof", source: "n1", target: "n2" }, // missing stance
+ ],
+ })
+
+ expect(result.status).toBe("structural_illegal")
+ // Transaction rolled back — no nodes either
+ expect(db.select().from(nodes).all()).toHaveLength(0)
+ const [clock] = db.select().from(graphClock).all()
+ expect(clock!.lsn).toBe(0)
+ })
+
+ it("diagnostics include which entry failed", () => {
+ const result = executor.commitGraph({
+ nodes: [{ ref: "n1", plane: "intent", kind: "goal", title: "OK" }],
+ edges: [
+ { category: "dependency", source: "n1", target: { existing: 9999 } },
+ ],
+ })
+
+ if (result.status !== "structural_illegal") throw new Error("unreachable")
+ expect(
+ result.diagnostics.some((d) => d.field.startsWith("edges[0]")),
+ ).toBe(true)
+ })
+
+ // --- edge cases ---
+
+ it("edge-only batch between existing nodes", () => {
+ const a = executor.createNode({
+ plane: "intent",
+ kind: "requirement",
+ title: "R1",
+ })
+ const b = executor.createNode({
+ plane: "intent",
+ kind: "assumption",
+ title: "A1",
+ })
+ if (a.status !== "success" || b.status !== "success")
+ throw new Error("unreachable")
+
+ const result = executor.commitGraph({
+ nodes: [],
+ edges: [
+ {
+ category: "dependency",
+ source: { existing: b.nodeId },
+ target: { existing: a.nodeId },
+ },
+ ],
+ })
+
+ expect(result.status).toBe("success")
+ if (result.status !== "success") throw new Error("unreachable")
+ expect(Object.keys(result.nodes)).toHaveLength(0)
+ expect(result.edges).toHaveLength(1)
+ })
+
+ it("node-only batch (no edges)", () => {
+ const result = executor.commitGraph({
+ nodes: [
+ { ref: "n1", plane: "intent", kind: "context", title: "C1" },
+ { ref: "n2", plane: "intent", kind: "context", title: "C2" },
+ ],
+ edges: [],
+ })
+
+ expect(result.status).toBe("success")
+ if (result.status !== "success") throw new Error("unreachable")
+ expect(Object.keys(result.nodes)).toHaveLength(2)
+ expect(result.edges).toEqual([])
+ })
+
+ it("empty batch → structural_illegal", () => {
+ const result = executor.commitGraph({ nodes: [], edges: [] })
+ expect(result.status).toBe("structural_illegal")
+ })
+
+ // --- mixed refs ---
+
+ it("edges can mix intra-batch source with existing target", () => {
+ const pre = executor.createNode({
+ plane: "intent",
+ kind: "goal",
+ title: "Existing",
+ })
+ if (pre.status !== "success") throw new Error("unreachable")
+
+ const result = executor.commitGraph({
+ nodes: [
+ { ref: "new", plane: "intent", kind: "requirement", title: "New" },
+ ],
+ edges: [
+ {
+ category: "realization",
+ source: { existing: pre.nodeId },
+ target: "new",
+ },
+ ],
+ })
+
+ expect(result.status).toBe("success")
+ if (result.status !== "success") throw new Error("unreachable")
+ const edgeRow = db.select().from(edges).all()[0]!
+ expect(edgeRow.source_id).toBe(pre.nodeId)
+ expect(edgeRow.target_id).toBe(result.nodes["new"])
+ })
+
+ // --- LSN behavior ---
+
+ it("uses one LSN for the entire batch (not per-entity)", () => {
+ const result = executor.commitGraph({
+ nodes: [
+ { ref: "n1", plane: "intent", kind: "goal", title: "G1" },
+ { ref: "n2", plane: "intent", kind: "goal", title: "G2" },
+ ],
+ edges: [{ category: "association", source: "n1", target: "n2" }],
+ })
+
+ if (result.status !== "success") throw new Error("unreachable")
+ const allNodes = db.select().from(nodes).all()
+ const allEdges = db.select().from(edges).all()
+ // All entities share the same LSN
+ for (const n of allNodes) {
+ expect(n.created_at_lsn).toBe(result.lsn)
+ }
+ for (const e of allEdges) {
+ expect(e.created_at_lsn).toBe(result.lsn)
+ }
+ })
+ })
+
+ // --- createReconciliationNeed ---
+
+ describe("createReconciliationNeed", () => {
+ it("creates a recon need targeting an edge and returns success with id and lsn", () => {
+ // Seed a node and edge first
+ const batch = executor.commitGraph({
+ nodes: [
+ { ref: "r1", plane: "intent", kind: "requirement", title: "R1" },
+ { ref: "a1", plane: "intent", kind: "assumption", title: "A1" },
+ ],
+ edges: [{ category: "dependency", source: "r1", target: "a1" }],
+ })
+ expect(batch.status).toBe("success")
+ if (batch.status !== "success") throw new Error("unreachable")
+ const edgeId = batch.edges[0]!
+
+ const result = executor.createReconciliationNeed({
+ target: { kind: "edge", edgeId },
+ needKind: "edge_revalidation",
+ reason: "upstream assumption changed",
+ })
+
+ expect(result.status).toBe("success")
+ if (result.status !== "success") throw new Error("unreachable")
+ expect(result.id).toBeTypeOf("number")
+ expect(result.lsn).toBeTypeOf("number")
+ })
+
+ it("creates a recon need targeting a node pair", () => {
+ const batch = executor.commitGraph({
+ nodes: [
+ { ref: "r1", plane: "intent", kind: "requirement", title: "R1" },
+ { ref: "r2", plane: "intent", kind: "requirement", title: "R2" },
+ ],
+ edges: [],
+ })
+ expect(batch.status).toBe("success")
+ if (batch.status !== "success") throw new Error("unreachable")
+ const aId = batch.nodes["r1"]!
+ const bId = batch.nodes["r2"]!
+
+ const result = executor.createReconciliationNeed({
+ target: { kind: "node_pair", aId, bId },
+ needKind: "possible_duplicate",
+ })
+
+ expect(result.status).toBe("success")
+ if (result.status !== "success") throw new Error("unreachable")
+ expect(result.id).toBeTypeOf("number")
+ })
+
+ it("rejects edge target with non-existent edgeId", () => {
+ const result = executor.createReconciliationNeed({
+ target: { kind: "edge", edgeId: 999 },
+ needKind: "edge_revalidation",
+ })
+
+ expect(result.status).toBe("structural_illegal")
+ if (result.status !== "structural_illegal") throw new Error("unreachable")
+ expect(result.diagnostics[0]!.field).toBe("target.edgeId")
+ })
+
+ it("rejects node_pair target with non-existent nodeId", () => {
+ const n = executor.createNode({
+ plane: "intent",
+ kind: "goal",
+ title: "G1",
+ })
+ expect(n.status).toBe("success")
+ if (n.status !== "success") throw new Error("unreachable")
+
+ const result = executor.createReconciliationNeed({
+ target: { kind: "node_pair", aId: n.nodeId, bId: 999 },
+ needKind: "possible_relation",
+ })
+
+ expect(result.status).toBe("structural_illegal")
+ if (result.status !== "structural_illegal") throw new Error("unreachable")
+ expect(result.diagnostics[0]!.field).toBe("target.bId")
+ })
+
+ it("allocates a new LSN for each recon need", () => {
+ const n = executor.createNode({
+ plane: "intent",
+ kind: "goal",
+ title: "G1",
+ })
+ expect(n.status).toBe("success")
+ if (n.status !== "success") throw new Error("unreachable")
+ const n2 = executor.createNode({
+ plane: "intent",
+ kind: "goal",
+ title: "G2",
+ })
+ expect(n2.status).toBe("success")
+ if (n2.status !== "success") throw new Error("unreachable")
+
+ const r1 = executor.createReconciliationNeed({
+ target: { kind: "node_pair", aId: n.nodeId, bId: n2.nodeId },
+ needKind: "possible_relation",
+ })
+ expect(r1.status).toBe("success")
+ if (r1.status !== "success") throw new Error("unreachable")
+
+ const r2 = executor.createReconciliationNeed({
+ target: { kind: "node_pair", aId: n.nodeId, bId: n2.nodeId },
+ needKind: "semantic_conflict",
+ })
+ expect(r2.status).toBe("success")
+ if (r2.status !== "success") throw new Error("unreachable")
+
+ expect(r2.lsn).toBeGreaterThan(r1.lsn)
+ })
+ })
+
+ // --- resolveReconciliationNeed ---
+
+ describe("resolveReconciliationNeed", () => {
+ it("resolves an open need and records resolvedAtLsn", () => {
+ const batch = executor.commitGraph({
+ nodes: [
+ { ref: "r1", plane: "intent", kind: "requirement", title: "R1" },
+ { ref: "a1", plane: "intent", kind: "assumption", title: "A1" },
+ ],
+ edges: [{ category: "dependency", source: "r1", target: "a1" }],
+ })
+ expect(batch.status).toBe("success")
+ if (batch.status !== "success") throw new Error("unreachable")
+
+ const create = executor.createReconciliationNeed({
+ target: { kind: "edge", edgeId: batch.edges[0]! },
+ needKind: "edge_revalidation",
+ })
+ expect(create.status).toBe("success")
+ if (create.status !== "success") throw new Error("unreachable")
+
+ const resolve = executor.resolveReconciliationNeed(create.id)
+ expect(resolve.status).toBe("success")
+ if (resolve.status !== "success") throw new Error("unreachable")
+ expect(resolve.lsn).toBeGreaterThan(create.lsn)
+ })
+
+ it("rejects non-existent need id", () => {
+ const result = executor.resolveReconciliationNeed(999)
+ expect(result.status).toBe("structural_illegal")
+ })
+
+ it("rejects already-resolved need", () => {
+ const batch = executor.commitGraph({
+ nodes: [
+ { ref: "r1", plane: "intent", kind: "requirement", title: "R1" },
+ { ref: "a1", plane: "intent", kind: "assumption", title: "A1" },
+ ],
+ edges: [{ category: "dependency", source: "r1", target: "a1" }],
+ })
+ expect(batch.status).toBe("success")
+ if (batch.status !== "success") throw new Error("unreachable")
+
+ const create = executor.createReconciliationNeed({
+ target: { kind: "edge", edgeId: batch.edges[0]! },
+ needKind: "edge_revalidation",
+ })
+ expect(create.status).toBe("success")
+ if (create.status !== "success") throw new Error("unreachable")
+
+ const resolve1 = executor.resolveReconciliationNeed(create.id)
+ expect(resolve1.status).toBe("success")
+
+ const resolve2 = executor.resolveReconciliationNeed(create.id)
+ expect(resolve2.status).toBe("structural_illegal")
+ if (resolve2.status !== "structural_illegal")
+ throw new Error("unreachable")
+ expect(resolve2.diagnostics[0]!.message).toContain("already resolved")
+ })
+ })
+})
diff --git a/src/graph/command-executor.ts b/src/graph/command-executor.ts
new file mode 100644
index 000000000..036b6764f
--- /dev/null
+++ b/src/graph/command-executor.ts
@@ -0,0 +1,843 @@
+/**
+ * CommandExecutor — the single public mutation boundary for graph truth.
+ *
+ * SPEC: D4-L (one shared mutation surface), D20-L (command execution owns
+ * authority seam), D16-L (one-transaction-per-commit, no bypass), D52-L
+ * (graph/ imports db/, no other layer imports db/).
+ *
+ * Every graph mutation routes through this class. The executor owns:
+ * - structural validation
+ * - one SQLite transaction per command
+ * - monotonic LSN allocation from graph_clock
+ * - change_log append
+ * - structured result return
+ *
+ * The result contract already includes all discriminants (success,
+ * structural_illegal, needs_human, policy_blocked, version_conflict)
+ * even though pre-M6 policy classification is minimal.
+ */
+
+import { eq, inArray, sql } from "drizzle-orm"
+
+import type { BrunchDb } from "../db/connection.js"
+import * as schema from "../db/schema.js"
+import type { EdgeCategory, EdgeStance } from "./schema/edges.js"
+import type { NodeBasis, NodePlane } from "./schema/nodes.js"
+
+// ---------------------------------------------------------------------------
+// Result types
+// ---------------------------------------------------------------------------
+
+/** A single validation problem discovered during structural checks. */
+export interface Diagnostic {
+ readonly field: string
+ readonly message: string
+}
+
+/** Successful command execution. */
+export interface CommandSuccess {
+ readonly status: "success"
+ readonly nodeId: number
+ readonly lsn: number
+}
+
+/** Structurally invalid input — validation failed before any write. */
+export interface StructuralIllegal {
+ readonly status: "structural_illegal"
+ readonly diagnostics: readonly Diagnostic[]
+}
+
+/** Action requires human confirmation (M6 placeholder). */
+export interface NeedsHuman {
+ readonly status: "needs_human"
+}
+
+/** Action blocked by authority policy (M6 placeholder). */
+export interface PolicyBlocked {
+ readonly status: "policy_blocked"
+}
+
+/** Optimistic concurrency conflict (M6 placeholder). */
+export interface VersionConflict {
+ readonly status: "version_conflict"
+}
+
+/** Successful commitGraph batch execution. */
+export interface CommitGraphSuccess {
+ readonly status: "success"
+ readonly lsn: number
+ readonly nodes: Readonly>
+ readonly edges: readonly number[]
+}
+
+/** Successful reconciliation-need creation. */
+export interface ReconNeedSuccess {
+ readonly status: "success"
+ readonly id: number
+ readonly lsn: number
+}
+
+/** Successful reconciliation-need resolution. */
+export interface ReconNeedResolveSuccess {
+ readonly status: "success"
+ readonly lsn: number
+}
+
+/** Union of all possible command results. */
+export type CommandResult = CommandSuccess | CommitGraphSuccess | ReconNeedSuccess | ReconNeedResolveSuccess | StructuralIllegal | NeedsHuman | PolicyBlocked | VersionConflict
+
+/** Result of a createNode command. */
+export type CreateNodeResult = CommandSuccess | StructuralIllegal
+
+/** Result of a commitGraph command. */
+export type CommitGraphResult = CommitGraphSuccess | StructuralIllegal
+
+/** Result of a createReconciliationNeed command. */
+export type CreateReconNeedResult = ReconNeedSuccess | StructuralIllegal
+
+/** Result of a resolveReconciliationNeed command. */
+export type ResolveReconNeedResult = ReconNeedResolveSuccess | StructuralIllegal
+
+// ---------------------------------------------------------------------------
+// Input types
+// ---------------------------------------------------------------------------
+
+/** Input for creating a single graph node. */
+export interface CreateNodeInput {
+ readonly plane: NodePlane
+ readonly kind: string
+ readonly title: string
+ readonly body?: string | undefined
+ readonly basis?: NodeBasis | undefined
+ readonly source?: string | undefined
+ readonly detail?: unknown
+}
+
+// ---------------------------------------------------------------------------
+// Reconciliation-need input types
+// ---------------------------------------------------------------------------
+
+/** Target for a reconciliation need — edge or node pair. */
+export type ReconNeedTargetEdge = {
+ readonly kind: "edge"
+ readonly edgeId: number
+}
+
+/** Target for a reconciliation need — node pair. */
+export type ReconNeedTargetNodePair = {
+ readonly kind: "node_pair"
+ readonly aId: number
+ readonly bId: number
+}
+
+/** Target for a reconciliation need. */
+export type ReconNeedTarget = ReconNeedTargetEdge | ReconNeedTargetNodePair
+
+/** Input for creating a reconciliation need. */
+export interface CreateReconNeedInput {
+ readonly target: ReconNeedTarget
+ readonly needKind: string
+ readonly reason?: string | undefined
+}
+
+// ---------------------------------------------------------------------------
+// Batch input types (commitGraph)
+// ---------------------------------------------------------------------------
+
+/** Reference to a node endpoint in a batch edge. */
+export type BatchEdgeRef = string | { readonly existing: number }
+
+/** A node to create inside a commitGraph batch. */
+export interface BatchNodeInput {
+ readonly ref: string
+ readonly plane: NodePlane
+ readonly kind: string
+ readonly title: string
+ readonly body?: string | undefined
+ readonly basis?: NodeBasis | undefined
+ readonly source?: string | undefined
+ readonly detail?: unknown
+}
+
+/** An edge to create inside a commitGraph batch. */
+export interface BatchEdgeInput {
+ readonly category: string
+ readonly source: BatchEdgeRef
+ readonly target: BatchEdgeRef
+ readonly stance?: string | undefined
+ readonly basis?: NodeBasis | undefined
+ readonly rationale?: string | undefined
+}
+
+/** Input for the commitGraph atomic batch mutation. */
+export interface CommitGraphInput {
+ readonly nodes: readonly BatchNodeInput[]
+ readonly edges: readonly BatchEdgeInput[]
+}
+
+// ---------------------------------------------------------------------------
+// Validation
+// ---------------------------------------------------------------------------
+
+const VALID_KINDS_BY_PLANE: Record = {
+ intent: schema.INTENT_KINDS as unknown as string[],
+ oracle: schema.ORACLE_KINDS as unknown as string[],
+ design: schema.DESIGN_KINDS as unknown as string[],
+ plan: schema.PLAN_KINDS as unknown as string[],
+}
+
+const KINDS_REQUIRING_DETAIL = new Set(["decision", "term"])
+
+function validateCreateNode(input: CreateNodeInput): Diagnostic[] {
+ const diagnostics: Diagnostic[] = []
+
+ // Title must be non-empty
+ if (!input.title.trim()) {
+ diagnostics.push({ field: "title", message: "title must be non-empty" })
+ }
+
+ // Kind must be valid for the given plane
+ const validKinds = VALID_KINDS_BY_PLANE[input.plane]
+ if (!validKinds?.includes(input.kind)) {
+ diagnostics.push({
+ field: "kind",
+ message: `"${input.kind}" is not a valid kind for plane "${input.plane}"`,
+ })
+ return diagnostics // can't validate detail if kind is wrong
+ }
+
+ // Detail requirement: decision and term REQUIRE detail
+ if (KINDS_REQUIRING_DETAIL.has(input.kind) && input.detail == null) {
+ diagnostics.push({
+ field: "detail",
+ message: `"${input.kind}" nodes require a detail object`,
+ })
+ return diagnostics
+ }
+
+ // Detail prohibition: all other kinds must NOT have detail
+ if (!KINDS_REQUIRING_DETAIL.has(input.kind) && input.detail != null) {
+ diagnostics.push({
+ field: "detail",
+ message: `"${input.kind}" nodes must not have a detail object`,
+ })
+ return diagnostics
+ }
+
+ // Validate detail shape per kind
+ if (input.kind === "decision" && input.detail != null) {
+ validateDecisionDetail(input.detail, diagnostics)
+ }
+ if (input.kind === "term" && input.detail != null) {
+ validateTermDetail(input.detail, diagnostics)
+ }
+
+ return diagnostics
+}
+
+function validateDecisionDetail(
+ detail: unknown,
+ diagnostics: Diagnostic[],
+): void {
+ if (typeof detail !== "object" || detail === null) {
+ diagnostics.push({ field: "detail", message: "must be an object" })
+ return
+ }
+
+ const d = detail as Record
+ const knownFields = new Set(["chosen_option", "rejected", "rationale"])
+
+ if (typeof d["chosen_option"] !== "string") {
+ diagnostics.push({
+ field: "detail.chosen_option",
+ message: "required string",
+ })
+ }
+
+ if (
+ !Array.isArray(d["rejected"]) ||
+ d["rejected"].length < 1 ||
+ !d["rejected"].every((r) => typeof r === "string")
+ ) {
+ diagnostics.push({
+ field: "detail.rejected",
+ message: "required non-empty string array",
+ })
+ }
+
+ if (typeof d["rationale"] !== "string") {
+ diagnostics.push({ field: "detail.rationale", message: "required string" })
+ }
+
+ // Closed validation: reject unknown fields
+ for (const key of Object.keys(d)) {
+ if (!knownFields.has(key)) {
+ diagnostics.push({ field: `detail.${key}`, message: "unknown field" })
+ }
+ }
+}
+
+function validateTermDetail(detail: unknown, diagnostics: Diagnostic[]): void {
+ if (typeof detail !== "object" || detail === null) {
+ diagnostics.push({ field: "detail", message: "must be an object" })
+ return
+ }
+
+ const d = detail as Record
+ const knownFields = new Set(["definition", "aliases"])
+
+ if (typeof d["definition"] !== "string") {
+ diagnostics.push({
+ field: "detail.definition",
+ message: "required string",
+ })
+ }
+
+ if (
+ d["aliases"] != null &&
+ (!Array.isArray(d["aliases"]) ||
+ !d["aliases"].every((a) => typeof a === "string"))
+ ) {
+ diagnostics.push({
+ field: "detail.aliases",
+ message: "must be a string array if present",
+ })
+ }
+
+ // Closed validation: reject unknown fields
+ for (const key of Object.keys(d)) {
+ if (!knownFields.has(key)) {
+ diagnostics.push({ field: `detail.${key}`, message: "unknown field" })
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+// Edge validation
+// ---------------------------------------------------------------------------
+
+const VALID_CATEGORIES = schema.EDGE_CATEGORIES as unknown as string[]
+const STANCE_REQUIRED_CATEGORIES = new Set(["proof", "support"])
+const VALID_STANCES = schema.EDGE_STANCES as unknown as string[]
+
+interface ResolvedEdge {
+ sourceId: number
+ targetId: number
+ category: EdgeCategory
+ stance: EdgeStance | null
+ basis: NodeBasis
+ rationale: string | null
+}
+
+interface EdgeValidationResult {
+ diagnostics: Diagnostic[]
+ resolved?: ResolvedEdge
+}
+
+function validateAndResolveBatchEdge(
+ input: BatchEdgeInput,
+ index: number,
+ refMap: ReadonlyMap,
+ existingNodeIds: ReadonlySet,
+): EdgeValidationResult {
+ const diagnostics: Diagnostic[] = []
+ const p = `edges[${index}]`
+
+ // Category must be in the closed set
+ if (!VALID_CATEGORIES.includes(input.category)) {
+ diagnostics.push({
+ field: `${p}.category`,
+ message: `"${input.category}" is not a valid edge category`,
+ })
+ return { diagnostics }
+ }
+
+ // Stance: required iff proof/support, invalid otherwise
+ const stanceRequired = STANCE_REQUIRED_CATEGORIES.has(input.category)
+ if (stanceRequired && input.stance == null) {
+ diagnostics.push({
+ field: `${p}.stance`,
+ message: `stance is required for "${input.category}" edges`,
+ })
+ }
+ if (!stanceRequired && input.stance != null) {
+ diagnostics.push({
+ field: `${p}.stance`,
+ message: `stance is not allowed for "${input.category}" edges`,
+ })
+ }
+ if (input.stance != null && !VALID_STANCES.includes(input.stance)) {
+ diagnostics.push({
+ field: `${p}.stance`,
+ message: `"${input.stance}" is not a valid stance`,
+ })
+ }
+
+ // Resolve source ref
+ let resolvedSourceId: number | undefined
+ if (typeof input.source === "string") {
+ resolvedSourceId = refMap.get(input.source)
+ if (resolvedSourceId === undefined) {
+ diagnostics.push({
+ field: `${p}.source`,
+ message: `unresolvable intra-batch ref "${input.source}"`,
+ })
+ }
+ } else {
+ resolvedSourceId = input.source.existing
+ if (!existingNodeIds.has(resolvedSourceId)) {
+ diagnostics.push({
+ field: `${p}.source`,
+ message: `existing node ${resolvedSourceId} not found`,
+ })
+ }
+ }
+
+ // Resolve target ref
+ let resolvedTargetId: number | undefined
+ if (typeof input.target === "string") {
+ resolvedTargetId = refMap.get(input.target)
+ if (resolvedTargetId === undefined) {
+ diagnostics.push({
+ field: `${p}.target`,
+ message: `unresolvable intra-batch ref "${input.target}"`,
+ })
+ }
+ } else {
+ resolvedTargetId = input.target.existing
+ if (!existingNodeIds.has(resolvedTargetId)) {
+ diagnostics.push({
+ field: `${p}.target`,
+ message: `existing node ${resolvedTargetId} not found`,
+ })
+ }
+ }
+
+ // Self-loop check (only if both resolved)
+ if (
+ resolvedSourceId !== undefined &&
+ resolvedTargetId !== undefined &&
+ resolvedSourceId === resolvedTargetId
+ ) {
+ diagnostics.push({
+ field: p,
+ message: "self-loop: source and target resolve to the same node",
+ })
+ }
+
+ if (diagnostics.length > 0) return { diagnostics }
+
+ return {
+ diagnostics,
+ resolved: {
+ sourceId: resolvedSourceId!,
+ targetId: resolvedTargetId!,
+ category: input.category as EdgeCategory,
+ stance: input.stance as EdgeStance ?? null,
+ basis: input.basis as NodeBasis ?? "explicit",
+ rationale: input.rationale ?? null,
+ },
+ }
+}
+
+/** Thrown inside a transaction to trigger rollback on edge validation failure. */
+class BatchValidationError extends Error {
+ constructor(readonly diagnostics: readonly Diagnostic[]) {
+ super("batch validation failed")
+ }
+}
+
+// ---------------------------------------------------------------------------
+// CommandExecutor
+// ---------------------------------------------------------------------------
+
+export class CommandExecutor {
+ constructor(private readonly db: BrunchDb) {}
+
+ /**
+ * Create a single graph node.
+ *
+ * Validates structurally, then executes inside one transaction:
+ * allocate LSN → insert node → append change_log → return result.
+ *
+ * On validation failure, nothing is written.
+ */
+ createNode(input: CreateNodeInput): CreateNodeResult {
+ const diagnostics = validateCreateNode(input)
+ if (diagnostics.length > 0) {
+ return { status: "structural_illegal", diagnostics }
+ }
+
+ return this.db.transaction((tx) => {
+ // 1. Allocate LSN (atomic increment)
+ const clock = tx
+ .update(schema.graphClock)
+ .set({ lsn: sql`${schema.graphClock.lsn} + 1` })
+ .where(eq(schema.graphClock.id, 1))
+ .returning()
+ .get()
+ const lsn = clock!.lsn
+
+ // 2. Insert node
+ const node = tx
+ .insert(schema.nodes)
+ .values({
+ plane: input.plane,
+ kind: input.kind,
+ title: input.title,
+ body: input.body ?? null,
+ basis: input.basis ?? "explicit",
+ source: input.source ?? null,
+ detail: input.detail != null ? JSON.stringify(input.detail) : null,
+ created_at_lsn: lsn,
+ updated_at_lsn: lsn,
+ })
+ .returning()
+ .get()
+ const nodeId = node!.id
+
+ // 3. Append change_log
+ tx.insert(schema.changeLog)
+ .values({
+ lsn,
+ operation: "create_node",
+ payload: JSON.stringify({
+ nodeId,
+ plane: input.plane,
+ kind: input.kind,
+ }),
+ })
+ .run()
+
+ return { status: "success" as const, nodeId, lsn }
+ })
+ }
+
+ /**
+ * Atomic batch creation of nodes and edges (D53-L).
+ *
+ * One transaction, one LSN. Intra-batch refs (strings) resolve to
+ * just-inserted NodeIds; existing refs ({ existing: id }) are verified
+ * against the database. All-or-nothing: if any entry fails structural
+ * validation, the entire batch is rejected (I34-L).
+ */
+ commitGraph(input: CommitGraphInput): CommitGraphResult {
+ // Empty batch is structural_illegal
+ if (input.nodes.length === 0 && input.edges.length === 0) {
+ return {
+ status: "structural_illegal",
+ diagnostics: [
+ { field: "batch", message: "empty batch — nothing to commit" },
+ ],
+ }
+ }
+
+ // --- Pre-transaction: validate all batch nodes (pure checks) ---
+ const preDiagnostics: Diagnostic[] = []
+ const seenRefs = new Set()
+
+ for (let i = 0; i < input.nodes.length; i++) {
+ const bn = input.nodes[i]!
+
+ // Duplicate ref check
+ if (seenRefs.has(bn.ref)) {
+ preDiagnostics.push({
+ field: `nodes[${i}].ref`,
+ message: `duplicate batch ref "${bn.ref}"`,
+ })
+ }
+ seenRefs.add(bn.ref)
+
+ // Structural node validation (reuse)
+ for (const d of validateCreateNode(bn)) {
+ preDiagnostics.push({
+ field: `nodes[${i}].${d.field}`,
+ message: d.message,
+ })
+ }
+ }
+
+ if (preDiagnostics.length > 0) {
+ return { status: "structural_illegal", diagnostics: preDiagnostics }
+ }
+
+ // --- Transaction: insert nodes, resolve refs, validate + insert edges ---
+ try {
+ return this.db.transaction((tx) => {
+ // 1. Allocate ONE LSN
+ const clock = tx
+ .update(schema.graphClock)
+ .set({ lsn: sql`${schema.graphClock.lsn} + 1` })
+ .where(eq(schema.graphClock.id, 1))
+ .returning()
+ .get()
+ const lsn = clock!.lsn
+
+ // 2. Insert all nodes, build ref → id map
+ const refMap = new Map()
+ for (const bn of input.nodes) {
+ const row = tx
+ .insert(schema.nodes)
+ .values({
+ plane: bn.plane,
+ kind: bn.kind,
+ title: bn.title,
+ body: bn.body ?? null,
+ basis: bn.basis ?? "explicit",
+ source: bn.source ?? null,
+ detail: bn.detail != null ? JSON.stringify(bn.detail) : null,
+ created_at_lsn: lsn,
+ updated_at_lsn: lsn,
+ })
+ .returning()
+ .get()
+ refMap.set(bn.ref, row!.id)
+ }
+
+ // 3. Collect and verify existing-node references
+ const existingRefs = new Set()
+ for (const edge of input.edges) {
+ if (typeof edge.source !== "string")
+ existingRefs.add(edge.source.existing)
+ if (typeof edge.target !== "string")
+ existingRefs.add(edge.target.existing)
+ }
+
+ const verifiedExisting = new Set()
+ if (existingRefs.size > 0) {
+ const rows = tx
+ .select({ id: schema.nodes.id })
+ .from(schema.nodes)
+ .where(inArray(schema.nodes.id, [...existingRefs]))
+ .all()
+ for (const row of rows) verifiedExisting.add(row.id)
+ }
+
+ // 4. Validate and resolve all edges
+ const edgeDiagnostics: Diagnostic[] = []
+ const resolvedEdges: ResolvedEdge[] = []
+
+ for (let i = 0; i < input.edges.length; i++) {
+ const result = validateAndResolveBatchEdge(
+ input.edges[i]!,
+ i,
+ refMap,
+ verifiedExisting,
+ )
+ edgeDiagnostics.push(...result.diagnostics)
+ if (result.resolved) resolvedEdges.push(result.resolved)
+ }
+
+ if (edgeDiagnostics.length > 0) {
+ throw new BatchValidationError(edgeDiagnostics)
+ }
+
+ // 5. Insert all edges
+ const edgeIds: number[] = []
+ for (const re of resolvedEdges) {
+ const row = tx
+ .insert(schema.edges)
+ .values({
+ category: re.category,
+ source_id: re.sourceId,
+ target_id: re.targetId,
+ stance: re.stance,
+ basis: re.basis,
+ rationale: re.rationale,
+ created_at_lsn: lsn,
+ updated_at_lsn: lsn,
+ })
+ .returning()
+ .get()
+ edgeIds.push(row!.id)
+ }
+
+ // 6. Append one change_log entry for the entire batch
+ tx.insert(schema.changeLog)
+ .values({
+ lsn,
+ operation: "commit_graph",
+ payload: JSON.stringify({
+ nodes: Object.fromEntries(refMap),
+ edges: edgeIds,
+ }),
+ })
+ .run()
+
+ return {
+ status: "success" as const,
+ lsn,
+ nodes: Object.fromEntries(refMap),
+ edges: edgeIds,
+ }
+ })
+ } catch (e) {
+ if (e instanceof BatchValidationError) {
+ return { status: "structural_illegal", diagnostics: e.diagnostics }
+ }
+ throw e
+ }
+ }
+
+ /**
+ * Create a reconciliation need.
+ *
+ * Validates that the target (edge or node pair) exists, then inserts
+ * inside one transaction with LSN allocation and change_log append.
+ */
+ createReconciliationNeed(input: CreateReconNeedInput): CreateReconNeedResult {
+ // Validate target references exist
+ return this.db.transaction((tx) => {
+ const diagnostics: Diagnostic[] = []
+
+ if (input.target.kind === "edge") {
+ const row = tx
+ .select({ id: schema.edges.id })
+ .from(schema.edges)
+ .where(eq(schema.edges.id, input.target.edgeId))
+ .get()
+ if (!row) {
+ diagnostics.push({
+ field: "target.edgeId",
+ message: `edge ${input.target.edgeId} does not exist`,
+ })
+ }
+ } else {
+ const aRow = tx
+ .select({ id: schema.nodes.id })
+ .from(schema.nodes)
+ .where(eq(schema.nodes.id, input.target.aId))
+ .get()
+ if (!aRow) {
+ diagnostics.push({
+ field: "target.aId",
+ message: `node ${input.target.aId} does not exist`,
+ })
+ }
+ const bRow = tx
+ .select({ id: schema.nodes.id })
+ .from(schema.nodes)
+ .where(eq(schema.nodes.id, input.target.bId))
+ .get()
+ if (!bRow) {
+ diagnostics.push({
+ field: "target.bId",
+ message: `node ${input.target.bId} does not exist`,
+ })
+ }
+ }
+
+ if (diagnostics.length > 0) {
+ return { status: "structural_illegal" as const, diagnostics }
+ }
+
+ // Allocate LSN
+ const clock = tx
+ .update(schema.graphClock)
+ .set({ lsn: sql`${schema.graphClock.lsn} + 1` })
+ .where(eq(schema.graphClock.id, 1))
+ .returning()
+ .get()
+ const lsn = clock!.lsn
+
+ // Insert reconciliation need
+ const row = tx
+ .insert(schema.reconciliationNeed)
+ .values({
+ target_kind: input.target.kind,
+ target_edge_id:
+ input.target.kind === "edge" ? input.target.edgeId : null,
+ target_a_id:
+ input.target.kind === "node_pair" ? input.target.aId : null,
+ target_b_id:
+ input.target.kind === "node_pair" ? input.target.bId : null,
+ kind: input.needKind,
+ reason: input.reason ?? null,
+ created_at_lsn: lsn,
+ })
+ .returning()
+ .get()
+
+ // Append change_log
+ tx.insert(schema.changeLog)
+ .values({
+ lsn,
+ operation: "create_reconciliation_need",
+ payload: JSON.stringify({
+ id: row!.id,
+ target: input.target,
+ kind: input.needKind,
+ }),
+ })
+ .run()
+
+ return { status: "success" as const, id: row!.id, lsn }
+ })
+ }
+
+ /**
+ * Resolve an open reconciliation need.
+ *
+ * Sets status to "resolved" and records the resolvedAtLsn.
+ * Rejects if the need does not exist or is already resolved.
+ */
+ resolveReconciliationNeed(id: number): ResolveReconNeedResult {
+ return this.db.transaction((tx) => {
+ const existing = tx
+ .select()
+ .from(schema.reconciliationNeed)
+ .where(eq(schema.reconciliationNeed.id, id))
+ .get()
+
+ if (!existing) {
+ return {
+ status: "structural_illegal" as const,
+ diagnostics: [
+ {
+ field: "id",
+ message: `reconciliation need ${id} does not exist`,
+ },
+ ],
+ }
+ }
+
+ if (existing.status === "resolved") {
+ return {
+ status: "structural_illegal" as const,
+ diagnostics: [
+ {
+ field: "id",
+ message: `reconciliation need ${id} is already resolved`,
+ },
+ ],
+ }
+ }
+
+ // Allocate LSN
+ const clock = tx
+ .update(schema.graphClock)
+ .set({ lsn: sql`${schema.graphClock.lsn} + 1` })
+ .where(eq(schema.graphClock.id, 1))
+ .returning()
+ .get()
+ const lsn = clock!.lsn
+
+ // Update status
+ tx.update(schema.reconciliationNeed)
+ .set({ status: "resolved", resolved_at_lsn: lsn })
+ .where(eq(schema.reconciliationNeed.id, id))
+ .run()
+
+ // Append change_log
+ tx.insert(schema.changeLog)
+ .values({
+ lsn,
+ operation: "resolve_reconciliation_need",
+ payload: JSON.stringify({ id }),
+ })
+ .run()
+
+ return { status: "success" as const, lsn }
+ })
+ }
+}
diff --git a/src/graph/index.ts b/src/graph/index.ts
new file mode 100644
index 000000000..6273eeecc
--- /dev/null
+++ b/src/graph/index.ts
@@ -0,0 +1,86 @@
+/**
+ * Public exports for the Brunch graph layer.
+ *
+ * Canonical reference: docs/design/GRAPH_MODEL.md
+ *
+ * Phase 1: edges, edge policy, reconciliation-need.
+ * Phase 2: node type definitions.
+ * M4 skeleton: CommandExecutor + result types.
+ */
+
+export type { EdgeId, Lsn, NodeId } from "./atoms.js"
+
+export type {
+ EdgeBasis,
+ EdgeCategory,
+ EdgeStance,
+ GraphEdge,
+} from "./schema/edges.js"
+
+export type {
+ DecisionDetail,
+ DesignKind,
+ GraphNode,
+ IntentKind,
+ IntentKindCategory,
+ NodeBasis,
+ NodeDetail,
+ NodeKind,
+ NodePlane,
+ OracleKind,
+ PlanKind,
+ TermDetail,
+} from "./schema/nodes.js"
+
+export { intentKindCategory } from "./schema/nodes.js"
+
+export type {
+ ReconciliationNeed,
+ ReconciliationNeedKind,
+ ReconciliationNeedTarget,
+} from "./schema/reconciliation-need.js"
+
+export {
+ CATEGORY_POLICY,
+ type CategoryPolicy,
+ type ProjectionEffect,
+ type ReconNeedTrigger,
+} from "./policy/category-policy.js"
+
+export {
+ getGraphOverview,
+ getNodeNeighborhood,
+ getOpenReconciliationNeeds,
+} from "./snapshot.js"
+export type {
+ GraphOverview,
+ NeighborhoodOptions,
+ NeighborhoodNotFound,
+ NeighborhoodResult,
+ NeighborhoodSuccess,
+} from "./snapshot.js"
+
+export { CommandExecutor } from "./command-executor.js"
+export type {
+ BatchEdgeInput,
+ BatchEdgeRef,
+ BatchNodeInput,
+ CommandResult,
+ CommandSuccess,
+ CommitGraphInput,
+ CommitGraphResult,
+ CommitGraphSuccess,
+ CreateNodeInput,
+ CreateNodeResult,
+ CreateReconNeedInput,
+ CreateReconNeedResult,
+ Diagnostic,
+ NeedsHuman,
+ PolicyBlocked,
+ ReconNeedResolveSuccess,
+ ReconNeedSuccess,
+ ReconNeedTarget,
+ ResolveReconNeedResult,
+ StructuralIllegal,
+ VersionConflict,
+} from "./command-executor.js"
diff --git a/src/graph/policy/category-policy.ts b/src/graph/policy/category-policy.ts
new file mode 100644
index 000000000..76fbd8227
--- /dev/null
+++ b/src/graph/policy/category-policy.ts
@@ -0,0 +1,92 @@
+/**
+ * Per-edge-category policy table.
+ *
+ * Canonical reference: docs/design/GRAPH_MODEL.md §"Per-category policy"
+ *
+ * This table replaces the prior multi-axis per-relation policy
+ * registry. Only the axes that have a present reader in M4 or M5
+ * are encoded here:
+ *
+ * - `cascadeOnSourceChange` — automatic block / mark-stale on the
+ * dependent (assumption-invalidation
+ * cascade). Only `dependency` cascades.
+ * - `reconNeedOnSourceChange` — generate a ReconciliationNeed pointing
+ * at the edge. `"advisory"` = generated
+ * only if a coherence rule asks for it;
+ * `true` = generated unconditionally.
+ * - `criteriaHelpSignal` — the interviewer uses this edge when
+ * suggesting criteria for the target
+ * node ("requirement with no `proof`
+ * incoming → suggest criterion").
+ * - `projectionEffect` — non-default effect on snapshot /
+ * neighborhood builders. `"none"` means
+ * the edge is rendered ordinarily.
+ *
+ * Phase 1 lock-and-materialize: data only. The CommandExecutor,
+ * coherence triggers, projection builders, and interviewer prompts
+ * consume this table in subsequent M4/M5 slices.
+ */
+
+import type { EdgeCategory } from "../schema/edges.js"
+
+export type ReconNeedTrigger = false | "advisory" | true
+
+export type ProjectionEffect = "none" | "hide_predecessor_from_active_context"
+
+export interface CategoryPolicy {
+ readonly cascadeOnSourceChange: boolean
+ readonly reconNeedOnSourceChange: ReconNeedTrigger
+ readonly criteriaHelpSignal: boolean
+ readonly projectionEffect: ProjectionEffect
+}
+
+export const CATEGORY_POLICY: Readonly> = {
+ dependency: {
+ cascadeOnSourceChange: true,
+ reconNeedOnSourceChange: true,
+ criteriaHelpSignal: false,
+ projectionEffect: "none",
+ },
+ proof: {
+ cascadeOnSourceChange: false,
+ reconNeedOnSourceChange: "advisory",
+ criteriaHelpSignal: true,
+ projectionEffect: "none",
+ },
+ support: {
+ cascadeOnSourceChange: false,
+ reconNeedOnSourceChange: "advisory",
+ criteriaHelpSignal: false,
+ projectionEffect: "none",
+ },
+ realization: {
+ cascadeOnSourceChange: false,
+ reconNeedOnSourceChange: "advisory",
+ criteriaHelpSignal: false,
+ projectionEffect: "none",
+ },
+ boundary: {
+ cascadeOnSourceChange: false,
+ reconNeedOnSourceChange: true,
+ criteriaHelpSignal: false,
+ projectionEffect: "none",
+ },
+ composition: {
+ cascadeOnSourceChange: false,
+ reconNeedOnSourceChange: false,
+ criteriaHelpSignal: false,
+ projectionEffect: "none",
+ },
+ association: {
+ cascadeOnSourceChange: false,
+ reconNeedOnSourceChange: false,
+ criteriaHelpSignal: false,
+ projectionEffect: "none",
+ },
+ supersession: {
+ cascadeOnSourceChange: false,
+ reconNeedOnSourceChange: false,
+ criteriaHelpSignal: false,
+ projectionEffect: "hide_predecessor_from_active_context",
+ },
+}
diff --git a/src/graph/schema/edges.ts b/src/graph/schema/edges.ts
new file mode 100644
index 000000000..35df26258
--- /dev/null
+++ b/src/graph/schema/edges.ts
@@ -0,0 +1,80 @@
+/**
+ * Graph edge type definitions.
+ *
+ * Canonical reference: docs/design/GRAPH_MODEL.md
+ * Supersedes: docs/architecture/pi-seam-extensions.md §"Edge types"
+ * (the prior named-relation catalogue)
+ *
+ * Phase 1 lock-and-materialize: type definitions only.
+ * Drizzle table definitions, structural validators, and the
+ * agent-facing link* command surface land with subsequent M4/M5 slices.
+ */
+
+import { EDGE_CATEGORIES, EDGE_STANCES, NODE_BASES } from "../../db/schema.js"
+import type { EdgeId, Lsn, NodeId } from "../atoms.js"
+
+/**
+ * Closed set of structural edge categories.
+ *
+ * Derived from `db/schema.ts` — the single enum source.
+ *
+ * - `dependency` dependency → dependent hard upstream; cascade
+ * - `proof` oracle → claim witness or refutation (stance required)
+ * - `support` support → claim motivation / rationale (stance required)
+ * - `realization` abstract → concrete expression / implementation
+ * - `boundary` boundary → subject scope / constraint / exclusion
+ * - `composition` whole → part containment / decomposition
+ * - `supersession` successor → predecessor replacement lineage (acyclic)
+ * - `association` peer ↔ peer weak relatedness (symmetric)
+ */
+export type EdgeCategory = typeof EDGE_CATEGORIES[number]
+
+/**
+ * Polarity for stance-bearing edges.
+ *
+ * Required for `proof` and `support`.
+ * Invalid (must be omitted) for every other category.
+ */
+export type EdgeStance = typeof EDGE_STANCES[number]
+
+/**
+ * How an edge entered graph truth.
+ *
+ * `explicit` is a direct user statement; `accepted_review_set` is a
+ * batch acceptance through `acceptReviewSet` (D27-L). Inferred edges
+ * do NOT live in graph truth — they live in structured-exchange
+ * preface or `capture_*` analysis until promoted through a review set
+ * (D47-L, D50-L).
+ */
+export type EdgeBasis = typeof NODE_BASES[number]
+
+// EdgeProvenance retired — change_log owns the full audit trail.
+
+/**
+ * A structurally-typed edge in the Brunch graph.
+ *
+ * Immutability after acceptance:
+ * - `category`, `sourceId`, `targetId`, `stance` are immutable.
+ * - `rationale` may be updated (advances `updatedAtLsn`).
+ * - To change category: delete and recreate.
+ *
+ * Stance:
+ * - REQUIRED iff `category` is `"proof"` or `"support"`.
+ * - INVALID (must be omitted) for every other category.
+ * - Structural validators in the CommandExecutor enforce this.
+ *
+ * No `status` field: accepted graph edges are present-or-absent.
+ * Stale edges surface as `ReconciliationNeed` records pointing at
+ * the edge (see `src/graph/schema/reconciliation-need.ts`).
+ */
+export interface GraphEdge {
+ readonly id: EdgeId
+ readonly category: EdgeCategory
+ readonly sourceId: NodeId
+ readonly targetId: NodeId
+ readonly stance?: EdgeStance
+ readonly basis: EdgeBasis
+ readonly rationale?: string
+ readonly createdAtLsn: Lsn
+ readonly updatedAtLsn: Lsn
+}
diff --git a/src/graph/schema/nodes.ts b/src/graph/schema/nodes.ts
new file mode 100644
index 000000000..9cce08be1
--- /dev/null
+++ b/src/graph/schema/nodes.ts
@@ -0,0 +1,143 @@
+/**
+ * Graph node type definitions.
+ *
+ * Canonical reference: docs/design/GRAPH_MODEL.md
+ *
+ * Phase 2 lock-and-materialize: type definitions only.
+ * Drizzle table definitions, structural validators, and the
+ * agent-facing node command surface land with subsequent slices.
+ */
+
+import {
+ DESIGN_KINDS,
+ INTENT_KINDS,
+ NODE_BASES,
+ ORACLE_KINDS,
+ PLAN_KINDS,
+} from "../../db/schema.js"
+import type { Lsn, NodeId } from "../atoms.js"
+
+// ---------------------------------------------------------------------------
+// Planes & basis
+// ---------------------------------------------------------------------------
+
+/**
+ * The four conceptual planes that partition the node space.
+ *
+ * Each plane groups node kinds that share a common concern:
+ * - `intent` what and why
+ * - `oracle` how we know
+ * - `design` how it's shaped
+ * - `plan` how it's sequenced
+ */
+export type NodePlane = "intent" | "oracle" | "design" | "plan"
+
+/**
+ * How a node entered graph truth.
+ *
+ * Derived from `db/schema.ts` — same semantics as EdgeBasis.
+ */
+export type NodeBasis = typeof NODE_BASES[number]
+
+// ---------------------------------------------------------------------------
+// Kind taxonomy — derived from db/schema.ts const arrays
+// ---------------------------------------------------------------------------
+
+/**
+ * Intent-plane kinds, spanning three derived categories:
+ * - basic: `goal`, `thesis`, `term`, `context`
+ * - structural: `requirement`, `assumption`, `constraint`, `invariant`
+ * - reasoning: `decision`, `criterion`, `example`
+ */
+export type IntentKind = typeof INTENT_KINDS[number]
+
+/** Oracle-plane kinds. */
+export type OracleKind = typeof ORACLE_KINDS[number]
+
+/** Design-plane kinds. */
+export type DesignKind = typeof DESIGN_KINDS[number]
+
+/** Plan-plane kinds. */
+export type PlanKind = typeof PLAN_KINDS[number]
+
+/** Union of every node kind across all planes. */
+export type NodeKind = IntentKind | OracleKind | DesignKind | PlanKind
+
+// ---------------------------------------------------------------------------
+// Intent kind categories (derived, not stored)
+// ---------------------------------------------------------------------------
+
+/**
+ * Derived grouping over {@link IntentKind}.
+ *
+ * Never persisted — computed via {@link intentKindCategory}.
+ */
+export type IntentKindCategory = "basic" | "structural" | "reasoning"
+
+/** Pure derivation: intent kind → category. */
+export function intentKindCategory(kind: IntentKind): IntentKindCategory {
+ switch (kind) {
+ case "goal":
+ case "thesis":
+ case "term":
+ case "context":
+ return "basic"
+ case "requirement":
+ case "assumption":
+ case "constraint":
+ case "invariant":
+ return "structural"
+ case "decision":
+ case "criterion":
+ case "example":
+ return "reasoning"
+ }
+}
+
+// ---------------------------------------------------------------------------
+// Per-kind detail schemas
+// ---------------------------------------------------------------------------
+
+/** Detail payload for `decision` nodes. */
+export interface DecisionDetail {
+ readonly chosen_option: string
+ readonly rejected: readonly string[]
+ readonly rationale: string
+}
+
+/** Detail payload for `term` nodes. */
+export interface TermDetail {
+ readonly definition: string
+ readonly aliases?: readonly string[]
+}
+
+/** Discriminated union of all per-kind detail payloads. */
+export type NodeDetail = DecisionDetail | TermDetail
+
+// ---------------------------------------------------------------------------
+// Main node interface
+// ---------------------------------------------------------------------------
+
+/**
+ * A typed node in the Brunch graph.
+ *
+ * Immutability after acceptance:
+ * - `plane`, `kind`, `id` are immutable.
+ * - `title`, `body`, `detail`, `source` may be updated (advances `updatedAtLsn`).
+ * - To change kind: delete and recreate.
+ *
+ * No `status` field: accepted graph nodes are present-or-absent.
+ * Stale nodes surface as `ReconciliationNeed` records.
+ */
+export interface GraphNode {
+ readonly id: NodeId
+ readonly plane: NodePlane
+ readonly kind: NodeKind
+ readonly title: string
+ readonly body?: string
+ readonly basis: NodeBasis
+ readonly source?: string
+ readonly detail?: NodeDetail
+ readonly createdAtLsn: Lsn
+ readonly updatedAtLsn: Lsn
+}
diff --git a/src/graph/schema/reconciliation-need.ts b/src/graph/schema/reconciliation-need.ts
new file mode 100644
index 000000000..a629bf045
--- /dev/null
+++ b/src/graph/schema/reconciliation-need.ts
@@ -0,0 +1,56 @@
+/**
+ * Reconciliation-need type definitions.
+ *
+ * Canonical reference: docs/design/GRAPH_MODEL.md §"ReconciliationNeed"
+ *
+ * A reconciliation_need is a first-class record of an open impasse
+ * over graph state — typically "this edge needs re-validation"
+ * (after an upstream change) or "these two nodes might need an edge."
+ *
+ * Reconciliation_needs reference graph state. They are NOT graph
+ * edges and do not appear in projection neighborhoods as edges.
+ * They surface to the user through next-turn delivery as advisory
+ * items (D29-L).
+ *
+ * Phase 1 lock-and-materialize: type definitions only.
+ * Drizzle table definitions and CommandExecutor write paths land
+ * with subsequent M4 slices.
+ */
+
+import type { EdgeId, Lsn, NodeId } from "../atoms.js"
+
+/**
+ * What sort of impasse this need records.
+ *
+ * Open extension — new kinds may be added as concrete needs surface.
+ * Most needs are `edge_revalidation`.
+ */
+export type ReconciliationNeedKind = "edge_revalidation" | "possible_relation" | "possible_duplicate" | "semantic_conflict"
+
+/**
+ * What this need is about.
+ *
+ * `edge` is the default — the need describes a relation whose
+ * semantic basis may have changed.
+ *
+ * `node_pair` covers cases where no edge exists yet (possible
+ * duplicate, possible relation). When such a need resolves to
+ * "yes, edge exists," create the edge and close the need.
+ */
+export type ReconciliationNeedTarget = {
+ readonly kind: "edge"
+ readonly edgeId: EdgeId
+} | {
+ readonly kind: "node_pair"
+ readonly aId: NodeId
+ readonly bId: NodeId
+}
+
+export interface ReconciliationNeed {
+ readonly id: string
+ readonly kind: ReconciliationNeedKind
+ readonly target: ReconciliationNeedTarget
+ readonly rationale?: string
+ readonly createdAtLsn: Lsn
+ readonly resolvedAtLsn?: Lsn
+}
diff --git a/src/graph/snapshot.test.ts b/src/graph/snapshot.test.ts
new file mode 100644
index 000000000..d460bca80
--- /dev/null
+++ b/src/graph/snapshot.test.ts
@@ -0,0 +1,354 @@
+/**
+ * Graph snapshot reader tests — acceptance criteria for I35-L.
+ *
+ * SPEC: D52-L (graph/ reads db/), I35-L (cursory + neighborhood)
+ * Scope card: Graph snapshot readers at cursory and neighborhood detail levels
+ *
+ * All graph state is seeded via CommandExecutor (no direct db writes).
+ */
+
+import { beforeEach, describe, expect, it } from "vitest"
+
+import { createDb, type BrunchDb } from "../db/connection.js"
+import { CommandExecutor } from "./command-executor.js"
+import {
+ getGraphOverview,
+ getNodeNeighborhood,
+ getOpenReconciliationNeeds,
+} from "./snapshot.js"
+
+function createTestDb(): BrunchDb {
+ return createDb(":memory:")
+}
+
+describe("getGraphOverview", () => {
+ let db: BrunchDb
+ let executor: CommandExecutor
+
+ beforeEach(() => {
+ db = createTestDb()
+ executor = new CommandExecutor(db)
+ })
+
+ it("returns empty arrays and zero counts on an empty graph", () => {
+ const overview = getGraphOverview(db)
+ expect(overview.nodes).toEqual([])
+ expect(overview.edges).toEqual([])
+ expect(overview.nodeCount).toBe(0)
+ expect(overview.edgeCount).toBe(0)
+ expect(overview.lsn).toBe(0)
+ })
+
+ it("returns current LSN from graph_clock", () => {
+ executor.createNode({ plane: "intent", kind: "goal", title: "G1" })
+ executor.createNode({ plane: "intent", kind: "thesis", title: "T1" })
+ const overview = getGraphOverview(db)
+ expect(overview.lsn).toBe(2)
+ })
+
+ it("returns typed domain objects with parsed detail JSON", () => {
+ executor.createNode({
+ plane: "intent",
+ kind: "decision",
+ title: "Use SQLite",
+ body: "Settled on SQLite",
+ detail: {
+ chosen_option: "SQLite",
+ rejected: ["PostgreSQL"],
+ rationale: "Simpler local deployment",
+ },
+ })
+
+ const overview = getGraphOverview(db)
+ expect(overview.nodes).toHaveLength(1)
+ const node = overview.nodes[0]!
+ expect(node.id).toBeTypeOf("number")
+ expect(node.plane).toBe("intent")
+ expect(node.kind).toBe("decision")
+ expect(node.title).toBe("Use SQLite")
+ expect(node.body).toBe("Settled on SQLite")
+ expect(node.basis).toBe("explicit")
+ expect(node.detail).toEqual({
+ chosen_option: "SQLite",
+ rejected: ["PostgreSQL"],
+ rationale: "Simpler local deployment",
+ })
+ expect(node.createdAtLsn).toBe(1)
+ expect(node.updatedAtLsn).toBe(1)
+ })
+
+ it("returns nodes and edges with correct counts", () => {
+ const batch = executor.commitGraph({
+ nodes: [
+ { ref: "r1", plane: "intent", kind: "requirement", title: "R1" },
+ { ref: "a1", plane: "intent", kind: "assumption", title: "A1" },
+ ],
+ edges: [{ category: "dependency", source: "r1", target: "a1" }],
+ })
+ expect(batch.status).toBe("success")
+
+ const overview = getGraphOverview(db)
+ expect(overview.nodeCount).toBe(2)
+ expect(overview.edgeCount).toBe(1)
+ expect(overview.nodes).toHaveLength(2)
+ expect(overview.edges).toHaveLength(1)
+ expect(overview.edges[0]!.category).toBe("dependency")
+ })
+
+ it("excludes superseded predecessors from overview", () => {
+ // Create R_v0, then R_v1 that supersedes R_v0
+ const r0 = executor.createNode({
+ plane: "intent",
+ kind: "requirement",
+ title: "R_offline_v0",
+ })
+ expect(r0.status).toBe("success")
+ if (r0.status !== "success") throw new Error("unreachable")
+ const r0Id = r0.nodeId
+
+ const batch = executor.commitGraph({
+ nodes: [
+ {
+ ref: "r1",
+ plane: "intent",
+ kind: "requirement",
+ title: "R_offline_v1",
+ },
+ ],
+ edges: [
+ {
+ category: "supersession",
+ source: "r1",
+ target: { existing: r0Id },
+ },
+ ],
+ })
+ expect(batch.status).toBe("success")
+
+ const overview = getGraphOverview(db)
+ // R_offline_v0 should be excluded (it is a superseded predecessor)
+ const titles = overview.nodes.map((n) => n.title)
+ expect(titles).toContain("R_offline_v1")
+ expect(titles).not.toContain("R_offline_v0")
+ // The supersession edge should still be present
+ expect(overview.edges).toHaveLength(1)
+ })
+})
+
+describe("getNodeNeighborhood", () => {
+ let db: BrunchDb
+ let executor: CommandExecutor
+
+ beforeEach(() => {
+ db = createTestDb()
+ executor = new CommandExecutor(db)
+ })
+
+ it("returns error for non-existent nodeId", () => {
+ const result = getNodeNeighborhood(db, 999)
+ expect(result.status).toBe("not_found")
+ })
+
+ it("returns anchor node and directly connected nodes/edges at 1 hop (default)", () => {
+ const batch = executor.commitGraph({
+ nodes: [
+ { ref: "r1", plane: "intent", kind: "requirement", title: "R1" },
+ { ref: "a1", plane: "intent", kind: "assumption", title: "A1" },
+ { ref: "g1", plane: "intent", kind: "goal", title: "G1" },
+ ],
+ edges: [
+ { category: "dependency", source: "r1", target: "a1" },
+ { category: "support", source: "g1", target: "r1", stance: "for" },
+ ],
+ })
+ expect(batch.status).toBe("success")
+ if (batch.status !== "success") throw new Error("unreachable")
+
+ const r1Id = batch.nodes["r1"]!
+ const result = getNodeNeighborhood(db, r1Id)
+ expect(result.status).toBe("success")
+ if (result.status !== "success") throw new Error("unreachable")
+
+ expect(result.anchor.title).toBe("R1")
+ // Should include A1 (dependency target) and G1 (support source)
+ expect(result.neighbors).toHaveLength(2)
+ const neighborTitles = result.neighbors.map((n) => n.title).sort()
+ expect(neighborTitles).toEqual(["A1", "G1"])
+ expect(result.edges).toHaveLength(2)
+ })
+
+ it("reaches 2-hop neighbors", () => {
+ // G1 -> R1 -> A1 (chain of depth 2 from G1)
+ const batch = executor.commitGraph({
+ nodes: [
+ { ref: "g1", plane: "intent", kind: "goal", title: "G1" },
+ { ref: "r1", plane: "intent", kind: "requirement", title: "R1" },
+ { ref: "a1", plane: "intent", kind: "assumption", title: "A1" },
+ ],
+ edges: [
+ { category: "support", source: "g1", target: "r1", stance: "for" },
+ { category: "dependency", source: "r1", target: "a1" },
+ ],
+ })
+ expect(batch.status).toBe("success")
+ if (batch.status !== "success") throw new Error("unreachable")
+
+ const g1Id = batch.nodes["g1"]!
+
+ // 1 hop: only R1
+ const hop1 = getNodeNeighborhood(db, g1Id, { hops: 1 })
+ expect(hop1.status).toBe("success")
+ if (hop1.status !== "success") throw new Error("unreachable")
+ expect(hop1.neighbors.map((n) => n.title)).toEqual(["R1"])
+
+ // 2 hops: R1 and A1
+ const hop2 = getNodeNeighborhood(db, g1Id, { hops: 2 })
+ expect(hop2.status).toBe("success")
+ if (hop2.status !== "success") throw new Error("unreachable")
+ const titles = hop2.neighbors.map((n) => n.title).sort()
+ expect(titles).toEqual(["A1", "R1"])
+ })
+
+ it("excludes superseded predecessors from neighborhood (unless anchor)", () => {
+ // R_v0 superseded by R_v1, with A1 depending on R_v1
+ const r0 = executor.createNode({
+ plane: "intent",
+ kind: "requirement",
+ title: "R_v0",
+ })
+ expect(r0.status).toBe("success")
+ if (r0.status !== "success") throw new Error("unreachable")
+ const r0Id = r0.nodeId
+
+ const batch = executor.commitGraph({
+ nodes: [
+ { ref: "r1", plane: "intent", kind: "requirement", title: "R_v1" },
+ { ref: "a1", plane: "intent", kind: "assumption", title: "A1" },
+ ],
+ edges: [
+ { category: "supersession", source: "r1", target: { existing: r0Id } },
+ { category: "dependency", source: "r1", target: "a1" },
+ ],
+ })
+ expect(batch.status).toBe("success")
+ if (batch.status !== "success") throw new Error("unreachable")
+
+ const r1Id = batch.nodes["r1"]!
+
+ // Neighborhood of R_v1: should include A1 but exclude R_v0
+ const result = getNodeNeighborhood(db, r1Id)
+ expect(result.status).toBe("success")
+ if (result.status !== "success") throw new Error("unreachable")
+
+ const neighborTitles = result.neighbors.map((n) => n.title)
+ expect(neighborTitles).toContain("A1")
+ expect(neighborTitles).not.toContain("R_v0")
+
+ // But if R_v0 is the anchor, it should still be returned
+ const r0Result = getNodeNeighborhood(db, r0Id)
+ expect(r0Result.status).toBe("success")
+ if (r0Result.status !== "success") throw new Error("unreachable")
+ expect(r0Result.anchor.title).toBe("R_v0")
+ })
+
+ it("returns typed GraphNode and GraphEdge domain objects", () => {
+ const batch = executor.commitGraph({
+ nodes: [
+ {
+ ref: "t1",
+ plane: "intent",
+ kind: "term",
+ title: "Widget",
+ detail: { definition: "A reusable component", aliases: ["gadget"] },
+ },
+ { ref: "r1", plane: "intent", kind: "requirement", title: "R1" },
+ ],
+ edges: [{ category: "boundary", source: "t1", target: "r1" }],
+ })
+ expect(batch.status).toBe("success")
+ if (batch.status !== "success") throw new Error("unreachable")
+
+ const t1Id = batch.nodes["t1"]!
+ const result = getNodeNeighborhood(db, t1Id)
+ expect(result.status).toBe("success")
+ if (result.status !== "success") throw new Error("unreachable")
+
+ // Anchor has parsed detail
+ expect(result.anchor.detail).toEqual({
+ definition: "A reusable component",
+ aliases: ["gadget"],
+ })
+
+ // Edge has typed fields
+ const edge = result.edges[0]!
+ expect(edge.category).toBe("boundary")
+ expect(edge.sourceId).toBe(t1Id)
+ expect(edge.createdAtLsn).toBeTypeOf("number")
+ })
+})
+
+describe("getOpenReconciliationNeeds", () => {
+ let db: BrunchDb
+ let executor: CommandExecutor
+
+ beforeEach(() => {
+ db = createTestDb()
+ executor = new CommandExecutor(db)
+ })
+
+ it("returns empty array when no needs exist", () => {
+ const needs = getOpenReconciliationNeeds(db)
+ expect(needs).toEqual([])
+ })
+
+ it("returns open needs as typed domain objects", () => {
+ const batch = executor.commitGraph({
+ nodes: [
+ { ref: "r1", plane: "intent", kind: "requirement", title: "R1" },
+ { ref: "a1", plane: "intent", kind: "assumption", title: "A1" },
+ ],
+ edges: [{ category: "dependency", source: "r1", target: "a1" }],
+ })
+ expect(batch.status).toBe("success")
+ if (batch.status !== "success") throw new Error("unreachable")
+
+ const create = executor.createReconciliationNeed({
+ target: { kind: "edge", edgeId: batch.edges[0]! },
+ needKind: "edge_revalidation",
+ reason: "upstream changed",
+ })
+ expect(create.status).toBe("success")
+ if (create.status !== "success") throw new Error("unreachable")
+
+ const needs = getOpenReconciliationNeeds(db)
+ expect(needs).toHaveLength(1)
+ expect(needs[0]!.kind).toBe("edge_revalidation")
+ expect(needs[0]!.target).toEqual({ kind: "edge", edgeId: batch.edges[0]! })
+ expect(needs[0]!.rationale).toBe("upstream changed")
+ expect(needs[0]!.createdAtLsn).toBeTypeOf("number")
+ })
+
+ it("excludes resolved needs", () => {
+ const batch = executor.commitGraph({
+ nodes: [
+ { ref: "r1", plane: "intent", kind: "requirement", title: "R1" },
+ { ref: "a1", plane: "intent", kind: "assumption", title: "A1" },
+ ],
+ edges: [{ category: "dependency", source: "r1", target: "a1" }],
+ })
+ expect(batch.status).toBe("success")
+ if (batch.status !== "success") throw new Error("unreachable")
+
+ const create = executor.createReconciliationNeed({
+ target: { kind: "edge", edgeId: batch.edges[0]! },
+ needKind: "edge_revalidation",
+ })
+ expect(create.status).toBe("success")
+ if (create.status !== "success") throw new Error("unreachable")
+
+ executor.resolveReconciliationNeed(create.id)
+
+ const needs = getOpenReconciliationNeeds(db)
+ expect(needs).toEqual([])
+ })
+})
diff --git a/src/graph/snapshot.ts b/src/graph/snapshot.ts
new file mode 100644
index 000000000..0b41ad11a
--- /dev/null
+++ b/src/graph/snapshot.ts
@@ -0,0 +1,285 @@
+/**
+ * Graph snapshot readers — cursory overview and node neighborhood.
+ *
+ * SPEC: I35-L (two detail levels), D52-L (graph/ reads db/)
+ *
+ * These are pure read functions over BrunchDb. They return typed
+ * domain objects (GraphNode, GraphEdge), not raw Drizzle rows.
+ * Superseded predecessors (nodes that are targets of a `supersession`
+ * edge) are excluded per CATEGORY_POLICY projectionEffect.
+ */
+
+import { eq, or, inArray } from "drizzle-orm"
+
+import type { BrunchDb } from "../db/connection.js"
+import * as schema from "../db/schema.js"
+import type { GraphEdge } from "./schema/edges.js"
+import type { GraphNode, NodeDetail } from "./schema/nodes.js"
+import type {
+ ReconciliationNeed,
+ ReconciliationNeedTarget,
+} from "./schema/reconciliation-need.js"
+
+// ---------------------------------------------------------------------------
+// Return types
+// ---------------------------------------------------------------------------
+
+/** Full-graph cursory overview. */
+export interface GraphOverview {
+ readonly nodes: readonly GraphNode[]
+ readonly edges: readonly GraphEdge[]
+ readonly nodeCount: number
+ readonly edgeCount: number
+ /** Current LSN from graph_clock. */
+ readonly lsn: number
+}
+
+/** Successful neighborhood result. */
+export interface NeighborhoodSuccess {
+ readonly status: "success"
+ readonly anchor: GraphNode
+ readonly neighbors: readonly GraphNode[]
+ readonly edges: readonly GraphEdge[]
+}
+
+/** Node not found. */
+export interface NeighborhoodNotFound {
+ readonly status: "not_found"
+}
+
+export type NeighborhoodResult = NeighborhoodSuccess | NeighborhoodNotFound
+
+export interface NeighborhoodOptions {
+ /** Number of hops from the anchor node. Defaults to 1. */
+ readonly hops?: number
+}
+
+// ---------------------------------------------------------------------------
+// Row → domain mapping
+// ---------------------------------------------------------------------------
+
+function rowToNode(row: typeof schema.nodes.$inferSelect): GraphNode {
+ return {
+ id: row.id,
+ plane: row.plane as GraphNode["plane"],
+ kind: row.kind as GraphNode["kind"],
+ title: row.title,
+ ...(row.body != null ? { body: row.body } : {}),
+ basis: row.basis as GraphNode["basis"],
+ ...(row.source != null ? { source: row.source } : {}),
+ ...(row.detail != null
+ ? { detail: JSON.parse(row.detail) as NodeDetail }
+ : {}),
+ createdAtLsn: row.created_at_lsn,
+ updatedAtLsn: row.updated_at_lsn,
+ }
+}
+
+function rowToEdge(row: typeof schema.edges.$inferSelect): GraphEdge {
+ const base = {
+ id: row.id,
+ category: row.category as GraphEdge["category"],
+ sourceId: row.source_id,
+ targetId: row.target_id,
+ basis: row.basis as GraphEdge["basis"],
+ createdAtLsn: row.created_at_lsn,
+ updatedAtLsn: row.updated_at_lsn,
+ }
+ return row.stance != null
+ ? row.rationale != null
+ ? {
+ ...base,
+ stance: row.stance as NonNullable,
+ rationale: row.rationale,
+ }
+ : { ...base, stance: row.stance as NonNullable }
+ : row.rationale != null
+ ? { ...base, rationale: row.rationale }
+ : base
+}
+
+// ---------------------------------------------------------------------------
+// Supersession helpers
+// ---------------------------------------------------------------------------
+
+/** Return the set of node ids that are superseded predecessors. */
+function getSupersededIds(db: BrunchDb): Set {
+ const rows = db
+ .select({ targetId: schema.edges.target_id })
+ .from(schema.edges)
+ .where(eq(schema.edges.category, "supersession"))
+ .all()
+ return new Set(rows.map((r) => r.targetId))
+}
+
+// ---------------------------------------------------------------------------
+// getGraphOverview
+// ---------------------------------------------------------------------------
+
+/**
+ * Cursory full-graph overview.
+ *
+ * Returns all accepted nodes and edges with current LSN.
+ * Superseded predecessors are excluded from the node list
+ * per CATEGORY_POLICY.supersession.projectionEffect.
+ */
+export function getGraphOverview(db: BrunchDb): GraphOverview {
+ const supersededIds = getSupersededIds(db)
+
+ const allNodeRows = db.select().from(schema.nodes).all()
+ const allEdgeRows = db.select().from(schema.edges).all()
+
+ const nodes = allNodeRows
+ .filter((r) => !supersededIds.has(r.id))
+ .map(rowToNode)
+
+ const edges = allEdgeRows.map(rowToEdge)
+
+ const clockRow = db.select().from(schema.graphClock).get()
+ const lsn = clockRow?.lsn ?? 0
+
+ return {
+ nodes,
+ edges,
+ nodeCount: nodes.length,
+ edgeCount: edges.length,
+ lsn,
+ }
+}
+
+// ---------------------------------------------------------------------------
+// getNodeNeighborhood
+// ---------------------------------------------------------------------------
+
+/**
+ * Neighborhood snapshot around a given node.
+ *
+ * Returns the anchor node, all reachable neighbors within `hops`
+ * distance (default 1), and the edges connecting them.
+ * Superseded predecessors are excluded from neighbors
+ * (unless the predecessor is the anchor itself).
+ */
+export function getNodeNeighborhood(
+ db: BrunchDb,
+ nodeId: number,
+ options?: NeighborhoodOptions,
+): NeighborhoodResult {
+ const hops = options?.hops ?? 1
+
+ // Verify anchor exists
+ const anchorRow = db
+ .select()
+ .from(schema.nodes)
+ .where(eq(schema.nodes.id, nodeId))
+ .get()
+
+ if (!anchorRow) {
+ return { status: "not_found" }
+ }
+
+ const supersededIds = getSupersededIds(db)
+ const anchor = rowToNode(anchorRow)
+
+ // BFS traversal: collect reachable node ids within hop distance
+ const visited = new Set([nodeId])
+ let frontier = new Set([nodeId])
+ const collectedEdgeIds = new Set()
+
+ for (let hop = 0; hop < hops; hop++) {
+ if (frontier.size === 0) break
+
+ // Find all edges touching frontier nodes
+ const frontierArr = [...frontier]
+ const edgeRows = db
+ .select()
+ .from(schema.edges)
+ .where(
+ or(
+ inArray(schema.edges.source_id, frontierArr),
+ inArray(schema.edges.target_id, frontierArr),
+ ),
+ )
+ .all()
+
+ const nextFrontier = new Set()
+ for (const edge of edgeRows) {
+ collectedEdgeIds.add(edge.id)
+ for (const peerId of [edge.source_id, edge.target_id]) {
+ if (!visited.has(peerId)) {
+ // Exclude superseded predecessors (unless it's the anchor)
+ if (supersededIds.has(peerId) && peerId !== nodeId) continue
+ visited.add(peerId)
+ nextFrontier.add(peerId)
+ }
+ }
+ }
+ frontier = nextFrontier
+ }
+
+ // Fetch neighbor nodes (exclude anchor)
+ const neighborIds = [...visited].filter((id) => id !== nodeId)
+ const neighborNodes: GraphNode[] = []
+ if (neighborIds.length > 0) {
+ const rows = db
+ .select()
+ .from(schema.nodes)
+ .where(inArray(schema.nodes.id, neighborIds))
+ .all()
+ neighborNodes.push(...rows.map(rowToNode))
+ }
+
+ // Fetch collected edges
+ const edgeIdArr = [...collectedEdgeIds]
+ const edgeNodes: GraphEdge[] = []
+ if (edgeIdArr.length > 0) {
+ const rows = db
+ .select()
+ .from(schema.edges)
+ .where(inArray(schema.edges.id, edgeIdArr))
+ .all()
+ edgeNodes.push(...rows.map(rowToEdge))
+ }
+
+ return {
+ status: "success",
+ anchor,
+ neighbors: neighborNodes,
+ edges: edgeNodes,
+ }
+}
+
+// ---------------------------------------------------------------------------
+// getOpenReconciliationNeeds
+// ---------------------------------------------------------------------------
+
+function rowToReconNeed(
+ row: typeof schema.reconciliationNeed.$inferSelect,
+): ReconciliationNeed {
+ const target: ReconciliationNeedTarget =
+ row.target_kind === "edge"
+ ? { kind: "edge", edgeId: row.target_edge_id! }
+ : { kind: "node_pair", aId: row.target_a_id!, bId: row.target_b_id! }
+
+ return {
+ id: String(row.id),
+ kind: row.kind as ReconciliationNeed["kind"],
+ target,
+ ...(row.reason != null ? { rationale: row.reason } : {}),
+ createdAtLsn: row.created_at_lsn,
+ ...(row.resolved_at_lsn != null
+ ? { resolvedAtLsn: row.resolved_at_lsn }
+ : {}),
+ }
+}
+
+/**
+ * Return all open (unresolved) reconciliation needs.
+ */
+export function getOpenReconciliationNeeds(db: BrunchDb): ReconciliationNeed[] {
+ const rows = db
+ .select()
+ .from(schema.reconciliationNeed)
+ .where(eq(schema.reconciliationNeed.status, "open"))
+ .all()
+ return rows.map(rowToReconNeed)
+}
diff --git a/src/session/README.md b/src/session/README.md
new file mode 100644
index 000000000..55af3f8da
--- /dev/null
+++ b/src/session/README.md
@@ -0,0 +1,56 @@
+# session/ — Session domain layer
+
+SPEC decisions: D6-L, D11-L, D12-L, D13-L, D21-L, D52-L
+
+## Owns
+
+Projection of Brunch's session semantics out of Pi's JSONL substrate,
+plus the coordination logic for workspace/spec/session lifecycle.
+
+- **Transcript projection** — reading Pi JSONL, projecting Brunch-relevant
+ structure (assistant/user rows, custom entries, tool results).
+
+- **Exchange extraction** — elicitation exchange projection: prompt-side
+ span + response-side span, per D13-L.
+
+- **Workspace coordination** — boot flow, spec/session selection,
+ `.brunch/state.json` management. The `WorkspaceSessionCoordinator`
+ is the only module that creates/opens Pi sessions for Brunch user flows
+ and writes `brunch.session_binding`.
+
+- **Session binding** — session↔spec binding entries in JSONL.
+
+- **Session envelope** — canonical session envelope reader (spec/session pair).
+
+- **LSN staleness tracking** — Pi extension records current LSN at session
+ start, checks at `prepareNextTurn`, injects `worldUpdate` with optional
+ re-snapshot when stale.
+
+## Does NOT own
+
+- Graph state, CommandExecutor, graph snapshots — those live in `graph/`.
+- Prompt composition, context building — those live in `agents/`.
+- Pi extension registration — those live in `.pi/extensions/`.
+
+## Imported by
+
+- `agents/contexts/` — for session/transcript snapshots
+- `rpc/` — for session.* and workspace.* RPC handlers
+- `.pi/extensions/` — for session lifecycle hooks
+
+## Migration from src/ root
+
+These files currently at `src/` root migrate here incrementally:
+
+| Current file | Session concern |
+|-----------------------------------|------------------------------------|
+| `workspace-session-coordinator.ts`| boot, spec/session selection |
+| `session-binding.ts` | session↔spec binding |
+| `brunch-session-envelope.ts` | session envelope reader |
+| `session-projection-reader.ts` | JSONL projection |
+| `session-transcript.ts` | transcript row projection |
+| `elicitation-exchange.ts` | exchange extraction |
+| `structured-exchange.ts` | structured exchange schemas/types |
+
+Move each file when it is next touched for substantive work, not as a
+bulk rename. Update imports at the call sites.
diff --git a/src/tui-client/.pi/__tests__/chrome.test.ts b/src/tui-client/.pi/__tests__/chrome.test.ts
index 40cdbefc3..a99f43574 100644
--- a/src/tui-client/.pi/__tests__/chrome.test.ts
+++ b/src/tui-client/.pi/__tests__/chrome.test.ts
@@ -22,6 +22,20 @@ describe("Brunch chrome projection", () => {
)
})
+ it("populates session.label from workspace session name when available", () => {
+ const workspace = readyWorkspace(
+ "/tmp/project",
+ "session-abc",
+ "My spec — session 1",
+ )
+ const state = chromeStateForWorkspace(workspace)
+
+ expect(state.session.label).toBe("My spec — session 1")
+ expect(formatBrunchChromeHeaderLines(state).join("\n")).toContain(
+ "My spec — session 1",
+ )
+ })
+
it("formats chrome header as wordmark plus runtime-state summary", async () => {
const state = {
cwd: "/tmp/project",
@@ -204,6 +218,7 @@ describe("Brunch chrome projection", () => {
function readyWorkspace(
cwd: string,
sessionId: string,
+ sessionName?: string,
): WorkspaceSessionReadyState {
const spec = { id: "spec-1", title: "Spec One" }
return {
@@ -213,6 +228,7 @@ function readyWorkspace(
session: {
id: sessionId,
file: `/sessions/${sessionId}.jsonl`,
+ name: sessionName,
manager: {} as WorkspaceSessionReadyState["session"]["manager"],
},
chrome: {
diff --git a/src/tui-client/.pi/__tests__/operational-mode.test.ts b/src/tui-client/.pi/__tests__/operational-mode.test.ts
index 6ae731124..11f86ce14 100644
--- a/src/tui-client/.pi/__tests__/operational-mode.test.ts
+++ b/src/tui-client/.pi/__tests__/operational-mode.test.ts
@@ -223,18 +223,85 @@ describe("Brunch agent runtime-state projection", () => {
it("rejects invalid runtime switch combinations before appending", () => {
const manager = new FakeRuntimeStateSessionManager()
- expect(() =>
- appendBrunchAgentRuntimeSwitch(manager, {
+ for (const invalidState of [
+ {
+ schemaVersion: 1,
+ operationalMode: "execute",
+ agentRole: "elicitor",
+ agentStrategy: "step-by-step",
+ agentLens: "step-by-step",
+ },
+ {
+ schemaVersion: 1,
+ operationalMode: "elicit",
+ agentRole: "reviewer",
+ agentStrategy: "step-by-step",
+ agentLens: "step-by-step",
+ },
+ {
schemaVersion: 1,
operationalMode: "elicit",
agentRole: "elicitor",
agentStrategy: "not-a-strategy",
agentLens: "step-by-step",
- } as unknown as BrunchAgentState),
- ).toThrow("Invalid BrunchAgentState runtime selection.")
+ },
+ {
+ schemaVersion: 1,
+ operationalMode: "elicit",
+ agentRole: "elicitor",
+ agentStrategy: "step-by-step",
+ agentLens: "not-a-lens",
+ },
+ ]) {
+ expect(() =>
+ appendBrunchAgentRuntimeSwitch(
+ manager,
+ invalidState as unknown as BrunchAgentState,
+ ),
+ ).toThrow("Invalid BrunchAgentState runtime selection.")
+ }
expect(manager.entries).toEqual([])
})
+ it("does not project invalid runtime mode, role, strategy, or lens entries", () => {
+ for (const invalidState of [
+ {
+ schemaVersion: 1,
+ operationalMode: "execute",
+ agentRole: "elicitor",
+ agentStrategy: "step-by-step",
+ agentLens: "step-by-step",
+ },
+ {
+ schemaVersion: 1,
+ operationalMode: "elicit",
+ agentRole: "reviewer",
+ agentStrategy: "step-by-step",
+ agentLens: "step-by-step",
+ },
+ {
+ schemaVersion: 1,
+ operationalMode: "elicit",
+ agentRole: "elicitor",
+ agentStrategy: "not-a-strategy",
+ agentLens: "step-by-step",
+ },
+ {
+ schemaVersion: 1,
+ operationalMode: "elicit",
+ agentRole: "elicitor",
+ agentStrategy: "step-by-step",
+ agentLens: "not-a-lens",
+ },
+ ]) {
+ expect(
+ projectBrunchAgentState([
+ runtimeEntry(invalidState as unknown as BrunchAgentState),
+ ]),
+ ).toMatchObject(DEFAULT_BRUNCH_AGENT_STATE)
+ }
+ })
+
it("appends runtime init from the extension session-start hook", async () => {
const manager = new FakeRuntimeStateSessionManager()
const events: Record unknown> = {}
diff --git a/src/tui-client/.pi/__tests__/prompting.test.ts b/src/tui-client/.pi/__tests__/prompting.test.ts
index 59f3ad549..4fd6f7555 100644
--- a/src/tui-client/.pi/__tests__/prompting.test.ts
+++ b/src/tui-client/.pi/__tests__/prompting.test.ts
@@ -6,8 +6,12 @@ import { describe, expect, it } from "vitest"
import { composeBrunchPrompt } from "../context/compose-brunch-prompt.js"
import {
+ BRUNCH_AGENT_RUNTIME_STATE_CUSTOM_TYPE,
DEFAULT_BRUNCH_AGENT_STATE,
+ appendBrunchAgentRuntimeSwitch,
type BrunchAgentState,
+ type BrunchAgentStateEntryData,
+ registerBrunchOperationalModePolicy,
} from "../extensions/operational-mode.js"
import { registerBrunchPrompting } from "../extensions/prompting.js"
import { createBrunchPiExtensionShell } from "../../pi-extension-shell.js"
@@ -25,6 +29,23 @@ function runtimeEntry(state: BrunchAgentState) {
}
}
+class FakeRuntimeStateSessionManager {
+ entries: Array<{
+ type: "custom"
+ customType: string
+ data: BrunchAgentStateEntryData
+ }> = []
+
+ getEntries() {
+ return this.entries
+ }
+
+ appendCustomEntry(customType: string, data: BrunchAgentStateEntryData) {
+ this.entries.push({ type: "custom", customType, data })
+ return `entry-${this.entries.length}`
+ }
+}
+
describe("Brunch prompt-pack topology", () => {
it("composes deterministic private prompt packs in stable order", () => {
const result = composeBrunchPrompt({
@@ -108,6 +129,77 @@ describe("Brunch prompt-pack topology", () => {
})
})
+ it("derives prompt and active tools from the same transcript-backed runtime state", async () => {
+ const manager = new FakeRuntimeStateSessionManager()
+ const events: Record unknown>> = {}
+ const activeTools: string[][] = []
+
+ const pi = {
+ on: (event: string, handler: (event: never, ctx?: never) => unknown) => {
+ events[event] ??= []
+ events[event].push(handler)
+ },
+ registerTool: (_tool: { name: string }) => {},
+ getAllTools: () =>
+ ["read", "grep", "bash", "edit", "write", "present_options"].map(
+ (name) => ({ name }),
+ ),
+ setActiveTools: (tools: string[]) => activeTools.push(tools),
+ }
+ registerBrunchOperationalModePolicy(pi as never)
+ registerBrunchPrompting(pi as never)
+
+ for (const handler of events.session_start ?? []) {
+ await handler({} as never, { sessionManager: manager } as never)
+ }
+ const defaultPromptResults = await Promise.all(
+ (events.before_agent_start ?? []).map((handler) =>
+ Promise.resolve(
+ handler({ systemPrompt: "base" } as never, {
+ sessionManager: manager,
+ } as never),
+ ),
+ ),
+ )
+ const latestState: BrunchAgentState = {
+ ...DEFAULT_BRUNCH_AGENT_STATE,
+ agentStrategy: "disambiguate-via-examples",
+ agentLens: "disambiguate-via-examples",
+ }
+ appendBrunchAgentRuntimeSwitch(manager, latestState, "user")
+ const switchedPromptResults = await Promise.all(
+ (events.before_agent_start ?? []).map((handler) =>
+ Promise.resolve(
+ handler({ systemPrompt: "base" } as never, {
+ sessionManager: manager,
+ } as never),
+ ),
+ ),
+ )
+ const defaultPrompt = defaultPromptResults.find(Boolean)
+ const switchedPrompt = switchedPromptResults.find(Boolean)
+
+ expect(manager.entries[0]?.customType).toBe(
+ BRUNCH_AGENT_RUNTIME_STATE_CUSTOM_TYPE,
+ )
+ expect(activeTools).toEqual([
+ ["read", "grep", "present_options"],
+ ["read", "grep", "present_options"],
+ ["read", "grep", "present_options"],
+ ])
+ expect(defaultPrompt).toMatchObject({
+ systemPrompt: expect.stringContaining("Agent strategy: step-by-step."),
+ })
+ expect(switchedPrompt).toMatchObject({
+ systemPrompt: expect.stringContaining(
+ "Agent strategy: disambiguate-via-examples.",
+ ),
+ })
+ })
+
it("is registered by the explicit shell after operational-mode policy", async () => {
const eventNames: string[] = []
diff --git a/src/tui-client/.pi/extensions/chrome.ts b/src/tui-client/.pi/extensions/chrome.ts
index 7764dd7b7..e0ed6a98b 100644
--- a/src/tui-client/.pi/extensions/chrome.ts
+++ b/src/tui-client/.pi/extensions/chrome.ts
@@ -132,7 +132,7 @@ export function chromeStateForWorkspace(
...workspace.chrome,
session: {
id: workspace.session.id,
- label: workspace.session.id,
+ label: workspace.session.name ?? workspace.session.id,
},
}
}
diff --git a/src/workspace-session-coordinator.test.ts b/src/workspace-session-coordinator.test.ts
index 81b864fd7..cdf40239d 100644
--- a/src/workspace-session-coordinator.test.ts
+++ b/src/workspace-session-coordinator.test.ts
@@ -565,4 +565,43 @@ describe("WorkspaceSessionCoordinator", () => {
expect(result.chrome.cwd).toBe(cwd)
expect(result.chrome.spec).toBeNull()
})
+
+ it("generates a display name for new sessions and persists it as session_info", async () => {
+ const cwd = await mkdtemp(join(tmpdir(), "brunch-ws-"))
+ const coordinator = createWorkspaceSessionCoordinator({ cwd })
+
+ const first = await coordinator.createSetupSession({
+ specTitle: "Scratch spec",
+ })
+
+ // Session should have a display name derived from spec title
+ const manager1 = SessionManager.open(first.session.file, undefined, cwd)
+ expect(manager1.getSessionName()).toBe("Scratch spec — session 1")
+
+ // Second session for same spec gets ordinal 2
+ const second = await coordinator.createSetupSessionForCurrentSpec()
+ expect(second.status).toBe("ready")
+ if (second.status !== "ready") return
+
+ const manager2 = SessionManager.open(second.session.file, undefined, cwd)
+ expect(manager2.getSessionName()).toBe("Scratch spec — session 2")
+ })
+
+ it("preserves existing display name on session resume", async () => {
+ const cwd = await mkdtemp(join(tmpdir(), "brunch-ws-"))
+ const coordinator = createWorkspaceSessionCoordinator({ cwd })
+
+ await coordinator.createSetupSession({
+ specTitle: "My spec",
+ })
+
+ // Reopen the same session
+ const reopened = await coordinator.openDefaultWorkspace()
+ expect(reopened.status).toBe("ready")
+ if (reopened.status !== "ready") return
+
+ // Name should be unchanged
+ const manager = SessionManager.open(reopened.session.file, undefined, cwd)
+ expect(manager.getSessionName()).toBe("My spec — session 1")
+ })
})
diff --git a/src/workspace-session-coordinator.ts b/src/workspace-session-coordinator.ts
index 9dd0ea8bc..1c8a544b7 100644
--- a/src/workspace-session-coordinator.ts
+++ b/src/workspace-session-coordinator.ts
@@ -44,6 +44,7 @@ export interface WorkspaceSessionReadyState {
session: {
id: string
file: string
+ name?: string
manager: SessionManager
}
chrome: WorkspaceSessionChromeState
@@ -322,12 +323,28 @@ async function createBoundSession(
spec: WorkspaceSpecState,
): Promise {
await ensureWorkspaceDirs(cwd)
+ const existingSessionCount = await countSessionsForSpec(cwd, spec.id)
const manager = SessionManager.create(cwd, sessionDir(cwd))
const sessionFile = manager.getSessionFile()
if (!sessionFile) {
throw new Error("Pi SessionManager did not create a persisted session file")
}
- return bindSessionToSpec(manager, spec)
+ return bindSessionToSpec(manager, spec, existingSessionCount + 1)
+}
+
+async function countSessionsForSpec(
+ cwd: string,
+ specId: string,
+): Promise {
+ const files = await listSessionFiles(cwd)
+ let count = 0
+ for (const file of files) {
+ const session = await inspectSessionFile(file)
+ if (session.available && session.specId === specId) {
+ count++
+ }
+ }
+ return count
}
async function openCurrentSession(
@@ -352,6 +369,7 @@ async function openCurrentSession(
function bindSessionToSpec(
manager: SessionManager,
spec: WorkspaceSpecState,
+ sessionOrdinal?: number,
): WorkspaceSessionReadyState["session"] {
const sessionFile = manager.getSessionFile()
if (!sessionFile) {
@@ -368,6 +386,11 @@ function bindSessionToSpec(
specTitle: spec.title,
}),
)
+ // Generate and persist a display name for new sessions
+ if (sessionOrdinal !== undefined) {
+ const displayName = sessionDisplayName(spec.title, sessionOrdinal)
+ manager.appendSessionInfo(displayName)
+ }
} else if (
existingBindings.length !== 1 ||
existingBindings[0]?.data.sessionId !== manager.getSessionId() ||
@@ -379,7 +402,17 @@ function bindSessionToSpec(
}
flushSessionWithoutAssistant(manager)
- return { id: manager.getSessionId(), file: sessionFile, manager }
+ const sessionName = manager.getSessionName()
+ return {
+ id: manager.getSessionId(),
+ file: sessionFile,
+ ...(sessionName != null ? { name: sessionName } : {}),
+ manager,
+ }
+}
+
+export function sessionDisplayName(specTitle: string, ordinal: number): string {
+ return `${specTitle} — session ${ordinal}`
}
interface FlushableSessionManager {
@@ -501,11 +534,19 @@ async function inspectSessionFile(file: string): Promise {
return { file, reason: "incompatible_binding", available: false }
}
+ const sessionInfoEntries = entries.filter(isSessionInfoEntry)
+ const lastInfo =
+ sessionInfoEntries.length > 0
+ ? sessionInfoEntries[sessionInfoEntries.length - 1] as { name?: string }
+ : undefined
+ const name = lastInfo?.name
+
return {
id: header.id,
file,
specId: binding.data.specId,
specTitle: binding.data.specTitle,
+ ...(name != null ? { name } : {}),
available: true,
}
}
@@ -696,3 +737,11 @@ function isSessionHeader(value: unknown): value is SessionHeader {
typeof (value as { id?: unknown }).id === "string"
)
}
+
+function isSessionInfoEntry(value: unknown): boolean {
+ return (
+ typeof value === "object" &&
+ value !== null &&
+ (value as { type?: unknown }).type === "session_info"
+ )
+}