panopticon-cli 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +19 -19
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/{event-store-BtuZCLHu.js → event-store-D7kLBd07.js} +1 -1
- package/dist/dashboard/{event-store-OS5jH3Eu.js → event-store-O9q0Gweh.js} +2 -2
- package/dist/dashboard/{event-store-OS5jH3Eu.js.map → event-store-O9q0Gweh.js.map} +1 -1
- package/dist/dashboard/{inspect-agent-CwT4mrvV.js → inspect-agent-B57kGDUV.js} +3 -3
- package/dist/dashboard/{inspect-agent-CwT4mrvV.js.map → inspect-agent-B57kGDUV.js.map} +1 -1
- package/dist/dashboard/{issue-service-singleton-z78bbRiO.js → issue-service-singleton-DQK42EqH.js} +1 -1
- package/dist/dashboard/{issue-service-singleton-0n9hcF71.js → issue-service-singleton-sb2HkB9f.js} +2 -2
- package/dist/dashboard/{issue-service-singleton-0n9hcF71.js.map → issue-service-singleton-sb2HkB9f.js.map} +1 -1
- package/dist/dashboard/{lifecycle-B6d3AE3n.js → lifecycle-ZTYdrr2O.js} +1 -1
- package/dist/dashboard/{merge-agent-DaIEvGJG.js → merge-agent-GLtMEsTu.js} +1 -1
- package/dist/dashboard/{merge-agent-CmqR1MFf.js → merge-agent-twroFuAh.js} +2 -2
- package/dist/dashboard/{merge-agent-CmqR1MFf.js.map → merge-agent-twroFuAh.js.map} +1 -1
- package/dist/dashboard/{projection-cache-Bkzs_90o.js → projection-cache-DQ9zegkK.js} +10 -10
- package/dist/dashboard/projection-cache-DQ9zegkK.js.map +1 -0
- package/dist/dashboard/public/assets/{dist-D-q87oB4.js → dist-C2sRcZJv.js} +1 -1
- package/dist/dashboard/public/assets/{index--G6_upSx.js → index-BCLmEMRf.js} +41 -41
- package/dist/dashboard/public/assets/index-BEdq7CFf.css +1 -0
- package/dist/dashboard/public/index.html +2 -2
- package/dist/dashboard/{review-status-DqJZDthU.js → review-status-CK3eBGyb.js} +1 -1
- package/dist/dashboard/{review-status-LQATWF6L.js → review-status-CV55Tl-n.js} +2 -2
- package/dist/dashboard/{review-status-LQATWF6L.js.map → review-status-CV55Tl-n.js.map} +1 -1
- package/dist/dashboard/server.js +85 -85
- package/dist/dashboard/server.js.map +1 -1
- package/dist/dashboard/{specialist-context-IX8ZZBxy.js → specialist-context-ColzlmGE.js} +2 -2
- package/dist/dashboard/{specialist-context-IX8ZZBxy.js.map → specialist-context-ColzlmGE.js.map} +1 -1
- package/dist/dashboard/{specialist-logs-BvOQ3XPt.js → specialist-logs-BhmDpFIq.js} +1 -1
- package/dist/dashboard/{specialists-C7Fyhq_j.js → specialists-C6s3U6tX.js} +21 -7
- package/dist/dashboard/specialists-C6s3U6tX.js.map +1 -0
- package/dist/dashboard/{specialists-B4aDa5xP.js → specialists-Cny632-T.js} +1 -1
- package/dist/dashboard/{test-agent-queue-C0WrVdrJ.js → test-agent-queue-tqI4VDsu.js} +3 -3
- package/dist/dashboard/{test-agent-queue-C0WrVdrJ.js.map → test-agent-queue-tqI4VDsu.js.map} +1 -1
- package/dist/dashboard/workflows-B2ARUpOa.js +2 -0
- package/dist/dashboard/{workflows-Cj6tzch6.js → workflows-N1UTipYl.js} +3 -3
- package/dist/dashboard/{workflows-Cj6tzch6.js.map → workflows-N1UTipYl.js.map} +1 -1
- package/dist/{merge-agent-BCPyotWG.js → merge-agent-VQH9z9t8.js} +2 -2
- package/dist/{merge-agent-BCPyotWG.js.map → merge-agent-VQH9z9t8.js.map} +1 -1
- package/dist/{review-status-p_HOugvo.js → review-status-2TdtHNcs.js} +1 -1
- package/dist/{review-status-BbY22dtx.js → review-status-Bm1bWNEa.js} +2 -2
- package/dist/{review-status-BbY22dtx.js.map → review-status-Bm1bWNEa.js.map} +1 -1
- package/dist/{specialist-context-CRBBW-z5.js → specialist-context-BdNFsfMG.js} +2 -2
- package/dist/{specialist-context-CRBBW-z5.js.map → specialist-context-BdNFsfMG.js.map} +1 -1
- package/dist/{specialist-logs-m0UvPm3F.js → specialist-logs-CLztE_bE.js} +1 -1
- package/dist/{specialists-ldNesMhg.js → specialists-DEKqgkxp.js} +21 -7
- package/dist/specialists-DEKqgkxp.js.map +1 -0
- package/dist/{specialists-DXDDLqoY.js → specialists-aUoUVWsN.js} +1 -1
- package/package.json +1 -1
- package/scripts/record-cost-event.js +15 -0
- package/scripts/record-cost-event.js.map +1 -1
- package/scripts/record-cost-event.ts +2 -0
- package/scripts/work-agent-stop-hook +26 -0
- package/dist/dashboard/projection-cache-Bkzs_90o.js.map +0 -1
- package/dist/dashboard/public/assets/index-CjpnhB4Q.css +0 -1
- package/dist/dashboard/specialists-C7Fyhq_j.js.map +0 -1
- package/dist/dashboard/workflows-BsUDQntr.js +0 -2
- package/dist/specialists-ldNesMhg.js.map +0 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as openEventDb, i as initEventStore, n as getEventStore, r as getSharedDb, t as createEventStore } from "./event-store-
|
|
1
|
+
import { a as openEventDb, i as initEventStore, n as getEventStore, r as getSharedDb, t as createEventStore } from "./event-store-O9q0Gweh.js";
|
|
2
2
|
export { createEventStore, getEventStore, getSharedDb, initEventStore, openEventDb };
|
|
@@ -133,7 +133,7 @@ async function initEventStore() {
|
|
|
133
133
|
const store = createEventStore(db);
|
|
134
134
|
store.compact();
|
|
135
135
|
_store = store;
|
|
136
|
-
import("./projection-cache-
|
|
136
|
+
import("./projection-cache-DQ9zegkK.js").then(({ initProjectionCache }) => {
|
|
137
137
|
initProjectionCache(db);
|
|
138
138
|
}).catch(() => {});
|
|
139
139
|
return store;
|
|
@@ -159,4 +159,4 @@ function getEventStore() {
|
|
|
159
159
|
//#endregion
|
|
160
160
|
export { openEventDb as a, initEventStore as i, getEventStore as n, getSharedDb as r, createEventStore as t };
|
|
161
161
|
|
|
162
|
-
//# sourceMappingURL=event-store-
|
|
162
|
+
//# sourceMappingURL=event-store-O9q0Gweh.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-store-OS5jH3Eu.js","names":[],"sources":["../../src/dashboard/server/event-store.ts"],"sourcesContent":["/**\n * Event Store — SQLite-backed append-only event log with PubSub (PAN-428)\n *\n * - Persists domain events to panopticon.db `events` table\n * - In-memory PubSub for live streaming to WebSocket clients\n * - Monotonic, gap-free sequence numbers (SQLite AUTOINCREMENT)\n * - 7-day retention with startup compaction\n * - Dual-runtime: bun:sqlite on Bun, better-sqlite3 on Node\n *\n * Usage:\n * const store = await initEventStore();\n * const seq = store.append({ type: 'agent.started', ... });\n * const past = store.readFrom(0);\n * const unsub = store.subscribe(event => console.log(event));\n */\n\nimport { EventEmitter } from 'node:events';\nimport { existsSync, mkdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { getPanopticonHome } from '../../lib/paths.js';\nimport type { DomainEvent } from '@panopticon/contracts';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface StoredEvent {\n sequence: number;\n type: string;\n timestamp: string;\n payload: unknown;\n}\n\nexport type EventSubscriber = (event: StoredEvent) => void;\nexport type Unsubscribe = () => void;\n\nexport interface EventStore {\n /** Append a domain event. Returns the assigned sequence number. */\n append(event: Omit<DomainEvent, 'sequence'>): number;\n /** Return all events with sequence > fromSequence (exclusive lower bound). */\n readFrom(fromSequence: number): StoredEvent[];\n /** Subscribe to live events. Returns an unsubscribe function. */\n subscribe(fn: EventSubscriber): Unsubscribe;\n /** Run 7-day retention compaction. Called at startup. */\n compact(): void;\n /** Return the highest sequence number in the store (0 if empty). */\n getLatestSequence(): number;\n}\n\n// ─── Minimal DB interface (compatible with bun:sqlite and better-sqlite3) ────\n//\n// Uses positional parameters (?) in all SQL to avoid runtime differences in\n// named-parameter binding syntax:\n// - bun:sqlite requires sigil in binding keys: { $name: value }\n// - better-sqlite3 requires no sigil: { name: value }\n// Positional parameters + arrays work identically in both runtimes.\n\ninterface PreparedStatement<R = Record<string, unknown>> {\n run(params?: unknown[]): { changes: number };\n get(params?: unknown[]): R | undefined | null;\n all(params?: unknown[]): R[];\n}\n\nexport interface DbAdapter {\n prepare<R = Record<string, unknown>>(sql: string): PreparedStatement<R>;\n exec(sql: string): void;\n}\n\n// ─── Row shape from SQLite ─────────────────────────────────────────────────────\n\ninterface EventRow {\n sequence: number;\n type: string;\n timestamp: string;\n payload: string;\n}\n\nfunction rowToStored(row: EventRow): StoredEvent {\n return {\n sequence: row.sequence,\n type: row.type,\n timestamp: row.timestamp,\n payload: JSON.parse(row.payload),\n };\n}\n\n// ─── Runtime-aware DB initializer ────────────────────────────────────────────\n\ndeclare const Bun: unknown;\n\n/**\n * Open the panopticon.db database using the appropriate driver for the runtime.\n * Under Bun: uses bun:sqlite (native, no native addons needed).\n * Under Node: uses the shared getDatabase() which applies migrations.\n */\nexport async function openEventDb(): Promise<DbAdapter> {\n const home = getPanopticonHome();\n if (!existsSync(home)) {\n mkdirSync(home, { recursive: true });\n }\n const dbPath = join(home, 'panopticon.db');\n\n if (typeof Bun !== 'undefined') {\n const { Database } = await import('bun:sqlite');\n const db = new Database(dbPath, { create: true });\n db.exec('PRAGMA journal_mode = WAL');\n db.exec('PRAGMA foreign_keys = ON');\n db.exec('PRAGMA synchronous = NORMAL');\n // Ensure required tables exist (Bun doesn't run the shared schema migrations)\n db.exec(`\n CREATE TABLE IF NOT EXISTS events (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n type TEXT NOT NULL,\n timestamp TEXT NOT NULL,\n payload TEXT NOT NULL DEFAULT '{}'\n )\n `);\n db.exec(`CREATE INDEX IF NOT EXISTS events_timestamp_idx ON events (timestamp)`);\n db.exec(`\n CREATE TABLE IF NOT EXISTS projection_cache (\n key TEXT PRIMARY KEY,\n data TEXT NOT NULL,\n sequence INTEGER NOT NULL,\n updated_at TEXT NOT NULL\n )\n `);\n return db as unknown as DbAdapter;\n } else {\n // Node.js: use shared database connection — migrations run there\n const { getDatabase } = await import('../../lib/database/index.js');\n return getDatabase() as unknown as DbAdapter;\n }\n}\n\n// ─── Factory ──────────────────────────────────────────────────────────────────\n\n/**\n * Create an EventStore from a pre-opened DbAdapter.\n * Call openEventDb() first to get the adapter.\n */\nexport function createEventStore(db: DbAdapter): EventStore {\n const emitter = new EventEmitter();\n // Allow many subscribers (one per WebSocket connection)\n emitter.setMaxListeners(0);\n\n // Prepared statements for hot path performance.\n // All SQL uses positional parameters (?) — both bun:sqlite and better-sqlite3\n // accept arrays for positional bindings with no runtime-specific differences.\n const insertStmt = db.prepare<void>(\n `INSERT INTO events (type, timestamp, payload) VALUES (?, ?, ?)`,\n );\n const readFromStmt = db.prepare<EventRow>(\n `SELECT sequence, type, timestamp, payload FROM events WHERE sequence > ? ORDER BY sequence ASC`,\n );\n const compactStmt = db.prepare<void>(\n `DELETE FROM events WHERE timestamp < ?`,\n );\n const latestSeqStmt = db.prepare<{ seq: number | null }>(\n `SELECT MAX(sequence) AS seq FROM events`,\n );\n const lastRowIdStmt = db.prepare<{ sequence: number }>(\n `SELECT last_insert_rowid() AS sequence`,\n );\n\n function append(event: Omit<DomainEvent, 'sequence'>): number {\n const timestamp =\n (event as Record<string, unknown>)['timestamp'] as string ?? new Date().toISOString();\n const payload = JSON.stringify((event as Record<string, unknown>)['payload'] ?? {});\n\n insertStmt.run([event.type, timestamp, payload]);\n\n const row = lastRowIdStmt.get();\n const sequence = row?.sequence ?? 0;\n\n const stored: StoredEvent = {\n sequence,\n type: event.type,\n timestamp,\n payload: (event as Record<string, unknown>)['payload'] ?? {},\n };\n\n emitter.emit('event', stored);\n return sequence;\n }\n\n function readFrom(fromSequence: number): StoredEvent[] {\n const rows = readFromStmt.all([fromSequence]);\n return rows.map(rowToStored);\n }\n\n function subscribe(fn: EventSubscriber): Unsubscribe {\n emitter.on('event', fn);\n return () => emitter.off('event', fn);\n }\n\n function compact(): void {\n const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();\n const result = compactStmt.run([sevenDaysAgo]);\n if (result.changes > 0) {\n console.log(`[event-store] Compacted ${result.changes} events older than 7 days`);\n }\n }\n\n function getLatestSequence(): number {\n const row = latestSeqStmt.get();\n return row?.seq ?? 0;\n }\n\n return { append, readFrom, subscribe, compact, getLatestSequence };\n}\n\n// ─── Module-level singleton ───────────────────────────────────────────────────\n\nlet _store: EventStore | null = null;\nlet _db: DbAdapter | null = null;\nlet _initPromise: Promise<EventStore> | null = null;\n\n/**\n * Initialize and return the process-singleton EventStore (async, Bun-compatible).\n * Idempotent — returns the same store on subsequent calls.\n */\nexport async function initEventStore(): Promise<EventStore> {\n if (_store) return _store;\n if (_initPromise) return _initPromise;\n\n _initPromise = openEventDb().then((db) => {\n _db = db;\n const store = createEventStore(db);\n store.compact();\n _store = store;\n // Initialize projection cache with same DB connection\n import('./services/projection-cache.js').then(({ initProjectionCache }) => {\n initProjectionCache(db);\n }).catch(() => { /* module not available yet */ });\n return store;\n });\n\n return _initPromise;\n}\n\n/**\n * Return the shared DbAdapter after initEventStore() has resolved.\n * Used by services that need access to the same DB connection.\n */\nexport function getSharedDb(): DbAdapter {\n if (!_db) {\n throw new Error('[event-store] getSharedDb() called before initEventStore() resolved.');\n }\n return _db;\n}\n\n/**\n * Synchronous accessor — returns the store if already initialized, throws otherwise.\n * Used by legacy callers that expect a sync API (event-store unit tests, etc.)\n */\nexport function getEventStore(): EventStore {\n if (!_store) {\n throw new Error(\n '[event-store] getEventStore() called before initEventStore() resolved. ' +\n 'Use initEventStore() for async initialization.',\n );\n }\n return _store;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"event-store-O9q0Gweh.js","names":[],"sources":["../../src/dashboard/server/event-store.ts"],"sourcesContent":["/**\n * Event Store — SQLite-backed append-only event log with PubSub (PAN-428)\n *\n * - Persists domain events to panopticon.db `events` table\n * - In-memory PubSub for live streaming to WebSocket clients\n * - Monotonic, gap-free sequence numbers (SQLite AUTOINCREMENT)\n * - 7-day retention with startup compaction\n * - Dual-runtime: bun:sqlite on Bun, better-sqlite3 on Node\n *\n * Usage:\n * const store = await initEventStore();\n * const seq = store.append({ type: 'agent.started', ... });\n * const past = store.readFrom(0);\n * const unsub = store.subscribe(event => console.log(event));\n */\n\nimport { EventEmitter } from 'node:events';\nimport { existsSync, mkdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { getPanopticonHome } from '../../lib/paths.js';\nimport type { DomainEvent } from '@panopticon/contracts';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface StoredEvent {\n sequence: number;\n type: string;\n timestamp: string;\n payload: unknown;\n}\n\nexport type EventSubscriber = (event: StoredEvent) => void;\nexport type Unsubscribe = () => void;\n\nexport interface EventStore {\n /** Append a domain event. Returns the assigned sequence number. */\n append(event: Omit<DomainEvent, 'sequence'>): number;\n /** Return all events with sequence > fromSequence (exclusive lower bound). */\n readFrom(fromSequence: number): StoredEvent[];\n /** Subscribe to live events. Returns an unsubscribe function. */\n subscribe(fn: EventSubscriber): Unsubscribe;\n /** Run 7-day retention compaction. Called at startup. */\n compact(): void;\n /** Return the highest sequence number in the store (0 if empty). */\n getLatestSequence(): number;\n}\n\n// ─── Minimal DB interface (compatible with bun:sqlite and better-sqlite3) ────\n//\n// Uses positional parameters (?) in all SQL to avoid runtime differences in\n// named-parameter binding syntax:\n// - bun:sqlite requires sigil in binding keys: { $name: value }\n// - better-sqlite3 requires no sigil: { name: value }\n// Positional parameters + arrays work identically in both runtimes.\n\ninterface PreparedStatement<R = Record<string, unknown>> {\n run(params?: unknown[]): { changes: number };\n get(params?: unknown[]): R | undefined | null;\n all(params?: unknown[]): R[];\n}\n\nexport interface DbAdapter {\n prepare<R = Record<string, unknown>>(sql: string): PreparedStatement<R>;\n exec(sql: string): void;\n}\n\n// ─── Row shape from SQLite ─────────────────────────────────────────────────────\n\ninterface EventRow {\n sequence: number;\n type: string;\n timestamp: string;\n payload: string;\n}\n\nfunction rowToStored(row: EventRow): StoredEvent {\n return {\n sequence: row.sequence,\n type: row.type,\n timestamp: row.timestamp,\n payload: JSON.parse(row.payload),\n };\n}\n\n// ─── Runtime-aware DB initializer ────────────────────────────────────────────\n\ndeclare const Bun: unknown;\n\n/**\n * Open the panopticon.db database using the appropriate driver for the runtime.\n * Under Bun: uses bun:sqlite (native, no native addons needed).\n * Under Node: uses the shared getDatabase() which applies migrations.\n */\nexport async function openEventDb(): Promise<DbAdapter> {\n const home = getPanopticonHome();\n if (!existsSync(home)) {\n mkdirSync(home, { recursive: true });\n }\n const dbPath = join(home, 'panopticon.db');\n\n if (typeof Bun !== 'undefined') {\n const { Database } = await import('bun:sqlite');\n const db = new Database(dbPath, { create: true });\n db.exec('PRAGMA journal_mode = WAL');\n db.exec('PRAGMA foreign_keys = ON');\n db.exec('PRAGMA synchronous = NORMAL');\n // Ensure required tables exist (Bun doesn't run the shared schema migrations)\n db.exec(`\n CREATE TABLE IF NOT EXISTS events (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n type TEXT NOT NULL,\n timestamp TEXT NOT NULL,\n payload TEXT NOT NULL DEFAULT '{}'\n )\n `);\n db.exec(`CREATE INDEX IF NOT EXISTS events_timestamp_idx ON events (timestamp)`);\n db.exec(`\n CREATE TABLE IF NOT EXISTS projection_cache (\n key TEXT PRIMARY KEY,\n data TEXT NOT NULL,\n sequence INTEGER NOT NULL,\n updated_at TEXT NOT NULL\n )\n `);\n return db as unknown as DbAdapter;\n } else {\n // Node.js: use shared database connection — migrations run there\n const { getDatabase } = await import('../../lib/database/index.js');\n return getDatabase() as unknown as DbAdapter;\n }\n}\n\n// ─── Factory ──────────────────────────────────────────────────────────────────\n\n/**\n * Create an EventStore from a pre-opened DbAdapter.\n * Call openEventDb() first to get the adapter.\n */\nexport function createEventStore(db: DbAdapter): EventStore {\n const emitter = new EventEmitter();\n // Allow many subscribers (one per WebSocket connection)\n emitter.setMaxListeners(0);\n\n // Prepared statements for hot path performance.\n // All SQL uses positional parameters (?) — both bun:sqlite and better-sqlite3\n // accept arrays for positional bindings with no runtime-specific differences.\n const insertStmt = db.prepare<void>(\n `INSERT INTO events (type, timestamp, payload) VALUES (?, ?, ?)`,\n );\n const readFromStmt = db.prepare<EventRow>(\n `SELECT sequence, type, timestamp, payload FROM events WHERE sequence > ? ORDER BY sequence ASC`,\n );\n const compactStmt = db.prepare<void>(\n `DELETE FROM events WHERE timestamp < ?`,\n );\n const latestSeqStmt = db.prepare<{ seq: number | null }>(\n `SELECT MAX(sequence) AS seq FROM events`,\n );\n const lastRowIdStmt = db.prepare<{ sequence: number }>(\n `SELECT last_insert_rowid() AS sequence`,\n );\n\n function append(event: Omit<DomainEvent, 'sequence'>): number {\n const timestamp =\n (event as Record<string, unknown>)['timestamp'] as string ?? new Date().toISOString();\n const payload = JSON.stringify((event as Record<string, unknown>)['payload'] ?? {});\n\n insertStmt.run([event.type, timestamp, payload]);\n\n const row = lastRowIdStmt.get();\n const sequence = row?.sequence ?? 0;\n\n const stored: StoredEvent = {\n sequence,\n type: event.type,\n timestamp,\n payload: (event as Record<string, unknown>)['payload'] ?? {},\n };\n\n emitter.emit('event', stored);\n return sequence;\n }\n\n function readFrom(fromSequence: number): StoredEvent[] {\n const rows = readFromStmt.all([fromSequence]);\n return rows.map(rowToStored);\n }\n\n function subscribe(fn: EventSubscriber): Unsubscribe {\n emitter.on('event', fn);\n return () => emitter.off('event', fn);\n }\n\n function compact(): void {\n const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();\n const result = compactStmt.run([sevenDaysAgo]);\n if (result.changes > 0) {\n console.log(`[event-store] Compacted ${result.changes} events older than 7 days`);\n }\n }\n\n function getLatestSequence(): number {\n const row = latestSeqStmt.get();\n return row?.seq ?? 0;\n }\n\n return { append, readFrom, subscribe, compact, getLatestSequence };\n}\n\n// ─── Module-level singleton ───────────────────────────────────────────────────\n\nlet _store: EventStore | null = null;\nlet _db: DbAdapter | null = null;\nlet _initPromise: Promise<EventStore> | null = null;\n\n/**\n * Initialize and return the process-singleton EventStore (async, Bun-compatible).\n * Idempotent — returns the same store on subsequent calls.\n */\nexport async function initEventStore(): Promise<EventStore> {\n if (_store) return _store;\n if (_initPromise) return _initPromise;\n\n _initPromise = openEventDb().then((db) => {\n _db = db;\n const store = createEventStore(db);\n store.compact();\n _store = store;\n // Initialize projection cache with same DB connection\n import('./services/projection-cache.js').then(({ initProjectionCache }) => {\n initProjectionCache(db);\n }).catch(() => { /* module not available yet */ });\n return store;\n });\n\n return _initPromise;\n}\n\n/**\n * Return the shared DbAdapter after initEventStore() has resolved.\n * Used by services that need access to the same DB connection.\n */\nexport function getSharedDb(): DbAdapter {\n if (!_db) {\n throw new Error('[event-store] getSharedDb() called before initEventStore() resolved.');\n }\n return _db;\n}\n\n/**\n * Synchronous accessor — returns the store if already initialized, throws otherwise.\n * Used by legacy callers that expect a sync API (event-store unit tests, etc.)\n */\nexport function getEventStore(): EventStore {\n if (!_store) {\n throw new Error(\n '[event-store] getEventStore() called before initEventStore() resolved. ' +\n 'Use initEventStore() for async initialization.',\n );\n }\n return _store;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;YAmBuD;AAwDvD,SAAS,YAAY,KAA4B;AAC/C,QAAO;EACL,UAAU,IAAI;EACd,MAAM,IAAI;EACV,WAAW,IAAI;EACf,SAAS,KAAK,MAAM,IAAI,QAAQ;EACjC;;;;;;;AAYH,eAAsB,cAAkC;CACtD,MAAM,OAAO,mBAAmB;AAChC,KAAI,CAAC,WAAW,KAAK,CACnB,WAAU,MAAM,EAAE,WAAW,MAAM,CAAC;CAEtC,MAAM,SAAS,KAAK,MAAM,gBAAgB;AAE1C,KAAI,OAAO,QAAQ,aAAa;EAC9B,MAAM,EAAE,aAAa,MAAM,OAAO;EAClC,MAAM,KAAK,IAAI,SAAS,QAAQ,EAAE,QAAQ,MAAM,CAAC;AACjD,KAAG,KAAK,4BAA4B;AACpC,KAAG,KAAK,2BAA2B;AACnC,KAAG,KAAK,8BAA8B;AAEtC,KAAG,KAAK;;;;;;;MAON;AACF,KAAG,KAAK,wEAAwE;AAChF,KAAG,KAAK;;;;;;;MAON;AACF,SAAO;QACF;EAEL,MAAM,EAAE,gBAAgB,MAAM,OAAO;AACrC,SAAO,aAAa;;;;;;;AAUxB,SAAgB,iBAAiB,IAA2B;CAC1D,MAAM,UAAU,IAAI,cAAc;AAElC,SAAQ,gBAAgB,EAAE;CAK1B,MAAM,aAAa,GAAG,QACpB,iEACD;CACD,MAAM,eAAe,GAAG,QACtB,iGACD;CACD,MAAM,cAAc,GAAG,QACrB,yCACD;CACD,MAAM,gBAAgB,GAAG,QACvB,0CACD;CACD,MAAM,gBAAgB,GAAG,QACvB,yCACD;CAED,SAAS,OAAO,OAA8C;EAC5D,MAAM,YACH,MAAkC,iCAA0B,IAAI,MAAM,EAAC,aAAa;EACvF,MAAM,UAAU,KAAK,UAAW,MAAkC,cAAc,EAAE,CAAC;AAEnF,aAAW,IAAI;GAAC,MAAM;GAAM;GAAW;GAAQ,CAAC;EAGhD,MAAM,WADM,cAAc,KAAK,EACT,YAAY;EAElC,MAAM,SAAsB;GAC1B;GACA,MAAM,MAAM;GACZ;GACA,SAAU,MAAkC,cAAc,EAAE;GAC7D;AAED,UAAQ,KAAK,SAAS,OAAO;AAC7B,SAAO;;CAGT,SAAS,SAAS,cAAqC;AAErD,SADa,aAAa,IAAI,CAAC,aAAa,CAAC,CACjC,IAAI,YAAY;;CAG9B,SAAS,UAAU,IAAkC;AACnD,UAAQ,GAAG,SAAS,GAAG;AACvB,eAAa,QAAQ,IAAI,SAAS,GAAG;;CAGvC,SAAS,UAAgB;EACvB,MAAM,gCAAe,IAAI,KAAK,KAAK,KAAK,GAAG,QAAc,KAAK,IAAK,EAAC,aAAa;EACjF,MAAM,SAAS,YAAY,IAAI,CAAC,aAAa,CAAC;AAC9C,MAAI,OAAO,UAAU,EACnB,SAAQ,IAAI,2BAA2B,OAAO,QAAQ,2BAA2B;;CAIrF,SAAS,oBAA4B;AAEnC,SADY,cAAc,KAAK,EACnB,OAAO;;AAGrB,QAAO;EAAE;EAAQ;EAAU;EAAW;EAAS;EAAmB;;AAKpE,IAAI,SAA4B;AAChC,IAAI,MAAwB;AAC5B,IAAI,eAA2C;;;;;AAM/C,eAAsB,iBAAsC;AAC1D,KAAI,OAAQ,QAAO;AACnB,KAAI,aAAc,QAAO;AAEzB,gBAAe,aAAa,CAAC,MAAM,OAAO;AACxC,QAAM;EACN,MAAM,QAAQ,iBAAiB,GAAG;AAClC,QAAM,SAAS;AACf,WAAS;AAET,SAAO,kCAAkC,MAAM,EAAE,0BAA0B;AACzE,uBAAoB,GAAG;IACvB,CAAC,YAAY,GAAmC;AAClD,SAAO;GACP;AAEF,QAAO;;;;;;AAOT,SAAgB,cAAyB;AACvC,KAAI,CAAC,IACH,OAAM,IAAI,MAAM,uEAAuE;AAEzF,QAAO;;;;;;AAOT,SAAgB,gBAA4B;AAC1C,KAAI,CAAC,OACH,OAAM,IAAI,MACR,wHAED;AAEH,QAAO"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { i as init_review_status } from "./review-status-
|
|
2
|
-
import { j as init_specialists } from "./specialists-
|
|
1
|
+
import { i as init_review_status } from "./review-status-CV55Tl-n.js";
|
|
2
|
+
import { j as init_specialists } from "./specialists-C6s3U6tX.js";
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
4
4
|
import { dirname, join } from "path";
|
|
5
5
|
import { homedir } from "os";
|
|
@@ -95,4 +95,4 @@ async function onInspectComplete(projectKey, issueId, beadId, status, workspaceP
|
|
|
95
95
|
//#endregion
|
|
96
96
|
export { onInspectComplete };
|
|
97
97
|
|
|
98
|
-
//# sourceMappingURL=inspect-agent-
|
|
98
|
+
//# sourceMappingURL=inspect-agent-B57kGDUV.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inspect-agent-CwT4mrvV.js","names":["execAsync"],"sources":["../../src/lib/cloister/inspect-checkpoints.ts","../../src/lib/cloister/inspect-agent.ts"],"sourcesContent":["/**\n * PAN-382: Inspection checkpoint system.\n *\n * Tracks commit SHAs where inspections passed, scoping subsequent\n * inspection diffs to only the changes since the last checkpoint.\n *\n * First inspection: diff from branch base (main...HEAD)\n * Subsequent: diff from last checkpoint SHA to HEAD\n */\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\n\nconst execAsync = promisify(exec);\n\nconst PANOPTICON_HOME = join(homedir(), '.panopticon');\n\nexport interface InspectCheckpoint {\n beadId: string;\n commitSha: string;\n passedAt: string; // ISO 8601\n}\n\nexport interface InspectCheckpointFile {\n issueId: string;\n checkpoints: InspectCheckpoint[];\n}\n\n/**\n * Get the directory for a project's inspect checkpoints.\n */\nfunction getCheckpointDir(projectKey: string): string {\n return join(PANOPTICON_HOME, 'specialists', projectKey, 'inspect-agent', 'checkpoints');\n}\n\n/**\n * Get the checkpoint file path for an issue.\n */\nfunction getCheckpointPath(projectKey: string, issueId: string): string {\n return join(getCheckpointDir(projectKey), `${issueId.toUpperCase()}.json`);\n}\n\n/**\n * Load checkpoints for an issue. Returns null if no checkpoints exist.\n */\nexport function loadCheckpoints(projectKey: string, issueId: string): InspectCheckpointFile | null {\n const filePath = getCheckpointPath(projectKey, issueId);\n if (!existsSync(filePath)) return null;\n\n try {\n return JSON.parse(readFileSync(filePath, 'utf-8'));\n } catch {\n return null;\n }\n}\n\n/**\n * Get the last checkpoint for an issue, or null if none exist.\n */\nexport function getLastCheckpoint(projectKey: string, issueId: string): InspectCheckpoint | null {\n const data = loadCheckpoints(projectKey, issueId);\n if (!data || data.checkpoints.length === 0) return null;\n return data.checkpoints[data.checkpoints.length - 1];\n}\n\n/**\n * Save a new checkpoint after a successful inspection.\n */\nexport function saveCheckpoint(\n projectKey: string,\n issueId: string,\n beadId: string,\n commitSha: string\n): InspectCheckpoint {\n const dir = getCheckpointDir(projectKey);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n const data = loadCheckpoints(projectKey, issueId) || {\n issueId: issueId.toUpperCase(),\n checkpoints: [],\n };\n\n const checkpoint: InspectCheckpoint = {\n beadId,\n commitSha,\n passedAt: new Date().toISOString(),\n };\n\n data.checkpoints.push(checkpoint);\n writeFileSync(getCheckpointPath(projectKey, issueId), JSON.stringify(data, null, 2));\n\n return checkpoint;\n}\n\n/**\n * Get the diff base for an inspection.\n *\n * - If a previous checkpoint exists, diff from that commit\n * - Otherwise, diff from the merge-base with main (full branch diff)\n *\n * Returns the commit SHA or ref to diff from.\n */\nexport async function getDiffBase(projectKey: string, issueId: string, workspacePath: string): Promise<string> {\n const lastCheckpoint = getLastCheckpoint(projectKey, issueId);\n\n if (lastCheckpoint) {\n return lastCheckpoint.commitSha;\n }\n\n // No checkpoint — use the merge-base with main\n try {\n const { stdout } = await execAsync('git merge-base main HEAD', {\n cwd: workspacePath,\n encoding: 'utf-8',\n });\n return stdout.trim();\n } catch {\n // Fallback to 'main' if merge-base fails\n return 'main';\n }\n}\n\n/**\n * Get the diff stats (files changed, insertions, deletions) for the inspection scope.\n */\nexport async function getDiffStats(workspacePath: string, diffBase: string): Promise<string> {\n try {\n const { stdout } = await execAsync(`git diff --stat ${diffBase}...HEAD`, {\n cwd: workspacePath,\n encoding: 'utf-8',\n });\n return stdout.trim() || 'No changes detected';\n } catch {\n return 'Unable to compute diff stats';\n }\n}\n\n/**\n * Get the current HEAD commit SHA.\n */\nexport async function getCurrentHead(workspacePath: string): Promise<string> {\n try {\n const { stdout } = await execAsync('git rev-parse HEAD', {\n cwd: workspacePath,\n encoding: 'utf-8',\n });\n return stdout.trim();\n } catch {\n return 'unknown';\n }\n}\n","/**\n * PAN-382: Inspect Agent — Per-step verification specialist.\n *\n * Spawns after each bead completion to verify the implementation matches\n * its specification and architectural constraints before the agent\n * proceeds to the next bead.\n */\n\nimport { readFileSync, existsSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport {\n getDiffBase,\n getDiffStats,\n getCurrentHead,\n saveCheckpoint,\n} from './inspect-checkpoints.js';\nimport { spawnEphemeralSpecialist, type SpecialistType } from './specialists.js';\nimport { setReviewStatus } from '../review-status.js';\n\nconst execAsync = promisify(exec);\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n/**\n * Context for an inspection request\n */\nexport interface InspectContext {\n projectKey: string;\n projectPath: string;\n issueId: string;\n beadId: string;\n workspace: string;\n branch?: string;\n}\n\n/**\n * Result of inspection\n */\nexport interface InspectResult {\n success: boolean;\n inspectResult: 'PASS' | 'BLOCKED';\n beadId: string;\n notes?: string;\n}\n\n/**\n * Read a bead's description using the bd CLI.\n */\nasync function getBeadDescription(beadId: string, workspacePath: string): Promise<string> {\n try {\n const { stdout } = await execAsync(`bd show ${beadId} --json`, {\n cwd: workspacePath,\n encoding: 'utf-8',\n });\n const bead = JSON.parse(stdout);\n const parts: string[] = [];\n if (bead.title) parts.push(`**Title:** ${bead.title}`);\n if (bead.description) parts.push(`**Description:** ${bead.description}`);\n if (bead.acceptance) parts.push(`**Acceptance Criteria:** ${bead.acceptance}`);\n if (bead.notes) parts.push(`**Notes:** ${bead.notes}`);\n if (bead.labels?.length) parts.push(`**Labels:** ${bead.labels.join(', ')}`);\n return parts.join('\\n\\n') || `Bead ${beadId} (no description available)`;\n } catch {\n // Fallback: try without --json\n try {\n const { stdout } = await execAsync(`bd show ${beadId}`, {\n cwd: workspacePath,\n encoding: 'utf-8',\n });\n return stdout.trim() || `Bead ${beadId} (no description available)`;\n } catch {\n return `Bead ${beadId} (unable to read bead description)`;\n }\n }\n}\n\n/**\n * Detect the compile/lint command for the workspace.\n */\nfunction detectCompileCommand(workspacePath: string): string {\n // Check for common project types\n const checks: Array<{ file: string; command: string }> = [\n { file: 'tsconfig.json', command: 'npx tsc --noEmit && npx eslint . --max-warnings=0 2>/dev/null || npx eslint .' },\n { file: 'package.json', command: 'npm run build 2>&1 | tail -20' },\n { file: 'pom.xml', command: './mvnw compile -q' },\n { file: 'Cargo.toml', command: 'cargo check' },\n { file: 'go.mod', command: 'go build ./...' },\n ];\n\n for (const check of checks) {\n // Check workspace root and common subdirectories\n for (const subdir of ['', 'fe', 'api', 'frontend', 'backend']) {\n const checkPath = subdir ? join(workspacePath, subdir, check.file) : join(workspacePath, check.file);\n if (existsSync(checkPath)) {\n const cwd = subdir ? `cd ${subdir} && ` : '';\n return `${cwd}${check.command}`;\n }\n }\n }\n\n return 'echo \"No compile command detected — skipping compile check\"';\n}\n\n/**\n * Build the prompt for the inspect specialist.\n */\nexport async function buildInspectPrompt(context: InspectContext): Promise<string> {\n const templatePath = join(__dirname, 'prompts', 'inspect-agent.md');\n\n if (!existsSync(templatePath)) {\n throw new Error(`Inspect agent prompt template not found at ${templatePath}`);\n }\n\n const template = readFileSync(templatePath, 'utf-8');\n\n // Get bead description\n const beadDescription = await getBeadDescription(context.beadId, context.workspace);\n\n // Get diff scope\n const diffBase = await getDiffBase(context.projectKey, context.issueId, context.workspace);\n const diffStats = await getDiffStats(context.workspace, diffBase);\n const compileCommand = detectCompileCommand(context.workspace);\n\n const apiUrl = process.env.DASHBOARD_URL || `http://localhost:${process.env.API_PORT || process.env.PORT || '3011'}`;\n\n // Replace template variables\n const prompt = template\n .replace(/\\{\\{apiUrl\\}\\}/g, apiUrl)\n .replace(/\\{\\{projectPath\\}\\}/g, context.projectPath)\n .replace(/\\{\\{issueId\\}\\}/g, context.issueId)\n .replace(/\\{\\{beadId\\}\\}/g, context.beadId)\n .replace(/\\{\\{workspacePath\\}\\}/g, context.workspace)\n .replace(/\\{\\{checkpoint\\}\\}/g, diffBase.substring(0, 8))\n .replace(/\\{\\{diffBase\\}\\}/g, diffBase)\n .replace(/\\{\\{diffStats\\}\\}/g, diffStats)\n .replace(/\\{\\{beadDescription\\}\\}/g, beadDescription)\n .replace(/\\{\\{compileCommand\\}\\}/g, compileCommand)\n .replace(/\\{\\{resultStatus\\}\\}/g, '${RESULT_STATUS}') // Placeholder for specialist to fill\n .replace(/\\{\\{resultNotes\\}\\}/g, '${RESULT_NOTES}'); // Placeholder for specialist to fill\n\n return `<!-- panopticon:orchestration-context-start -->\\n${prompt}\\n<!-- panopticon:orchestration-context-end -->`;\n}\n\n/**\n * Spawn the inspect specialist for a bead.\n */\nexport async function spawnInspectAgent(context: InspectContext): Promise<{\n success: boolean;\n runId?: string;\n tmuxSession?: string;\n message: string;\n error?: string;\n}> {\n // Build the prompt\n const prompt = await buildInspectPrompt(context);\n\n // Update status to inspecting\n setReviewStatus(context.issueId.toUpperCase(), {\n inspectStatus: 'inspecting',\n inspectNotes: `Inspecting bead ${context.beadId}`,\n });\n\n // Spawn the ephemeral specialist\n return spawnEphemeralSpecialist(context.projectKey, 'inspect-agent' as SpecialistType, {\n issueId: context.issueId,\n branch: context.branch,\n workspace: context.workspace,\n promptOverride: prompt,\n });\n}\n\n/**\n * Handle inspect completion — called when the inspect specialist signals done.\n * Saves checkpoint on PASS.\n */\nexport async function onInspectComplete(\n projectKey: string,\n issueId: string,\n beadId: string,\n status: 'passed' | 'failed',\n workspacePath: string\n): Promise<void> {\n if (status === 'passed') {\n const commitSha = await getCurrentHead(workspacePath);\n saveCheckpoint(projectKey, issueId, beadId, commitSha);\n console.log(`[inspect] Checkpoint saved for ${issueId} bead ${beadId} at ${commitSha.substring(0, 8)}`);\n\n } else {\n console.log(`[inspect] Bead ${beadId} blocked for ${issueId} — no checkpoint saved`);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAeA,MAAMA,cAAY,UAAU,KAAK;AAEjC,MAAM,kBAAkB,KAAK,SAAS,EAAE,cAAc;;;;AAgBtD,SAAS,iBAAiB,YAA4B;AACpD,QAAO,KAAK,iBAAiB,eAAe,YAAY,iBAAiB,cAAc;;;;;AAMzF,SAAS,kBAAkB,YAAoB,SAAyB;AACtE,QAAO,KAAK,iBAAiB,WAAW,EAAE,GAAG,QAAQ,aAAa,CAAC,OAAO;;;;;AAM5E,SAAgB,gBAAgB,YAAoB,SAA+C;CACjG,MAAM,WAAW,kBAAkB,YAAY,QAAQ;AACvD,KAAI,CAAC,WAAW,SAAS,CAAE,QAAO;AAElC,KAAI;AACF,SAAO,KAAK,MAAM,aAAa,UAAU,QAAQ,CAAC;SAC5C;AACN,SAAO;;;;;;AAgBX,SAAgB,eACd,YACA,SACA,QACA,WACmB;CACnB,MAAM,MAAM,iBAAiB,WAAW;AACxC,KAAI,CAAC,WAAW,IAAI,CAClB,WAAU,KAAK,EAAE,WAAW,MAAM,CAAC;CAGrC,MAAM,OAAO,gBAAgB,YAAY,QAAQ,IAAI;EACnD,SAAS,QAAQ,aAAa;EAC9B,aAAa,EAAE;EAChB;CAED,MAAM,aAAgC;EACpC;EACA;EACA,2BAAU,IAAI,MAAM,EAAC,aAAa;EACnC;AAED,MAAK,YAAY,KAAK,WAAW;AACjC,eAAc,kBAAkB,YAAY,QAAQ,EAAE,KAAK,UAAU,MAAM,MAAM,EAAE,CAAC;AAEpF,QAAO;;;;;AAiDT,eAAsB,eAAe,eAAwC;AAC3E,KAAI;EACF,MAAM,EAAE,WAAW,MAAMA,YAAU,sBAAsB;GACvD,KAAK;GACL,UAAU;GACX,CAAC;AACF,SAAO,OAAO,MAAM;SACd;AACN,SAAO;;;;;kBCrIsE;oBAC3B;AAEpC,UAAU,KAAK;AAGf,QADC,cAAc,OAAO,KAAK,IAAI,CACZ;;;;;AA0JrC,eAAsB,kBACpB,YACA,SACA,QACA,QACA,eACe;AACf,KAAI,WAAW,UAAU;EACvB,MAAM,YAAY,MAAM,eAAe,cAAc;AACrD,iBAAe,YAAY,SAAS,QAAQ,UAAU;AACtD,UAAQ,IAAI,kCAAkC,QAAQ,QAAQ,OAAO,MAAM,UAAU,UAAU,GAAG,EAAE,GAAG;OAGvG,SAAQ,IAAI,kBAAkB,OAAO,eAAe,QAAQ,wBAAwB"}
|
|
1
|
+
{"version":3,"file":"inspect-agent-B57kGDUV.js","names":["execAsync"],"sources":["../../src/lib/cloister/inspect-checkpoints.ts","../../src/lib/cloister/inspect-agent.ts"],"sourcesContent":["/**\n * PAN-382: Inspection checkpoint system.\n *\n * Tracks commit SHAs where inspections passed, scoping subsequent\n * inspection diffs to only the changes since the last checkpoint.\n *\n * First inspection: diff from branch base (main...HEAD)\n * Subsequent: diff from last checkpoint SHA to HEAD\n */\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\n\nconst execAsync = promisify(exec);\n\nconst PANOPTICON_HOME = join(homedir(), '.panopticon');\n\nexport interface InspectCheckpoint {\n beadId: string;\n commitSha: string;\n passedAt: string; // ISO 8601\n}\n\nexport interface InspectCheckpointFile {\n issueId: string;\n checkpoints: InspectCheckpoint[];\n}\n\n/**\n * Get the directory for a project's inspect checkpoints.\n */\nfunction getCheckpointDir(projectKey: string): string {\n return join(PANOPTICON_HOME, 'specialists', projectKey, 'inspect-agent', 'checkpoints');\n}\n\n/**\n * Get the checkpoint file path for an issue.\n */\nfunction getCheckpointPath(projectKey: string, issueId: string): string {\n return join(getCheckpointDir(projectKey), `${issueId.toUpperCase()}.json`);\n}\n\n/**\n * Load checkpoints for an issue. Returns null if no checkpoints exist.\n */\nexport function loadCheckpoints(projectKey: string, issueId: string): InspectCheckpointFile | null {\n const filePath = getCheckpointPath(projectKey, issueId);\n if (!existsSync(filePath)) return null;\n\n try {\n return JSON.parse(readFileSync(filePath, 'utf-8'));\n } catch {\n return null;\n }\n}\n\n/**\n * Get the last checkpoint for an issue, or null if none exist.\n */\nexport function getLastCheckpoint(projectKey: string, issueId: string): InspectCheckpoint | null {\n const data = loadCheckpoints(projectKey, issueId);\n if (!data || data.checkpoints.length === 0) return null;\n return data.checkpoints[data.checkpoints.length - 1];\n}\n\n/**\n * Save a new checkpoint after a successful inspection.\n */\nexport function saveCheckpoint(\n projectKey: string,\n issueId: string,\n beadId: string,\n commitSha: string\n): InspectCheckpoint {\n const dir = getCheckpointDir(projectKey);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n const data = loadCheckpoints(projectKey, issueId) || {\n issueId: issueId.toUpperCase(),\n checkpoints: [],\n };\n\n const checkpoint: InspectCheckpoint = {\n beadId,\n commitSha,\n passedAt: new Date().toISOString(),\n };\n\n data.checkpoints.push(checkpoint);\n writeFileSync(getCheckpointPath(projectKey, issueId), JSON.stringify(data, null, 2));\n\n return checkpoint;\n}\n\n/**\n * Get the diff base for an inspection.\n *\n * - If a previous checkpoint exists, diff from that commit\n * - Otherwise, diff from the merge-base with main (full branch diff)\n *\n * Returns the commit SHA or ref to diff from.\n */\nexport async function getDiffBase(projectKey: string, issueId: string, workspacePath: string): Promise<string> {\n const lastCheckpoint = getLastCheckpoint(projectKey, issueId);\n\n if (lastCheckpoint) {\n return lastCheckpoint.commitSha;\n }\n\n // No checkpoint — use the merge-base with main\n try {\n const { stdout } = await execAsync('git merge-base main HEAD', {\n cwd: workspacePath,\n encoding: 'utf-8',\n });\n return stdout.trim();\n } catch {\n // Fallback to 'main' if merge-base fails\n return 'main';\n }\n}\n\n/**\n * Get the diff stats (files changed, insertions, deletions) for the inspection scope.\n */\nexport async function getDiffStats(workspacePath: string, diffBase: string): Promise<string> {\n try {\n const { stdout } = await execAsync(`git diff --stat ${diffBase}...HEAD`, {\n cwd: workspacePath,\n encoding: 'utf-8',\n });\n return stdout.trim() || 'No changes detected';\n } catch {\n return 'Unable to compute diff stats';\n }\n}\n\n/**\n * Get the current HEAD commit SHA.\n */\nexport async function getCurrentHead(workspacePath: string): Promise<string> {\n try {\n const { stdout } = await execAsync('git rev-parse HEAD', {\n cwd: workspacePath,\n encoding: 'utf-8',\n });\n return stdout.trim();\n } catch {\n return 'unknown';\n }\n}\n","/**\n * PAN-382: Inspect Agent — Per-step verification specialist.\n *\n * Spawns after each bead completion to verify the implementation matches\n * its specification and architectural constraints before the agent\n * proceeds to the next bead.\n */\n\nimport { readFileSync, existsSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport {\n getDiffBase,\n getDiffStats,\n getCurrentHead,\n saveCheckpoint,\n} from './inspect-checkpoints.js';\nimport { spawnEphemeralSpecialist, type SpecialistType } from './specialists.js';\nimport { setReviewStatus } from '../review-status.js';\n\nconst execAsync = promisify(exec);\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n/**\n * Context for an inspection request\n */\nexport interface InspectContext {\n projectKey: string;\n projectPath: string;\n issueId: string;\n beadId: string;\n workspace: string;\n branch?: string;\n}\n\n/**\n * Result of inspection\n */\nexport interface InspectResult {\n success: boolean;\n inspectResult: 'PASS' | 'BLOCKED';\n beadId: string;\n notes?: string;\n}\n\n/**\n * Read a bead's description using the bd CLI.\n */\nasync function getBeadDescription(beadId: string, workspacePath: string): Promise<string> {\n try {\n const { stdout } = await execAsync(`bd show ${beadId} --json`, {\n cwd: workspacePath,\n encoding: 'utf-8',\n });\n const bead = JSON.parse(stdout);\n const parts: string[] = [];\n if (bead.title) parts.push(`**Title:** ${bead.title}`);\n if (bead.description) parts.push(`**Description:** ${bead.description}`);\n if (bead.acceptance) parts.push(`**Acceptance Criteria:** ${bead.acceptance}`);\n if (bead.notes) parts.push(`**Notes:** ${bead.notes}`);\n if (bead.labels?.length) parts.push(`**Labels:** ${bead.labels.join(', ')}`);\n return parts.join('\\n\\n') || `Bead ${beadId} (no description available)`;\n } catch {\n // Fallback: try without --json\n try {\n const { stdout } = await execAsync(`bd show ${beadId}`, {\n cwd: workspacePath,\n encoding: 'utf-8',\n });\n return stdout.trim() || `Bead ${beadId} (no description available)`;\n } catch {\n return `Bead ${beadId} (unable to read bead description)`;\n }\n }\n}\n\n/**\n * Detect the compile/lint command for the workspace.\n */\nfunction detectCompileCommand(workspacePath: string): string {\n // Check for common project types\n const checks: Array<{ file: string; command: string }> = [\n { file: 'tsconfig.json', command: 'npx tsc --noEmit && npx eslint . --max-warnings=0 2>/dev/null || npx eslint .' },\n { file: 'package.json', command: 'npm run build 2>&1 | tail -20' },\n { file: 'pom.xml', command: './mvnw compile -q' },\n { file: 'Cargo.toml', command: 'cargo check' },\n { file: 'go.mod', command: 'go build ./...' },\n ];\n\n for (const check of checks) {\n // Check workspace root and common subdirectories\n for (const subdir of ['', 'fe', 'api', 'frontend', 'backend']) {\n const checkPath = subdir ? join(workspacePath, subdir, check.file) : join(workspacePath, check.file);\n if (existsSync(checkPath)) {\n const cwd = subdir ? `cd ${subdir} && ` : '';\n return `${cwd}${check.command}`;\n }\n }\n }\n\n return 'echo \"No compile command detected — skipping compile check\"';\n}\n\n/**\n * Build the prompt for the inspect specialist.\n */\nexport async function buildInspectPrompt(context: InspectContext): Promise<string> {\n const templatePath = join(__dirname, 'prompts', 'inspect-agent.md');\n\n if (!existsSync(templatePath)) {\n throw new Error(`Inspect agent prompt template not found at ${templatePath}`);\n }\n\n const template = readFileSync(templatePath, 'utf-8');\n\n // Get bead description\n const beadDescription = await getBeadDescription(context.beadId, context.workspace);\n\n // Get diff scope\n const diffBase = await getDiffBase(context.projectKey, context.issueId, context.workspace);\n const diffStats = await getDiffStats(context.workspace, diffBase);\n const compileCommand = detectCompileCommand(context.workspace);\n\n const apiUrl = process.env.DASHBOARD_URL || `http://localhost:${process.env.API_PORT || process.env.PORT || '3011'}`;\n\n // Replace template variables\n const prompt = template\n .replace(/\\{\\{apiUrl\\}\\}/g, apiUrl)\n .replace(/\\{\\{projectPath\\}\\}/g, context.projectPath)\n .replace(/\\{\\{issueId\\}\\}/g, context.issueId)\n .replace(/\\{\\{beadId\\}\\}/g, context.beadId)\n .replace(/\\{\\{workspacePath\\}\\}/g, context.workspace)\n .replace(/\\{\\{checkpoint\\}\\}/g, diffBase.substring(0, 8))\n .replace(/\\{\\{diffBase\\}\\}/g, diffBase)\n .replace(/\\{\\{diffStats\\}\\}/g, diffStats)\n .replace(/\\{\\{beadDescription\\}\\}/g, beadDescription)\n .replace(/\\{\\{compileCommand\\}\\}/g, compileCommand)\n .replace(/\\{\\{resultStatus\\}\\}/g, '${RESULT_STATUS}') // Placeholder for specialist to fill\n .replace(/\\{\\{resultNotes\\}\\}/g, '${RESULT_NOTES}'); // Placeholder for specialist to fill\n\n return `<!-- panopticon:orchestration-context-start -->\\n${prompt}\\n<!-- panopticon:orchestration-context-end -->`;\n}\n\n/**\n * Spawn the inspect specialist for a bead.\n */\nexport async function spawnInspectAgent(context: InspectContext): Promise<{\n success: boolean;\n runId?: string;\n tmuxSession?: string;\n message: string;\n error?: string;\n}> {\n // Build the prompt\n const prompt = await buildInspectPrompt(context);\n\n // Update status to inspecting\n setReviewStatus(context.issueId.toUpperCase(), {\n inspectStatus: 'inspecting',\n inspectNotes: `Inspecting bead ${context.beadId}`,\n });\n\n // Spawn the ephemeral specialist\n return spawnEphemeralSpecialist(context.projectKey, 'inspect-agent' as SpecialistType, {\n issueId: context.issueId,\n branch: context.branch,\n workspace: context.workspace,\n promptOverride: prompt,\n });\n}\n\n/**\n * Handle inspect completion — called when the inspect specialist signals done.\n * Saves checkpoint on PASS.\n */\nexport async function onInspectComplete(\n projectKey: string,\n issueId: string,\n beadId: string,\n status: 'passed' | 'failed',\n workspacePath: string\n): Promise<void> {\n if (status === 'passed') {\n const commitSha = await getCurrentHead(workspacePath);\n saveCheckpoint(projectKey, issueId, beadId, commitSha);\n console.log(`[inspect] Checkpoint saved for ${issueId} bead ${beadId} at ${commitSha.substring(0, 8)}`);\n\n } else {\n console.log(`[inspect] Bead ${beadId} blocked for ${issueId} — no checkpoint saved`);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAeA,MAAMA,cAAY,UAAU,KAAK;AAEjC,MAAM,kBAAkB,KAAK,SAAS,EAAE,cAAc;;;;AAgBtD,SAAS,iBAAiB,YAA4B;AACpD,QAAO,KAAK,iBAAiB,eAAe,YAAY,iBAAiB,cAAc;;;;;AAMzF,SAAS,kBAAkB,YAAoB,SAAyB;AACtE,QAAO,KAAK,iBAAiB,WAAW,EAAE,GAAG,QAAQ,aAAa,CAAC,OAAO;;;;;AAM5E,SAAgB,gBAAgB,YAAoB,SAA+C;CACjG,MAAM,WAAW,kBAAkB,YAAY,QAAQ;AACvD,KAAI,CAAC,WAAW,SAAS,CAAE,QAAO;AAElC,KAAI;AACF,SAAO,KAAK,MAAM,aAAa,UAAU,QAAQ,CAAC;SAC5C;AACN,SAAO;;;;;;AAgBX,SAAgB,eACd,YACA,SACA,QACA,WACmB;CACnB,MAAM,MAAM,iBAAiB,WAAW;AACxC,KAAI,CAAC,WAAW,IAAI,CAClB,WAAU,KAAK,EAAE,WAAW,MAAM,CAAC;CAGrC,MAAM,OAAO,gBAAgB,YAAY,QAAQ,IAAI;EACnD,SAAS,QAAQ,aAAa;EAC9B,aAAa,EAAE;EAChB;CAED,MAAM,aAAgC;EACpC;EACA;EACA,2BAAU,IAAI,MAAM,EAAC,aAAa;EACnC;AAED,MAAK,YAAY,KAAK,WAAW;AACjC,eAAc,kBAAkB,YAAY,QAAQ,EAAE,KAAK,UAAU,MAAM,MAAM,EAAE,CAAC;AAEpF,QAAO;;;;;AAiDT,eAAsB,eAAe,eAAwC;AAC3E,KAAI;EACF,MAAM,EAAE,WAAW,MAAMA,YAAU,sBAAsB;GACvD,KAAK;GACL,UAAU;GACX,CAAC;AACF,SAAO,OAAO,MAAM;SACd;AACN,SAAO;;;;;kBCrIsE;oBAC3B;AAEpC,UAAU,KAAK;AAGf,QADC,cAAc,OAAO,KAAK,IAAI,CACZ;;;;;AA0JrC,eAAsB,kBACpB,YACA,SACA,QACA,QACA,eACe;AACf,KAAI,WAAW,UAAU;EACvB,MAAM,YAAY,MAAM,eAAe,cAAc;AACrD,iBAAe,YAAY,SAAS,QAAQ,UAAU;AACtD,UAAQ,IAAI,kCAAkC,QAAQ,QAAQ,OAAO,MAAM,UAAU,UAAU,GAAG,EAAE,GAAG;OAGvG,SAAQ,IAAI,kBAAkB,OAAO,eAAe,QAAQ,wBAAwB"}
|
package/dist/dashboard/{issue-service-singleton-z78bbRiO.js → issue-service-singleton-DQK42EqH.js}
RENAMED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { i as startSharedIssueService, n as init_issue_service_singleton, t as getSharedIssueService } from "./issue-service-singleton-
|
|
1
|
+
import { i as startSharedIssueService, n as init_issue_service_singleton, t as getSharedIssueService } from "./issue-service-singleton-sb2HkB9f.js";
|
|
2
2
|
init_issue_service_singleton();
|
|
3
3
|
export { getSharedIssueService, startSharedIssueService };
|
package/dist/dashboard/{issue-service-singleton-0n9hcF71.js → issue-service-singleton-sb2HkB9f.js}
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { n as __esmMin, r as __exportAll } from "./chunk-DORXReHP.js";
|
|
2
2
|
import { a as validateRallyConfig, i as init_tracker_config, n as getLinearApiKey, r as getRallyConfig, t as getGitHubConfig } from "./tracker-config-CNM_5rEf.js";
|
|
3
3
|
import { n as init_dist_src, t as Octokit } from "./dist-src-oG2iHzgI.js";
|
|
4
|
-
import { a as loadReviewStatuses, i as init_review_status } from "./review-status-
|
|
4
|
+
import { a as loadReviewStatuses, i as init_review_status } from "./review-status-CV55Tl-n.js";
|
|
5
5
|
import { existsSync, mkdirSync } from "fs";
|
|
6
6
|
import { join } from "path";
|
|
7
7
|
import { homedir } from "os";
|
|
@@ -1123,4 +1123,4 @@ var init_issue_service_singleton = __esmMin((() => {
|
|
|
1123
1123
|
//#endregion
|
|
1124
1124
|
export { startSharedIssueService as i, init_issue_service_singleton as n, issue_service_singleton_exports as r, getSharedIssueService as t };
|
|
1125
1125
|
|
|
1126
|
-
//# sourceMappingURL=issue-service-singleton-
|
|
1126
|
+
//# sourceMappingURL=issue-service-singleton-sb2HkB9f.js.map
|