@c3-oss/prosa 0.2.0 → 0.3.0

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/bin/prosa.js CHANGED
@@ -1120,8 +1120,6 @@ var PROSA_PARSER_VERSION = "0.1.0";
1120
1120
  var PROSA_SCHEMA_VERSION = 2;
1121
1121
 
1122
1122
  // src/cli/commands/compile.ts
1123
- import os2 from "os";
1124
- import path14 from "path";
1125
1123
  import { Command } from "commander";
1126
1124
 
1127
1125
  // src/core/bundle.ts
@@ -1627,6 +1625,10 @@ function closeBundle(bundle) {
1627
1625
  closeDb(bundle.db);
1628
1626
  }
1629
1627
 
1628
+ // src/services/compile.ts
1629
+ import os2 from "os";
1630
+ import path14 from "path";
1631
+
1630
1632
  // src/importers/claude/index.ts
1631
1633
  init_cas();
1632
1634
  init_db();
@@ -5628,35 +5630,9 @@ function isMissingParquetError(error) {
5628
5630
  return /No files found|does not exist|not found/i.test(message) && /\.parquet/i.test(message);
5629
5631
  }
5630
5632
 
5631
- // src/cli/commands/compile.ts
5633
+ // src/services/compile.ts
5632
5634
  init_indexing();
5633
-
5634
- // src/cli/logger.ts
5635
- import pino from "pino";
5636
- import pretty from "pino-pretty";
5637
- function createCliLogger(options) {
5638
- const loggerOptions = {
5639
- base: void 0,
5640
- level: options.verbose === true ? "debug" : "info"
5641
- };
5642
- if (options.jsonLogs === true) {
5643
- return pino(loggerOptions, pino.destination({ dest: 2, sync: true }));
5644
- }
5645
- return pino(
5646
- loggerOptions,
5647
- pretty({
5648
- colorize: process.stderr.isTTY,
5649
- destination: 2,
5650
- ignore: "pid,hostname",
5651
- singleLine: true,
5652
- sync: true,
5653
- translateTime: "SYS:yyyy-mm-dd HH:MM:ss.l"
5654
- })
5655
- );
5656
- }
5657
-
5658
- // src/cli/commands/compile.ts
5659
- var PROVIDERS = [
5635
+ var COMPILE_PROVIDERS = [
5660
5636
  {
5661
5637
  name: "codex",
5662
5638
  description: "Import Codex CLI session histories into the bundle.",
@@ -5686,13 +5662,129 @@ var PROVIDERS = [
5686
5662
  compile: compileCursor
5687
5663
  }
5688
5664
  ];
5665
+ function getCompileProvider(source) {
5666
+ const provider = COMPILE_PROVIDERS.find((p) => p.name === source);
5667
+ if (!provider) {
5668
+ throw new Error(`unknown compile source: ${source}`);
5669
+ }
5670
+ return provider;
5671
+ }
5672
+ function resolveCompilePath(p) {
5673
+ if (p === "~") return os2.homedir();
5674
+ if (p.startsWith("~/")) return path14.join(os2.homedir(), p.slice(2));
5675
+ return path14.resolve(p);
5676
+ }
5677
+ async function runCompileImports(options) {
5678
+ const { bundle, providers, deferIndex, logger } = options;
5679
+ let importedAny = false;
5680
+ const summaries = [];
5681
+ let tantivy = null;
5682
+ let tantivyError = null;
5683
+ try {
5684
+ if (deferIndex) {
5685
+ logger?.info("disabling FTS5 triggers for deferred indexing");
5686
+ disableFts5Triggers(bundle);
5687
+ }
5688
+ for (const provider of providers) {
5689
+ const sourcePath = resolveCompilePath(options.sessionsPath ?? provider.defaultSessionsPath());
5690
+ const providerLogger = logger?.child({
5691
+ source_tool: provider.name,
5692
+ source_path: sourcePath
5693
+ });
5694
+ providerLogger?.info("starting compile");
5695
+ const r = await provider.compile(bundle, sourcePath, { logger: providerLogger });
5696
+ importedAny ||= r.counts.source_files_imported > 0;
5697
+ providerLogger?.info(
5698
+ {
5699
+ batch_id: r.batch.batch_id,
5700
+ counts: r.counts
5701
+ },
5702
+ "compile finished"
5703
+ );
5704
+ const summary = {
5705
+ source: provider.name,
5706
+ sourcePath,
5707
+ batchId: r.batch.batch_id,
5708
+ batch: r.batch,
5709
+ counts: r.counts
5710
+ };
5711
+ summaries.push(summary);
5712
+ options.onProviderComplete?.(summary);
5713
+ }
5714
+ logger?.info({ changed: importedAny, fts5_deferred: deferIndex }, "marking indexes");
5715
+ markIndexesAfterImport(bundle, {
5716
+ changed: importedAny,
5717
+ fts5Deferred: deferIndex
5718
+ });
5719
+ if (importedAny) {
5720
+ try {
5721
+ logger?.info("rebuilding tantivy index");
5722
+ const status = await rebuildTantivyIndex(bundle);
5723
+ tantivy = { indexedDocCount: status.indexed_doc_count };
5724
+ options.onTantivyComplete?.(tantivy);
5725
+ } catch (error) {
5726
+ tantivyError = error instanceof Error ? error.message : String(error);
5727
+ logger?.error({ err: error }, "tantivy rebuild failed; SQLite data is intact");
5728
+ }
5729
+ }
5730
+ } finally {
5731
+ if (deferIndex) {
5732
+ logger?.info("re-enabling FTS5 triggers");
5733
+ enableFts5Triggers(bundle);
5734
+ }
5735
+ }
5736
+ return {
5737
+ providers: summaries,
5738
+ importedAny,
5739
+ tantivy,
5740
+ tantivyError
5741
+ };
5742
+ }
5743
+ async function exportCompileParquet(options) {
5744
+ const storePath = resolveCompilePath(options.storePath);
5745
+ options.logger?.info({ store_path: storePath }, "exporting parquet");
5746
+ const result = await exportBundleParquet({ bundlePath: storePath });
5747
+ return {
5748
+ outDir: result.outDir,
5749
+ manifestPath: result.manifestPath,
5750
+ tableCount: Object.keys(result.files).length,
5751
+ files: result.files,
5752
+ counts: result.counts
5753
+ };
5754
+ }
5755
+
5756
+ // src/cli/logger.ts
5757
+ import pino from "pino";
5758
+ import pretty from "pino-pretty";
5759
+ function createCliLogger(options) {
5760
+ const loggerOptions = {
5761
+ base: void 0,
5762
+ level: options.verbose === true ? "debug" : "info"
5763
+ };
5764
+ if (options.jsonLogs === true) {
5765
+ return pino(loggerOptions, pino.destination({ dest: 2, sync: true }));
5766
+ }
5767
+ return pino(
5768
+ loggerOptions,
5769
+ pretty({
5770
+ colorize: process.stderr.isTTY,
5771
+ destination: 2,
5772
+ ignore: "pid,hostname",
5773
+ singleLine: true,
5774
+ sync: true,
5775
+ translateTime: "SYS:yyyy-mm-dd HH:MM:ss.l"
5776
+ })
5777
+ );
5778
+ }
5779
+
5780
+ // src/cli/commands/compile.ts
5689
5781
  function compileCommand() {
5690
5782
  const command = addCompileLogOptions(
5691
5783
  new Command("compile").description(
5692
5784
  "Import session histories from one agent CLI into the bundle."
5693
5785
  )
5694
5786
  );
5695
- for (const provider of PROVIDERS) {
5787
+ for (const provider of COMPILE_PROVIDERS) {
5696
5788
  command.addCommand(providerCompileCommand(provider));
5697
5789
  }
5698
5790
  command.action(() => {
@@ -5703,7 +5795,7 @@ function compileCommand() {
5703
5795
  function compileAllCommand() {
5704
5796
  return addCompileLogOptions(new Command("compile-all")).description("Import all agent CLI session histories using default source paths.").option("--defer-index", "skip immediate FTS5 updates; run `prosa index fts5` later").action(async (options) => {
5705
5797
  await runCompiles({
5706
- providers: PROVIDERS,
5798
+ providers: COMPILE_PROVIDERS,
5707
5799
  storePath: defaultBundlePath(),
5708
5800
  deferIndex: options.deferIndex === true,
5709
5801
  logOptions: options
@@ -5732,76 +5824,42 @@ function addCompileLogOptions(command) {
5732
5824
  }
5733
5825
  async function runCompiles(options) {
5734
5826
  const logger = createCliLogger(options.logOptions);
5735
- const storePath = resolvePath(options.storePath);
5827
+ const storePath = resolveCompilePath(options.storePath);
5736
5828
  logger.info({ store_path: storePath }, "opening bundle");
5737
5829
  const bundle = await openBundle(storePath);
5738
5830
  let importedAny = false;
5739
5831
  try {
5740
- if (options.deferIndex) {
5741
- logger.info("disabling FTS5 triggers for deferred indexing");
5742
- disableFts5Triggers(bundle);
5743
- }
5744
- for (const provider of options.providers) {
5745
- const sourcePath = resolvePath(options.sessionsPath ?? provider.defaultSessionsPath());
5746
- const providerLogger = logger.child({
5747
- source_tool: provider.name,
5748
- source_path: sourcePath
5749
- });
5750
- providerLogger.info("starting compile");
5751
- const r = await provider.compile(bundle, sourcePath, { logger: providerLogger });
5752
- importedAny ||= r.counts.source_files_imported > 0;
5753
- providerLogger.info(
5754
- {
5755
- batch_id: r.batch.batch_id,
5756
- counts: r.counts
5757
- },
5758
- "compile finished"
5759
- );
5760
- printCounts(provider.name, r.batch.batch_id, r.counts);
5761
- }
5762
- logger.info({ changed: importedAny, fts5_deferred: options.deferIndex }, "marking indexes");
5763
- markIndexesAfterImport(bundle, {
5764
- changed: importedAny,
5765
- fts5Deferred: options.deferIndex
5766
- });
5767
- if (importedAny) {
5768
- try {
5769
- logger.info("rebuilding tantivy index");
5770
- const status = await rebuildTantivyIndex(bundle);
5771
- process.stdout.write(`tantivy: indexed ${status.indexed_doc_count} docs
5832
+ const result = await runCompileImports({
5833
+ bundle,
5834
+ providers: options.providers,
5835
+ deferIndex: options.deferIndex,
5836
+ sessionsPath: options.sessionsPath,
5837
+ logger,
5838
+ onProviderComplete: printCounts,
5839
+ onTantivyComplete: (status) => {
5840
+ process.stdout.write(`tantivy: indexed ${status.indexedDocCount} docs
5772
5841
  `);
5773
- } catch (error) {
5774
- logger.error({ err: error }, "tantivy rebuild failed; SQLite data is intact");
5775
5842
  }
5776
- }
5843
+ });
5844
+ importedAny = result.importedAny;
5777
5845
  } finally {
5778
- if (options.deferIndex) {
5779
- logger.info("re-enabling FTS5 triggers");
5780
- enableFts5Triggers(bundle);
5781
- }
5782
5846
  closeBundle(bundle);
5783
5847
  logger.info({ store_path: storePath }, "bundle closed");
5784
5848
  }
5785
5849
  if (importedAny) {
5786
5850
  try {
5787
- logger.info({ store_path: storePath }, "exporting parquet");
5788
- const result = await exportBundleParquet({ bundlePath: storePath });
5789
- const tableCount = Object.keys(result.files).length;
5790
- process.stdout.write(`parquet: wrote ${tableCount} tables to ${result.outDir}
5851
+ const result = await exportCompileParquet({ storePath, logger });
5852
+ process.stdout.write(`parquet: wrote ${result.tableCount} tables to ${result.outDir}
5791
5853
  `);
5792
5854
  } catch (error) {
5793
5855
  logger.error({ err: error }, "parquet export failed; SQLite data is intact");
5794
5856
  }
5795
5857
  }
5796
5858
  }
5797
- function resolvePath(p) {
5798
- if (p === "~") return os2.homedir();
5799
- if (p.startsWith("~/")) return path14.join(os2.homedir(), p.slice(2));
5800
- return path14.resolve(p);
5801
- }
5802
- function printCounts(label, batchId, c) {
5859
+ function printCounts(summary) {
5860
+ const c = summary.counts;
5803
5861
  process.stdout.write(
5804
- `${label} import: batch=${batchId}
5862
+ `${summary.source} import: batch=${summary.batchId}
5805
5863
  source_files seen=${c.source_files_seen} imported=${c.source_files_imported} skipped=${c.source_files_skipped}
5806
5864
  sessions=${c.sessions} turns=${c.turns} messages=${c.messages} blocks=${c.content_blocks}
5807
5865
  events=${c.events} tool_calls=${c.tool_calls} tool_results=${c.tool_results}
@@ -6128,10 +6186,13 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
6128
6186
 
6129
6187
  // src/mcp/guidance.ts
6130
6188
  var PROSA_MCP_INSTRUCTIONS = `
6131
- prosa is a read-only memory over local agent session histories. Use it to find prior work,
6132
- commands, decisions, file touches, and full transcripts before answering from memory.
6189
+ prosa is a local memory over local agent session histories. Use it to import recent sessions,
6190
+ find prior work, commands, decisions, file touches, and full transcripts before answering from
6191
+ memory.
6133
6192
 
6134
6193
  Recommended workflow:
6194
+ - Use compile to refresh the bundle when recent local sessions may not be indexed yet. With no
6195
+ input it imports all supported providers from default paths.
6135
6196
  - For open-ended questions, start with search_sessions using 2-5 concrete terms.
6136
6197
  - For questions about a file or path, start with find_touched_files, then inspect the returned sessions.
6137
6198
  - After search results, call get_session for the most relevant session_ids before drawing conclusions.
@@ -6181,7 +6242,76 @@ init_search();
6181
6242
  init_sessions();
6182
6243
  function registerProsaTools(server, bundle, options = {}) {
6183
6244
  const searchEngine = options.searchEngine ?? "fts5";
6245
+ const storePath = options.storePath ?? bundle.path;
6184
6246
  registerProsaPrompts(server);
6247
+ server.registerTool(
6248
+ "compile",
6249
+ {
6250
+ title: "Compile sessions",
6251
+ description: "Import local agent session histories into the active prosa bundle. With no input, compiles all providers from default paths. With source, compiles that provider; sessions_path may override that provider path.",
6252
+ inputSchema: {
6253
+ source: z.enum(["cursor", "codex", "claude", "gemini"]).optional(),
6254
+ sessions_path: z.string().min(1).optional()
6255
+ },
6256
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true }
6257
+ },
6258
+ async ({ source, sessions_path }) => {
6259
+ if (sessions_path && !source) {
6260
+ return {
6261
+ content: [
6262
+ {
6263
+ type: "text",
6264
+ text: "sessions_path requires source because providers use incompatible source layouts"
6265
+ }
6266
+ ],
6267
+ isError: true
6268
+ };
6269
+ }
6270
+ try {
6271
+ const result = await runCompileImports({
6272
+ bundle,
6273
+ providers: source ? [getCompileProvider(source)] : COMPILE_PROVIDERS,
6274
+ deferIndex: false,
6275
+ sessionsPath: sessions_path
6276
+ });
6277
+ const parquet = result.importedAny ? await exportCompileParquet({ storePath }) : null;
6278
+ return {
6279
+ content: [
6280
+ {
6281
+ type: "text",
6282
+ text: JSON.stringify(
6283
+ {
6284
+ providers: result.providers.map((provider) => ({
6285
+ source: provider.source,
6286
+ source_path: provider.sourcePath,
6287
+ batch_id: provider.batchId,
6288
+ counts: provider.counts
6289
+ })),
6290
+ imported_any: result.importedAny,
6291
+ tantivy: result.tantivy ? { indexed_doc_count: result.tantivy.indexedDocCount } : null,
6292
+ tantivy_error: result.tantivyError,
6293
+ parquet: parquet ? {
6294
+ out_dir: parquet.outDir,
6295
+ manifest_path: parquet.manifestPath,
6296
+ table_count: parquet.tableCount,
6297
+ files: parquet.files,
6298
+ counts: parquet.counts
6299
+ } : null
6300
+ },
6301
+ null,
6302
+ 2
6303
+ )
6304
+ }
6305
+ ]
6306
+ };
6307
+ } catch (error) {
6308
+ return {
6309
+ content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }],
6310
+ isError: true
6311
+ };
6312
+ }
6313
+ }
6314
+ );
6185
6315
  server.registerTool(
6186
6316
  "list_sessions",
6187
6317
  {
@@ -6494,7 +6624,7 @@ function registerProsaPrompts(server) {
6494
6624
 
6495
6625
  // src/mcp/server.ts
6496
6626
  async function listenMcpStdioServer(bundle, options = {}) {
6497
- const server = createMcpServer(bundle, options.searchEngine ?? "fts5");
6627
+ const server = createMcpServer(bundle, options.searchEngine ?? "fts5", options.storePath);
6498
6628
  const transport = new StdioServerTransport();
6499
6629
  await server.connect(transport);
6500
6630
  return {
@@ -6508,10 +6638,13 @@ async function listenMcpServer(bundle, options) {
6508
6638
  const mcpPath = options.path ?? "/mcp";
6509
6639
  const sessions = /* @__PURE__ */ new Map();
6510
6640
  const searchEngine = options.searchEngine ?? "fts5";
6641
+ const storePath = options.storePath ?? bundle.path;
6511
6642
  const httpServer = http.createServer((req, res) => {
6512
- handleRequest(req, res, mcpPath, sessions, bundle, searchEngine).catch((error) => {
6513
- writeError(res, error);
6514
- });
6643
+ handleRequest(req, res, mcpPath, sessions, bundle, searchEngine, storePath).catch(
6644
+ (error) => {
6645
+ writeError(res, error);
6646
+ }
6647
+ );
6515
6648
  });
6516
6649
  await new Promise((resolve, reject) => {
6517
6650
  httpServer.once("error", reject);
@@ -6534,7 +6667,7 @@ async function listenMcpServer(bundle, options) {
6534
6667
  }
6535
6668
  };
6536
6669
  }
6537
- async function handleRequest(req, res, mcpPath, sessions, bundle, searchEngine) {
6670
+ async function handleRequest(req, res, mcpPath, sessions, bundle, searchEngine, storePath) {
6538
6671
  if (!req.url || !req.url.startsWith(mcpPath)) {
6539
6672
  res.writeHead(404).end();
6540
6673
  return;
@@ -6556,14 +6689,14 @@ async function handleRequest(req, res, mcpPath, sessions, bundle, searchEngine)
6556
6689
  res.writeHead(404).end();
6557
6690
  return;
6558
6691
  }
6559
- entry = await openSession(bundle, sessions, searchEngine);
6692
+ entry = await openSession(bundle, sessions, searchEngine, storePath);
6560
6693
  }
6561
6694
  const bodyText = await readBody(req);
6562
6695
  const body = bodyText.length > 0 ? safeJsonParse(bodyText) : void 0;
6563
6696
  await entry.transport.handleRequest(req, res, body);
6564
6697
  }
6565
- async function openSession(bundle, store, searchEngine) {
6566
- const server = createMcpServer(bundle, searchEngine);
6698
+ async function openSession(bundle, store, searchEngine, storePath) {
6699
+ const server = createMcpServer(bundle, searchEngine, storePath);
6567
6700
  const transport = new StreamableHTTPServerTransport({
6568
6701
  sessionIdGenerator: () => randomUUID(),
6569
6702
  onsessioninitialized: (id) => {
@@ -6581,7 +6714,7 @@ async function openSession(bundle, store, searchEngine) {
6581
6714
  await server.connect(transport);
6582
6715
  return { server, transport };
6583
6716
  }
6584
- function createMcpServer(bundle, searchEngine) {
6717
+ function createMcpServer(bundle, searchEngine, storePath) {
6585
6718
  const server = new McpServer(
6586
6719
  {
6587
6720
  name: "prosa",
@@ -6589,7 +6722,7 @@ function createMcpServer(bundle, searchEngine) {
6589
6722
  },
6590
6723
  { instructions: PROSA_MCP_INSTRUCTIONS }
6591
6724
  );
6592
- registerProsaTools(server, bundle, { searchEngine });
6725
+ registerProsaTools(server, bundle, { searchEngine, storePath });
6593
6726
  return server;
6594
6727
  }
6595
6728
  async function readBody(req) {
@@ -6630,7 +6763,8 @@ function writeError(res, error) {
6630
6763
  function mcpCommand() {
6631
6764
  const serve = new Command5("serve").description("Start a local MCP server over the prosa bundle.").option("--store <path>", "bundle directory", defaultBundlePath()).option("--transport <transport>", "MCP transport: stdio|http", "stdio").option("--host <host>", "bind host", "127.0.0.1").option("--port <port>", "bind port", "7331").option("--path <path>", "HTTP path", "/mcp").option("--search-engine <engine>", "search engine: fts5|tantivy", "fts5").action(
6632
6765
  async (options) => {
6633
- const bundle = await openBundle(path18.resolve(options.store));
6766
+ const storePath = path18.resolve(options.store);
6767
+ const bundle = await openBundle(storePath);
6634
6768
  try {
6635
6769
  const transport = parseMcpTransport(options.transport);
6636
6770
  const searchEngine = parseSearchEngine(options.searchEngine);
@@ -6643,7 +6777,8 @@ function mcpCommand() {
6643
6777
  host: options.host,
6644
6778
  port,
6645
6779
  path: options.path,
6646
- searchEngine
6780
+ searchEngine,
6781
+ storePath
6647
6782
  });
6648
6783
  process.stdout.write(`prosa mcp server listening at ${server2.url}
6649
6784
  `);
@@ -6651,7 +6786,7 @@ function mcpCommand() {
6651
6786
  registerShutdown(server2.close, bundle);
6652
6787
  return;
6653
6788
  }
6654
- const server = await listenMcpStdioServer(bundle, { searchEngine });
6789
+ const server = await listenMcpStdioServer(bundle, { searchEngine, storePath });
6655
6790
  registerShutdown(server.close, bundle);
6656
6791
  } catch (error) {
6657
6792
  closeBundle(bundle);