#!/usr/bin/env node import Database from 'better-sqlite3'; /*! * SPDX-License-Identifier: AGPL-3.0-or-later * Copyright © 2026 Diego Lima Nogueira de Paula */ declare function showBanner(): Promise; /*! * SPDX-License-Identifier: AGPL-3.0-or-later * Copyright © 2026 Diego Lima Nogueira de Paula * * This file is part of mcp-graph. * * mcp-graph is free software: you can redistribute it and/or modify it under the * terms of the GNU Affero General Public License v3.0 or later, as published by * the Free Software Foundation. See LICENSE for the full terms. * * mcp-graph is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR * A PARTICULAR PURPOSE. * * Commercial licenses are available — see COMMERCIAL.md. */ type GraphEventType = 'node:created' | 'node:updated' | 'node:deleted' | 'edge:created' | 'edge:deleted' | 'import:completed' | 'bulk:updated' | 'knowledge:indexed' | 'knowledge:deleted' | 'knowledge:quality_updated' | 'phase:transitioned' | 'sprint:planned' | 'validation:completed' | 'code:reindexed' | 'log:entry' | 'error:detected' | 'healing:memory_created' | 'healing:scan_completed' | 'healing:actions_executed' | 'healing:report_generated' | 'siebel:sif_imported' | 'siebel:composer_action' | 'siebel:objects_indexed' | 'siebel:sif_generated' | 'translation:job_created' | 'translation:analyzed' | 'translation:finalized' | 'translation:error' | 'dream:cycle_started' | 'dream:phase_started' | 'dream:phase_completed' | 'dream:cycle_completed' | 'dream:cycle_cancelled' | 'dream:cycle_failed' | 'constitution:created' | 'constitution:updated' | 'constitution:check_completed' | 'plugin:installed' | 'plugin:removed' | 'plugin:enabled' | 'plugin:disabled' | 'plugin:error' | 'preset:applied' | 'preset:created' | 'spec:created' | 'spec:updated' | 'spec:synced' | 'harness:scan_completed' | 'harness:regression_detected' | 'task:claimed' | 'task:released' | 'agent:heartbeat' | 'autopilot:paused' | 'autopilot:escalation' | 'autopilot:rollback' | 'trace:created' | 'trace:completed' | 'span:created' | 'guardrail:executed' | 'decision:logged' | 'experiment:completed' | 'security:injection_detected' | 'security:exfiltration_detected' | 'cost:budget_exceeded' | 'error:retry_attempted' | 'error:retry_exhausted' | 'context:pressure_warning' | 'tool:result_persisted' | 'session:chained' | 'agent:delegated' | 'agent:delegation_completed' | 'agent:delegation_failed' | 'pipeline:started' | 'pipeline:step_completed' | 'pipeline:completed' | 'ots:submitted' | 'ots:confirmed' | 'ots:retry_scheduled' | 'subtask_artifact:created' | 'memory:pressure_warning' | 'memory:pressure_critical' | 'sentrux:scan_complete' | 'self_healing:signal_collected' | 'self_healing:pattern_detected' | 'self_healing:healing_proposed' | 'self_healing:healing_auto_applied'; interface GraphEvent { type: GraphEventType; timestamp: string; payload: Record; } /*! * SPDX-License-Identifier: AGPL-3.0-or-later * Copyright © 2026 Diego Lima Nogueira de Paula * * This file is part of mcp-graph. * * mcp-graph is free software: you can redistribute it and/or modify it under the * terms of the GNU Affero General Public License v3.0 or later, as published by * the Free Software Foundation. See LICENSE for the full terms. * * mcp-graph is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR * A PARTICULAR PURPOSE. * * Commercial licenses are available — see COMMERCIAL.md. */ type EventHandler = (event: GraphEvent) => void; type EventType = GraphEventType | '*'; /** * Typed event bus for graph mutations. * Wraps Node.js EventEmitter with typed GraphEvent payloads. */ declare class GraphEventBus { private emitter; private wrappedHandlers; constructor(); /** Emit a graph event with error boundaries — one crashing handler won't stop others */ emit(event: GraphEvent): void; /** Listen for a specific event type */ on(type: EventType, handler: EventHandler): void; /** Listen for a specific event type (once) */ once(type: EventType, handler: EventHandler): void; /** Remove a specific listener */ off(type: EventType, handler: EventHandler): void; /** Remove all listeners */ removeAllListeners(): void; /** Get listener count for a type */ listenerCount(type: EventType): number; /** Helper: create and emit event in one call */ emitTyped(type: GraphEventType, payload: Record): void; private wrapHandler; private getWrappedHandler; private deleteWrappedHandler; } /*! * SPDX-License-Identifier: AGPL-3.0-or-later * Copyright © 2026 Diego Lima Nogueira de Paula * * This file is part of mcp-graph. * * mcp-graph is free software: you can redistribute it and/or modify it under the * terms of the GNU Affero General Public License v3.0 or later, as published by * the Free Software Foundation. See LICENSE for the full terms. * * mcp-graph is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR * A PARTICULAR PURPOSE. * * Commercial licenses are available — see COMMERCIAL.md. */ type NodeType = 'epic' | 'task' | 'subtask' | 'requirement' | 'constraint' | 'milestone' | 'acceptance_criteria' | 'risk' | 'decision' | 'interface' | 'formula' | 'state_machine' | 'contract' | 'scenario' | 'performance_budget' | 'asset' | 'data_table' | 'metric' | 'config_schema' | 'constitution' | 'journey_run' | 'browser_test'; type NodeStatus = 'backlog' | 'ready' | 'in_progress' | 'blocked' | 'done'; type XpSize = 'XS' | 'S' | 'M' | 'L' | 'XL'; type RelationType = 'parent_of' | 'child_of' | 'depends_on' | 'blocks' | 'related_to' | 'priority_over' | 'implements' | 'derived_from' | 'provides' | 'consumes' | 'requires_asset' | 'decomposed_into' | 'tests' | 'validates_adr' | 'mirrors_unit'; interface SourceRef { file: string; startLine?: number; endLine?: number; confidence?: number; } interface GraphNode { id: string; type: NodeType; title: string; description?: string; status: NodeStatus; priority: 1 | 2 | 3 | 4 | 5; xpSize?: XpSize; estimateMinutes?: number; tags?: string[]; parentId?: string | null; sprint?: string | null; sourceRef?: SourceRef; acceptanceCriteria?: string[]; testFiles?: string[]; blocked?: boolean; metadata?: { inferred?: boolean; origin?: string; [key: string]: unknown; }; /** * §extracta — Why this node was last regenerated. Null/undefined for * nodes that have never been regenerated. Set by `node update` when the * caller passes `evolutionReason`. Drives analyze(evolution_audit). */ evolutionReason?: string | null; /** * §extracta — Cumulative count of regenerations. Incremented every time * `node update` is called with a non-null `evolutionReason`. */ evolutionCount?: number; createdAt: string; updatedAt: string; } interface GraphEdge { id: string; from: string; to: string; relationType: RelationType; weight?: number; reason?: string; metadata?: { inferred?: boolean; confidence?: number; [key: string]: unknown; }; createdAt: string; } interface GraphIndexes { byId: Record; childrenByParent: Record; incomingByNode: Record; outgoingByNode: Record; } interface GraphProject { id: string; name: string; fsPath?: string; createdAt: string; updatedAt: string; } interface GraphMeta { sourceFiles: string[]; lastImport: string | null; } interface GraphDocument { version: string; project: GraphProject; nodes: GraphNode[]; edges: GraphEdge[]; indexes: GraphIndexes; meta: GraphMeta; } /*! * SPDX-License-Identifier: AGPL-3.0-or-later * Copyright © 2026 Diego Lima Nogueira de Paula */ /** * Promise-based mutual exclusion lock for serializing async write sequences. * Prevents interleaving of multi-step write operations that span async boundaries. * Reads bypass the mutex — only writes need serialization. */ declare class AsyncMutex { private _locked; private _queue; /** Returns true when the lock is currently held. */ get isLocked(): boolean; /** * Acquire the lock. Resolves when the lock is granted. * Returns a release function — caller MUST call it to free the lock. */ acquire(): Promise<() => void>; private _release; /** * Run `fn` exclusively under the lock. Automatically acquires and releases. * Works with both sync and async functions. */ run(fn: () => T | Promise): Promise; } /** Options for mutation operations (multi-agent support, ADR-10). */ interface MutationOptions { agentId?: string; expectedVersion?: number; } declare class SqliteStore { private db; private projectId; private _eventBus; /** Serializes multi-step write sequences that span async boundaries. */ readonly writeMutex: AsyncMutex; /** Cached prepared statements keyed by literal SQL text — avoids re-parsing on hot paths. */ private readonly statements; private constructor(); /** * Return a prepared statement for the given SQL, caching it for reuse. * Only safe for queries with literal SQL text (no string interpolation per-call). */ private getStmt; /** Attach an event bus to emit mutation events */ set eventBus(bus: GraphEventBus | null); get eventBus(): GraphEventBus | null; /** * Open (or create) a store at basePath/workflow-graph/graph.db. * Pass ":memory:" for in-memory testing. */ static open(basePath?: string): SqliteStore; /** * Open a store at an absolute DB file path. * Creates the file and parent dirs if they don't exist. * Useful for global mode where the DB is at ~/.mcp-graph/graph.db. */ static openDb(dbPath: string): SqliteStore; /** Expose the raw database instance for extension modules (e.g. DocsCacheStore). */ getDb(): Database.Database; /** * Run `fn` exclusively under the write mutex. * Use this to serialize multi-step write sequences that span async boundaries. * Single-step writes (insertNode, updateNode, etc.) already use SQLite transactions * and are safe without this wrapper; use it when you need to group multiple writes * as an atomic async unit at the application level. */ withWriteLock(fn: () => T | Promise): Promise; close(): void; initProject(name?: string): GraphProject; getProject(): GraphProject | null; /** Alias for getProject — returns the currently active project. */ getActiveProject(): GraphProject | null; /** List all projects in the database. */ listProjects(): GraphProject[]; /** Switch the active project. Throws if project ID does not exist. */ activateProject(projectId: string): void; /** * Find a project by its filesystem path. * Returns null if no project is registered at that path. */ findProjectByPath(fsPath: string): GraphProject | null; /** * Register a project with a filesystem path. * If a project already exists at that path, returns the existing one. * Creates and activates a new project otherwise. */ registerProject(name: string, fsPath: string): GraphProject; /** * Set or update the filesystem path for a project. */ setProjectFsPath(projectId: string, fsPath: string): void; private ensureProject; insertNode(node: GraphNode, options?: MutationOptions): void; getNodeById(id: string): GraphNode | null; getAllNodes(): GraphNode[]; /** Paginated + filtered node query for dashboard API. */ queryNodes(opts: { limit?: number; offset?: number; status?: NodeStatus[]; type?: NodeType[]; search?: string; }): { nodes: GraphNode[]; totalCount: number; }; getNodesByType(type: NodeType): GraphNode[]; getNodesByStatus(status: NodeStatus): GraphNode[]; getChildNodes(parentId: string): GraphNode[]; updateNodeStatus(id: string, status: NodeStatus, options?: MutationOptions): GraphNode | null; /** Walk up the parent chain from newParentId; return true if nodeId is found (cycle). */ private detectParentCycle; updateNode(id: string, fields: Partial>, options?: MutationOptions): GraphNode | null; getNodeHistory(nodeId: string): Array<{ field: string; oldValue: string | null; newValue: string | null; changedAt: string; agentId: string | null; }>; deleteNode(id: string): boolean; deleteEdge(id: string): boolean; insertEdge(edge: GraphEdge): void; getEdgesFrom(nodeId: string): GraphEdge[]; getEdgesTo(nodeId: string): GraphEdge[]; getAllEdges(): GraphEdge[]; /** * Check if a source file has been previously imported. */ hasImport(sourceFile: string): boolean; /** * Delete all nodes (and their edges) that were imported from a specific source file. * Also removes the import history entry so re-import is clean. */ clearImportedNodes(sourceFile: string): { nodesDeleted: number; edgesDeleted: number; }; bulkInsert(nodes: GraphNode[], edges: GraphEdge[]): void; /** * Merge-insert nodes and edges using INSERT OR IGNORE semantics for both. * Existing nodes (by ID) and edges (by unique constraint) are silently skipped. * Returns actual counts of rows inserted. */ mergeInsert(nodes: GraphNode[], edges: GraphEdge[]): { nodesInserted: number; edgesInserted: number; }; createSnapshot(): number; recordImport(sourceFile: string, nodesCreated: number, edgesCreated: number): void; getStats(): { totalNodes: number; totalEdges: number; byType: Record; byStatus: Record; }; getProjectSetting(key: string): string | null; setProjectSetting(key: string, value: string): void; /** * Search nodes using FTS5 with BM25 ranking. * Returns nodes ordered by relevance score. */ searchNodes(query: string, limit?: number): Array; bulkUpdateStatus(ids: string[], status: NodeStatus): { updated: string[]; notFound: string[]; }; restoreSnapshot(snapshotId: number): { nodesValid: number; nodesInvalid: number; edgesRestored: number; }; listSnapshots(): Array<{ snapshotId: number; createdAt: string; }>; toGraphDocument(): GraphDocument; } /*! * SPDX-License-Identifier: AGPL-3.0-or-later * Copyright © 2026 Diego Lima Nogueira de Paula * * This file is part of agent-graph-flow. * * agent-graph-flow is free software: you can redistribute it and/or modify it * under the terms of the GNU Affero General Public License v3.0 or later, as * published by the Free Software Foundation. See LICENSE for the full terms. */ interface OpenStoreOptions { /** * When true, fail before SqliteStore.open() touches the filesystem if * `/workflow-graph/graph.db` does not exist. Use for read-only CLI * commands (stats, list) so they do not silently materialize an empty * workflow-graph/ directory in the user's cwd. */ requireExisting?: boolean; } /** * Open a SqliteStore for a CLI command, presenting a friendly error and * exiting non-zero if the database file is corrupt or (when `requireExisting`) * absent. */ declare function openStoreOrFail(dir: string, opts?: OpenStoreOptions): SqliteStore; export { showBanner as banner, openStoreOrFail as openStore };