{"version":3,"file":"workspace-cli.d.ts","sourceRoot":"","sources":["../../src/core/workspace-cli.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,KAAK,eAAe,EAA2B,MAAM,0BAA0B,CAAC;AAGzF,wBAAgB,iBAAiB,IAAI,MAAM,CAQ1C;AAmHD,wBAAsB,+BAA+B,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CA0BtF;AAsID,YAAY,EAAE,eAAe,EAAE,CAAC","sourcesContent":["/**\n * CLI surface for workspace indexing and retrieval.\n *\n * Commands: `jensen index ...`, `jensen search ...`, `jensen retrieval ...`,\n * and `jensen doctor index|embeddings|retrieval`. All default commands are\n * bounded; `--json` produces machine-readable output.\n */\n\nimport { embeddingSummary, resolveIndexConfig } from \"./workspace/config.js\";\nimport { type EmbeddingConfig, resolveEmbeddingBackend } from \"./workspace/embedding.js\";\nimport { WorkspaceIndex } from \"./workspace/index.js\";\n\nexport function workspaceCliUsage(): string {\n\treturn [\n\t\t\"Usage: jensen <index|search|retrieval> ...\",\n\t\t\"  index status|build|refresh|rebuild|verify|generations|inspect <gen>|files|symbols|stats|prune [--preview|--execute] [--json] [--root <dir>]\",\n\t\t\"  search [lexical|semantic|symbol|hybrid] <query> [--limit N] [--root <dir>] [--json]\",\n\t\t\"  retrieval plan <query> | retrieval explain <result-id> [--root <dir>] [--json]\",\n\t\t\"  doctor index|embeddings|retrieval [--root <dir>] [--json]\",\n\t].join(\"\\n\");\n}\n\nfunction flagOr(options: string[], long: string, fallback?: string): string | undefined {\n\tconst idx = options.indexOf(long);\n\tif (idx !== -1 && options[idx + 1] && !options[idx + 1].startsWith(\"--\")) return options[idx + 1];\n\treturn fallback;\n}\n\nfunction json(args: string[]): boolean {\n\treturn args.includes(\"--json\");\n}\n\nasync function makeIndex(args: string[]): Promise<{ index: WorkspaceIndex; root: string }> {\n\tconst root = flagOr(args, \"--root\", process.cwd()) ?? process.cwd();\n\tconst index = new WorkspaceIndex(root, resolveIndexConfig());\n\treturn { index, root };\n}\n\nfunction print(value: unknown, isJson: boolean): void {\n\tif (isJson) console.log(JSON.stringify(value, null, 2));\n\telse console.log(formatHuman(value));\n}\n\nfunction formatHuman(value: unknown): string {\n\tif (Array.isArray(value)) return value.map((v) => (typeof v === \"string\" ? v : JSON.stringify(v))).join(\"\\n\");\n\tif (value && typeof value === \"object\") return JSON.stringify(value, null, 2);\n\treturn String(value);\n}\n\nasync function doctorReport(args: string[], isJson: boolean): Promise<number> {\n\tconst { index } = await makeIndex(args);\n\tconst sub = args[1];\n\tconst checks: Array<{ name: string; status: string; message: string }> = [];\n\tconst status = index.status();\n\n\tif (!sub || sub === \"index\") {\n\t\tconst v = index.verify();\n\t\tchecks.push({\n\t\t\tname: \"index.storage\",\n\t\t\tstatus: v.valid ? \"pass\" : \"fail\",\n\t\t\tmessage: v.valid ? \"index readable and consistent\" : v.issues.join(\"; \") || \"corrupt\",\n\t\t});\n\t\tchecks.push({\n\t\t\tname: \"index.ready_generation\",\n\t\t\tstatus: status.hasReadyGeneration ? \"pass\" : \"fail\",\n\t\t\tmessage: status.hasReadyGeneration\n\t\t\t\t? `generation ${status.currentGeneration}`\n\t\t\t\t: \"no ready generation — run `jensen index build`\",\n\t\t});\n\t\tchecks.push({\n\t\t\tname: \"index.schema\",\n\t\t\tstatus: status.schemaVersion === 1 ? \"pass\" : \"warn\",\n\t\t\tmessage: `schemaVersion ${status.schemaVersion}`,\n\t\t});\n\t\tchecks.push({\n\t\t\tname: \"index.freshness\",\n\t\t\tstatus:\n\t\t\t\tstatus.freshnessCheck === \"current\"\n\t\t\t\t\t? \"pass\"\n\t\t\t\t\t: status.freshnessCheck === \"possibly_stale\"\n\t\t\t\t\t\t? \"warn\"\n\t\t\t\t\t\t: \"unavailable\",\n\t\t\tmessage: `freshness ${status.freshnessCheck ?? \"unknown\"}`,\n\t\t});\n\t\tchecks.push({\n\t\t\tname: \"index.disk\",\n\t\t\tstatus: \"pass\",\n\t\t\tmessage: `storage ${status.storageDir}`,\n\t\t});\n\t}\n\tif (!sub || sub === \"embeddings\") {\n\t\tconst cfg = resolveIndexConfig();\n\t\tconst summary = embeddingSummary(cfg);\n\t\tconst mode = summary.mode;\n\t\tchecks.push({\n\t\t\tname: \"embeddings.mode\",\n\t\t\tstatus: mode === \"disabled\" ? \"disabled\" : mode === \"fixture\" ? \"pass\" : mode === \"local\" ? \"pass\" : \"warn\",\n\t\t\tmessage: `mode=${mode} local=${summary.local} model=${summary.modelId ?? \"n/a\"} dims=${summary.dimensions ?? \"n/a\"}`,\n\t\t});\n\t\tif (mode === \"remote\") {\n\t\t\tchecks.push({\n\t\t\t\tname: \"embeddings.remote\",\n\t\t\t\tstatus: \"warn\",\n\t\t\t\tmessage: \"remote embedding enabled (policy authorized)\",\n\t\t\t});\n\t\t}\n\t\tchecks.push({\n\t\t\tname: \"embeddings.backend\",\n\t\t\tstatus: mode === \"disabled\" ? \"disabled\" : \"pass\",\n\t\t\tmessage: resolveEmbeddingBackend(cfg.embedding).backendId,\n\t\t});\n\t}\n\tif (!sub || sub === \"retrieval\") {\n\t\tchecks.push({\n\t\t\tname: \"retrieval.lexical\",\n\t\t\tstatus: \"pass\",\n\t\t\tmessage: \"portable postings + BM25 backend\",\n\t\t});\n\t\tchecks.push({\n\t\t\tname: \"retrieval.generations\",\n\t\t\tstatus: status.hasReadyGeneration ? \"pass\" : \"unavailable\",\n\t\t\tmessage: `${index.generations().length} generation(s)`,\n\t\t});\n\t}\n\tprint(\n\t\t{\n\t\t\tchecks,\n\t\t\tsummary: `read-only workspace index diagnostics for ${status.root}`,\n\t\t\texitCode: checks.some((c) => c.status === \"fail\") ? 2 : checks.some((c) => c.status === \"warn\") ? 1 : 0,\n\t\t},\n\t\tisJson,\n\t);\n\treturn checks.some((c) => c.status === \"fail\") ? 2 : checks.some((c) => c.status === \"warn\") ? 1 : 0;\n}\n\nexport async function handleWorkspaceRetrievalCommand(args: string[]): Promise<boolean> {\n\tconst namespace = args[0];\n\n\tif (namespace === \"doctor\" && (args[1] === \"index\" || args[1] === \"embeddings\" || args[1] === \"retrieval\")) {\n\t\tawait doctorReport(args, json(args));\n\t\treturn true;\n\t}\n\n\tif (![\"index\", \"search\", \"retrieval\"].includes(namespace ?? \"\")) return false;\n\tconst isJson = json(args);\n\ttry {\n\t\tif (namespace === \"index\") {\n\t\t\tawait handleIndex(args.slice(1), isJson);\n\t\t\treturn true;\n\t\t}\n\t\tif (namespace === \"search\") {\n\t\t\tawait handleSearch(args.slice(1), isJson);\n\t\t\treturn true;\n\t\t}\n\t\tawait handleRetrieval(args.slice(1), isJson);\n\t\treturn true;\n\t} catch (error) {\n\t\tconsole.error(error instanceof Error ? error.message : String(error));\n\t\tprocess.exitCode = 2;\n\t\treturn true;\n\t}\n}\n\nasync function handleIndex(args: string[], isJson: boolean): Promise<void> {\n\tconst cmd = args[0];\n\tconst { index } = await makeIndex(args);\n\tswitch (cmd) {\n\t\tcase \"status\": {\n\t\t\tprint(index.status(), isJson);\n\t\t\tbreak;\n\t\t}\n\t\tcase \"build\": {\n\t\t\tconst report = await index.build();\n\t\t\tprint(report, isJson);\n\t\t\tbreak;\n\t\t}\n\t\tcase \"refresh\": {\n\t\t\tconst report = await index.refresh();\n\t\t\tprint(report, isJson);\n\t\t\tbreak;\n\t\t}\n\t\tcase \"rebuild\": {\n\t\t\tconst report = await index.rebuild();\n\t\t\tprint(report, isJson);\n\t\t\tbreak;\n\t\t}\n\t\tcase \"verify\": {\n\t\t\tprint(index.verify(true), isJson);\n\t\t\tbreak;\n\t\t}\n\t\tcase \"generations\": {\n\t\t\tprint(index.generations(), isJson);\n\t\t\tbreak;\n\t\t}\n\t\tcase \"inspect\": {\n\t\t\tconst genArg = args[1];\n\t\t\tconst gen = genArg && !genArg.startsWith(\"--\") ? genArg : (index.status().currentGeneration ?? \"\");\n\t\t\tconst detail = index.inspectGeneration(gen);\n\t\t\tif (!detail) throw new Error(`Unknown generation: ${gen}`);\n\t\t\tprint(detail, isJson);\n\t\t\tbreak;\n\t\t}\n\t\tcase \"files\": {\n\t\t\tprint(index.files(), isJson);\n\t\t\tbreak;\n\t\t}\n\t\tcase \"symbols\": {\n\t\t\tprint(index.symbols(), isJson);\n\t\t\tbreak;\n\t\t}\n\t\tcase \"stats\": {\n\t\t\tprint(index.stats(), isJson);\n\t\t\tbreak;\n\t\t}\n\t\tcase \"prune\": {\n\t\t\tconst preview = !args.includes(\"--execute\");\n\t\t\tconst result = index.prune({ preview });\n\t\t\tprint(result, isJson);\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\tthrow new Error(`Unknown index subcommand: ${cmd}\\n${workspaceCliUsage()}`);\n\t}\n\tindex.close();\n}\n\nasync function handleSearch(args: string[], isJson: boolean): Promise<void> {\n\t// search [mode] <query> [--limit N] [--root dir]\n\tlet mode = \"hybrid\";\n\tlet queryArgs: string[] = [];\n\tconst first = args[0];\n\tif (first && [\"lexical\", \"semantic\", \"symbol\", \"hybrid\", \"path\"].includes(first)) {\n\t\tmode = first;\n\t\tqueryArgs = args.slice(1);\n\t} else {\n\t\tqueryArgs = args;\n\t}\n\tconst query = queryArgs.find((a) => !a.startsWith(\"--\")) ?? \"\";\n\tif (!query) throw new Error(\"search requires a query\");\n\tconst limitRaw = flagOr(args, \"--limit\", undefined) ?? undefined;\n\tconst limit = limitRaw ? Number(limitRaw) : undefined;\n\tconst { index } = await makeIndex(args);\n\tconst { plan, results } = index.search({ query, mode: mode as never, limit });\n\tprint({ plan: { mode: plan.mode, normalizedQuery: plan.normalizedQuery }, results }, isJson);\n\tindex.close();\n}\n\nasync function handleRetrieval(args: string[], isJson: boolean): Promise<void> {\n\tconst cmd = args[0];\n\tconst { index } = await makeIndex(args);\n\tif (cmd === \"plan\") {\n\t\tconst query = args[1] && !args[1].startsWith(\"--\") ? args[1] : undefined;\n\t\tif (!query) throw new Error(\"retrieval plan requires a query\");\n\t\tconst { plan } = index.search({ query });\n\t\tprint(plan, isJson);\n\t} else if (cmd === \"explain\") {\n\t\tconst resultId = args[1] && !args[1].startsWith(\"--\") ? args[1] : undefined;\n\t\tif (!resultId) throw new Error(\"retrieval explain requires a result-id\");\n\t\t// Result ids are stable: generation + chunk. Look up the chunk.\n\t\tconst status = index.status();\n\t\tconst gen = status.currentGeneration ?? \"\";\n\t\tconst chunkId = resultId.replace(`${gen}_`, \"\");\n\t\tconst db = openRaw(index);\n\t\tconst chunk = db.chunkById(gen, chunkId);\n\t\tconst file = chunk ? db.getFile(gen, chunk.fileId) : undefined;\n\t\tprint(\n\t\t\t{\n\t\t\t\tresultId,\n\t\t\t\tgeneration: gen,\n\t\t\t\tpath: file?.workspaceRelativePath,\n\t\t\t\tstartLine: chunk?.startLine,\n\t\t\t\tendLine: chunk?.endLine,\n\t\t\t\tsnippet: chunk?.text?.slice(0, 400),\n\t\t\t\tnote: \"score signals and reason codes are available in search output for each result\",\n\t\t\t},\n\t\t\tisJson,\n\t\t);\n\t\tcloseRaw(db);\n\t} else {\n\t\tthrow new Error(`Unknown retrieval subcommand: ${cmd}\\n${workspaceCliUsage()}`);\n\t}\n\tindex.close();\n}\n\n// Raw DB access for explain lookups (avoids re-opening workspace id mismatch).\nimport { WorkspaceDb } from \"./workspace/storage.js\";\n\nfunction openRaw(index: WorkspaceIndex): WorkspaceDb {\n\tconst status = index.status();\n\treturn WorkspaceDb.open(status.storageDir, status.workspaceId);\n}\nfunction closeRaw(db: WorkspaceDb): void {\n\tdb.close();\n}\n\nexport type { EmbeddingConfig };\n"]}