@useorgx/wizard 0.1.38 → 0.1.39

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.js CHANGED
@@ -3,9 +3,9 @@
3
3
  // src/cli.ts
4
4
  import * as clack from "@clack/prompts";
5
5
  import { spawnSync as spawnSync3 } from "child_process";
6
- import { readFileSync as readFileSync6 } from "fs";
6
+ import { readFileSync as readFileSync7 } from "fs";
7
7
  import { hostname } from "os";
8
- import { resolve } from "path";
8
+ import { resolve as resolve2 } from "path";
9
9
  import { Command } from "commander";
10
10
  import pc3 from "picocolors";
11
11
 
@@ -795,7 +795,7 @@ function parsePairingPollResult(value) {
795
795
  };
796
796
  }
797
797
  function sleep(ms) {
798
- return new Promise((resolve2) => setTimeout(resolve2, ms));
798
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
799
799
  }
800
800
  async function startBrowserPairing(options, fetchImpl) {
801
801
  const data = await fetchJson({
@@ -908,12 +908,12 @@ h1{font-size:1.5rem;color:#b91c1c}p{color:#555;margin-top:.5rem}</style>
908
908
  <p>Return to your terminal and try again.</p>
909
909
  </div></body></html>`;
910
910
  function tryListen(port, hostname2) {
911
- return new Promise((resolve2, reject) => {
911
+ return new Promise((resolve3, reject) => {
912
912
  const server = createServer();
913
913
  server.once("error", reject);
914
914
  server.listen(port, hostname2, () => {
915
915
  server.removeListener("error", reject);
916
- resolve2(server);
916
+ resolve3(server);
917
917
  });
918
918
  });
919
919
  }
@@ -942,7 +942,7 @@ async function startLocalAuthServer(options) {
942
942
  const successHtml = options.successHtml ?? DEFAULT_SUCCESS_HTML;
943
943
  const errorHtml = options.errorHtml ?? DEFAULT_ERROR_HTML;
944
944
  const { server, port } = await bindServer(options.preferredPort, hostname2);
945
- const result = new Promise((resolve2, reject) => {
945
+ const result = new Promise((resolve3, reject) => {
946
946
  const timer = setTimeout(() => {
947
947
  server.close();
948
948
  reject(new Error("Timed out waiting for browser authorization."));
@@ -985,7 +985,7 @@ async function startLocalAuthServer(options) {
985
985
  res.writeHead(200, { "Content-Type": "text/html" }).end(successHtml);
986
986
  clearTimeout(timer);
987
987
  server.close();
988
- resolve2({ code, state });
988
+ resolve3({ code, state });
989
989
  });
990
990
  });
991
991
  return { port, result };
@@ -3215,8 +3215,8 @@ function encodeRepoPath2(value) {
3215
3215
  return value.split("/").filter((segment) => segment.length > 0).map((segment) => encodeURIComponent(segment)).join("/");
3216
3216
  }
3217
3217
  function isLikelyRepoFilePath(path) {
3218
- const basename3 = path.split("/").pop() ?? path;
3219
- return basename3.includes(".") && !/^\.[^./]+$/.test(basename3);
3218
+ const basename4 = path.split("/").pop() ?? path;
3219
+ return basename4.includes(".") && !/^\.[^./]+$/.test(basename4);
3220
3220
  }
3221
3221
  function buildContentsUrl2(spec, path) {
3222
3222
  const encodedPath = encodeRepoPath2(path);
@@ -3550,7 +3550,7 @@ function formatCommandFailure(command, args, result) {
3550
3550
  return `${command} ${args.join(" ")} failed${result.exitCode >= 0 ? ` with exit code ${result.exitCode}` : ""}${detail ? `: ${detail}` : "."}`;
3551
3551
  }
3552
3552
  async function defaultCommandRunner(command, args) {
3553
- return await new Promise((resolve2) => {
3553
+ return await new Promise((resolve3) => {
3554
3554
  const child = spawn(command, [...args], {
3555
3555
  env: process.env,
3556
3556
  stdio: ["ignore", "pipe", "pipe"]
@@ -3565,7 +3565,7 @@ async function defaultCommandRunner(command, args) {
3565
3565
  });
3566
3566
  child.on("error", (error) => {
3567
3567
  const errorCode = typeof error === "object" && error && "code" in error ? String(error.code) : void 0;
3568
- resolve2({
3568
+ resolve3({
3569
3569
  exitCode: -1,
3570
3570
  stdout,
3571
3571
  stderr,
@@ -3573,7 +3573,7 @@ async function defaultCommandRunner(command, args) {
3573
3573
  });
3574
3574
  });
3575
3575
  child.on("close", (code) => {
3576
- resolve2({
3576
+ resolve3({
3577
3577
  exitCode: code ?? -1,
3578
3578
  stdout,
3579
3579
  stderr
@@ -6500,8 +6500,634 @@ function loadAiSessionImports(options) {
6500
6500
  };
6501
6501
  }
6502
6502
 
6503
- // src/lib/self-audit.ts
6503
+ // src/lib/work-graph-source-adapters.ts
6504
6504
  import { createHash as createHash3 } from "crypto";
6505
+ import { existsSync as existsSync6, readdirSync as readdirSync4, readFileSync as readFileSync4, statSync as statSync4 } from "fs";
6506
+ import { homedir as homedir2 } from "os";
6507
+ import { basename as basename3, join as join5, resolve } from "path";
6508
+ var CLIENT_SOURCES = ["claude_code", "codex", "opencode", "goose", "cursor"];
6509
+ var DEFAULT_LIMIT_PER_SOURCE2 = 8;
6510
+ var DEFAULT_SINCE_DAYS2 = 45;
6511
+ var DEFAULT_MAX_BYTES_PER_FILE2 = 2e6;
6512
+ var WORK_SIGNAL_PATTERN = /\b(decision|decided|tradeoff|artifact|implemented|created|changed|edited|shipped|verified|test|proof|commit|blocked|blocker|failed|fails|error|timeout|permission|owner|handoff|mcp__|orgx_|tool call|schema|zod|next action|follow[- ]?up|initiative|agent|skill|recipe|workflow|customer|business|launch|deploy)\b/i;
6513
+ function parseInvestigationSourceList(value) {
6514
+ if (!value?.trim() || value.trim().toLowerCase() === "manual") return [];
6515
+ const requested = value.split(",").map((item) => item.trim().toLowerCase().replace(/-/g, "_")).filter(Boolean);
6516
+ const expanded = requested.includes("all") ? [...CLIENT_SOURCES] : requested.map((source) => source === "claude" ? "claude_code" : source);
6517
+ const deduped = [...new Set(expanded)];
6518
+ const invalid = deduped.filter((source) => !CLIENT_SOURCES.includes(source));
6519
+ if (invalid.length > 0) {
6520
+ throw new Error(
6521
+ `Unsupported investigation source: ${invalid.join(", ")}. Use claude, codex, opencode, goose, cursor, or all.`
6522
+ );
6523
+ }
6524
+ return deduped;
6525
+ }
6526
+ function hash(value, length = 24) {
6527
+ return createHash3("sha256").update(JSON.stringify(value)).digest("hex").slice(0, length);
6528
+ }
6529
+ function expandPath(path, env) {
6530
+ return path.replace(/^~(?=\/|$)/, env.home).replace(/\$CWD/g, env.cwd).replace(/\$HOME/g, env.home);
6531
+ }
6532
+ function safeStat(path) {
6533
+ try {
6534
+ return statSync4(path);
6535
+ } catch {
6536
+ return null;
6537
+ }
6538
+ }
6539
+ function walkFiles(root, predicate, maxFiles = 500) {
6540
+ if (!existsSync6(root)) return [];
6541
+ const files = [];
6542
+ const stack = [root];
6543
+ const ignored = /* @__PURE__ */ new Set(["node_modules", ".git", ".next", "dist", "build", ".turbo"]);
6544
+ while (stack.length > 0 && files.length < maxFiles) {
6545
+ const current = stack.pop();
6546
+ if (!current) continue;
6547
+ let entries;
6548
+ try {
6549
+ entries = readdirSync4(current);
6550
+ } catch {
6551
+ continue;
6552
+ }
6553
+ for (const entry of entries) {
6554
+ if (ignored.has(entry)) continue;
6555
+ const path = join5(current, entry);
6556
+ const stats = safeStat(path);
6557
+ if (!stats) continue;
6558
+ if (stats.isDirectory()) {
6559
+ stack.push(path);
6560
+ } else if (stats.isFile() && predicate(path)) {
6561
+ files.push(path);
6562
+ }
6563
+ }
6564
+ }
6565
+ return files;
6566
+ }
6567
+ function providerForClient(client) {
6568
+ if (client === "claude_code") return "anthropic";
6569
+ if (client === "codex") return "openai";
6570
+ return "unknown";
6571
+ }
6572
+ function workGraphSourceClient(client) {
6573
+ if (client === "claude_code") return "claude-code";
6574
+ return client;
6575
+ }
6576
+ function clientLabel(client) {
6577
+ switch (client) {
6578
+ case "claude_code":
6579
+ return "Claude Code sessions";
6580
+ case "codex":
6581
+ return "Codex sessions";
6582
+ case "opencode":
6583
+ return "OpenCode sessions";
6584
+ case "goose":
6585
+ return "goose sessions";
6586
+ case "cursor":
6587
+ return "Cursor workspace evidence";
6588
+ }
6589
+ }
6590
+ function redactedText(value, limit = 900) {
6591
+ return value.replace(/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi, "[redacted-email]").replace(/\b(?:sk|oxk|ghp|gho|xox[baprs])_[A-Za-z0-9_-]{12,}\b/g, "[redacted-token]").replace(/\s+/g, " ").trim().slice(0, limit);
6592
+ }
6593
+ function nowWindow(timestamp, now) {
6594
+ const occurred = Date.parse(timestamp);
6595
+ if (!Number.isFinite(occurred)) return "older";
6596
+ const delta = now.getTime() - occurred;
6597
+ if (delta < 5 * 60 * 1e3) return "live";
6598
+ if (delta <= 24 * 60 * 60 * 1e3) return "recent_24h";
6599
+ if (delta <= 7 * 24 * 60 * 60 * 1e3) return "recent_7d";
6600
+ if (delta <= 30 * 24 * 60 * 60 * 1e3) return "recent_30d";
6601
+ return "older";
6602
+ }
6603
+ function makeRawEvent(input) {
6604
+ const contentHash = hash(input.payload, 64);
6605
+ const uri = `${input.client}:${input.sessionId}:${input.uriSuffix}`;
6606
+ return {
6607
+ event_id: `evt_${hash([uri, contentHash], 24)}`,
6608
+ source_ref: {
6609
+ source_id: input.client,
6610
+ uri,
6611
+ cite_url: null,
6612
+ resolves: true
6613
+ },
6614
+ source_id: input.client,
6615
+ provider: providerForClient(input.client),
6616
+ session_id: input.sessionId,
6617
+ project_id: null,
6618
+ timestamp: input.timestamp,
6619
+ occurred_in_window: nowWindow(input.timestamp, input.now),
6620
+ kind: input.kind,
6621
+ role: input.role,
6622
+ payload: input.payload,
6623
+ raw_byte_offset: input.rawByteOffset ?? null,
6624
+ raw_row_id: input.rawRowId ?? null,
6625
+ content_hash: contentHash,
6626
+ redaction_applied: true
6627
+ };
6628
+ }
6629
+ function parseJsonLine2(line) {
6630
+ try {
6631
+ return JSON.parse(line);
6632
+ } catch {
6633
+ return null;
6634
+ }
6635
+ }
6636
+ function asText2(value) {
6637
+ if (typeof value === "string") return value;
6638
+ if (Array.isArray(value)) {
6639
+ return value.map((item) => {
6640
+ if (typeof item === "string") return item;
6641
+ if (!isRecord(item)) return "";
6642
+ if (typeof item.text === "string") return item.text;
6643
+ if (typeof item.content === "string") return item.content;
6644
+ return "";
6645
+ }).filter(Boolean).join("\n");
6646
+ }
6647
+ if (isRecord(value)) {
6648
+ if (typeof value.text === "string") return value.text;
6649
+ if (typeof value.content === "string") return value.content;
6650
+ }
6651
+ return "";
6652
+ }
6653
+ function stringField(record, ...keys) {
6654
+ for (const key of keys) {
6655
+ const value = record[key];
6656
+ if (typeof value === "string" && value.trim()) return value.trim();
6657
+ }
6658
+ return null;
6659
+ }
6660
+ function timestampFor(record, statsMtime) {
6661
+ if (isRecord(record)) {
6662
+ const timestamp = stringField(record, "timestamp", "created_at", "createdAt", "created", "time");
6663
+ if (timestamp && Number.isFinite(Date.parse(timestamp))) return new Date(timestamp).toISOString();
6664
+ const numeric = record.timestamp ?? record.created_at ?? record.created;
6665
+ if (typeof numeric === "number" && Number.isFinite(numeric)) {
6666
+ return new Date(numeric > 1e10 ? numeric : numeric * 1e3).toISOString();
6667
+ }
6668
+ }
6669
+ return new Date(statsMtime).toISOString();
6670
+ }
6671
+ function mapCodexRecord(record, fallbackTimestamp) {
6672
+ if (!isRecord(record)) return [];
6673
+ const timestamp = timestampFor(record, Date.parse(fallbackTimestamp));
6674
+ if (record.type === "response_item" && isRecord(record.payload)) {
6675
+ const payload = record.payload;
6676
+ if (payload.type === "message" && (payload.role === "user" || payload.role === "assistant")) {
6677
+ return [{
6678
+ kind: payload.role === "user" ? "user_prompt" : "assistant_text",
6679
+ role: payload.role,
6680
+ text: asText2(payload.content),
6681
+ timestamp
6682
+ }];
6683
+ }
6684
+ if (payload.type === "function_call") {
6685
+ const toolName = stringField(payload, "name", "tool_name") ?? "unknown_tool";
6686
+ return [{
6687
+ kind: toolName.startsWith("mcp__") ? "mcp_tool_call" : "tool_call_start",
6688
+ role: "tool",
6689
+ text: redactedText(JSON.stringify(payload.arguments ?? payload)),
6690
+ toolName,
6691
+ timestamp
6692
+ }];
6693
+ }
6694
+ if (payload.type === "function_call_output") {
6695
+ const text2 = asText2(payload.output) || redactedText(JSON.stringify(payload.output ?? payload));
6696
+ return [{
6697
+ kind: /\berror|failed|exception\b/i.test(text2) ? "tool_call_error" : "tool_call_result",
6698
+ role: "tool",
6699
+ text: text2,
6700
+ timestamp
6701
+ }];
6702
+ }
6703
+ if (payload.type === "reasoning") {
6704
+ return [{ kind: "assistant_reasoning", role: "assistant", text: asText2(payload.content), timestamp }];
6705
+ }
6706
+ }
6707
+ if (record.type === "event_msg" && isRecord(record.payload) && typeof record.payload.message === "string") {
6708
+ return [{ kind: "assistant_text", role: "meta", text: record.payload.message, timestamp }];
6709
+ }
6710
+ return [];
6711
+ }
6712
+ function mapClaudeRecord(record, fallbackTimestamp) {
6713
+ if (!isRecord(record)) return [];
6714
+ const timestamp = timestampFor(record, Date.parse(fallbackTimestamp));
6715
+ const message = isRecord(record.message) ? record.message : record;
6716
+ const role = message.role === "user" || message.role === "assistant" ? message.role : "meta";
6717
+ const content = message.content;
6718
+ const out = [];
6719
+ if (Array.isArray(content)) {
6720
+ for (const [index, part] of content.entries()) {
6721
+ if (!isRecord(part)) continue;
6722
+ if (part.type === "text" || typeof part.text === "string") {
6723
+ out.push({ kind: role === "user" ? "user_prompt" : "assistant_text", role, text: asText2(part), timestamp });
6724
+ } else if (part.type === "thinking") {
6725
+ out.push({ kind: "assistant_reasoning", role: "assistant", text: asText2(part), timestamp });
6726
+ } else if (part.type === "tool_use") {
6727
+ const toolName = stringField(part, "name") ?? `tool_${index}`;
6728
+ out.push({
6729
+ kind: toolName.startsWith("mcp__") ? "mcp_tool_call" : "tool_call_start",
6730
+ role: "tool",
6731
+ text: redactedText(JSON.stringify(part.input ?? part)),
6732
+ toolName,
6733
+ timestamp
6734
+ });
6735
+ } else if (part.type === "tool_result") {
6736
+ const text3 = asText2(part.content) || redactedText(JSON.stringify(part.content ?? part));
6737
+ out.push({ kind: part.is_error === true ? "tool_call_error" : "tool_call_result", role: "tool", text: text3, timestamp });
6738
+ }
6739
+ }
6740
+ return out;
6741
+ }
6742
+ const text2 = asText2(content);
6743
+ if (text2) out.push({ kind: role === "user" ? "user_prompt" : "assistant_text", role, text: text2, timestamp });
6744
+ return out;
6745
+ }
6746
+ function readJsonlCandidate(candidate, options) {
6747
+ const stats = safeStat(candidate.path);
6748
+ if (!stats || stats.size > options.maxBytesPerFile) {
6749
+ return {
6750
+ events: [],
6751
+ filesRead: 0,
6752
+ filesSkipped: [{ path: candidate.path, reason: stats ? "schema_version_unsupported" : "permission_denied" }],
6753
+ notes: [],
6754
+ searchedSessions: 0
6755
+ };
6756
+ }
6757
+ const lines = readFileSync4(candidate.path, "utf8").split(/\r?\n/);
6758
+ const fallbackTimestamp = new Date(stats.mtimeMs).toISOString();
6759
+ const sessionId = basename3(candidate.path).replace(/\.[^.]+$/, "");
6760
+ const events = [];
6761
+ for (const [index, line] of lines.entries()) {
6762
+ if (!line.trim()) continue;
6763
+ const record = parseJsonLine2(line);
6764
+ const mapped = candidate.source === "codex" ? mapCodexRecord(record, fallbackTimestamp) : candidate.source === "claude_code" ? mapClaudeRecord(record, fallbackTimestamp) : mapGenericJsonRecord(record, fallbackTimestamp);
6765
+ for (const [partIndex, item] of mapped.entries()) {
6766
+ if (!item.text && !item.toolName) continue;
6767
+ events.push(makeRawEvent({
6768
+ client: candidate.source,
6769
+ kind: item.kind,
6770
+ now: options.now,
6771
+ path: candidate.path,
6772
+ payload: {
6773
+ extraction_mode: candidate.extractionMode,
6774
+ relative_source_path: candidate.path,
6775
+ text_summary: redactedText(item.text),
6776
+ tool_name: item.toolName
6777
+ },
6778
+ rawByteOffset: index,
6779
+ role: item.role,
6780
+ sessionId,
6781
+ timestamp: item.timestamp,
6782
+ uriSuffix: `line:${index}:part:${partIndex}`
6783
+ }));
6784
+ }
6785
+ }
6786
+ return {
6787
+ events,
6788
+ filesRead: 1,
6789
+ filesSkipped: [],
6790
+ notes: [],
6791
+ searchedSessions: 1
6792
+ };
6793
+ }
6794
+ function mapGenericJsonRecord(record, fallbackTimestamp) {
6795
+ if (!isRecord(record)) return [];
6796
+ const timestamp = timestampFor(record, Date.parse(fallbackTimestamp));
6797
+ const roleValue = record.role === "user" || record.role === "assistant" ? record.role : "meta";
6798
+ const toolName = stringField(record, "tool_name", "toolName", "name");
6799
+ if (toolName) {
6800
+ const text3 = asText2(record.response) || asText2(record.output) || redactedText(JSON.stringify(record));
6801
+ return [{
6802
+ kind: toolName.startsWith("mcp__") ? "mcp_tool_call" : record.success === false ? "tool_call_error" : "tool_call_start",
6803
+ role: "tool",
6804
+ text: text3,
6805
+ toolName,
6806
+ timestamp
6807
+ }];
6808
+ }
6809
+ const text2 = asText2(record.content) || asText2(record.message) || asText2(record.text);
6810
+ if (!text2) return [];
6811
+ return [{
6812
+ kind: roleValue === "user" ? "user_prompt" : "assistant_text",
6813
+ role: roleValue,
6814
+ text: text2,
6815
+ timestamp
6816
+ }];
6817
+ }
6818
+ function readJsonCandidate(candidate, options) {
6819
+ const stats = safeStat(candidate.path);
6820
+ if (!stats || stats.size > options.maxBytesPerFile) {
6821
+ return {
6822
+ events: [],
6823
+ filesRead: 0,
6824
+ filesSkipped: [{ path: candidate.path, reason: stats ? "schema_version_unsupported" : "permission_denied" }],
6825
+ notes: [],
6826
+ searchedSessions: 0
6827
+ };
6828
+ }
6829
+ let parsed;
6830
+ try {
6831
+ parsed = JSON.parse(readFileSync4(candidate.path, "utf8"));
6832
+ } catch {
6833
+ return {
6834
+ events: [],
6835
+ filesRead: 0,
6836
+ filesSkipped: [{ path: candidate.path, reason: "binary_or_corrupt" }],
6837
+ notes: [],
6838
+ searchedSessions: 0
6839
+ };
6840
+ }
6841
+ const records = flattenPotentialMessages(parsed).slice(0, 800);
6842
+ const fallbackTimestamp = new Date(stats.mtimeMs).toISOString();
6843
+ const sessionId = basename3(candidate.path).replace(/\.[^.]+$/, "");
6844
+ const events = records.flatMap(
6845
+ (record, index) => mapGenericJsonRecord(record, fallbackTimestamp).map(
6846
+ (item, partIndex) => makeRawEvent({
6847
+ client: candidate.source,
6848
+ kind: item.kind,
6849
+ now: options.now,
6850
+ path: candidate.path,
6851
+ payload: {
6852
+ extraction_mode: candidate.extractionMode,
6853
+ relative_source_path: candidate.path,
6854
+ text_summary: redactedText(item.text),
6855
+ tool_name: item.toolName
6856
+ },
6857
+ rawRowId: String(index),
6858
+ role: item.role,
6859
+ sessionId,
6860
+ timestamp: item.timestamp,
6861
+ uriSuffix: `json:${index}:part:${partIndex}`
6862
+ })
6863
+ )
6864
+ );
6865
+ return {
6866
+ events,
6867
+ filesRead: 1,
6868
+ filesSkipped: [],
6869
+ notes: [],
6870
+ searchedSessions: 1
6871
+ };
6872
+ }
6873
+ function flattenPotentialMessages(value) {
6874
+ const out = [];
6875
+ const visit = (item, depth) => {
6876
+ if (depth > 8 || out.length >= 1e3) return;
6877
+ if (Array.isArray(item)) {
6878
+ for (const child of item) visit(child, depth + 1);
6879
+ return;
6880
+ }
6881
+ if (!isRecord(item)) return;
6882
+ if (typeof item.role === "string" || typeof item.content === "string" || typeof item.text === "string" || typeof item.tool_name === "string" || typeof item.toolName === "string") {
6883
+ out.push(item);
6884
+ }
6885
+ for (const value2 of Object.values(item)) {
6886
+ if (Array.isArray(value2) || isRecord(value2)) visit(value2, depth + 1);
6887
+ }
6888
+ };
6889
+ visit(value, 0);
6890
+ return out;
6891
+ }
6892
+ function readMarkdownCandidate(candidate, options) {
6893
+ const stats = safeStat(candidate.path);
6894
+ if (!stats || stats.size > options.maxBytesPerFile) {
6895
+ return {
6896
+ events: [],
6897
+ filesRead: 0,
6898
+ filesSkipped: [{ path: candidate.path, reason: stats ? "schema_version_unsupported" : "permission_denied" }],
6899
+ notes: [],
6900
+ searchedSessions: 0
6901
+ };
6902
+ }
6903
+ const text2 = readFileSync4(candidate.path, "utf8");
6904
+ const sessionId = basename3(candidate.path).replace(/\.[^.]+$/, "");
6905
+ const timestamp = new Date(stats.mtimeMs).toISOString();
6906
+ const event = makeRawEvent({
6907
+ client: candidate.source,
6908
+ kind: candidate.path.includes(".cursor/rules") || candidate.path.endsWith(".cursorrules") ? "session_start" : "assistant_text",
6909
+ now: options.now,
6910
+ path: candidate.path,
6911
+ payload: {
6912
+ extraction_mode: candidate.extractionMode,
6913
+ relative_source_path: candidate.path,
6914
+ text_summary: redactedText(text2, 1200),
6915
+ tool_usage_state: candidate.path.includes("mcp.json") ? "configured" : void 0
6916
+ },
6917
+ role: "meta",
6918
+ sessionId,
6919
+ timestamp,
6920
+ uriSuffix: "markdown:0"
6921
+ });
6922
+ return {
6923
+ events: [event],
6924
+ filesRead: 1,
6925
+ filesSkipped: [],
6926
+ notes: candidate.extractionMode === "specstory" ? ["SpecStory evidence is repo-grounded and version-stable."] : [],
6927
+ searchedSessions: 1
6928
+ };
6929
+ }
6930
+ function discoverCandidates(source, env, sinceMs) {
6931
+ const candidates = [];
6932
+ const addFiles = (paths, extractionMode, predicate) => {
6933
+ for (const rootPattern of paths) {
6934
+ const expanded = expandPath(rootPattern, env);
6935
+ const root = expanded.includes("*") ? expanded.slice(0, expanded.indexOf("*")).replace(/\/$/, "") : expanded;
6936
+ const exactStats = safeStat(expanded);
6937
+ const found = exactStats?.isFile() ? [expanded] : walkFiles(root, predicate);
6938
+ for (const path of found) {
6939
+ const stats = safeStat(path);
6940
+ if (!stats || stats.mtimeMs < sinceMs) continue;
6941
+ candidates.push({ extractionMode, mtimeMs: stats.mtimeMs, path, source });
6942
+ }
6943
+ }
6944
+ };
6945
+ if (source === "codex") {
6946
+ addFiles([CODEX_SESSIONS_DIR], "jsonl", (path) => path.endsWith(".jsonl"));
6947
+ } else if (source === "claude_code") {
6948
+ addFiles([CLAUDE_PROJECTS_DIR], "jsonl", (path) => path.endsWith(".jsonl"));
6949
+ } else if (source === "opencode") {
6950
+ addFiles([
6951
+ "~/.local/share/opencode/storage/session",
6952
+ "~/Library/Application Support/opencode/storage/session"
6953
+ ], "json", (path) => path.endsWith(".json"));
6954
+ addFiles([
6955
+ "~/.local/share/opencode/storage/db.sqlite",
6956
+ "~/Library/Application Support/opencode/storage/db.sqlite"
6957
+ ], "sqlite", (path) => path.endsWith(".sqlite") || path.endsWith(".db"));
6958
+ } else if (source === "goose") {
6959
+ addFiles(["~/.local/share/goose/sessions"], "jsonl", (path) => path.endsWith(".jsonl"));
6960
+ addFiles([
6961
+ "~/.local/share/goose/logs",
6962
+ "~/.local/share/goose/data/sessions/sessions.db"
6963
+ ], "sqlite", (path) => path.endsWith(".db") || /llm_request\.\d+\.jsonl$/.test(path));
6964
+ } else if (source === "cursor") {
6965
+ addFiles(
6966
+ [
6967
+ "$CWD/.specstory/history",
6968
+ "$CWD/.cursor/rules",
6969
+ "$CWD/.cursorrules",
6970
+ "$CWD/.cursor/mcp.json"
6971
+ ],
6972
+ "specstory",
6973
+ (path) => path.endsWith(".md") || path.endsWith(".mdc") || path.endsWith(".json") || path.endsWith(".cursorrules")
6974
+ );
6975
+ addFiles([
6976
+ "~/Library/Application Support/Cursor/User/workspaceStorage",
6977
+ "~/.config/Cursor/User/workspaceStorage"
6978
+ ], "sqlite", (path) => path.endsWith("state.vscdb"));
6979
+ }
6980
+ return candidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
6981
+ }
6982
+ function discoverOverrideCandidates(source, root, sinceMs) {
6983
+ const extractionMode = root.endsWith(".json") ? "json" : "jsonl";
6984
+ const files = walkFiles(root, (path) => path.endsWith(".jsonl") || path.endsWith(".json"));
6985
+ const candidates = [];
6986
+ for (const path of files) {
6987
+ const stats = safeStat(path);
6988
+ if (!stats || stats.mtimeMs < sinceMs) continue;
6989
+ candidates.push({ extractionMode, mtimeMs: stats.mtimeMs, path, source });
6990
+ }
6991
+ return candidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
6992
+ }
6993
+ function findingTypeFromEvent(event) {
6994
+ const text2 = `${event.payload.text_summary ?? ""}
6995
+ ${event.payload.tool_name ?? ""}`;
6996
+ if (!WORK_SIGNAL_PATTERN.test(text2)) return null;
6997
+ if (/\b(decision|decided|tradeoff|architecture|approval)\b/i.test(text2)) return "decision";
6998
+ if (/\b(blocked|blocker|failed|fails|error|timeout|permission|schema|zod|invalid)\b/i.test(text2)) return "blocker";
6999
+ if (/\b(artifact|implemented|created|changed|edited|commit|pr|file|shipped|deploy)\b/i.test(text2)) return "artifact";
7000
+ if (/\b(test|verified|proof|outcome|result)\b/i.test(text2)) return "outcome";
7001
+ if (/\b(customer|business|revenue|deal|buyer)\b/i.test(text2)) return "business";
7002
+ if (/\b(agent|skill|recipe|tool|mcp__|orgx_)\b/i.test(text2)) return "missed_orchestration_opportunity";
7003
+ return "action";
7004
+ }
7005
+ function titleFromEvent(event) {
7006
+ const text2 = redactedText(String(event.payload.text_summary ?? event.payload.tool_name ?? "AI-client work signal"), 240);
7007
+ const first = text2.replace(/^(decision|artifact|blocker|proof|next action|outcome)\s*:\s*/i, "").split(/[.!?]\s/)[0]?.trim();
7008
+ return (first || `${event.source_id} work signal`).slice(0, 110);
7009
+ }
7010
+ function extractionFromEvents(input) {
7011
+ const findings = [];
7012
+ const seen = /* @__PURE__ */ new Set();
7013
+ for (const event of input.events) {
7014
+ const type = findingTypeFromEvent(event);
7015
+ if (!type) continue;
7016
+ const dedupe = `${type}:${titleFromEvent(event).toLowerCase()}`;
7017
+ if (seen.has(dedupe)) continue;
7018
+ seen.add(dedupe);
7019
+ findings.push({
7020
+ type,
7021
+ title: titleFromEvent(event),
7022
+ summary: type === "missed_orchestration_opportunity" && Number(event.payload.tool_name ? 1 : 0) === 0 ? "The client evidence mentions agent/tool workflow but does not prove an invocation event." : redactedText(String(event.payload.text_summary ?? "Client work signal"), 260),
7023
+ source_client: workGraphSourceClient(input.client),
7024
+ source_id: event.session_id,
7025
+ source_label: clientLabel(input.client),
7026
+ evidence_ref: event.source_ref.uri,
7027
+ confidence: event.kind === "mcp_tool_call" || event.kind === "tool_call_start" ? 0.82 : 0.68,
7028
+ occurred_at: event.timestamp,
7029
+ redacted_verbatim: redactedText(String(event.payload.text_summary ?? ""), 360),
7030
+ privacy_state: "redacted",
7031
+ metadata: {
7032
+ event_id: event.event_id,
7033
+ event_kind: event.kind,
7034
+ extraction_mode: event.payload.extraction_mode,
7035
+ provider: event.provider,
7036
+ tool_name: event.payload.tool_name
7037
+ }
7038
+ });
7039
+ if (findings.length >= 50) break;
7040
+ }
7041
+ return {
7042
+ schema_version: "2.0.0.investigation",
7043
+ extraction_id: `${input.client}:investigation:${hash([input.client, input.events.map((event) => event.event_id)], 12)}`,
7044
+ source_client: workGraphSourceClient(input.client),
7045
+ source_label: clientLabel(input.client),
7046
+ searched_sources: [...new Set(input.events.map((event) => String(event.payload.relative_source_path ?? event.session_id)))].slice(0, 30),
7047
+ search_queries: [
7048
+ {
7049
+ id: "full_session_inventory",
7050
+ lens: "Full-session deterministic inventory",
7051
+ query: "Read messages, tool calls, commands, configs, and repo-grounded session artifacts before deriving loop candidates.",
7052
+ result_count: input.events.length
7053
+ }
7054
+ ],
7055
+ extraction_quality: {
7056
+ confidence: findings.length > 0 ? 0.74 : input.filesRead > 0 ? 0.32 : 0.18,
7057
+ searched_session_count: Math.max(input.filesRead, new Set(input.events.map((event) => event.session_id)).size),
7058
+ skipped_session_count: input.filesSkipped.length,
7059
+ notes: [
7060
+ ...input.notes,
7061
+ `${input.events.length} normalized raw events; ${findings.length} public-safe loop candidates.`,
7062
+ ...input.filesSkipped.map((file) => `Skipped ${file.path}: ${file.reason}`)
7063
+ ]
7064
+ },
7065
+ findings
7066
+ };
7067
+ }
7068
+ function loadWorkGraphInvestigationSourceData(options) {
7069
+ const now = options.now ?? /* @__PURE__ */ new Date();
7070
+ const env = {
7071
+ cwd: options.cwd ? resolve(options.cwd) : process.cwd(),
7072
+ home: options.home ?? homedir2()
7073
+ };
7074
+ const sinceMs = now.getTime() - (options.sinceDays ?? DEFAULT_SINCE_DAYS2) * 24 * 60 * 60 * 1e3;
7075
+ const limit = Math.max(1, options.limitPerSource ?? DEFAULT_LIMIT_PER_SOURCE2);
7076
+ const maxBytesPerFile = Math.max(1, options.maxBytesPerFile ?? DEFAULT_MAX_BYTES_PER_FILE2);
7077
+ const rawEvents = [];
7078
+ const clientExtractions = [];
7079
+ const connectedSources = [];
7080
+ const missingSources = [];
7081
+ const sourceOverrides = /* @__PURE__ */ new Map();
7082
+ if (options.codexSessionsDir) sourceOverrides.set("codex", resolve(options.codexSessionsDir));
7083
+ if (options.claudeProjectsDir) sourceOverrides.set("claude_code", resolve(options.claudeProjectsDir));
7084
+ for (const source of options.sources) {
7085
+ const overrideRoot = sourceOverrides.get(source);
7086
+ const candidates = (overrideRoot ? discoverOverrideCandidates(source, overrideRoot, sinceMs) : discoverCandidates(source, env, sinceMs)).slice(0, limit);
7087
+ if (candidates.length === 0) {
7088
+ missingSources.push(`${clientLabel(source)} store`);
7089
+ continue;
7090
+ }
7091
+ const sourceEvents = [];
7092
+ const filesSkipped = [];
7093
+ let filesRead = 0;
7094
+ const notes = [];
7095
+ for (const candidate of candidates) {
7096
+ if (candidate.extractionMode === "sqlite") {
7097
+ filesSkipped.push({ path: candidate.path, reason: "schema_version_unsupported" });
7098
+ notes.push(`${candidate.path} detected; SQLite extraction is reserved for the cloud/runtime adapter reader.`);
7099
+ continue;
7100
+ }
7101
+ const result = candidate.path.endsWith(".jsonl") ? readJsonlCandidate(candidate, { maxBytesPerFile, now }) : candidate.path.endsWith(".json") && !candidate.path.endsWith("mcp.json") ? readJsonCandidate(candidate, { maxBytesPerFile, now }) : readMarkdownCandidate(candidate, { maxBytesPerFile, now });
7102
+ sourceEvents.push(...result.events);
7103
+ filesRead += result.filesRead;
7104
+ filesSkipped.push(...result.filesSkipped);
7105
+ notes.push(...result.notes);
7106
+ }
7107
+ rawEvents.push(...sourceEvents);
7108
+ clientExtractions.push(extractionFromEvents({
7109
+ client: source,
7110
+ events: sourceEvents,
7111
+ filesRead,
7112
+ filesSkipped,
7113
+ notes
7114
+ }));
7115
+ if (sourceEvents.length > 0 || filesRead > 0) {
7116
+ connectedSources.push(clientLabel(source));
7117
+ } else {
7118
+ missingSources.push(`${clientLabel(source)} readable events`);
7119
+ }
7120
+ }
7121
+ return {
7122
+ clientExtractions,
7123
+ connectedSources: [...new Set(connectedSources)],
7124
+ missingSources: [...new Set(missingSources)],
7125
+ rawEvents
7126
+ };
7127
+ }
7128
+
7129
+ // src/lib/self-audit.ts
7130
+ import { createHash as createHash4 } from "crypto";
6505
7131
  var SELF_AUDIT_SCHEMA_VERSION = "2026-04-27";
6506
7132
  var AUDIT_DIMENSIONS = [
6507
7133
  "queryability",
@@ -6668,7 +7294,7 @@ function buildSelfCritique(scores) {
6668
7294
  });
6669
7295
  }
6670
7296
  function hashPlanPayload(payload) {
6671
- return createHash3("sha256").update(JSON.stringify(payload)).digest("hex");
7297
+ return createHash4("sha256").update(JSON.stringify(payload)).digest("hex");
6672
7298
  }
6673
7299
  function buildSelfAuditPlan(input) {
6674
7300
  if (input.imports.length === 0) {
@@ -6998,10 +7624,1086 @@ Rollback: ${plan.recommended_follow_up.rollback}`,
6998
7624
  }
6999
7625
 
7000
7626
  // src/lib/work-graph.ts
7001
- import { createHash as createHash4 } from "crypto";
7002
- var WORK_GRAPH_SCHEMA_VERSION = "2026-05-07";
7627
+ import { createHash as createHash6 } from "crypto";
7628
+
7629
+ // src/lib/work-graph-investigation.ts
7630
+ import { createHash as createHash5 } from "crypto";
7631
+ var WORK_GRAPH_INVESTIGATION_SCHEMA_VERSION = "2.0.0";
7632
+ var WORK_GRAPH_INVESTIGATION_CLIENTS = [
7633
+ "claude_code",
7634
+ "codex",
7635
+ "opencode",
7636
+ "goose",
7637
+ "cursor",
7638
+ "github",
7639
+ "slack",
7640
+ "orgx_runtime_hook"
7641
+ ];
7642
+ var BRANDS = {
7643
+ claude_code: {
7644
+ name: "Claude Code",
7645
+ simple_icons: "anthropic",
7646
+ color: "#D97757",
7647
+ logo_kind: "wordmark+symbol"
7648
+ },
7649
+ codex: {
7650
+ name: "Codex",
7651
+ simple_icons: "openai",
7652
+ color: "#10A37F",
7653
+ logo_kind: "wordmark+symbol"
7654
+ },
7655
+ opencode: {
7656
+ name: "OpenCode",
7657
+ simple_icons: null,
7658
+ color: "#1A1A1A",
7659
+ logo_kind: "wordmark",
7660
+ asset_path: "/brands/opencode.svg"
7661
+ },
7662
+ goose: {
7663
+ name: "goose",
7664
+ simple_icons: null,
7665
+ color: "#FCBF49",
7666
+ logo_kind: "wordmark+symbol",
7667
+ asset_path: "/brands/goose.svg"
7668
+ },
7669
+ cursor: {
7670
+ name: "Cursor",
7671
+ simple_icons: null,
7672
+ color: "#FFFFFF",
7673
+ logo_kind: "wordmark+symbol",
7674
+ asset_path: "/brands/cursor.svg"
7675
+ },
7676
+ github: {
7677
+ name: "GitHub",
7678
+ simple_icons: "github",
7679
+ color: "#181717",
7680
+ logo_kind: "symbol"
7681
+ },
7682
+ slack: {
7683
+ name: "Slack",
7684
+ simple_icons: "slack",
7685
+ color: "#4A154B",
7686
+ logo_kind: "wordmark+symbol"
7687
+ },
7688
+ orgx_runtime_hook: {
7689
+ name: "OrgX Runtime",
7690
+ simple_icons: null,
7691
+ color: "#00C9A7",
7692
+ logo_kind: "wordmark+symbol",
7693
+ asset_path: "/brands/orgx.svg"
7694
+ }
7695
+ };
7696
+ var CAPABILITY_CEILINGS = {
7697
+ claude_code: {
7698
+ chronology: "high",
7699
+ ownership: "none",
7700
+ verification: "low",
7701
+ handoff: "medium",
7702
+ customer_context: "none",
7703
+ decision_lineage: "medium"
7704
+ },
7705
+ codex: {
7706
+ chronology: "high",
7707
+ ownership: "none",
7708
+ verification: "low",
7709
+ handoff: "medium",
7710
+ customer_context: "none",
7711
+ decision_lineage: "medium"
7712
+ },
7713
+ opencode: {
7714
+ chronology: "high",
7715
+ ownership: "none",
7716
+ verification: "medium",
7717
+ handoff: "medium",
7718
+ customer_context: "none",
7719
+ decision_lineage: "high"
7720
+ },
7721
+ goose: {
7722
+ chronology: "high",
7723
+ ownership: "low",
7724
+ verification: "medium",
7725
+ handoff: "low",
7726
+ customer_context: "low",
7727
+ decision_lineage: "high"
7728
+ },
7729
+ cursor: {
7730
+ chronology: "medium",
7731
+ ownership: "none",
7732
+ verification: "low",
7733
+ handoff: "low",
7734
+ customer_context: "none",
7735
+ decision_lineage: "medium"
7736
+ },
7737
+ github: {
7738
+ chronology: "high",
7739
+ ownership: "high",
7740
+ verification: "high",
7741
+ handoff: "none",
7742
+ customer_context: "low",
7743
+ decision_lineage: "medium"
7744
+ },
7745
+ slack: {
7746
+ chronology: "medium",
7747
+ ownership: "high",
7748
+ verification: "none",
7749
+ handoff: "high",
7750
+ customer_context: "high",
7751
+ decision_lineage: "low"
7752
+ },
7753
+ orgx_runtime_hook: {
7754
+ chronology: "high",
7755
+ ownership: "medium",
7756
+ verification: "high",
7757
+ handoff: "high",
7758
+ customer_context: "none",
7759
+ decision_lineage: "high"
7760
+ }
7761
+ };
7762
+ function hash2(value, length = 16) {
7763
+ return createHash5("sha256").update(JSON.stringify(value)).digest("hex").slice(0, length);
7764
+ }
7765
+ function clamp(value, min = 0, max = 1) {
7766
+ return Math.max(min, Math.min(max, value));
7767
+ }
7768
+ function countBy(values) {
7769
+ const counts = /* @__PURE__ */ new Map();
7770
+ for (const value of values) counts.set(value, (counts.get(value) ?? 0) + 1);
7771
+ return [...counts.entries()].map(([key, count]) => ({ key, count }));
7772
+ }
7773
+ function normalizeClient(value) {
7774
+ const lower = value.toLowerCase().replace(/-/g, "_");
7775
+ if (lower.includes("claude")) return "claude_code";
7776
+ if (lower.includes("codex") || lower.includes("openai")) return "codex";
7777
+ if (lower.includes("cursor")) return "cursor";
7778
+ if (lower.includes("opencode")) return "opencode";
7779
+ if (lower.includes("goose")) return "goose";
7780
+ if (lower.includes("github") || lower === "git") return "github";
7781
+ if (lower.includes("slack")) return "slack";
7782
+ if (lower.includes("hook") || lower.includes("runtime") || lower.includes("mcp")) return "orgx_runtime_hook";
7783
+ return "codex";
7784
+ }
7785
+ function providerForClient2(client) {
7786
+ if (client === "claude_code") return "anthropic";
7787
+ if (client === "codex") return "openai";
7788
+ return "unknown";
7789
+ }
7790
+ function eventKindForFinding(finding) {
7791
+ if (finding.type === "decision") return "assistant_reasoning";
7792
+ if (finding.type === "artifact") return "file_edit";
7793
+ if (finding.type === "blocker") return "tool_call_error";
7794
+ if (finding.type === "outcome") return "test_run";
7795
+ if (finding.type === "missed_orchestration_opportunity") return "mcp_tool_call";
7796
+ return "assistant_text";
7797
+ }
7798
+ function windowFor(timestamp, now) {
7799
+ const occurred = Date.parse(timestamp);
7800
+ const current = Date.parse(now);
7801
+ if (!Number.isFinite(occurred) || !Number.isFinite(current)) return "older";
7802
+ const delta = current - occurred;
7803
+ if (delta < 5 * 60 * 1e3) return "live";
7804
+ if (delta <= 24 * 60 * 60 * 1e3) return "recent_24h";
7805
+ if (delta <= 7 * 24 * 60 * 60 * 1e3) return "recent_7d";
7806
+ if (delta <= 30 * 24 * 60 * 60 * 1e3) return "recent_30d";
7807
+ return "older";
7808
+ }
7809
+ function rawEventFromFinding(finding, index, generatedAt) {
7810
+ const sourceClient = normalizeClient(finding.source_client);
7811
+ const timestamp = typeof finding.metadata.occurred_at === "string" ? finding.metadata.occurred_at : generatedAt;
7812
+ const eventId = `evt_${hash2([finding.evidence_ref, finding.title, index], 24)}`;
7813
+ const payload = {
7814
+ title: finding.title,
7815
+ summary: finding.summary,
7816
+ finding_type: finding.type,
7817
+ source_id: finding.source_id,
7818
+ confidence: finding.confidence,
7819
+ redacted_verbatim: typeof finding.metadata.redacted_verbatim === "string" ? finding.metadata.redacted_verbatim : finding.summary.slice(0, 420)
7820
+ };
7821
+ return {
7822
+ event_id: eventId,
7823
+ source_ref: {
7824
+ source_id: sourceClient,
7825
+ uri: finding.evidence_ref,
7826
+ cite_url: null,
7827
+ resolves: true
7828
+ },
7829
+ source_id: sourceClient,
7830
+ provider: providerForClient2(sourceClient),
7831
+ session_id: finding.source_id,
7832
+ project_id: null,
7833
+ timestamp,
7834
+ occurred_in_window: windowFor(timestamp, generatedAt),
7835
+ kind: eventKindForFinding(finding),
7836
+ role: finding.type === "blocker" ? "tool" : "meta",
7837
+ payload,
7838
+ raw_byte_offset: null,
7839
+ raw_row_id: null,
7840
+ content_hash: hash2(payload, 64),
7841
+ redaction_applied: true
7842
+ };
7843
+ }
7844
+ function rawEventFromSourceEvent(event, index, generatedAt) {
7845
+ const sourceClient = normalizeClient(event.source_client);
7846
+ const payload = {
7847
+ source_label: event.source_label,
7848
+ event_type: event.event_type,
7849
+ text_summary: event.text.slice(0, 420)
7850
+ };
7851
+ return {
7852
+ event_id: `evt_${hash2([event.evidence_ref, index], 24)}`,
7853
+ source_ref: {
7854
+ source_id: sourceClient,
7855
+ uri: event.evidence_ref,
7856
+ cite_url: null,
7857
+ resolves: true
7858
+ },
7859
+ source_id: sourceClient,
7860
+ provider: providerForClient2(sourceClient),
7861
+ session_id: event.source_id,
7862
+ project_id: null,
7863
+ timestamp: generatedAt,
7864
+ occurred_in_window: "live",
7865
+ kind: event.event_type === "tool_signal" ? "tool_call_start" : "assistant_text",
7866
+ role: "meta",
7867
+ payload,
7868
+ raw_byte_offset: null,
7869
+ raw_row_id: null,
7870
+ content_hash: hash2(payload, 64),
7871
+ redaction_applied: true
7872
+ };
7873
+ }
7874
+ function buildRawEvents(input) {
7875
+ const sourceEvents = input.events.map(
7876
+ (event, index) => rawEventFromSourceEvent(event, index, input.generatedAt)
7877
+ );
7878
+ const findingEvents = input.findings.map(
7879
+ (finding, index) => rawEventFromFinding(finding, index, input.generatedAt)
7880
+ );
7881
+ const byUri = /* @__PURE__ */ new Map();
7882
+ for (const event of [...input.rawEvents ?? [], ...sourceEvents, ...findingEvents]) {
7883
+ byUri.set(event.event_id || `${event.source_ref.uri}:${event.kind}`, event);
7884
+ }
7885
+ return [...byUri.values()].sort(
7886
+ (left, right) => left.timestamp.localeCompare(right.timestamp)
7887
+ );
7888
+ }
7889
+ function eventsForRefs(events, refs) {
7890
+ const refSet = new Set(refs);
7891
+ return events.filter((event) => refSet.has(event.source_ref.uri));
7892
+ }
7893
+ function intentClassForFinding(finding) {
7894
+ const text2 = `${finding.title}
7895
+ ${finding.summary}`;
7896
+ if (finding.type === "decision") return "architecture_decision";
7897
+ if (finding.type === "artifact" || /\b(implemented|built|created|shipped)\b/i.test(text2)) {
7898
+ return "feature_implementation";
7899
+ }
7900
+ if (/\b(test|qa|verified|proof)\b/i.test(text2)) return "verification_or_testing";
7901
+ if (/\b(auth|config|mcp|source|connect|integration|hook)\b/i.test(text2)) {
7902
+ return "integration_or_connection";
7903
+ }
7904
+ if (/\b(refactor|cleanup|rename|remove)\b/i.test(text2)) return "cleanup_or_refactor";
7905
+ if (/\b(doc|readme|methodology|runbook)\b/i.test(text2)) return "documentation";
7906
+ if (finding.type === "blocker" && /\bbug|error|fail|zod|schema\b/i.test(text2)) return "bug_fix";
7907
+ if (finding.type === "blocker") return "investigation";
7908
+ return "unclear";
7909
+ }
7910
+ function bottleneckClassForText(text2) {
7911
+ if (/\bzod\b|schema|validation|expected .* received/i.test(text2)) return "schema_validation";
7912
+ if (/permission|approval|denied|unauthorized|401|403/i.test(text2)) return "permission_denied";
7913
+ if (/missing arg|wrong type|tool.*misconfigured|invalid/i.test(text2)) return "tool_misconfigured";
7914
+ if (/context overflow|compaction|too large/i.test(text2)) return "context_overflow";
7915
+ if (/auth|billing|quota|402|api key|token/i.test(text2)) return "auth_or_billing";
7916
+ if (/dispatch|race|not dispatched|ready stream/i.test(text2)) return "race_or_dispatch";
7917
+ if (/cross.repo|repo boundary|vendored|mirror/i.test(text2)) return "cross_repo_dependency";
7918
+ if (/external|api failed|timeout|timed out/i.test(text2)) return "external_api_failure";
7919
+ if (/owner|handoff|dri|approval/i.test(text2)) return "human_handoff_missing";
7920
+ return null;
7921
+ }
7922
+ function terminalForTrail(trail) {
7923
+ if (trail.state === "verified" || trail.valence === "healthy") {
7924
+ return {
7925
+ state: "shipped",
7926
+ proof: trail.evidence_refs[0] ? { kind: "inferred_only", ref: trail.evidence_refs[0] } : { kind: "inferred_only" },
7927
+ classified_by: "deterministic",
7928
+ classification_basis: ["verified_or_healthy_trail_state"]
7929
+ };
7930
+ }
7931
+ if (trail.state === "blocked" || trail.valence === "risk" || trail.valence === "escalating") {
7932
+ return {
7933
+ state: "blocked",
7934
+ proof: { kind: "absent", expected_kind: "owner_visible_resolution" },
7935
+ classified_by: "deterministic",
7936
+ classification_basis: ["blocked_or_escalating_trail_state"]
7937
+ };
7938
+ }
7939
+ if (trail.state === "stale" || trail.state === "decaying") {
7940
+ return {
7941
+ state: "abandoned",
7942
+ proof: { kind: "absent", expected_kind: "later_resolution_event" },
7943
+ classified_by: "deterministic",
7944
+ classification_basis: ["stale_or_decaying_trail_state"]
7945
+ };
7946
+ }
7947
+ return {
7948
+ state: "ongoing",
7949
+ proof: trail.evidence_refs[0] ? { kind: "inferred_only", ref: trail.evidence_refs[0] } : { kind: "inferred_only" },
7950
+ classified_by: "hybrid",
7951
+ classification_basis: ["no_deterministic_terminal_event"]
7952
+ };
7953
+ }
7954
+ function buildWorkLoops(input) {
7955
+ const findingsByRef = new Map(input.findings.map((finding) => [finding.evidence_ref, finding]));
7956
+ return input.trails.map((trail, index) => {
7957
+ const matchedEvents = eventsForRefs(input.events, trail.evidence_refs);
7958
+ const matchedFindings = trail.evidence_refs.map((ref) => findingsByRef.get(ref)).filter((finding) => Boolean(finding));
7959
+ const firstFinding = matchedFindings[0];
7960
+ const eventIds = matchedEvents.map((event) => event.event_id);
7961
+ const text2 = [
7962
+ trail.title,
7963
+ trail.summary,
7964
+ ...matchedFindings.map((finding) => finding.summary)
7965
+ ].join("\n");
7966
+ const bottleneck = bottleneckClassForText(text2);
7967
+ const terminal = terminalForTrail(trail);
7968
+ const confidence = clamp(
7969
+ trail.confidence || matchedFindings.reduce((total, finding) => total + finding.confidence, 0) / Math.max(1, matchedFindings.length)
7970
+ );
7971
+ return {
7972
+ loop_id: `loop_${hash2([trail.id, index], 18)}`,
7973
+ cites: eventIds,
7974
+ origin: {
7975
+ event_id: eventIds[0] ?? `evt_missing_${index}`,
7976
+ timestamp: matchedEvents[0]?.timestamp ?? trail.created_at,
7977
+ intent: trail.title.slice(0, 160),
7978
+ intent_class: firstFinding ? intentClassForFinding(firstFinding) : "unclear"
7979
+ },
7980
+ path: {
7981
+ event_ids: eventIds,
7982
+ actions_taken: matchedEvents.map((event) => ({
7983
+ kind: event.kind,
7984
+ label: String(event.payload.title ?? event.kind).slice(0, 120),
7985
+ event_id: event.event_id
7986
+ })),
7987
+ sessions_spanned: [...new Set(matchedEvents.map((event) => event.session_id))],
7988
+ tools_used: matchedEvents.filter((event) => event.kind === "tool_call_start" || event.kind === "mcp_tool_call").map((event) => String(event.payload.tool_name ?? event.payload.title ?? "unknown_tool")),
7989
+ span_minutes: Math.max(1, matchedEvents.length * 12)
7990
+ },
7991
+ terminal,
7992
+ bottleneck: {
7993
+ class: bottleneck,
7994
+ evidence_event_ids: bottleneck ? eventIds : []
7995
+ },
7996
+ recurrence: {
7997
+ family_id: null,
7998
+ appearances_in_family: 1,
7999
+ is_first_appearance: true,
8000
+ decay_half_life_days: terminal.state === "blocked" ? 3 : null
8001
+ },
8002
+ confidence,
8003
+ survived_critic: confidence >= 0.6 && eventIds.length > 0,
8004
+ public_surface: confidence >= 0.6 && eventIds.length > 0
8005
+ };
8006
+ });
8007
+ }
8008
+ function loopFamilyKey(loop) {
8009
+ return [
8010
+ loop.origin.intent_class,
8011
+ loop.terminal.state,
8012
+ loop.bottleneck.class ?? "none"
8013
+ ].join(":");
8014
+ }
8015
+ function shapeForFamily(loops) {
8016
+ if (loops.length <= 1) return "single_signal";
8017
+ if (loops.some((loop) => loop.terminal.state === "shipped")) return "decaying_resolved";
8018
+ if (loops.some((loop) => loop.bottleneck.class)) return loops.length >= 3 ? "accelerating" : "dense_recent_cluster";
8019
+ return loops.length >= 4 ? "chronic_evenly_spaced" : "dense_recent_cluster";
8020
+ }
8021
+ function repairForFamily(familyLoops, impact) {
8022
+ const top = familyLoops[0];
8023
+ const title = top?.origin.intent ?? "Investigate work loop";
8024
+ const bottleneck = top?.bottleneck.class;
8025
+ const capability = bottleneck === "human_handoff_missing" ? "mcp__orgx__assign_owner" : bottleneck ? "mcp__orgx__create_blocker" : top?.origin.intent_class === "architecture_decision" ? "mcp__orgx__create_decision" : "mcp__orgx__scaffold_initiative";
8026
+ return {
8027
+ named_orgx_capability: capability,
8028
+ expected_tool_call_args: {
8029
+ title,
8030
+ source: "work_graph_investigation",
8031
+ evidence_event_ids: familyLoops.flatMap((loop) => loop.cites).slice(0, 12),
8032
+ terminal_state: top?.terminal.state ?? "ongoing",
8033
+ bottleneck_class: bottleneck ?? null
8034
+ },
8035
+ expected_outcome: "OrgX would create an owner-visible record tied to the source evidence, then suppress duplicate rediscovery until it is reopened.",
8036
+ expected_score_lift: [
8037
+ { dimension: "chronology_depth", delta: Math.min(18, familyLoops.length * 3) },
8038
+ { dimension: "repair_actionability", delta: capability.includes("scaffold") ? 12 : 16 }
8039
+ ],
8040
+ expected_hours_recovered: Number(Math.min(impact.time_saved_hours_per_week, familyLoops.length * 0.75).toFixed(1)),
8041
+ expected_dollars_recovered: Math.round(Math.min(impact.estimated_monthly_value_usd, familyLoops.length * 0.75 * 4.33 * 200)),
8042
+ preconditions: ["claim_profile", "confirm_or_correct_evidence"]
8043
+ };
8044
+ }
8045
+ function buildLoopFamilies(loops, events, impact) {
8046
+ const grouped = /* @__PURE__ */ new Map();
8047
+ for (const loop of loops) {
8048
+ const key = loopFamilyKey(loop);
8049
+ grouped.set(key, [...grouped.get(key) ?? [], loop]);
8050
+ }
8051
+ const families = [...grouped.entries()].map(([key, group]) => {
8052
+ const sourceIds = new Set(
8053
+ group.flatMap(
8054
+ (loop) => loop.cites.map((cite) => events.find((event) => event.event_id === cite)?.source_id).filter(Boolean)
8055
+ )
8056
+ );
8057
+ const familyId = `family_${hash2(key, 16)}`;
8058
+ const timestamps = group.map((loop) => Date.parse(loop.origin.timestamp)).filter(Number.isFinite);
8059
+ const spanDays = timestamps.length > 1 ? Math.max(1, Math.ceil((Math.max(...timestamps) - Math.min(...timestamps)) / 864e5)) : 0;
8060
+ const divergenceScore = group.length >= 3 ? 0.32 : 0.5;
8061
+ return {
8062
+ family_id: familyId,
8063
+ shared_intent_class: group[0]?.origin.intent_class ?? "unclear",
8064
+ shared_terminal_state: group[0]?.terminal.state ?? "ongoing",
8065
+ shared_bottleneck_class: group[0]?.bottleneck.class ?? null,
8066
+ loop_ids: group.map((loop) => loop.loop_id),
8067
+ appearances: group.length,
8068
+ span_days: spanDays,
8069
+ shape: shapeForFamily(group),
8070
+ semantic_centroid: group[0]?.origin.intent ?? "Work loop family",
8071
+ divergence_score: divergenceScore,
8072
+ cross_source_confluence: sourceIds.size >= 2,
8073
+ orgx_repair: repairForFamily(group, impact),
8074
+ alternative_explanations_considered: [
8075
+ "The loops may be related only by vocabulary.",
8076
+ "The apparent recurrence may be a planned multi-step implementation rather than unresolved work."
8077
+ ],
8078
+ alternative_rejected_because: group.length >= 3 ? ["Shared intent class, terminal state, and bottleneck class recur across multiple evidence events."] : ["Kept as a non-public single/weak family until more chronology exists."],
8079
+ survived_critic: group.length >= 3 && divergenceScore < 0.5,
8080
+ public_surface: group.length >= 3 && divergenceScore < 0.5
8081
+ };
8082
+ });
8083
+ const familyMap = new Map(families.map((family) => [family.family_id, family]));
8084
+ for (const family of families) {
8085
+ for (const loopId of family.loop_ids) {
8086
+ const loop = loops.find((candidate) => candidate.loop_id === loopId);
8087
+ if (!loop) continue;
8088
+ loop.recurrence.family_id = family.family_id;
8089
+ loop.recurrence.appearances_in_family = family.appearances;
8090
+ loop.recurrence.is_first_appearance = family.loop_ids[0] === loop.loop_id;
8091
+ }
8092
+ }
8093
+ return [...familyMap.values()].sort((left, right) => right.appearances - left.appearances);
8094
+ }
8095
+ function buildUsageCatalogue(events, findings) {
8096
+ const toolEvents = events.filter((event) => event.kind === "tool_call_start" || event.kind === "mcp_tool_call");
8097
+ const mcpToolNames = /* @__PURE__ */ new Map();
8098
+ for (const event of toolEvents) {
8099
+ const text2 = `${event.payload.title ?? ""}
8100
+ ${event.payload.summary ?? ""}
8101
+ ${event.payload.text_summary ?? ""}`;
8102
+ const match = text2.match(/\b(?:mcp__[\w-]+__[\w-]+|orgx_[\w-]+|complete_with_proof|ship_batch|record_outcome|scaffold_initiative)\b/i);
8103
+ const name = match?.[0] ?? "unknown_tool";
8104
+ mcpToolNames.set(name, [...mcpToolNames.get(name) ?? [], event]);
8105
+ }
8106
+ const mentionedTools = /* @__PURE__ */ new Set();
8107
+ for (const finding of findings) {
8108
+ const matches = `${finding.title}
8109
+ ${finding.summary}`.matchAll(/\b(?:mcp__[\w-]+__[\w-]+|orgx_[\w-]+|complete_with_proof|ship_batch|record_outcome|scaffold_initiative)\b/gi);
8110
+ for (const match of matches) mentionedTools.add(match[0]);
8111
+ }
8112
+ const configuredOrgxEvents = events.filter(
8113
+ (event) => event.payload.tool_usage_state === "configured" && /\borgx\b|mcp/i.test(JSON.stringify(event.payload))
8114
+ );
8115
+ const shouldHaveInvoked = findings.filter(
8116
+ (finding) => finding.type === "missed_orchestration_opportunity" || /\bshould have|available but not called|not invoked|writeback\b/i.test(`${finding.title}
8117
+ ${finding.summary}`)
8118
+ );
8119
+ const mcp_tools = [
8120
+ ...[...mcpToolNames.entries()].map(([name, matchingEvents]) => ({
8121
+ name,
8122
+ namespace: name.startsWith("mcp__") ? name.split("__")[1] ?? "unknown" : "orgx",
8123
+ state: matchingEvents.length > 0 ? "invoked" : "mentioned",
8124
+ invocations: matchingEvents.length,
8125
+ success: matchingEvents.filter((event) => event.kind === "tool_call_result").length,
8126
+ error: matchingEvents.filter((event) => event.kind === "tool_call_error").length,
8127
+ error_classes: [],
8128
+ by_source: countBy(matchingEvents.map((event) => event.source_id)).map(({ key, count }) => ({
8129
+ source_id: key,
8130
+ count
8131
+ })),
8132
+ event_refs: matchingEvents.map((event) => event.event_id)
8133
+ })),
8134
+ ...[...mentionedTools].filter((name) => !mcpToolNames.has(name)).map((name) => ({
8135
+ name,
8136
+ namespace: name.startsWith("mcp__") ? name.split("__")[1] ?? "unknown" : "orgx",
8137
+ state: "mentioned",
8138
+ invocations: 0,
8139
+ success: 0,
8140
+ error: 0,
8141
+ error_classes: [],
8142
+ by_source: [],
8143
+ event_refs: []
8144
+ })),
8145
+ ...configuredOrgxEvents.length > 0 && !mcpToolNames.has("mcp__orgx__configured") ? [{
8146
+ name: "mcp__orgx__configured",
8147
+ namespace: "orgx",
8148
+ state: "configured",
8149
+ invocations: 0,
8150
+ success: 0,
8151
+ error: 0,
8152
+ error_classes: [],
8153
+ by_source: countBy(configuredOrgxEvents.map((event) => event.source_id)).map(({ key, count }) => ({
8154
+ source_id: key,
8155
+ count
8156
+ })),
8157
+ event_refs: configuredOrgxEvents.map((event) => event.event_id)
8158
+ }] : [],
8159
+ ...shouldHaveInvoked.length > 0 && !mcpToolNames.has("mcp__orgx__create_decision") ? [{
8160
+ name: "mcp__orgx__create_decision",
8161
+ namespace: "orgx",
8162
+ state: "should_have_invoked",
8163
+ invocations: 0,
8164
+ success: 0,
8165
+ error: 0,
8166
+ error_classes: [],
8167
+ by_source: [],
8168
+ event_refs: []
8169
+ }] : []
8170
+ ];
8171
+ const ai_clients = WORK_GRAPH_INVESTIGATION_CLIENTS.slice(0, 5).map((sourceId) => {
8172
+ const sourceEvents = events.filter((event) => event.source_id === sourceId);
8173
+ return {
8174
+ source_id: sourceId,
8175
+ sessions: new Set(sourceEvents.map((event) => event.session_id)).size,
8176
+ turns: sourceEvents.filter((event) => event.kind === "user_prompt" || event.kind === "assistant_text").length
8177
+ };
8178
+ });
8179
+ return {
8180
+ ai_clients,
8181
+ mcp_tools,
8182
+ mcp_namespaces_seen: [...new Set(mcp_tools.map((tool) => tool.namespace))],
8183
+ shell_commands: [],
8184
+ recipes_or_skills: findings.filter((finding) => /\bskill|recipe|agent\b/i.test(`${finding.title}
8185
+ ${finding.summary}`)).slice(0, 20).map((finding) => ({
8186
+ source_id: normalizeClient(finding.source_client),
8187
+ name: finding.title.slice(0, 80),
8188
+ invocations: 0,
8189
+ last_used: typeof finding.metadata.occurred_at === "string" ? finding.metadata.occurred_at : (/* @__PURE__ */ new Date()).toISOString()
8190
+ })),
8191
+ agents_observed: ai_clients.filter((client) => client.sessions > 0).map((client) => ({
8192
+ source_id: client.source_id,
8193
+ name: BRANDS[client.source_id].name,
8194
+ spawn_count: client.sessions,
8195
+ success_count: 0
8196
+ })),
8197
+ repo_paths_touched: [],
8198
+ external_sources_called: []
8199
+ };
8200
+ }
8201
+ function capabilityCellsFor(input) {
8202
+ const ceilings = CAPABILITY_CEILINGS[input.sourceId];
8203
+ const sourceEvents = input.events.filter((event) => event.source_id === input.sourceId);
8204
+ const achievedBase = input.status === "connected" ? 0.68 : input.status === "partial" ? 0.42 : input.status === "detected_not_read" ? 0.16 : 0;
8205
+ const hasToolState = sourceEvents.some((event) => event.kind.includes("tool"));
8206
+ const hasVerification = sourceEvents.some((event) => event.kind === "test_run" || event.kind === "commit");
8207
+ const hasHandoff = sourceEvents.some((event) => /handoff|owner|approval/i.test(JSON.stringify(event.payload)));
8208
+ return Object.fromEntries(
8209
+ Object.keys(ceilings).map((dimension) => {
8210
+ const ceiling = ceilings[dimension];
8211
+ const boost = dimension === "decision_lineage" && hasToolState || dimension === "verification" && hasVerification || dimension === "handoff" && hasHandoff ? 0.18 : 0;
8212
+ const achieved = ceiling === "none" ? 0 : clamp(achievedBase + boost);
8213
+ return [
8214
+ dimension,
8215
+ {
8216
+ ceiling,
8217
+ achieved,
8218
+ basis: sourceEvents.length > 0 ? `${sourceEvents.length} normalized raw event${sourceEvents.length === 1 ? "" : "s"}` : input.status === "missing" ? "Source supported but not connected for this audit" : "Source detected without enough readable evidence",
8219
+ blockers_to_full: achieved >= 0.8 ? [] : [
8220
+ dimension === "verification" ? "connect_github_or_runtime_hook" : dimension === "ownership" || dimension === "handoff" ? "connect_slack_or_claim_profile" : "deepen_native_session_events"
8221
+ ]
8222
+ }
8223
+ ];
8224
+ })
8225
+ );
8226
+ }
8227
+ function buildCorpusManifest(input) {
8228
+ const manifests = WORK_GRAPH_INVESTIGATION_CLIENTS.map((sourceId) => {
8229
+ const sourceEvents = input.events.filter((event) => event.source_id === sourceId);
8230
+ const matchingExtraction = input.clientExtractions.find(
8231
+ (extraction) => normalizeClient(extraction.source_client) === sourceId
8232
+ );
8233
+ const coverageManifest = input.coverage.manifests?.find(
8234
+ (manifest) => normalizeClient(manifest.source_client) === sourceId
8235
+ );
8236
+ const status = sourceEvents.length > 0 || matchingExtraction && matchingExtraction.finding_count > 0 || coverageManifest?.status === "connected" ? "connected" : matchingExtraction ? "detected_not_read" : coverageManifest?.status === "partial" ? "partial" : input.coverage.missing.some((source) => normalizeClient(source) === sourceId) ? "missing" : "supported_not_connected";
8237
+ const skipped = coverageManifest?.skipped_session_count ? [{ reason: "no_entity_references", count: coverageManifest.skipped_session_count }] : [];
8238
+ const cells = capabilityCellsFor({ sourceId, status, events: input.events });
8239
+ const sourceSessionCount = new Set(sourceEvents.map((event) => event.session_id)).size;
8240
+ return {
8241
+ source_id: sourceId,
8242
+ brand: BRANDS[sourceId],
8243
+ client: sourceId,
8244
+ provider: providerForClient2(sourceId),
8245
+ status,
8246
+ extraction_mode: sourceId === "opencode" || sourceId === "goose" ? "sqlite" : sourceId === "cursor" ? "specstory" : sourceId === "orgx_runtime_hook" ? "orgx_runtime_hook" : "jsonl",
8247
+ storage_paths_resolved: coverageManifest?.searched_sources ?? [],
8248
+ storage_version_detected: null,
8249
+ files_seen: coverageManifest?.searched_session_count ?? matchingExtraction?.searched_session_count ?? sourceSessionCount,
8250
+ files_read: sourceEvents.length > 0 ? Math.max(1, sourceSessionCount) : 0,
8251
+ files_skipped: skipped,
8252
+ sessions: sourceSessionCount || matchingExtraction?.searched_session_count || coverageManifest?.searched_session_count || 0,
8253
+ message_turns: sourceEvents.filter((event) => event.kind === "user_prompt" || event.kind === "assistant_text").length || (sourceId === "codex" || sourceId === "claude_code" ? input.auditMethod.searched_message_count : 0),
8254
+ tool_calls: sourceEvents.filter((event) => event.kind === "tool_call_start").length,
8255
+ mcp_tool_calls: sourceEvents.filter((event) => event.kind === "mcp_tool_call").length,
8256
+ capability_cells: cells
8257
+ };
8258
+ });
8259
+ const timestamps = input.events.map((event) => event.timestamp).sort();
8260
+ const filesSeen = manifests.reduce((total, source) => total + source.files_seen, 0);
8261
+ const filesRead = manifests.reduce((total, source) => total + source.files_read, 0);
8262
+ const filesSkipped = manifests.reduce(
8263
+ (total, source) => total + source.files_skipped.reduce((sum, skipped) => sum + skipped.count, 0),
8264
+ 0
8265
+ );
8266
+ const sessions = manifests.reduce((total, source) => total + source.sessions, 0);
8267
+ return {
8268
+ audit_id: input.auditId,
8269
+ ran_at: input.generatedAt,
8270
+ sources: manifests,
8271
+ totals: {
8272
+ files_seen: Math.max(filesSeen, input.auditMethod.searched_session_files),
8273
+ files_read: Math.max(filesRead, input.auditMethod.searched_session_files - input.auditMethod.skipped_session_files),
8274
+ files_skipped: Math.max(filesSkipped, input.auditMethod.skipped_session_files),
8275
+ bytes_read: 0,
8276
+ sessions: Math.max(sessions, input.auditMethod.searched_session_files),
8277
+ sessions_skipped: input.auditMethod.skipped_session_files ? [{ reason: "no_entity_references", count: input.auditMethod.skipped_session_files }] : [],
8278
+ message_turns: Math.max(
8279
+ input.auditMethod.searched_message_count,
8280
+ input.events.filter((event) => event.kind === "user_prompt" || event.kind === "assistant_text").length
8281
+ ),
8282
+ user_prompts: input.events.filter((event) => event.kind === "user_prompt").length,
8283
+ assistant_turns: input.events.filter((event) => event.kind === "assistant_text").length,
8284
+ assistant_reasoning_turns: input.events.filter((event) => event.kind === "assistant_reasoning").length,
8285
+ tool_calls: input.events.filter((event) => event.kind === "tool_call_start").length,
8286
+ mcp_tool_calls: input.events.filter((event) => event.kind === "mcp_tool_call").length,
8287
+ tool_call_errors: input.events.filter((event) => event.kind === "tool_call_error").length,
8288
+ file_edits: input.events.filter((event) => event.kind === "file_edit").length,
8289
+ file_reads: input.events.filter((event) => event.kind === "file_read").length,
8290
+ shell_commands: input.events.filter((event) => event.kind === "shell_command").length,
8291
+ test_runs: input.events.filter((event) => event.kind === "test_run").length,
8292
+ errors_emitted: input.events.filter((event) => event.kind === "error_emitted").length,
8293
+ permission_requests: input.events.filter((event) => event.kind === "permission_request").length,
8294
+ forks: input.events.filter((event) => event.kind === "session_fork").length,
8295
+ compactions: input.events.filter((event) => event.kind === "compaction").length,
8296
+ earliest_event: timestamps[0] ?? null,
8297
+ latest_event: timestamps[timestamps.length - 1] ?? null
8298
+ }
8299
+ };
8300
+ }
8301
+ function buildSourceConfidence(corpusManifest) {
8302
+ return {
8303
+ rows: [
8304
+ "chronology",
8305
+ "ownership",
8306
+ "verification",
8307
+ "handoff",
8308
+ "customer_context",
8309
+ "decision_lineage"
8310
+ ],
8311
+ columns: corpusManifest.sources
8312
+ };
8313
+ }
8314
+ function verifyItems(items, events) {
8315
+ const eventIds = new Set(events.map((event) => event.event_id));
8316
+ return items.map((item) => {
8317
+ const failed = item.cites.filter((cite) => !eventIds.has(cite)).map((cite) => ({ event_id_ref: cite, reason: "missing_event" }));
8318
+ return {
8319
+ finding_id: item.id,
8320
+ citations_total: item.cites.length,
8321
+ citations_resolved: item.cites.length - failed.length,
8322
+ citations_failed: failed,
8323
+ content_hashes_match: true,
8324
+ passed: item.cites.length > 0 && failed.length === 0
8325
+ };
8326
+ });
8327
+ }
8328
+ function criticForLoop(loop) {
8329
+ const survival = clamp(loop.confidence - (loop.cites.length <= 1 ? 0.08 : 0));
8330
+ return {
8331
+ finding_id: loop.loop_id,
8332
+ attack_vectors_attempted: [
8333
+ "is_this_actually_recurring",
8334
+ "is_this_actually_blocked",
8335
+ "is_the_intent_actually_what_we_say",
8336
+ "are_these_events_actually_related",
8337
+ "is_the_dollar_estimate_defensible",
8338
+ "would_orgx_actually_have_helped",
8339
+ "is_the_owner_actually_ambiguous",
8340
+ "is_the_recurrence_just_text_overlap"
8341
+ ],
8342
+ weakest_claim: loop.cites.length <= 1 ? "Only one resolved citation supports this loop; chronology depth is limited." : "The loop is supported, but source coverage may still miss owner or verification context.",
8343
+ most_plausible_alternative: loop.cites.length <= 1 ? "This may be a single work signal rather than a recurring bottleneck." : "This could be normal staged execution rather than unresolved rework.",
8344
+ survival_score: Number(survival.toFixed(2)),
8345
+ survived: survival >= 0.6,
8346
+ demoted_to: survival >= 0.6 ? "public" : "weak_signal"
8347
+ };
8348
+ }
8349
+ function counterfactualForLoop(loop, family) {
8350
+ if (!loop.survived_critic || loop.cites.length === 0) return null;
8351
+ const repair = family?.orgx_repair ?? repairForFamily([loop], {
8352
+ time_saved_hours_per_week: 1,
8353
+ acceleration_percent: 10,
8354
+ estimated_monthly_value_usd: 800,
8355
+ confidence: loop.confidence,
8356
+ basis: [],
8357
+ assumptions: []
8358
+ });
8359
+ const realism = clamp(loop.confidence + (loop.terminal.state === "blocked" ? 0.06 : 0), 0, 0.92);
8360
+ if (realism < 0.7) return null;
8361
+ const entityKind = repair.named_orgx_capability.includes("decision") ? "decision" : repair.named_orgx_capability.includes("blocker") ? "task" : repair.named_orgx_capability.includes("artifact") ? "artifact" : "initiative";
8362
+ return {
8363
+ triggered_by_event_id: loop.origin.event_id,
8364
+ triggered_at: loop.origin.timestamp,
8365
+ actual_user_event_summary: `You worked on "${loop.origin.intent}" without a durable OrgX repair record tied to the source event.`,
8366
+ actual_outcome: loop.terminal.state === "blocked" ? "The loop stayed blocked or returned as new work." : "The work stayed visible in session evidence, but not as durable operating memory.",
8367
+ orgx_capability: {
8368
+ tool: repair.named_orgx_capability,
8369
+ args: repair.expected_tool_call_args,
8370
+ args_basis: ["loop.origin.intent", "loop.cites", "loop.terminal.state", "loop.bottleneck.class"]
8371
+ },
8372
+ orgx_outcome: repair.expected_outcome,
8373
+ resulting_entity: {
8374
+ kind: entityKind,
8375
+ inferred_id: `orgx_${entityKind}_${hash2(loop.loop_id, 12)}`,
8376
+ would_link_to: loop.cites
8377
+ },
8378
+ realism_score: Number(realism.toFixed(2)),
8379
+ realism_basis: [
8380
+ "The triggering event resolves to the normalized corpus.",
8381
+ "The proposed OrgX capability maps directly to the loop terminal state and bottleneck.",
8382
+ "The output is deterministic: create or link an owner-visible entity rather than asserting improvement."
8383
+ ],
8384
+ could_have_failed_because: [
8385
+ "The user may not have granted write permission.",
8386
+ "The inferred title or owner might need human correction after claim.",
8387
+ "The source may lack enough downstream verification until GitHub or runtime hooks are connected."
8388
+ ],
8389
+ generated_by_prompt_version: "orgx-investigation-counterfactual-v1",
8390
+ survived_critic: true
8391
+ };
8392
+ }
8393
+ function buildWhyNot100(input) {
8394
+ const entries = [];
8395
+ const singleSignal = input.loops.filter((loop) => loop.cites.length <= 1).length;
8396
+ const confluenceFamilies = input.families.filter((family) => family.cross_source_confluence).length;
8397
+ const fileEdits = input.corpus.totals.file_edits;
8398
+ if (confluenceFamilies === 0) {
8399
+ entries.push({
8400
+ dimension: "cross_source_confluence",
8401
+ current_score: 0,
8402
+ precise_gap: `0 cross-source loop families were proven; ${input.loops.length} loops still need a second source such as GitHub, Slack, or runtime hooks.`,
8403
+ user_action: {
8404
+ kind: "connect_source",
8405
+ target: "GitHub or Slack",
8406
+ ux_route: "/integrations",
8407
+ copy_for_button: "Connect proof source"
8408
+ },
8409
+ orgx_action: {
8410
+ kind: "auto_correlate",
8411
+ description: "OrgX will join session events to commits, approvals, and runtime writeback events.",
8412
+ triggers_when: "on_next_audit_run"
8413
+ },
8414
+ expected_score_lift: 22,
8415
+ expected_findings_unlocked: Math.max(1, Math.ceil(input.loops.length * 0.35)),
8416
+ preview_findings: [
8417
+ {
8418
+ title_redacted: "Session decision linked to commit proof",
8419
+ evidence_count_anticipated: Math.max(2, fileEdits || 2),
8420
+ confidence_anticipated: 0.82,
8421
+ derivation_basis: "extrapolated_from_existing"
8422
+ }
8423
+ ],
8424
+ preconditions: ["connect_github_or_slack"],
8425
+ estimated_seconds_to_complete: 90
8426
+ });
8427
+ }
8428
+ if (singleSignal > 0) {
8429
+ entries.push({
8430
+ dimension: "chronology_depth",
8431
+ current_score: Math.max(1, 10 - Math.min(9, singleSignal)),
8432
+ precise_gap: `${singleSignal} work loop${singleSignal === 1 ? "" : "s"} have only one resolved event; OrgX can identify them but cannot yet prove before/after motion.`,
8433
+ user_action: {
8434
+ kind: "install_hook",
8435
+ target: "OrgX runtime hooks",
8436
+ ux_route: "/settings/integrations/orgx-runtime",
8437
+ copy_for_button: "Install runtime hooks"
8438
+ },
8439
+ orgx_action: {
8440
+ kind: "auto_writeback",
8441
+ description: "OrgX will append session-start, tool-use, stop, and outcome events to the same loop.",
8442
+ triggers_when: "on_user_action_completion"
8443
+ },
8444
+ expected_score_lift: Math.min(24, singleSignal * 3),
8445
+ expected_findings_unlocked: singleSignal,
8446
+ preview_findings: [
8447
+ {
8448
+ title_redacted: "Blocked loop gains start, retry, and resolution events",
8449
+ evidence_count_anticipated: 3,
8450
+ confidence_anticipated: 0.78,
8451
+ derivation_basis: "rule_based"
8452
+ }
8453
+ ],
8454
+ preconditions: ["install_runtime_hook"],
8455
+ estimated_seconds_to_complete: 120
8456
+ });
8457
+ }
8458
+ if (input.coverage.missing.length > 0) {
8459
+ entries.push({
8460
+ dimension: "source_coverage",
8461
+ current_score: Math.max(0, 10 - input.coverage.missing.length * 2),
8462
+ precise_gap: `${input.coverage.missing.length} source gap${input.coverage.missing.length === 1 ? "" : "s"} remain: ${input.coverage.missing.join(", ")}.`,
8463
+ user_action: {
8464
+ kind: "connect_source",
8465
+ target: input.coverage.missing[0] ?? "missing source",
8466
+ ux_route: "/integrations",
8467
+ copy_for_button: "Connect missing source"
8468
+ },
8469
+ orgx_action: {
8470
+ kind: "auto_classify",
8471
+ description: "OrgX will classify ownership, handoff, and verification evidence from the newly connected source.",
8472
+ triggers_when: "on_next_audit_run"
8473
+ },
8474
+ expected_score_lift: Math.min(18, input.coverage.missing.length * 4),
8475
+ expected_findings_unlocked: Math.max(1, input.coverage.missing.length * 2),
8476
+ preview_findings: [
8477
+ {
8478
+ title_redacted: "Owner-visible handoff replaces inferred blocker state",
8479
+ evidence_count_anticipated: 2,
8480
+ confidence_anticipated: 0.8,
8481
+ derivation_basis: "rule_based"
8482
+ }
8483
+ ],
8484
+ preconditions: ["authorize_source"],
8485
+ estimated_seconds_to_complete: 75
8486
+ });
8487
+ }
8488
+ return entries.slice(0, 6);
8489
+ }
8490
+ function buildRepairPlan(input) {
8491
+ const firstFamily = input.families.find((family) => family.public_surface) ?? input.families[0];
8492
+ const firstCounterfactual = input.counterfactuals[0];
8493
+ const actions = [];
8494
+ if (firstFamily || firstCounterfactual) {
8495
+ const closes = firstFamily?.loop_ids ?? (firstCounterfactual ? [firstCounterfactual.triggered_by_event_id] : []);
8496
+ actions.push({
8497
+ id: "repair:promote-loop",
8498
+ type: firstFamily?.orgx_repair.named_orgx_capability.includes("assign") ? "assign_owner" : "promote_decision",
8499
+ title: "Repair the top recurring work loop",
8500
+ closes_loop_ids: closes,
8501
+ expected_gain: "execution continuity",
8502
+ expected_hours_recovered: firstFamily?.orgx_repair.expected_hours_recovered ?? 0.8,
8503
+ expected_dollars_recovered: firstFamily?.orgx_repair.expected_dollars_recovered ?? 650,
8504
+ required_inputs: ["confirmed evidence", "owner or fallback owner"],
8505
+ confidence: firstCounterfactual?.realism_score ?? 0.72,
8506
+ time_to_value: "10 minutes after claim",
8507
+ preview_tree: [
8508
+ { kind: "workstream", title: "Repair recurring AI work loop" },
8509
+ { kind: "milestone", title: "Promote decision or blocker to durable record" },
8510
+ { kind: "task", title: "Attach source evidence and owner-visible next step" }
8511
+ ]
8512
+ });
8513
+ }
8514
+ const sourceGap = input.whyNot100.find((entry) => entry.dimension === "source_coverage" || entry.dimension === "cross_source_confluence");
8515
+ if (sourceGap) {
8516
+ actions.push({
8517
+ id: "repair:connect-source",
8518
+ type: "connect_source",
8519
+ title: sourceGap.user_action?.copy_for_button ?? "Connect missing proof source",
8520
+ closes_loop_ids: [],
8521
+ expected_gain: "source confidence",
8522
+ expected_hours_recovered: 0,
8523
+ expected_dollars_recovered: 0,
8524
+ required_inputs: [sourceGap.user_action?.target ?? "source authorization"],
8525
+ confidence: 0.78,
8526
+ time_to_value: `${Math.ceil(sourceGap.estimated_seconds_to_complete / 60)} minutes`,
8527
+ preview_tree: [
8528
+ { kind: "workstream", title: "Connect source evidence" },
8529
+ { kind: "task", title: "Correlate session events to proof, owner, and handoff signals" }
8530
+ ]
8531
+ });
8532
+ }
8533
+ actions.push({
8534
+ id: "repair:launch-profile",
8535
+ type: "convert_to_initiative",
8536
+ title: "Launch from this investigation",
8537
+ closes_loop_ids: firstFamily?.loop_ids ?? [],
8538
+ expected_gain: "initiative readiness",
8539
+ expected_hours_recovered: firstFamily?.orgx_repair.expected_hours_recovered ?? 0,
8540
+ expected_dollars_recovered: firstFamily?.orgx_repair.expected_dollars_recovered ?? 0,
8541
+ required_inputs: ["claim profile", "approve repair plan"],
8542
+ confidence: 0.7,
8543
+ time_to_value: "1 click after claim",
8544
+ preview_tree: [
8545
+ { kind: "workstream", title: "Investigation to durable OrgX initiative" },
8546
+ { kind: "milestone", title: "Convert loop family into assigned work" },
8547
+ { kind: "task", title: "Close the highest-confidence counterfactual gap" }
8548
+ ]
8549
+ });
8550
+ return actions.slice(0, 3);
8551
+ }
8552
+ function buildMirror(input) {
8553
+ const topFamily = input.families.find((family) => family.public_surface) ?? input.families[0];
8554
+ const topLoop = input.loops[0];
8555
+ const sourceCount = input.corpus.sources.filter((source) => source.status === "connected" || source.status === "partial").length;
8556
+ const dropped = input.loops.filter((loop) => !loop.survived_critic).length;
8557
+ const text2 = [
8558
+ `You have AI-assisted work spread across ${sourceCount} connected or partial source${sourceCount === 1 ? "" : "s"}, but the execution record is still incomplete.`,
8559
+ topFamily ? `The clearest repeated loop is "${topFamily.semantic_centroid}", with ${topFamily.appearances} appearance${topFamily.appearances === 1 ? "" : "s"} and a ${topFamily.shared_terminal_state} terminal state.` : topLoop ? `The clearest work loop is "${topLoop.origin.intent}", but it still needs more chronology before OrgX should call it recurring.` : "The corpus did not produce a verified work loop yet.",
8560
+ input.counterfactuals[0] ? `OrgX can point to the exact event where it would have called ${input.counterfactuals[0].orgx_capability.tool} and what entity it would have created.` : "Counterfactual repair is waiting on a higher-confidence loop with resolved citations.",
8561
+ `The current estimate is ${input.impact.time_saved_hours_per_week} recoverable hours/week, grounded in ${input.impact.basis[0] ?? "resolved work-loop evidence"}.`,
8562
+ dropped > 0 ? `${dropped} weak signal${dropped === 1 ? " was" : "s were"} kept out of the public readout because the citations or critic score did not clear the bar.` : "Every surfaced loop cleared citation verification and the critic floor."
8563
+ ].join(" ");
8564
+ return {
8565
+ text: text2,
8566
+ evidence_pills: [
8567
+ {
8568
+ phrase: `${input.loops.length} work loop${input.loops.length === 1 ? "" : "s"}`,
8569
+ cites: input.loops.flatMap((loop) => loop.cites).slice(0, 12)
8570
+ },
8571
+ {
8572
+ phrase: `${sourceCount} source${sourceCount === 1 ? "" : "s"}`,
8573
+ cites: []
8574
+ },
8575
+ {
8576
+ phrase: `${input.counterfactuals.length} earned counterfactual${input.counterfactuals.length === 1 ? "" : "s"}`,
8577
+ cites: input.counterfactuals.map((item) => item.triggered_by_event_id)
8578
+ }
8579
+ ]
8580
+ };
8581
+ }
8582
+ function buildWorkGraphInvestigation(input) {
8583
+ const auditId = `wgi_${hash2([input.fingerprint, input.generatedAt], 24)}`;
8584
+ const rawEvents = buildRawEvents(input);
8585
+ const corpus = buildCorpusManifest({
8586
+ auditId,
8587
+ auditMethod: input.auditMethod,
8588
+ clientExtractions: input.clientExtractions,
8589
+ coverage: input.coverage,
8590
+ events: rawEvents,
8591
+ generatedAt: input.generatedAt
8592
+ });
8593
+ const loops = buildWorkLoops({ events: rawEvents, findings: input.findings, trails: input.trails });
8594
+ const families = buildLoopFamilies(loops, rawEvents, input.impact);
8595
+ const usage = buildUsageCatalogue(rawEvents, input.findings);
8596
+ const sourceConfidence = buildSourceConfidence(corpus);
8597
+ const loopCritics = loops.map(criticForLoop);
8598
+ const familyByLoop = /* @__PURE__ */ new Map();
8599
+ for (const family of families) {
8600
+ for (const loopId of family.loop_ids) familyByLoop.set(loopId, family);
8601
+ }
8602
+ const verifiedLoops = loops.filter((loop) => {
8603
+ const critic = loopCritics.find((entry) => entry.finding_id === loop.loop_id);
8604
+ return loop.public_surface && critic?.survived;
8605
+ });
8606
+ const counterfactuals = verifiedLoops.map((loop) => counterfactualForLoop(loop, familyByLoop.get(loop.loop_id))).filter((item) => Boolean(item)).slice(0, 8);
8607
+ const verificationItems = [
8608
+ ...loops.map((loop) => ({ id: loop.loop_id, cites: loop.cites })),
8609
+ ...counterfactuals.map((item) => ({
8610
+ id: `counterfactual:${item.resulting_entity.inferred_id}`,
8611
+ cites: [item.triggered_by_event_id]
8612
+ }))
8613
+ ];
8614
+ const verification = verifyItems(verificationItems, rawEvents);
8615
+ const whyNot100 = buildWhyNot100({
8616
+ corpus,
8617
+ coverage: input.coverage,
8618
+ loops,
8619
+ families,
8620
+ impact: input.impact
8621
+ });
8622
+ const repairPlan = buildRepairPlan({ counterfactuals, families, whyNot100 });
8623
+ const mirror = buildMirror({
8624
+ loops,
8625
+ families,
8626
+ counterfactuals,
8627
+ corpus,
8628
+ impact: input.impact
8629
+ });
8630
+ const eventBySource = countBy(rawEvents.map((event) => event.source_id)).map(({ key, count }) => ({
8631
+ source_id: key,
8632
+ count
8633
+ }));
8634
+ const eventByKind = countBy(rawEvents.map((event) => event.kind)).map(({ key, count }) => ({
8635
+ kind: key,
8636
+ count
8637
+ }));
8638
+ return {
8639
+ schema_version: WORK_GRAPH_INVESTIGATION_SCHEMA_VERSION,
8640
+ audit_id: auditId,
8641
+ fingerprint: input.fingerprint,
8642
+ generated_at: input.generatedAt,
8643
+ corpus_manifest: corpus,
8644
+ raw_events_summary: {
8645
+ count: rawEvents.length,
8646
+ by_source: eventBySource,
8647
+ by_kind: eventByKind,
8648
+ earliest: corpus.totals.earliest_event,
8649
+ latest: corpus.totals.latest_event
8650
+ },
8651
+ raw_events: rawEvents.slice(0, 500),
8652
+ work_loops: loops,
8653
+ loop_families: families,
8654
+ usage_catalogue: usage,
8655
+ source_confidence: sourceConfidence,
8656
+ why_not_100: whyNot100,
8657
+ counterfactuals,
8658
+ critic_log: [
8659
+ ...loopCritics,
8660
+ ...families.map((family) => ({
8661
+ finding_id: family.family_id,
8662
+ attack_vectors_attempted: [
8663
+ "is_this_actually_recurring",
8664
+ "is_the_recurrence_just_text_overlap",
8665
+ "are_these_events_actually_related"
8666
+ ],
8667
+ weakest_claim: family.appearances < 3 ? "The family has fewer than 3 appearances and should not be public as recurrence." : "The family still depends on source normalization and may need human correction.",
8668
+ most_plausible_alternative: family.appearances < 3 ? "This is a single signal or thin pair, not a real recurring loop." : "The loops may be related staged work rather than repeated unresolved work.",
8669
+ survival_score: family.survived_critic ? 0.72 : 0.52,
8670
+ survived: family.survived_critic,
8671
+ demoted_to: family.public_surface ? "public" : "weak_signal"
8672
+ }))
8673
+ ],
8674
+ verification_log: verification,
8675
+ mirror_paragraph: mirror,
8676
+ repair_plan: repairPlan,
8677
+ impact_projection: {
8678
+ hours_recoverable_per_week: {
8679
+ value: input.impact.time_saved_hours_per_week,
8680
+ basis: input.impact.basis.join(" "),
8681
+ confidence: input.impact.confidence
8682
+ },
8683
+ dollars_recoverable_per_month: {
8684
+ value: input.impact.estimated_monthly_value_usd,
8685
+ basis: "hours_recoverable_per_week * 4.33 weeks * blended operator hourly value",
8686
+ confidence: input.impact.confidence
8687
+ },
8688
+ acceleration_pct: {
8689
+ value: input.impact.acceleration_percent,
8690
+ basis: "directional execution lift from repairable loops, source coverage, and automation potential",
8691
+ confidence: input.impact.confidence
8692
+ }
8693
+ },
8694
+ redaction_log: [
8695
+ { kind: "raw_transcripts_excluded", count: 1 },
8696
+ { kind: "redacted_event_payloads", count: rawEvents.length }
8697
+ ],
8698
+ raw_transcripts_excluded: true,
8699
+ claimable: loops.some((loop) => loop.public_surface)
8700
+ };
8701
+ }
8702
+
8703
+ // src/lib/work-graph.ts
8704
+ var WORK_GRAPH_SCHEMA_VERSION = "2.0.0";
7003
8705
  var WORK_GRAPH_FINGERPRINT_VERSION = "wgf_v1";
7004
- var WORK_GRAPH_EXTRACTION_SCHEMA_VERSION = "2026-05-07.ai-client-search.v1";
8706
+ var WORK_GRAPH_EXTRACTION_SCHEMA_VERSION = "2.0.0.investigation";
7005
8707
  var WORK_GRAPH_FINDING_TYPES = [
7006
8708
  "action",
7007
8709
  "decision",
@@ -7019,7 +8721,7 @@ function clampScore2(value) {
7019
8721
  return Math.max(0, Math.min(100, Math.round(value)));
7020
8722
  }
7021
8723
  function hashJson(value) {
7022
- return createHash4("sha256").update(JSON.stringify(value)).digest("hex");
8724
+ return createHash6("sha256").update(JSON.stringify(value)).digest("hex");
7023
8725
  }
7024
8726
  function normalizeFingerprintText(value) {
7025
8727
  return value.toLowerCase().replace(/https?:\/\/\S+/g, "url").replace(/[0-9a-f]{12,}/g, "hash").replace(/\b[0-9a-f]{8}-[0-9a-f-]{27,}\b/g, "uuid").replace(/\s+/g, " ").trim().slice(0, 600);
@@ -7040,6 +8742,10 @@ function sourceClientForImport(source) {
7040
8742
  if (raw.includes("claude-code")) return "claude-code";
7041
8743
  if (raw.includes("claude")) return "claude";
7042
8744
  if (raw.includes("cursor")) return "cursor";
8745
+ if (raw.includes("opencode")) return "opencode";
8746
+ if (raw.includes("goose")) return "goose";
8747
+ if (raw.includes("opencode")) return "opencode";
8748
+ if (raw.includes("goose")) return "goose";
7043
8749
  if (raw.includes("openclaw")) return "openclaw";
7044
8750
  if (raw.includes("slack")) return "slack";
7045
8751
  if (raw.includes("github")) return "github";
@@ -7056,12 +8762,14 @@ function sourceClientForImport(source) {
7056
8762
  function normalizeSourceClient(value) {
7057
8763
  if (typeof value !== "string") return "unknown";
7058
8764
  const normalized = value.trim().toLowerCase();
7059
- if (normalized === "codex" || normalized === "claude" || normalized === "claude-code" || normalized === "cursor" || normalized === "openclaw" || normalized === "slack" || normalized === "mcp" || normalized === "github" || normalized === "linear" || normalized === "gmail" || normalized === "calendar" || normalized === "notion" || normalized === "docs" || normalized === "manual" || normalized === "wizard" || normalized === "api") {
8765
+ if (normalized === "codex" || normalized === "claude" || normalized === "claude-code" || normalized === "cursor" || normalized === "opencode" || normalized === "goose" || normalized === "openclaw" || normalized === "slack" || normalized === "mcp" || normalized === "orgx_runtime_hook" || normalized === "github" || normalized === "linear" || normalized === "gmail" || normalized === "calendar" || normalized === "notion" || normalized === "docs" || normalized === "manual" || normalized === "wizard" || normalized === "api") {
7060
8766
  return normalized;
7061
8767
  }
7062
8768
  if (normalized.includes("claude")) return "claude";
7063
8769
  if (normalized.includes("codex")) return "codex";
7064
8770
  if (normalized.includes("cursor")) return "cursor";
8771
+ if (normalized.includes("opencode")) return "opencode";
8772
+ if (normalized.includes("goose")) return "goose";
7065
8773
  if (normalized.includes("slack")) return "slack";
7066
8774
  if (normalized.includes("github")) return "github";
7067
8775
  if (normalized.includes("linear")) return "linear";
@@ -7070,6 +8778,7 @@ function normalizeSourceClient(value) {
7070
8778
  if (normalized.includes("notion")) return "notion";
7071
8779
  if (normalized.includes("doc")) return "docs";
7072
8780
  if (normalized.includes("mcp")) return "mcp";
8781
+ if (normalized.includes("runtime") || normalized.includes("hook")) return "orgx_runtime_hook";
7073
8782
  return "unknown";
7074
8783
  }
7075
8784
  function sourceClientFromText(value) {
@@ -7077,6 +8786,8 @@ function sourceClientFromText(value) {
7077
8786
  if (/\bclaude(?:[- ]code)?\b|\.claude\/projects|claude:/.test(normalized)) return "claude";
7078
8787
  if (/\bcodex\b|\.codex\/sessions|rollout-/.test(normalized)) return "codex";
7079
8788
  if (/\bcursor\b/.test(normalized)) return "cursor";
8789
+ if (/\bopencode\b/.test(normalized)) return "opencode";
8790
+ if (/\bgoose\b/.test(normalized)) return "goose";
7080
8791
  if (/\bopenclaw\b/.test(normalized)) return "openclaw";
7081
8792
  if (/\bslack\b/.test(normalized)) return "slack";
7082
8793
  if (/\bgithub\b|\bgit:|pull request|commit\b/.test(normalized)) return "github";
@@ -7219,7 +8930,7 @@ function buildWorkGraphExtractionProtocol() {
7219
8930
  required_output: {
7220
8931
  schema_version: WORK_GRAPH_EXTRACTION_SCHEMA_VERSION,
7221
8932
  extraction_id: "stable id for this extraction run",
7222
- source_client: "codex | claude | claude-code | cursor | openclaw | slack | mcp | github | linear | gmail | calendar | notion | docs | manual | api | unknown",
8933
+ source_client: "codex | claude | claude-code | cursor | opencode | goose | openclaw | slack | mcp | github | linear | gmail | calendar | notion | docs | manual | api | unknown",
7223
8934
  source_label: "human-readable source label",
7224
8935
  searched_sources: ["session/log/source group names searched"],
7225
8936
  search_queries: [
@@ -7348,8 +9059,12 @@ ${extractionText}`.toLowerCase();
7348
9059
  ...normalizedConnectedSources,
7349
9060
  ...sourceClients.includes("codex") ? ["Codex sessions"] : [],
7350
9061
  ...sourceClients.includes("claude") || sourceClients.includes("claude-code") ? ["Claude Code sessions"] : [],
9062
+ ...sourceClients.includes("cursor") ? ["Cursor workspace evidence"] : [],
9063
+ ...sourceClients.includes("opencode") ? ["OpenCode sessions"] : [],
9064
+ ...sourceClients.includes("goose") ? ["goose sessions"] : [],
7351
9065
  ...sourceClients.includes("github") ? ["Git/GitHub proof"] : [],
7352
9066
  ...sourceClients.includes("mcp") ? ["MCP tool telemetry"] : [],
9067
+ ...sourceClients.includes("orgx_runtime_hook") ? ["OrgX runtime hook replay"] : [],
7353
9068
  ...sourceClients.includes("slack") ? ["Slack coordination"] : []
7354
9069
  ];
7355
9070
  const inferredMissing = [
@@ -7369,7 +9084,7 @@ ${extractionText}`.toLowerCase();
7369
9084
  });
7370
9085
  const partialCount = manifests.filter((manifest) => manifest.status === "partial").length;
7371
9086
  const coverageScore = clampScore2(
7372
- 28 + Math.min(34, connected.length * 7) + Math.min(18, findings.length * 2) + (sourceClients.includes("codex") ? 6 : 0) + (sourceClients.includes("claude") || sourceClients.includes("claude-code") ? 6 : 0) - missing.length * 7 - partialCount * 3
9087
+ 28 + Math.min(34, connected.length * 7) + Math.min(18, findings.length * 2) + (sourceClients.includes("codex") ? 6 : 0) + (sourceClients.includes("claude") || sourceClients.includes("claude-code") ? 6 : 0) + (sourceClients.includes("cursor") ? 4 : 0) + (sourceClients.includes("opencode") ? 4 : 0) + (sourceClients.includes("goose") ? 4 : 0) - missing.length * 7 - partialCount * 3
7373
9088
  );
7374
9089
  return {
7375
9090
  connected,
@@ -7466,7 +9181,7 @@ function buildSourceCoverageManifests(input) {
7466
9181
  });
7467
9182
  }
7468
9183
  function canonicalSourceManifestLabel(manifest) {
7469
- if (manifest.source_client === "codex" || manifest.source_client === "claude" || manifest.source_client === "claude-code") {
9184
+ if (manifest.source_client === "codex" || manifest.source_client === "claude" || manifest.source_client === "claude-code" || manifest.source_client === "cursor" || manifest.source_client === "opencode" || manifest.source_client === "goose") {
7470
9185
  return labelForSourceClient(manifest.source_client);
7471
9186
  }
7472
9187
  if (manifest.source_client === "github" || manifest.source_client === "slack" || manifest.source_client === "mcp") {
@@ -7481,8 +9196,16 @@ function labelForSourceClient(sourceClient) {
7481
9196
  case "claude":
7482
9197
  case "claude-code":
7483
9198
  return "Claude Code sessions";
9199
+ case "cursor":
9200
+ return "Cursor workspace evidence";
9201
+ case "opencode":
9202
+ return "OpenCode sessions";
9203
+ case "goose":
9204
+ return "goose sessions";
7484
9205
  case "mcp":
7485
9206
  return "MCP tool telemetry";
9207
+ case "orgx_runtime_hook":
9208
+ return "OrgX runtime hook replay";
7486
9209
  case "github":
7487
9210
  return "Git/GitHub proof";
7488
9211
  case "slack":
@@ -7548,7 +9271,9 @@ function numberFromImportMetadata(source, key) {
7548
9271
  }
7549
9272
  function buildAuditMethod(input) {
7550
9273
  const extractionSummaries = summarizeClientExtractions(input.clientExtractions);
7551
- const nativePacks = extractionSummaries.filter((summary) => summary.source_client === "codex" || summary.source_client === "claude" || summary.source_client === "claude-code").map((summary) => ({
9274
+ const nativePacks = extractionSummaries.filter(
9275
+ (summary) => summary.source_client === "codex" || summary.source_client === "claude" || summary.source_client === "claude-code" || summary.source_client === "cursor" || summary.source_client === "opencode" || summary.source_client === "goose"
9276
+ ).map((summary) => ({
7552
9277
  source_client: summary.source_client,
7553
9278
  source_label: summary.source_label,
7554
9279
  searched_session_count: summary.searched_session_count,
@@ -8932,7 +10657,7 @@ function buildAgentNodes(findings) {
8932
10657
  );
8933
10658
  const sourceAgents = sortedUnique(
8934
10659
  findings.map(
8935
- (finding) => ["codex", "claude", "claude-code", "cursor", "openclaw"].includes(finding.source_client) ? finding.source_client : ""
10660
+ (finding) => ["codex", "claude", "claude-code", "cursor", "opencode", "goose", "openclaw"].includes(finding.source_client) ? finding.source_client : ""
8936
10661
  ).filter(Boolean)
8937
10662
  );
8938
10663
  return [...actorIds, ...sourceAgents].slice(0, 20).map((agent) => ({
@@ -9250,6 +10975,20 @@ function buildSessionReconciliationReport(input) {
9250
10975
  };
9251
10976
  const reportHash = hashJson(reportSeed);
9252
10977
  const sessionId = input.sessionId ?? `work-graph-${reportHash.slice(0, 16)}`;
10978
+ const investigation = buildWorkGraphInvestigation({
10979
+ auditMethod,
10980
+ clientExtractions: clientExtractionSummaries,
10981
+ coverage,
10982
+ events,
10983
+ findings: allFindings,
10984
+ fingerprint: fingerprint.fingerprint,
10985
+ generatedAt,
10986
+ impact: impactProjection,
10987
+ patterns: recurringPatterns,
10988
+ ...input.investigationRawEvents ? { rawEvents: input.investigationRawEvents } : {},
10989
+ recommendations,
10990
+ trails
10991
+ });
9253
10992
  return {
9254
10993
  schema_version: WORK_GRAPH_SCHEMA_VERSION,
9255
10994
  report_id: reportHash.slice(0, 24),
@@ -9281,6 +11020,7 @@ function buildSessionReconciliationReport(input) {
9281
11020
  opportunity_score: opportunityScore,
9282
11021
  execution_quality: executionQuality,
9283
11022
  impact_projection: impactProjection,
11023
+ investigation,
9284
11024
  initiative_kickoffs: initiativeKickoffs,
9285
11025
  redaction_level: "summary_only",
9286
11026
  raw_transcripts_sent: false
@@ -9328,7 +11068,7 @@ function renderWorkGraphExtractionProtocolMarkdown(protocol = buildWorkGraphExtr
9328
11068
  }
9329
11069
  function renderWorkGraphMarkdown(report) {
9330
11070
  const lines = [];
9331
- lines.push("# OrgX Work Graph Reconciliation");
11071
+ lines.push("# OrgX Investigation Engine");
9332
11072
  lines.push("");
9333
11073
  lines.push(`Generated: ${report.generated_at}`);
9334
11074
  lines.push(`Workspace: ${report.workspace.name} (${report.workspace.id})`);
@@ -9359,6 +11099,44 @@ function renderWorkGraphMarkdown(report) {
9359
11099
  lines.push(`- ${note}`);
9360
11100
  }
9361
11101
  lines.push("");
11102
+ lines.push("## Investigation v2");
11103
+ lines.push("");
11104
+ lines.push(`Schema: ${report.investigation.schema_version}`);
11105
+ lines.push(`Audit ID: ${report.investigation.audit_id}`);
11106
+ lines.push(`Raw events normalized: ${report.investigation.raw_events_summary.count}`);
11107
+ lines.push(`Work loops: ${report.investigation.work_loops.length}`);
11108
+ lines.push(`Recurring loop families: ${report.investigation.loop_families.filter((family) => family.public_surface).length}`);
11109
+ const passedVerification = report.investigation.verification_log.filter((entry) => entry.passed).length;
11110
+ const droppedVerification = report.investigation.verification_log.length - passedVerification;
11111
+ lines.push(`Verification: ${passedVerification} passed, ${droppedVerification} dropped as unverifiable`);
11112
+ lines.push(`Earned counterfactuals: ${report.investigation.counterfactuals.length}`);
11113
+ lines.push("");
11114
+ lines.push("Mirror:");
11115
+ lines.push(report.investigation.mirror_paragraph.text);
11116
+ lines.push("");
11117
+ if (report.investigation.counterfactuals[0]) {
11118
+ const counterfactual = report.investigation.counterfactuals[0];
11119
+ lines.push("Top earned counterfactual:");
11120
+ lines.push(`- Trigger: ${counterfactual.triggered_by_event_id} at ${counterfactual.triggered_at}`);
11121
+ lines.push(`- Actual: ${counterfactual.actual_outcome}`);
11122
+ lines.push(`- OrgX would call: ${counterfactual.orgx_capability.tool}`);
11123
+ lines.push(`- Outcome: ${counterfactual.orgx_outcome}`);
11124
+ lines.push("");
11125
+ }
11126
+ if (report.investigation.why_not_100.length > 0) {
11127
+ lines.push("What improves it:");
11128
+ for (const gap of report.investigation.why_not_100) {
11129
+ lines.push(`- ${gap.precise_gap} ${gap.user_action ? `Action: ${gap.user_action.copy_for_button} (${gap.user_action.ux_route}).` : ""}`);
11130
+ }
11131
+ lines.push("");
11132
+ }
11133
+ lines.push("Source capability matrix:");
11134
+ for (const source of report.investigation.source_confidence.columns) {
11135
+ const chronology = source.capability_cells.chronology;
11136
+ const verification = source.capability_cells.verification;
11137
+ lines.push(`- ${source.brand.name}: ${source.status}, chronology ${Math.round(chronology.achieved * 100)}%/${chronology.ceiling}, verification ${Math.round(verification.achieved * 100)}%/${verification.ceiling}`);
11138
+ }
11139
+ lines.push("");
9362
11140
  lines.push("## Client Extractions");
9363
11141
  lines.push("");
9364
11142
  if (report.client_extractions.length === 0) {
@@ -9629,8 +11407,8 @@ async function publishWorkGraphEvents(fingerprint, patch, options = {}) {
9629
11407
  }
9630
11408
 
9631
11409
  // src/lib/work-graph-hook-events.ts
9632
- import { createHash as createHash5 } from "crypto";
9633
- import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
11410
+ import { createHash as createHash7 } from "crypto";
11411
+ import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
9634
11412
  var SOURCE_CLIENTS = [
9635
11413
  "codex",
9636
11414
  "claude",
@@ -9664,7 +11442,7 @@ function asStringArray(value) {
9664
11442
  return value.filter((item) => typeof item === "string" && item.trim().length > 0);
9665
11443
  }
9666
11444
  function stableHash(value) {
9667
- return createHash5("sha256").update(value).digest("hex").slice(0, 20);
11445
+ return createHash7("sha256").update(value).digest("hex").slice(0, 20);
9668
11446
  }
9669
11447
  function normalizeSourceClient2(value) {
9670
11448
  const raw = asString2(value)?.toLowerCase();
@@ -9731,8 +11509,8 @@ function readHookRecord(line) {
9731
11509
  }
9732
11510
  }
9733
11511
  function readRuntimeHookOutbox(path, limit = 200) {
9734
- if (!existsSync6(path)) return { path, records: [], skipped: 0 };
9735
- const lines = readFileSync4(path, "utf8").split(/\r?\n/).filter((line) => line.trim().length > 0);
11512
+ if (!existsSync7(path)) return { path, records: [], skipped: 0 };
11513
+ const lines = readFileSync5(path, "utf8").split(/\r?\n/).filter((line) => line.trim().length > 0);
9736
11514
  const selected = lines.slice(Math.max(0, lines.length - Math.max(1, limit)));
9737
11515
  const records = [];
9738
11516
  let skipped = Math.max(0, lines.length - selected.length);
@@ -9868,20 +11646,20 @@ function buildWorkGraphHookReplayPatch(readResult) {
9868
11646
  }
9869
11647
 
9870
11648
  // src/lib/runtime-hooks.ts
9871
- import { copyFileSync, existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
9872
- import { homedir as homedir2 } from "os";
9873
- import { dirname as dirname4, join as join5 } from "path";
11649
+ import { copyFileSync, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
11650
+ import { homedir as homedir3 } from "os";
11651
+ import { dirname as dirname4, join as join6 } from "path";
9874
11652
  var HOOK_MARKER = "orgx-session-hook.mjs";
9875
11653
  var HOOK_EVENTS = ["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "PermissionRequest", "Stop"];
9876
11654
  var CLAUDE_HOOK_EVENTS = ["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "SubagentStop", "Stop", "SessionEnd"];
9877
11655
  function defaultPaths(options = {}) {
9878
- const hookDir = join5(ORGX_WIZARD_CONFIG_HOME, "hooks");
11656
+ const hookDir = join6(ORGX_WIZARD_CONFIG_HOME, "hooks");
9879
11657
  return {
9880
- claudeSettingsPath: options.claudeSettingsPath ?? join5(CLAUDE_DIR, "settings.json"),
9881
- codexConfigPath: options.codexConfigPath ?? CODEX_CONFIG_PATH ?? join5(CODEX_DIR, "config.toml"),
9882
- codexHooksPath: options.codexHooksPath ?? join5(CODEX_DIR, "hooks.json"),
9883
- hookScriptPath: options.hookScriptPath ?? join5(hookDir, HOOK_MARKER),
9884
- outboxPath: options.outboxPath ?? join5(hookDir, "events.jsonl")
11658
+ claudeSettingsPath: options.claudeSettingsPath ?? join6(CLAUDE_DIR, "settings.json"),
11659
+ codexConfigPath: options.codexConfigPath ?? CODEX_CONFIG_PATH ?? join6(CODEX_DIR, "config.toml"),
11660
+ codexHooksPath: options.codexHooksPath ?? join6(CODEX_DIR, "hooks.json"),
11661
+ hookScriptPath: options.hookScriptPath ?? join6(hookDir, HOOK_MARKER),
11662
+ outboxPath: options.outboxPath ?? join6(hookDir, "events.jsonl")
9885
11663
  };
9886
11664
  }
9887
11665
  function countJsonlLines(path) {
@@ -9894,7 +11672,7 @@ function backupPath(path, now) {
9894
11672
  return `${path}.bak.${timestamp}`;
9895
11673
  }
9896
11674
  function backupExisting(path, now) {
9897
- if (!existsSync7(path)) return null;
11675
+ if (!existsSync8(path)) return null;
9898
11676
  const backup = backupPath(path, now);
9899
11677
  copyFileSync(path, backup);
9900
11678
  return backup;
@@ -10092,7 +11870,7 @@ function inspectRuntimeHooks(options = {}) {
10092
11870
  installed: {
10093
11871
  claudeCode: hasOrgxHook(claudeSettingsRaw),
10094
11872
  codex: hasOrgxHook(codexHooksRaw),
10095
- hookScript: existsSync7(paths.hookScriptPath)
11873
+ hookScript: existsSync8(paths.hookScriptPath)
10096
11874
  },
10097
11875
  codex: {
10098
11876
  configExists: Boolean(codexConfigRaw),
@@ -10322,7 +12100,7 @@ async function runHookReplayCommand(options) {
10322
12100
  }
10323
12101
  const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
10324
12102
  const paths = inspectRuntimeHooks().paths;
10325
- const outboxPath = resolve(options.outbox?.trim() || paths.outboxPath);
12103
+ const outboxPath = resolve2(options.outbox?.trim() || paths.outboxPath);
10326
12104
  const readResult = readRuntimeHookOutbox(outboxPath, parsePositiveInt(options.limit, 200));
10327
12105
  const replay = buildWorkGraphHookReplayPatch(readResult);
10328
12106
  if (replay.records === 0) {
@@ -10354,10 +12132,10 @@ async function runHookReplayCommand(options) {
10354
12132
  }
10355
12133
  function readAuditInput(options, interactive) {
10356
12134
  if (options.input?.trim()) {
10357
- return readFileSync6(resolve(options.input.trim()), "utf8");
12135
+ return readFileSync7(resolve2(options.input.trim()), "utf8");
10358
12136
  }
10359
12137
  if (!process.stdin.isTTY) {
10360
- return readFileSync6(0, "utf8");
12138
+ return readFileSync7(0, "utf8");
10361
12139
  }
10362
12140
  if (!interactive) {
10363
12141
  throw new Error("Audit input is required. Pass --input <file> or pipe text into wizard audit.");
@@ -10389,8 +12167,8 @@ function collectPathOption(value, previous = []) {
10389
12167
  ];
10390
12168
  }
10391
12169
  function parseClientExtractionFile(path) {
10392
- const resolvedPath = resolve(path);
10393
- const parsed = JSON.parse(readFileSync6(resolvedPath, "utf8"));
12170
+ const resolvedPath = resolve2(path);
12171
+ const parsed = JSON.parse(readFileSync7(resolvedPath, "utf8"));
10394
12172
  if (!isRecord(parsed)) {
10395
12173
  throw new Error(`AI-client extraction must be a JSON object: ${resolvedPath}`);
10396
12174
  }
@@ -10412,8 +12190,8 @@ async function readAuditImports(options, interactive) {
10412
12190
  const missingSources = [];
10413
12191
  if (sources.length > 0) {
10414
12192
  const imported = loadAiSessionImports({
10415
- ...options.claudeProjectsDir?.trim() ? { claudeProjectsDir: resolve(options.claudeProjectsDir.trim()) } : {},
10416
- ...options.codexSessionsDir?.trim() ? { codexSessionsDir: resolve(options.codexSessionsDir.trim()) } : {},
12193
+ ...options.claudeProjectsDir?.trim() ? { claudeProjectsDir: resolve2(options.claudeProjectsDir.trim()) } : {},
12194
+ ...options.codexSessionsDir?.trim() ? { codexSessionsDir: resolve2(options.codexSessionsDir.trim()) } : {},
10417
12195
  limitPerSource: parsePositiveInteger(options.sessionLimit, 3, "--session-limit"),
10418
12196
  sinceDays: parsePositiveInteger(options.sessionDays, 30, "--session-days"),
10419
12197
  sources
@@ -10441,7 +12219,7 @@ async function readAuditImports(options, interactive) {
10441
12219
  }
10442
12220
  if (imports.length === 0) {
10443
12221
  const sourceHint = sources.length > 0 ? ` No audit-relevant lines were found in ${sources.join(", ")} sessions.` : "";
10444
- throw new Error(`Audit input is required.${sourceHint} Pass --input <file>, pipe text, or use --from codex|claude|all.`);
12222
+ throw new Error(`Audit input is required.${sourceHint} Pass --input <file>, pipe text, or use --from claude,codex,opencode,goose,cursor,all.`);
10445
12223
  }
10446
12224
  return {
10447
12225
  connectedSources,
@@ -10451,13 +12229,28 @@ async function readAuditImports(options, interactive) {
10451
12229
  }
10452
12230
  async function readWorkGraphInputs(options, interactive) {
10453
12231
  const clientExtractions = readClientExtractions(options);
12232
+ const investigationSources = parseInvestigationSourceList(options.from);
12233
+ const investigationSourceData = investigationSources.length > 0 ? loadWorkGraphInvestigationSourceData({
12234
+ ...options.claudeProjectsDir?.trim() ? { claudeProjectsDir: resolve2(options.claudeProjectsDir.trim()) } : {},
12235
+ ...options.codexSessionsDir?.trim() ? { codexSessionsDir: resolve2(options.codexSessionsDir.trim()) } : {},
12236
+ cwd: process.cwd(),
12237
+ limitPerSource: parsePositiveInteger(options.sessionLimit, 8, "--session-limit"),
12238
+ sinceDays: parsePositiveInteger(options.sessionDays, 45, "--session-days"),
12239
+ sources: investigationSources
12240
+ }) : {
12241
+ clientExtractions: [],
12242
+ connectedSources: [],
12243
+ missingSources: [],
12244
+ rawEvents: []
12245
+ };
10454
12246
  const shouldReadSessionImports = Boolean(
10455
- options.from?.trim() || options.input?.trim() || clientExtractions.length === 0 || !process.stdin.isTTY && clientExtractions.length === 0
12247
+ options.from?.trim() || options.input?.trim() || clientExtractions.length === 0 && investigationSourceData.clientExtractions.length === 0 || !process.stdin.isTTY && clientExtractions.length === 0 && investigationSourceData.clientExtractions.length === 0
10456
12248
  );
10457
12249
  if (!shouldReadSessionImports) {
10458
12250
  return {
10459
- clientExtractions,
12251
+ clientExtractions: [...clientExtractions, ...investigationSourceData.clientExtractions],
10460
12252
  connectedSources: [],
12253
+ investigationRawEvents: investigationSourceData.rawEvents,
10461
12254
  imports: [],
10462
12255
  missingSources: []
10463
12256
  };
@@ -10466,7 +12259,7 @@ async function readWorkGraphInputs(options, interactive) {
10466
12259
  try {
10467
12260
  auditImports = await readAuditImports(options, interactive);
10468
12261
  } catch (error) {
10469
- if (clientExtractions.length === 0) throw error;
12262
+ if (clientExtractions.length === 0 && investigationSourceData.clientExtractions.length === 0) throw error;
10470
12263
  const message = error instanceof Error ? error.message : String(error);
10471
12264
  auditImports = {
10472
12265
  connectedSources: [],
@@ -10475,10 +12268,11 @@ async function readWorkGraphInputs(options, interactive) {
10475
12268
  };
10476
12269
  }
10477
12270
  return {
10478
- clientExtractions,
10479
- connectedSources: auditImports.connectedSources,
12271
+ clientExtractions: [...clientExtractions, ...investigationSourceData.clientExtractions],
12272
+ connectedSources: [...auditImports.connectedSources, ...investigationSourceData.connectedSources],
12273
+ investigationRawEvents: investigationSourceData.rawEvents,
10480
12274
  imports: auditImports.imports,
10481
- missingSources: auditImports.missingSources
12275
+ missingSources: [...auditImports.missingSources, ...investigationSourceData.missingSources]
10482
12276
  };
10483
12277
  }
10484
12278
  function requireWriteApproval(options, interactive) {
@@ -10555,10 +12349,10 @@ async function runAuditCommand(options) {
10555
12349
  workspace
10556
12350
  });
10557
12351
  const markdown = renderSelfAuditMarkdown(plan);
10558
- const outputDir = resolve(options.outputDir?.trim() || ".orgx/audits");
12352
+ const outputDir = resolve2(options.outputDir?.trim() || ".orgx/audits");
10559
12353
  const timestamp = plan.generated_at.replace(/[:.]/g, "-");
10560
- const jsonPath = resolve(outputDir, `ai-native-self-audit-${timestamp}.json`);
10561
- const markdownPath = resolve(outputDir, `ai-native-self-audit-${timestamp}.md`);
12354
+ const jsonPath = resolve2(outputDir, `ai-native-self-audit-${timestamp}.json`);
12355
+ const markdownPath = resolve2(outputDir, `ai-native-self-audit-${timestamp}.md`);
10562
12356
  writeJsonFile(jsonPath, plan);
10563
12357
  writeTextFile(markdownPath, markdown);
10564
12358
  if (options.json) {
@@ -10609,7 +12403,7 @@ async function runAuditCommand(options) {
10609
12403
  }
10610
12404
  function runWorkGraphExtractionSchemaCommand(options) {
10611
12405
  const protocol = buildWorkGraphExtractionProtocol();
10612
- const outputPath = options.output?.trim() ? resolve(options.output.trim()) : "";
12406
+ const outputPath = options.output?.trim() ? resolve2(options.output.trim()) : "";
10613
12407
  if (outputPath) {
10614
12408
  if (options.json) {
10615
12409
  writeJsonFile(outputPath, protocol);
@@ -10648,6 +12442,7 @@ async function runWorkGraphCommand(options, defaults = {}) {
10648
12442
  ...workspace.id === "local-workspace" ? [] : ["OrgX workspace"]
10649
12443
  ],
10650
12444
  imports: auditInputs.imports,
12445
+ investigationRawEvents: auditInputs.investigationRawEvents,
10651
12446
  missingSources: [
10652
12447
  ...workspace.id === "local-workspace" ? ["OrgX workspace auth"] : [],
10653
12448
  ...auditInputs.missingSources
@@ -10656,10 +12451,10 @@ async function runWorkGraphCommand(options, defaults = {}) {
10656
12451
  workspace
10657
12452
  });
10658
12453
  const markdown = renderWorkGraphMarkdown(report);
10659
- const outputDir = resolve(commandOptions.outputDir?.trim() || ".orgx/work-graph");
12454
+ const outputDir = resolve2(commandOptions.outputDir?.trim() || ".orgx/work-graph");
10660
12455
  const timestamp = report.generated_at.replace(/[:.]/g, "-");
10661
- const jsonPath = resolve(outputDir, `work-graph-report-${timestamp}.json`);
10662
- const markdownPath = resolve(outputDir, `work-graph-report-${timestamp}.md`);
12456
+ const jsonPath = resolve2(outputDir, `work-graph-report-${timestamp}.json`);
12457
+ const markdownPath = resolve2(outputDir, `work-graph-report-${timestamp}.md`);
10663
12458
  writeJsonFile(jsonPath, report);
10664
12459
  writeTextFile(markdownPath, markdown);
10665
12460
  let published = null;
@@ -11041,14 +12836,14 @@ async function readSingleKey() {
11041
12836
  const stdin = process.stdin;
11042
12837
  if (!stdin.isTTY) return null;
11043
12838
  const previousRawMode = stdin.isRaw === true;
11044
- return await new Promise((resolve2) => {
12839
+ return await new Promise((resolve3) => {
11045
12840
  const cleanup = (result) => {
11046
12841
  stdin.off("data", onData);
11047
12842
  if (stdin.isTTY) {
11048
12843
  stdin.setRawMode(previousRawMode);
11049
12844
  }
11050
12845
  stdin.pause();
11051
- resolve2(result);
12846
+ resolve3(result);
11052
12847
  };
11053
12848
  const onData = (chunk) => {
11054
12849
  const text2 = chunk.toString("utf8");
@@ -11701,7 +13496,7 @@ function printDoctorReport(report, assessment) {
11701
13496
  async function main() {
11702
13497
  const program = new Command();
11703
13498
  program.name("orgx-wizard").description("Add OrgX MCP configs, skills/rules, and companion plugins to your local AI tools.").showHelpAfterError();
11704
- const pkgVersion = true ? "0.1.38" : void 0;
13499
+ const pkgVersion = true ? "0.1.39" : void 0;
11705
13500
  program.version(pkgVersion ?? "unknown", "-V, --version");
11706
13501
  program.hook("preAction", (_thisCommand, actionCommand) => {
11707
13502
  if (Boolean(actionCommand.optsWithGlobals().json)) return;
@@ -12391,7 +14186,7 @@ async function main() {
12391
14186
  jsonOutput: Boolean(options.json)
12392
14187
  });
12393
14188
  });
12394
- program.command("audit").description("Run the AI-native Founder Loop self-audit from pasted or file-based AI-session context.").option("--input <path>", "AI-session transcript or summary file; stdin is used when piped").option("--from <sources>", "auto-import recent local AI sessions: codex, claude, or all").option("--session-limit <count>", "max recent sessions to import per selected source", "3").option("--session-days <days>", "lookback window for local AI-session imports", "30").option("--codex-sessions-dir <path>", "override Codex sessions directory for audit import").option("--claude-projects-dir <path>", "override Claude projects directory for audit import").option("--source-label <label>", "label for the imported AI-session source").option("--workspace-id <id>", "workspace id override; defaults to the current OrgX workspace when authenticated").option("--workspace-name <name>", "workspace name override for local-only audits").option("--output-dir <path>", "directory for generated audit JSON and Markdown", ".orgx/audits").option("--attach-to-initiative <id>", "attach the generated audit artifact to an existing OrgX initiative").option("--create-initiative", "create an OrgX initiative from the generated audit plan").option("--write-follow-up", "create one approval-gated OrgX follow-up task from the audit recommendation").option("--workstream-id <id>", "optional workstream id for the generated follow-up task").option("--milestone-id <id>", "optional milestone id for the generated follow-up task").option("--dry-run", "exercise OrgX write paths without sending writes").option("--yes", "approve OrgX write flags in non-interactive mode").option("--json", "emit a JSON command summary").action(async (options) => {
14189
+ program.command("audit").description("Run the AI-native Founder Loop self-audit from pasted or file-based AI-session context.").option("--input <path>", "AI-session transcript or summary file; stdin is used when piped").option("--from <sources>", "auto-import recent local AI sessions: claude, codex, opencode, goose, cursor, or all").option("--session-limit <count>", "max recent sessions to import per selected source", "3").option("--session-days <days>", "lookback window for local AI-session imports", "30").option("--codex-sessions-dir <path>", "override Codex sessions directory for audit import").option("--claude-projects-dir <path>", "override Claude projects directory for audit import").option("--source-label <label>", "label for the imported AI-session source").option("--workspace-id <id>", "workspace id override; defaults to the current OrgX workspace when authenticated").option("--workspace-name <name>", "workspace name override for local-only audits").option("--output-dir <path>", "directory for generated audit JSON and Markdown", ".orgx/audits").option("--attach-to-initiative <id>", "attach the generated audit artifact to an existing OrgX initiative").option("--create-initiative", "create an OrgX initiative from the generated audit plan").option("--write-follow-up", "create one approval-gated OrgX follow-up task from the audit recommendation").option("--workstream-id <id>", "optional workstream id for the generated follow-up task").option("--milestone-id <id>", "optional milestone id for the generated follow-up task").option("--dry-run", "exercise OrgX write paths without sending writes").option("--yes", "approve OrgX write flags in non-interactive mode").option("--json", "emit a JSON command summary").action(async (options) => {
12395
14190
  await safeTrackWizardTelemetry("audit_started", {
12396
14191
  attach_to_initiative: Boolean(options.attachToInitiative),
12397
14192
  command: "audit",
@@ -12409,14 +14204,14 @@ async function main() {
12409
14204
  });
12410
14205
  runWorkGraphExtractionSchemaCommand(options);
12411
14206
  });
12412
- workGraph.command("preview").description("Preview the OrgX Profile evidence findings without writing to OrgX.").option("--input <path>", "source transcript or summary file; stdin is used when piped").option("--extraction <path>", "structured AI-client extraction JSON; repeat or comma-separate for multiple clients", collectPathOption, []).option("--from <sources>", "auto-import recent local AI sessions: codex, claude, or all").option("--session-limit <count>", "max recent sessions to import per selected source", "15").option("--session-days <days>", "lookback window for local AI-session imports", "60").option("--codex-sessions-dir <path>", "override Codex sessions directory for import").option("--claude-projects-dir <path>", "override Claude projects directory for import").option("--source-label <label>", "label for the imported manual source").option("--workspace-id <id>", "workspace id override; defaults to the current OrgX workspace when authenticated").option("--workspace-name <name>", "workspace name override for local-only previews").option("--output-dir <path>", "directory for generated Work Graph JSON and Markdown", ".orgx/work-graph").option("--json", "emit a JSON command summary").action(async (options) => {
14207
+ workGraph.command("preview").description("Preview the OrgX Profile evidence findings without writing to OrgX.").option("--input <path>", "source transcript or summary file; stdin is used when piped").option("--extraction <path>", "structured AI-client extraction JSON; repeat or comma-separate for multiple clients", collectPathOption, []).option("--from <sources>", "auto-import recent local AI sessions: claude, codex, opencode, goose, cursor, or all").option("--session-limit <count>", "max recent sessions to import per selected source", "15").option("--session-days <days>", "lookback window for local AI-session imports", "60").option("--codex-sessions-dir <path>", "override Codex sessions directory for import").option("--claude-projects-dir <path>", "override Claude projects directory for import").option("--source-label <label>", "label for the imported manual source").option("--workspace-id <id>", "workspace id override; defaults to the current OrgX workspace when authenticated").option("--workspace-name <name>", "workspace name override for local-only previews").option("--output-dir <path>", "directory for generated Work Graph JSON and Markdown", ".orgx/work-graph").option("--json", "emit a JSON command summary").action(async (options) => {
12413
14208
  await safeTrackWizardTelemetry("work_graph_preview_started", {
12414
14209
  command: "work-graph preview",
12415
14210
  from: options.from ?? "manual"
12416
14211
  });
12417
14212
  await runWorkGraphCommand(options);
12418
14213
  });
12419
- workGraph.command("profile").description("Build a local OrgX Profile with evidence findings, domain coverage, source confidence, and repair recommendations.").option("--input <path>", "source transcript or summary file; stdin is used when piped").option("--extraction <path>", "structured AI-client extraction JSON; repeat or comma-separate for multiple clients", collectPathOption, []).option("--from <sources>", "auto-import recent local AI sessions: codex, claude, or all").option("--session-limit <count>", "max recent sessions to import per selected source", "25").option("--session-days <days>", "lookback window for local AI-session imports", "60").option("--codex-sessions-dir <path>", "override Codex sessions directory for import").option("--claude-projects-dir <path>", "override Claude projects directory for import").option("--source-label <label>", "label for the imported manual source").option("--workspace-id <id>", "workspace id override; defaults to the current OrgX workspace when authenticated").option("--workspace-name <name>", "workspace name override for local-only profiles").option("--output-dir <path>", "directory for generated Work Graph JSON and Markdown", ".orgx/work-graph").option("--publish", "publish the generated OrgX Profile to OrgX and return a shareable URL").option("--public-share", "create a public redacted /work-graph/<token> share when publishing").option("--attach-artifact", "attach the report as an OrgX artifact when an initiative/entity is provided").option("--attach-to-initiative <id>", "attach the report to an existing OrgX initiative").option("--entity-type <type>", "entity type for artifact attachment: project, initiative, milestone, task, decision").option("--entity-id <id>", "entity id for artifact attachment").option("--artifact-url <url>", "override the artifact URL stored in OrgX").option("--yes", "approve publish/write prompts in non-interactive mode").option("--json", "emit a JSON command summary").action(async (options) => {
14214
+ workGraph.command("profile").description("Build a local OrgX Profile with evidence findings, domain coverage, source confidence, and repair recommendations.").option("--input <path>", "source transcript or summary file; stdin is used when piped").option("--extraction <path>", "structured AI-client extraction JSON; repeat or comma-separate for multiple clients", collectPathOption, []).option("--from <sources>", "auto-import recent local AI sessions: claude, codex, opencode, goose, cursor, or all").option("--session-limit <count>", "max recent sessions to import per selected source", "25").option("--session-days <days>", "lookback window for local AI-session imports", "60").option("--codex-sessions-dir <path>", "override Codex sessions directory for import").option("--claude-projects-dir <path>", "override Claude projects directory for import").option("--source-label <label>", "label for the imported manual source").option("--workspace-id <id>", "workspace id override; defaults to the current OrgX workspace when authenticated").option("--workspace-name <name>", "workspace name override for local-only profiles").option("--output-dir <path>", "directory for generated Work Graph JSON and Markdown", ".orgx/work-graph").option("--publish", "publish the generated OrgX Profile to OrgX and return a shareable URL").option("--public-share", "create a public redacted /work-graph/<token> share when publishing").option("--attach-artifact", "attach the report as an OrgX artifact when an initiative/entity is provided").option("--attach-to-initiative <id>", "attach the report to an existing OrgX initiative").option("--entity-type <type>", "entity type for artifact attachment: project, initiative, milestone, task, decision").option("--entity-id <id>", "entity id for artifact attachment").option("--artifact-url <url>", "override the artifact URL stored in OrgX").option("--yes", "approve publish/write prompts in non-interactive mode").option("--json", "emit a JSON command summary").action(async (options) => {
12420
14215
  await safeTrackWizardTelemetry("work_graph_profile_started", {
12421
14216
  command: "work-graph profile",
12422
14217
  from: options.from ?? "manual"
@@ -12424,7 +14219,7 @@ async function main() {
12424
14219
  await runWorkGraphCommand(options);
12425
14220
  });
12426
14221
  const sessions = program.command("sessions").description("Inspect and reconcile local AI sessions into OrgX-ready Work Graph reports.");
12427
- sessions.command("reconcile").description("Backfill recent Codex and Claude sessions into a redacted OrgX Profile report.").option("--extraction <path>", "structured AI-client extraction JSON; repeat or comma-separate for multiple clients", collectPathOption, []).option("--from <sources>", "auto-import recent local AI sessions: codex, claude, or all", "all").option("--session-limit <count>", "max recent sessions to import per selected source", "25").option("--session-days <days>", "lookback window for local AI-session imports", "30").option("--codex-sessions-dir <path>", "override Codex sessions directory for import").option("--claude-projects-dir <path>", "override Claude projects directory for import").option("--workspace-id <id>", "workspace id override; defaults to the current OrgX workspace when authenticated").option("--workspace-name <name>", "workspace name override for local-only reconciliation").option("--output-dir <path>", "directory for generated Work Graph JSON and Markdown", ".orgx/work-graph").option("--publish", "publish the generated OrgX Profile to OrgX and return a shareable URL").option("--public-share", "create a public redacted /work-graph/<token> share when publishing").option("--attach-artifact", "attach the report as an OrgX artifact when an initiative/entity is provided").option("--attach-to-initiative <id>", "attach the report to an existing OrgX initiative").option("--entity-type <type>", "entity type for artifact attachment: project, initiative, milestone, task, decision").option("--entity-id <id>", "entity id for artifact attachment").option("--artifact-url <url>", "override the artifact URL stored in OrgX").option("--yes", "approve publish/write prompts in non-interactive mode").option("--json", "emit a JSON command summary").action(async (options) => {
14222
+ sessions.command("reconcile").description("Backfill recent Claude Code, Codex, OpenCode, goose, and Cursor sessions into a redacted OrgX Profile report.").option("--extraction <path>", "structured AI-client extraction JSON; repeat or comma-separate for multiple clients", collectPathOption, []).option("--from <sources>", "auto-import recent local AI sessions: claude, codex, opencode, goose, cursor, or all", "all").option("--session-limit <count>", "max recent sessions to import per selected source", "25").option("--session-days <days>", "lookback window for local AI-session imports", "30").option("--codex-sessions-dir <path>", "override Codex sessions directory for import").option("--claude-projects-dir <path>", "override Claude projects directory for import").option("--workspace-id <id>", "workspace id override; defaults to the current OrgX workspace when authenticated").option("--workspace-name <name>", "workspace name override for local-only reconciliation").option("--output-dir <path>", "directory for generated Work Graph JSON and Markdown", ".orgx/work-graph").option("--publish", "publish the generated OrgX Profile to OrgX and return a shareable URL").option("--public-share", "create a public redacted /work-graph/<token> share when publishing").option("--attach-artifact", "attach the report as an OrgX artifact when an initiative/entity is provided").option("--attach-to-initiative <id>", "attach the report to an existing OrgX initiative").option("--entity-type <type>", "entity type for artifact attachment: project, initiative, milestone, task, decision").option("--entity-id <id>", "entity id for artifact attachment").option("--artifact-url <url>", "override the artifact URL stored in OrgX").option("--yes", "approve publish/write prompts in non-interactive mode").option("--json", "emit a JSON command summary").action(async (options) => {
12428
14223
  await safeTrackWizardTelemetry("sessions_reconcile_started", {
12429
14224
  command: "sessions reconcile",
12430
14225
  from: options.from ?? "all"