@useorgx/wizard 0.1.32 → 0.1.36

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,7 +3,7 @@
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 readFileSync5 } from "fs";
6
+ import { readFileSync as readFileSync6 } from "fs";
7
7
  import { hostname } from "os";
8
8
  import { resolve } from "path";
9
9
  import { Command } from "commander";
@@ -6990,6 +6990,7 @@ var WORK_GRAPH_FINDING_TYPES = [
6990
6990
  "business",
6991
6991
  "product_surface",
6992
6992
  "goal",
6993
+ "outcome",
6993
6994
  "initiative_candidate",
6994
6995
  "missed_orchestration_opportunity"
6995
6996
  ];
@@ -7022,6 +7023,10 @@ function sourceClientForImport(source) {
7022
7023
  if (raw.includes("slack")) return "slack";
7023
7024
  if (raw.includes("github")) return "github";
7024
7025
  if (raw.includes("linear")) return "linear";
7026
+ if (raw.includes("gmail") || raw.includes("email")) return "gmail";
7027
+ if (raw.includes("calendar") || raw.includes("meeting")) return "calendar";
7028
+ if (raw.includes("notion")) return "notion";
7029
+ if (raw.includes("doc")) return "docs";
7025
7030
  if (raw.includes("mcp")) return "mcp";
7026
7031
  if (raw.includes("api")) return "api";
7027
7032
  if (raw.includes("manual") || raw.includes("wizard-audit-input")) return "manual";
@@ -7030,7 +7035,7 @@ function sourceClientForImport(source) {
7030
7035
  function normalizeSourceClient(value) {
7031
7036
  if (typeof value !== "string") return "unknown";
7032
7037
  const normalized = value.trim().toLowerCase();
7033
- if (normalized === "codex" || normalized === "claude" || normalized === "claude-code" || normalized === "cursor" || normalized === "openclaw" || normalized === "slack" || normalized === "mcp" || normalized === "github" || normalized === "linear" || normalized === "manual" || normalized === "wizard" || normalized === "api") {
7038
+ 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") {
7034
7039
  return normalized;
7035
7040
  }
7036
7041
  if (normalized.includes("claude")) return "claude";
@@ -7039,9 +7044,46 @@ function normalizeSourceClient(value) {
7039
7044
  if (normalized.includes("slack")) return "slack";
7040
7045
  if (normalized.includes("github")) return "github";
7041
7046
  if (normalized.includes("linear")) return "linear";
7047
+ if (normalized.includes("gmail") || normalized.includes("email")) return "gmail";
7048
+ if (normalized.includes("calendar") || normalized.includes("meeting")) return "calendar";
7049
+ if (normalized.includes("notion")) return "notion";
7050
+ if (normalized.includes("doc")) return "docs";
7042
7051
  if (normalized.includes("mcp")) return "mcp";
7043
7052
  return "unknown";
7044
7053
  }
7054
+ function sourceClientFromText(value) {
7055
+ const normalized = value.toLowerCase();
7056
+ if (/\bclaude-code\b|\.claude\/projects|claude:/.test(normalized)) return "claude";
7057
+ if (/\bcodex\b|\.codex\/sessions|rollout-/.test(normalized)) return "codex";
7058
+ if (/\bcursor\b/.test(normalized)) return "cursor";
7059
+ if (/\bopenclaw\b/.test(normalized)) return "openclaw";
7060
+ if (/\bslack\b/.test(normalized)) return "slack";
7061
+ if (/\bgithub\b|\bgit:|pull request|commit\b/.test(normalized)) return "github";
7062
+ if (/\blinear\b/.test(normalized)) return "linear";
7063
+ if (/\bgmail\b|\bemail\b/.test(normalized)) return "gmail";
7064
+ if (/\bcalendar\b|\bmeeting\b/.test(normalized)) return "calendar";
7065
+ if (/\bnotion\b/.test(normalized)) return "notion";
7066
+ if (/\bdocs?\b|google drive/.test(normalized)) return "docs";
7067
+ if (/\bmcp\b|mcp__|orgx_emit|scaffold_initiative|ship_batch/.test(normalized)) return "mcp";
7068
+ if (/\borgx api\b|\bapi\b/.test(normalized)) return "api";
7069
+ return "unknown";
7070
+ }
7071
+ function sourceClientForExtractionFinding(extraction, finding) {
7072
+ const explicit = normalizeSourceClient(finding.source_client);
7073
+ if (explicit !== "unknown") return explicit;
7074
+ const inferred = sourceClientFromText([
7075
+ finding.source_id ?? "",
7076
+ finding.source_label ?? "",
7077
+ finding.evidence_ref ?? "",
7078
+ finding.summary ?? ""
7079
+ ].join("\n"));
7080
+ if (inferred !== "unknown") return inferred;
7081
+ return normalizeSourceClient(extraction.source_client);
7082
+ }
7083
+ function arrayOfStrings(value) {
7084
+ if (!Array.isArray(value)) return [];
7085
+ return value.filter((item) => typeof item === "string" && item.trim().length > 0);
7086
+ }
7045
7087
  function normalizeFindingType(value) {
7046
7088
  if (typeof value !== "string") return null;
7047
7089
  const normalized = value.trim().toLowerCase();
@@ -7052,9 +7094,23 @@ function normalizeConfidence(value, fallback = 0.72) {
7052
7094
  return Math.max(0.1, Math.min(0.99, Number(value.toFixed(2))));
7053
7095
  }
7054
7096
  function titleFromText(text2, fallback) {
7055
- const normalized = text2.replace(/^\s*(decision|artifact|commitment|next action|follow[- ]?up|outcome|roi|economics|open loop|gap|blocker|risk|goal)\s*:\s*/i, "").trim();
7097
+ const lower = text2.toLowerCase();
7098
+ if (lower.includes("mcp__orgx__list_entities") && lower.includes("zod")) {
7099
+ return "OrgX MCP list_entities is failing schema validation";
7100
+ }
7101
+ if (lower.includes("scaffold_initiative") && lower.includes("auto_continue") && lower.includes("dispatch")) {
7102
+ return "scaffold_initiative creates ready streams without dispatching agent runs";
7103
+ }
7104
+ if (lower.includes("operation qa loop") && lower.includes("0/") && lower.includes("entities")) {
7105
+ return "OrgX scaffold fails to create Operation QA Loop entities";
7106
+ }
7107
+ if (lower.includes("production-only redirect") || lower.includes("livedemopageclient")) {
7108
+ return "Production route behavior changed without durable approval";
7109
+ }
7110
+ const normalized = text2.replace(/[`*_>#]/g, "").replace(/^\s*[-*\d.)]+\s*/, "").replace(/^\s*(decision|artifact|commitment|next action|follow[- ]?up|outcome|roi|economics|open loop|gap|blocker|risk|goal|summary)\s*:\s*/i, "").replace(/^\s*(fix|fixing|debug|investigate|continue|todo)\s+(the\s+)?(bug|issue|problem)\s*[:.-]?\s*/i, "").replace(/^\s*one\s+bug\s+surfaced\s*:\s*/i, "").replace(/\s+/g, " ").trim();
7056
7111
  const firstSentence = normalized.split(/[.!?]\s/)[0]?.trim() || normalized;
7057
- return (firstSentence || fallback).slice(0, 160);
7112
+ const firstClause = firstSentence.split(/\s[-:;]\s/)[0]?.trim() || firstSentence;
7113
+ return (firstClause || fallback).slice(0, 110);
7058
7114
  }
7059
7115
  function normalizeClientExtractionId(extraction, index) {
7060
7116
  return extraction.extraction_id?.trim() || `${extraction.source_client || "unknown"}:client-extraction:${index + 1}`;
@@ -7093,9 +7149,15 @@ function buildWorkGraphExtractionProtocol() {
7093
7149
  {
7094
7150
  id: "people_businesses",
7095
7151
  lens: "People and business trails",
7096
- query: "Find users, customers, buyers, stakeholders, reviewers, teams, businesses, accounts, and market signals connected to work or decisions.",
7152
+ query: "Find users, customers, buyers, stakeholders, reviewers, teams, businesses, accounts, deal context, support signals, and market signals connected to work or decisions.",
7097
7153
  return_when: "A person or business changes priority, ownership, revenue potential, customer pain, or follow-up urgency."
7098
7154
  },
7155
+ {
7156
+ id: "coordination_sources",
7157
+ lens: "Coordination and calendar/email trails",
7158
+ query: "Find Slack threads, emails, meetings, docs, handoffs, approvals, owner changes, follow-ups, and coordination gaps that explain how work moved or stalled outside the AI client.",
7159
+ return_when: "The coordination source proves ownership, urgency, approval, customer context, or a missing source needed for attribution."
7160
+ },
7099
7161
  {
7100
7162
  id: "product_surfaces",
7101
7163
  lens: "Product surface trails",
@@ -7124,7 +7186,7 @@ function buildWorkGraphExtractionProtocol() {
7124
7186
  required_output: {
7125
7187
  schema_version: WORK_GRAPH_EXTRACTION_SCHEMA_VERSION,
7126
7188
  extraction_id: "stable id for this extraction run",
7127
- source_client: "codex | claude | claude-code | cursor | openclaw | slack | mcp | github | linear | manual | api | unknown",
7189
+ source_client: "codex | claude | claude-code | cursor | openclaw | slack | mcp | github | linear | gmail | calendar | notion | docs | manual | api | unknown",
7128
7190
  source_label: "human-readable source label",
7129
7191
  searched_sources: ["session/log/source group names searched"],
7130
7192
  search_queries: [
@@ -7143,9 +7205,10 @@ function buildWorkGraphExtractionProtocol() {
7143
7205
  },
7144
7206
  findings: [
7145
7207
  {
7146
- type: "decision | artifact | blocker | person | business | product_surface | goal | action | initiative_candidate | missed_orchestration_opportunity",
7208
+ type: "decision | artifact | blocker | person | business | product_surface | goal | outcome | action | initiative_candidate | missed_orchestration_opportunity",
7147
7209
  title: "short durable title, not a raw line",
7148
7210
  summary: "one sentence explaining why this matters",
7211
+ source_client: "optional override when a blended extractor found evidence from another client",
7149
7212
  source_id: "session/tool/source id",
7150
7213
  source_label: "source label",
7151
7214
  evidence_ref: "stable evidence pointer",
@@ -7219,6 +7282,12 @@ function includesAny2(text2, patterns) {
7219
7282
  return patterns.some((pattern) => pattern.test(text2));
7220
7283
  }
7221
7284
  function buildCoverage(imports, connectedSources, missingSources, clientExtractions = []) {
7285
+ const findings = buildClientExtractionFindings(clientExtractions);
7286
+ const sourceClients = sortedUnique([
7287
+ ...imports.map(sourceClientForImport),
7288
+ ...findings.map((finding) => finding.source_client),
7289
+ ...clientExtractions.map((extraction) => normalizeSourceClient(extraction.source_client))
7290
+ ].filter((source) => source !== "unknown"));
7222
7291
  const extractionText = clientExtractions.flatMap((extraction) => [
7223
7292
  extraction.source_client,
7224
7293
  extraction.source_label ?? "",
@@ -7235,14 +7304,150 @@ ${extractionText}`.toLowerCase();
7235
7304
  const mcpObserved = /\bmcp\b|mcp__|tool call|tools\/call|call_tool|orgx_emit_activity/i.test(allText);
7236
7305
  const orgxMcpCalled = /mcp__orgx__|orgx_emit_activity|orgx_apply_changeset|complete_with_proof|scaffold_initiative/i.test(allText);
7237
7306
  const skillOnlySignal = orgxObserved && !orgxMcpCalled && /\bskill|instructions|agent|workflow\b/i.test(allText);
7307
+ const inferredConnected = [
7308
+ ...connectedSources,
7309
+ ...sourceClients.includes("codex") ? ["Codex sessions"] : [],
7310
+ ...sourceClients.includes("claude") || sourceClients.includes("claude-code") ? ["Claude Code sessions"] : [],
7311
+ ...sourceClients.includes("github") ? ["Git/GitHub proof"] : [],
7312
+ ...sourceClients.includes("mcp") ? ["MCP tool telemetry"] : [],
7313
+ ...sourceClients.includes("slack") ? ["Slack coordination"] : []
7314
+ ];
7315
+ const inferredMissing = [
7316
+ ...missingSources,
7317
+ ...sourceClients.includes("slack") ? [] : ["Slack coordination"],
7318
+ ...sourceClients.includes("github") ? [] : ["GitHub PR/commit proof"],
7319
+ ...allText.includes("hook") || allText.includes("outbox") ? [] : ["Runtime hook outbox replay"]
7320
+ ];
7321
+ const connected = sortedUnique(inferredConnected.filter(Boolean));
7322
+ const missing = sortedUnique(inferredMissing.filter((source) => !connected.includes(source)));
7323
+ const manifests = buildSourceCoverageManifests({
7324
+ clientExtractions,
7325
+ connectedSources: connected,
7326
+ findings,
7327
+ imports,
7328
+ missingSources: missing
7329
+ });
7330
+ const partialCount = manifests.filter((manifest) => manifest.status === "partial").length;
7331
+ const coverageScore = clampScore2(
7332
+ 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
7333
+ );
7238
7334
  return {
7239
- connected: [...connectedSources],
7240
- missing: [...missingSources],
7335
+ connected,
7336
+ missing,
7241
7337
  mcpObserved,
7242
7338
  orgxObserved,
7243
7339
  orgxMcpCalled,
7244
- skillOnlySignal
7340
+ skillOnlySignal,
7341
+ coverage_score: coverageScore,
7342
+ manifests,
7343
+ notes: [
7344
+ "OrgX/MCP writeback is one coverage signal; it is not required for the audit to find useful work.",
7345
+ missing.length > 0 ? `Coverage is limited by missing sources: ${missing.join(", ")}.` : "Connected sources are sufficient for a first-pass operating profile, but still require user review."
7346
+ ]
7347
+ };
7348
+ }
7349
+ function buildSourceCoverageManifests(input) {
7350
+ const manifests = /* @__PURE__ */ new Map();
7351
+ const addManifest = (manifest) => {
7352
+ const key = `${manifest.source_client}:${manifest.source_label}`;
7353
+ const previous = manifests.get(key);
7354
+ if (!previous) {
7355
+ manifests.set(key, manifest);
7356
+ return;
7357
+ }
7358
+ manifests.set(key, {
7359
+ ...previous,
7360
+ status: previous.status === "connected" || manifest.status === "connected" ? "connected" : previous.status === "partial" || manifest.status === "partial" ? "partial" : "missing",
7361
+ searched_sources: sortedUnique([...previous.searched_sources, ...manifest.searched_sources]),
7362
+ searched_session_count: previous.searched_session_count + manifest.searched_session_count,
7363
+ skipped_session_count: previous.skipped_session_count + manifest.skipped_session_count,
7364
+ query_count: previous.query_count + manifest.query_count,
7365
+ finding_count: previous.finding_count + manifest.finding_count,
7366
+ confidence: Math.max(previous.confidence, manifest.confidence),
7367
+ notes: sortedUnique([...previous.notes, ...manifest.notes])
7368
+ });
7245
7369
  };
7370
+ for (const extraction of input.clientExtractions) {
7371
+ const extractionFindings = buildClientExtractionFindings([extraction]);
7372
+ const byClient = /* @__PURE__ */ new Map();
7373
+ for (const finding of extractionFindings) {
7374
+ byClient.set(finding.source_client, [...byClient.get(finding.source_client) ?? [], finding]);
7375
+ }
7376
+ for (const [sourceClient, findings] of byClient) {
7377
+ const searchedSources = extraction.searched_sources ?? [];
7378
+ addManifest({
7379
+ source_client: sourceClient,
7380
+ source_label: labelForSourceClient(sourceClient),
7381
+ status: findings.length > 0 ? "connected" : "partial",
7382
+ searched_sources: searchedSources,
7383
+ searched_session_count: extraction.extraction_quality?.searched_session_count ?? 0,
7384
+ skipped_session_count: extraction.extraction_quality?.skipped_session_count ?? 0,
7385
+ query_count: extraction.search_queries?.length ?? 0,
7386
+ finding_count: findings.length,
7387
+ confidence: normalizeConfidence(extraction.extraction_quality?.confidence, 0.74),
7388
+ notes: extraction.extraction_quality?.notes ?? []
7389
+ });
7390
+ }
7391
+ }
7392
+ for (const source of input.imports) {
7393
+ const sourceClient = sourceClientForImport(source);
7394
+ addManifest({
7395
+ source_client: sourceClient,
7396
+ source_label: source.sourceLabel,
7397
+ status: "connected",
7398
+ searched_sources: [source.sourceId],
7399
+ searched_session_count: 1,
7400
+ skipped_session_count: 0,
7401
+ query_count: 0,
7402
+ finding_count: input.findings.filter((finding) => finding.source_id === source.sourceId).length,
7403
+ confidence: 0.62,
7404
+ notes: ["Fallback line-level import; lower confidence than client-native extraction."]
7405
+ });
7406
+ }
7407
+ for (const source of input.missingSources) {
7408
+ addManifest({
7409
+ source_client: sourceClientFromText(source),
7410
+ source_label: source,
7411
+ status: "missing",
7412
+ searched_sources: [],
7413
+ searched_session_count: 0,
7414
+ skipped_session_count: 0,
7415
+ query_count: 0,
7416
+ finding_count: 0,
7417
+ confidence: 0,
7418
+ notes: ["Not connected or not searched in this audit pass."]
7419
+ });
7420
+ }
7421
+ return [...manifests.values()].sort((left, right) => {
7422
+ const rank = { connected: 0, partial: 1, missing: 2 };
7423
+ return rank[left.status] - rank[right.status] || right.finding_count - left.finding_count;
7424
+ });
7425
+ }
7426
+ function labelForSourceClient(sourceClient) {
7427
+ switch (sourceClient) {
7428
+ case "codex":
7429
+ return "Codex sessions";
7430
+ case "claude":
7431
+ case "claude-code":
7432
+ return "Claude Code sessions";
7433
+ case "mcp":
7434
+ return "MCP tool telemetry";
7435
+ case "github":
7436
+ return "Git/GitHub proof";
7437
+ case "slack":
7438
+ return "Slack coordination";
7439
+ case "linear":
7440
+ return "Linear issues";
7441
+ case "gmail":
7442
+ return "Email coordination";
7443
+ case "calendar":
7444
+ return "Calendar/meeting context";
7445
+ case "notion":
7446
+ case "docs":
7447
+ return "Docs";
7448
+ default:
7449
+ return sourceClient.replace(/[-_]/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
7450
+ }
7246
7451
  }
7247
7452
  function summarizeClientExtractions(clientExtractions) {
7248
7453
  return clientExtractions.map((extraction, index) => {
@@ -7252,9 +7457,12 @@ function summarizeClientExtractions(clientExtractions) {
7252
7457
  source_client: normalizeSourceClient(extraction.source_client),
7253
7458
  source_label: extraction.source_label?.trim() || `${extraction.source_client || "Unknown"} AI-client extraction`,
7254
7459
  searched_source_count: extraction.searched_sources?.length ?? 0,
7460
+ searched_session_count: extraction.extraction_quality?.searched_session_count ?? 0,
7461
+ skipped_session_count: extraction.extraction_quality?.skipped_session_count ?? 0,
7255
7462
  query_count: extraction.search_queries?.length ?? 0,
7256
7463
  finding_count: extraction.findings.length,
7257
- confidence: normalizeConfidence(extraction.extraction_quality?.confidence, 0.74)
7464
+ confidence: normalizeConfidence(extraction.extraction_quality?.confidence, 0.74),
7465
+ notes: extraction.extraction_quality?.notes ?? []
7258
7466
  };
7259
7467
  });
7260
7468
  }
@@ -7275,6 +7483,8 @@ function buildClientExtractionEvents(clientExtractions) {
7275
7483
  finding_count: summary.finding_count,
7276
7484
  query_count: summary.query_count,
7277
7485
  searched_source_count: summary.searched_source_count,
7486
+ searched_session_count: summary.searched_session_count,
7487
+ skipped_session_count: summary.skipped_session_count,
7278
7488
  raw_transcript_sent: false
7279
7489
  }
7280
7490
  }));
@@ -7283,13 +7493,14 @@ function buildClientExtractionFindings(clientExtractions) {
7283
7493
  const findings = [];
7284
7494
  clientExtractions.forEach((extraction, extractionIndex) => {
7285
7495
  const extractionId = normalizeClientExtractionId(extraction, extractionIndex);
7286
- const sourceClient = normalizeSourceClient(extraction.source_client);
7287
- const sourceLabel = extraction.source_label?.trim() || `${sourceClient} AI-client extraction`;
7496
+ const extractionSourceClient = normalizeSourceClient(extraction.source_client);
7497
+ const sourceLabel2 = extraction.source_label?.trim() || `${extractionSourceClient} AI-client extraction`;
7288
7498
  extraction.findings.forEach((finding, findingIndex) => {
7289
7499
  const type = normalizeFindingType(finding.type);
7290
7500
  const title = finding.title?.trim();
7291
7501
  const summary = finding.summary?.trim();
7292
7502
  if (!type || !title || !summary) return;
7503
+ const sourceClient = sourceClientForExtractionFinding(extraction, finding);
7293
7504
  findings.push({
7294
7505
  type,
7295
7506
  title: title.slice(0, 180),
@@ -7299,7 +7510,7 @@ function buildClientExtractionFindings(clientExtractions) {
7299
7510
  evidence_ref: finding.evidence_ref?.trim() || `${extractionId}:F${findingIndex + 1}`,
7300
7511
  confidence: normalizeConfidence(finding.confidence, normalizeConfidence(extraction.extraction_quality?.confidence, 0.76)),
7301
7512
  metadata: {
7302
- source_label: finding.source_label?.trim() || sourceLabel,
7513
+ source_label: finding.source_label?.trim() || sourceLabel2,
7303
7514
  extraction_id: extractionId,
7304
7515
  schema_version: extraction.schema_version ?? null,
7305
7516
  occurred_at: finding.occurred_at ?? null,
@@ -7313,6 +7524,124 @@ function buildClientExtractionFindings(clientExtractions) {
7313
7524
  });
7314
7525
  return findings;
7315
7526
  }
7527
+ function nativeExtractionFindingType(line) {
7528
+ const lower = line.toLowerCase();
7529
+ if (/^\s*(?:decision|decided|choice|approved|rejected)\s*:/i.test(line)) return "decision";
7530
+ if (/^\s*(?:artifact|receipt|proof)\s*:/i.test(line)) return "artifact";
7531
+ if (/^\s*(?:outcome|result|impact)\s*:/i.test(line)) return "outcome";
7532
+ if (/^\s*(?:roi|economics)\s*:/i.test(line)) return "business";
7533
+ if (/^\s*(?:next action|follow[- ]?up|commitment)\s*:/i.test(line)) return "action";
7534
+ if (/^\s*(?:blocker|risk|gap|open loop|rollback)\s*:/i.test(line)) return lower.includes("source") ? "missed_orchestration_opportunity" : "blocker";
7535
+ if (/\b(?:failed|fails|error|invalid|unavailable|timeout|timed out|not called|not dispatched|missing|broken)\b/i.test(line)) {
7536
+ if (/\b(?:tool|mcp|orgx|writeback|source|hook|outbox)\b/i.test(line)) return "missed_orchestration_opportunity";
7537
+ return "blocker";
7538
+ }
7539
+ if (/\b(?:created|generated|implemented|shipped|verified|passed)\b/i.test(line)) return "artifact";
7540
+ if (/\b(?:owner|dri|assigned)\b/i.test(line)) return "person";
7541
+ if (/\b(?:initiative|workstream|milestone|launch|goal)\b/i.test(line)) return "goal";
7542
+ return null;
7543
+ }
7544
+ function nativeEpisodeType(line) {
7545
+ if (/\b(?:plan|decide|decision|choice|tradeoff|approve|reject)\b/i.test(line)) return "planning";
7546
+ if (/\b(?:implemented|created|generated|edited|file|diff|commit|artifact)\b/i.test(line)) return "implementation";
7547
+ if (/\b(?:failed|fails|error|timeout|invalid|debug|retry|blocked|blocker)\b/i.test(line)) return "debugging";
7548
+ if (/\b(?:verified|passed|test|browser|qa|proof|outcome)\b/i.test(line)) return "verification";
7549
+ return "handoff";
7550
+ }
7551
+ function nativeFindingConfidence(type, line) {
7552
+ let confidence = type === "decision" || type === "artifact" || type === "outcome" ? 0.78 : 0.72;
7553
+ if (/^\s*(?:decision|artifact|blocker|outcome|proof|result|next action|open loop|risk|gap)\s*:/i.test(line)) confidence += 0.08;
7554
+ if (/\b(?:mcp__orgx__|scaffold_initiative|orgx_emit_activity|complete_with_proof|github|codex|claude)\b/i.test(line)) confidence += 0.05;
7555
+ if (line.length < 32) confidence -= 0.1;
7556
+ return normalizeConfidence(confidence, 0.72);
7557
+ }
7558
+ function nativeRelatedEntityNames(line) {
7559
+ const candidates = [
7560
+ ...Array.from(line.matchAll(/`([^`]{3,80})`/g)).map((match) => match[1] ?? ""),
7561
+ ...Array.from(line.matchAll(/\b(?:OrgX|MCP|Codex|Claude|Slack|GitHub|Cursor|ChatGPT|Work Graph|Profile|Scaffold Initiative|Operation QA Loop|Runtime Hook|Eject Bay|Trail Inspector)\b/gi)).map((match) => match[0] ?? "")
7562
+ ];
7563
+ return sortedUnique(
7564
+ candidates.map((candidate) => candidate.trim()).filter((candidate) => candidate.length >= 3).slice(0, 8)
7565
+ );
7566
+ }
7567
+ function supportsNativeExtraction(sourceClient) {
7568
+ return sourceClient === "codex" || sourceClient === "claude" || sourceClient === "claude-code";
7569
+ }
7570
+ function buildNativeAiClientExtractions(imports, generatedAt = (/* @__PURE__ */ new Date()).toISOString()) {
7571
+ const grouped = /* @__PURE__ */ new Map();
7572
+ for (const source of imports) {
7573
+ const sourceClient = sourceClientForImport(source);
7574
+ if (!supportsNativeExtraction(sourceClient)) continue;
7575
+ const bucket = grouped.get(sourceClient) ?? {
7576
+ findings: [],
7577
+ searchedSources: [],
7578
+ searchedSessionCount: 0,
7579
+ skippedSessionCount: 0
7580
+ };
7581
+ bucket.searchedSources.push(source.sourceId);
7582
+ bucket.searchedSessionCount += 1;
7583
+ source.text.split(/\r?\n/).forEach((line, lineIndex) => {
7584
+ const trimmed = line.trim();
7585
+ if (!trimmed) return;
7586
+ const type = nativeExtractionFindingType(trimmed);
7587
+ if (!type) {
7588
+ bucket.skippedSessionCount += 1;
7589
+ return;
7590
+ }
7591
+ bucket.findings.push({
7592
+ type,
7593
+ title: titleFromText(trimmed, type.replace(/_/g, " ")),
7594
+ summary: trimmed,
7595
+ source_client: sourceClient,
7596
+ source_id: source.sourceId,
7597
+ source_label: source.sourceLabel,
7598
+ evidence_ref: `${source.sourceId}:native:${lineIndex + 1}`,
7599
+ confidence: nativeFindingConfidence(type, trimmed),
7600
+ occurred_at: generatedAt,
7601
+ redacted_verbatim: trimmed.slice(0, 420),
7602
+ privacy_state: "redacted",
7603
+ metadata: {
7604
+ episode_type: nativeEpisodeType(trimmed),
7605
+ related_entity_names: nativeRelatedEntityNames(trimmed),
7606
+ related_source_ids: [source.sourceId],
7607
+ state: type === "blocker" || type === "missed_orchestration_opportunity" ? "blocked" : type === "outcome" || type === "artifact" ? "verified" : "observed"
7608
+ }
7609
+ });
7610
+ });
7611
+ grouped.set(sourceClient, bucket);
7612
+ }
7613
+ return [...grouped.entries()].filter(([, bucket]) => bucket.findings.length > 0).map(([sourceClient, bucket]) => ({
7614
+ schema_version: WORK_GRAPH_EXTRACTION_SCHEMA_VERSION,
7615
+ extraction_id: `${sourceClient}:native-extraction:${shortHash({
7616
+ sources: bucket.searchedSources,
7617
+ findings: bucket.findings.map((finding) => finding.evidence_ref)
7618
+ }, 12)}`,
7619
+ source_client: sourceClient,
7620
+ source_label: `${labelForSourceClient(sourceClient)} native extraction`,
7621
+ searched_sources: sortedUnique(bucket.searchedSources),
7622
+ search_queries: [
7623
+ {
7624
+ id: "native_session_episodes",
7625
+ lens: `${labelForSourceClient(sourceClient)} episode extraction`,
7626
+ query: "Extract decisions, artifacts, blockers, outcomes, handoffs, and missed OrgX writeback from local AI-client session lines.",
7627
+ result_count: bucket.findings.length
7628
+ }
7629
+ ],
7630
+ extraction_quality: {
7631
+ confidence: normalizeConfidence(
7632
+ bucket.findings.reduce((total, finding) => total + (finding.confidence ?? 0.72), 0) / bucket.findings.length,
7633
+ 0.74
7634
+ ),
7635
+ searched_session_count: bucket.searchedSessionCount,
7636
+ skipped_session_count: bucket.skippedSessionCount,
7637
+ notes: [
7638
+ "Auto-generated by orgx-wizard from local AI-client session imports.",
7639
+ "Raw transcripts are excluded; only redacted evidence snippets and source refs are retained."
7640
+ ]
7641
+ },
7642
+ findings: bucket.findings
7643
+ }));
7644
+ }
7316
7645
  function buildDerivedFindings(imports) {
7317
7646
  const findings = [];
7318
7647
  let derivedIndex = 0;
@@ -7466,9 +7795,84 @@ function scoreOpportunity(coverage, findings) {
7466
7795
  orgx_fit: orgxFit
7467
7796
  };
7468
7797
  }
7798
+ function estimateImpactProjection(input) {
7799
+ const { coverage, findings, opportunityScore, patterns, trails } = input;
7800
+ const blockerEvents = trails.filter((trail) => trail.subject_entity_type === "blocker").reduce((total, trail) => total + Math.max(1, trail.events.length), 0);
7801
+ const decisionEvents = trails.filter((trail) => trail.subject_entity_type === "decision").reduce((total, trail) => total + Math.max(1, trail.events.length), 0);
7802
+ const artifactEvents = trails.filter((trail) => trail.subject_entity_type === "artifact").reduce((total, trail) => total + Math.max(1, trail.events.length), 0);
7803
+ const missedCount = findings.filter((finding) => finding.type === "missed_orchestration_opportunity").length;
7804
+ const repeatedEvents = trails.filter((trail) => trail.events.length > 1).reduce((total, trail) => total + trail.events.length, 0);
7805
+ const rawHours = blockerEvents * 0.75 + decisionEvents * 0.45 + artifactEvents * 0.25 + missedCount * 0.65 + repeatedEvents * 0.35 + coverage.missing.length * 0.4;
7806
+ const hours = Number(Math.max(0.5, Math.min(40, rawHours)).toFixed(1));
7807
+ const acceleration = clampScore2(
7808
+ 6 + Math.round(opportunityScore.automation_potential * 0.22) + patterns.filter((pattern) => pattern.severity === "critical" || pattern.severity === "high").length * 5 + (coverage.manifests?.filter((manifest) => manifest.status === "connected").length ?? 0) * 2
7809
+ );
7810
+ const confidenceInputs = [
7811
+ ...findings.map((finding) => finding.confidence),
7812
+ ...patterns.map((pattern) => pattern.confidence),
7813
+ coverage.coverage_score ? coverage.coverage_score / 100 : 0.55
7814
+ ];
7815
+ const confidence = Number((confidenceInputs.reduce((total, value) => total + value, 0) / Math.max(1, confidenceInputs.length)).toFixed(2));
7816
+ return {
7817
+ time_saved_hours_per_week: hours,
7818
+ acceleration_percent: Math.min(70, acceleration),
7819
+ estimated_monthly_value_usd: Math.round(hours * 4.33 * 200),
7820
+ confidence,
7821
+ basis: [
7822
+ `${blockerEvents} blocker event${blockerEvents === 1 ? "" : "s"} at 45 minutes of reconstruction/coordination each.`,
7823
+ `${decisionEvents} decision event${decisionEvents === 1 ? "" : "s"} at 27 minutes of rediscovery/promotion each.`,
7824
+ `${missedCount} missed orchestration signal${missedCount === 1 ? "" : "s"} at 39 minutes of manual writeback each.`,
7825
+ `${coverage.missing.length} missing source${coverage.missing.length === 1 ? "" : "s"} reducing attribution confidence.`
7826
+ ],
7827
+ assumptions: [
7828
+ "Uses a conservative $200/hour blended founder/operator cost for public-safe value estimates.",
7829
+ "Acceleration estimates the execution lift from turning repeated trails into automated attribution, decisions, and source connections.",
7830
+ "Impact is directional until the user claims the fingerprint and confirms or corrects trail evidence."
7831
+ ]
7832
+ };
7833
+ }
7834
+ function scoreExecutionQuality(input) {
7835
+ const { coverage, findings, impact, patterns, recommendations, trails } = input;
7836
+ const uniqueClients = sortedUnique(findings.map((finding) => finding.source_client));
7837
+ const unknownCount = findings.filter((finding) => finding.source_client === "unknown").length;
7838
+ const multiEventTrails = trails.filter((trail) => trail.events.length > 1).length;
7839
+ const evidenceCoverage = clampScore2(
7840
+ (coverage.coverage_score ?? 50) + Math.min(18, coverage.connected.length * 3) - coverage.missing.length * 4
7841
+ );
7842
+ const sourceAttribution = clampScore2(
7843
+ 34 + uniqueClients.filter((client) => client !== "unknown").length * 10 + (coverage.manifests?.filter((manifest) => manifest.status === "connected").length ?? 0) * 6 - unknownCount * 8
7844
+ );
7845
+ const trailDepth = clampScore2(
7846
+ 25 + (trails.length > 0 ? Math.round(multiEventTrails / trails.length * 45) : 0) + Math.min(20, trails.reduce((total, trail) => total + trail.events.length, 0) * 2)
7847
+ );
7848
+ const insightDepth = clampScore2(
7849
+ 30 + Math.min(28, patterns.length * 8) + Math.min(18, findings.filter((finding) => ["blocker", "decision", "outcome", "missed_orchestration_opportunity"].includes(finding.type)).length * 3)
7850
+ );
7851
+ const actionability = clampScore2(
7852
+ 30 + Math.min(35, recommendations.length * 10) + Math.min(20, recommendations.filter((recommendation) => recommendation.priority === "p0").length * 10)
7853
+ );
7854
+ const impactConfidence = clampScore2(impact.confidence * 100);
7855
+ const overall = clampScore2(
7856
+ evidenceCoverage * 0.22 + sourceAttribution * 0.18 + trailDepth * 0.18 + insightDepth * 0.18 + actionability * 0.14 + impactConfidence * 0.1
7857
+ );
7858
+ return {
7859
+ overall,
7860
+ evidence_coverage: evidenceCoverage,
7861
+ source_attribution: sourceAttribution,
7862
+ trail_depth: trailDepth,
7863
+ insight_depth: insightDepth,
7864
+ actionability,
7865
+ impact_confidence: impactConfidence,
7866
+ notes: [
7867
+ "10/10 requires client-native extraction per source, multi-event trails, source-specific evidence, and action recommendations tied to impact.",
7868
+ multiEventTrails === trails.length ? "Every trail has multiple evidence events." : `${trails.length - multiEventTrails} trail${trails.length - multiEventTrails === 1 ? "" : "s"} still need more chronology before they should be called recurring.`,
7869
+ coverage.missing.length > 0 ? `Missing source coverage: ${coverage.missing.join(", ")}.` : "No required source gaps were declared for this run."
7870
+ ]
7871
+ };
7872
+ }
7469
7873
  function inferFinalState(findings) {
7470
7874
  if (findings.some((finding) => finding.type === "blocker")) return "blocked";
7471
- if (findings.some((finding) => finding.type === "artifact" || /\b(shipped|completed|done|verified)\b/i.test(finding.summary))) {
7875
+ if (findings.some((finding) => finding.type === "artifact" || finding.type === "outcome" || /\b(shipped|completed|done|verified)\b/i.test(finding.summary))) {
7472
7876
  return "completed";
7473
7877
  }
7474
7878
  if (findings.some((finding) => finding.type === "action" || finding.type === "decision")) return "in_progress";
@@ -7534,6 +7938,8 @@ function entityTypeForFinding(finding) {
7534
7938
  case "goal":
7535
7939
  case "initiative_candidate":
7536
7940
  return "initiative";
7941
+ case "outcome":
7942
+ return "outcome";
7537
7943
  case "action":
7538
7944
  return /\b(outcome|shipped|completed|verified)\b/i.test(finding.summary) ? "outcome" : "task";
7539
7945
  case "missed_orchestration_opportunity":
@@ -7586,6 +7992,8 @@ function eventTypeForFinding(finding) {
7586
7992
  case "goal":
7587
7993
  case "initiative_candidate":
7588
7994
  return "initiative_created";
7995
+ case "outcome":
7996
+ return "outcome_recorded";
7589
7997
  case "action":
7590
7998
  return /\b(outcome|result|impact|roi)\b/i.test(finding.summary) ? "outcome_recorded" : "recommendation_generated";
7591
7999
  case "missed_orchestration_opportunity":
@@ -7596,7 +8004,7 @@ function trailStateForFindings(findings) {
7596
8004
  if (findings.some((finding) => finding.type === "blocker")) return "blocked";
7597
8005
  if (findings.some((finding) => finding.type === "missed_orchestration_opportunity")) return "missing_evidence";
7598
8006
  if (findings.some((finding) => /\b(contradict|conflict)\b/i.test(finding.summary))) return "contradicted";
7599
- if (findings.some((finding) => /\b(verified|proof|passed|complete_with_proof)\b/i.test(finding.summary))) return "verified";
8007
+ if (findings.some((finding) => finding.type === "outcome" || /\b(verified|proof|passed|complete_with_proof)\b/i.test(finding.summary))) return "verified";
7600
8008
  if (findings.some((finding) => finding.type === "decision")) return "inferred";
7601
8009
  return "observed";
7602
8010
  }
@@ -7604,6 +8012,7 @@ function trailValenceForFindings(findings, recurrence) {
7604
8012
  if (findings.some((finding) => finding.type === "blocker")) return recurrence > 1 ? "escalating" : "risk";
7605
8013
  if (findings.some((finding) => finding.type === "missed_orchestration_opportunity")) return "leak";
7606
8014
  if (findings.some((finding) => finding.type === "decision") && recurrence > 1) return "wasteful_recurrence";
8015
+ if (findings.some((finding) => finding.type === "outcome")) return "healthy";
7607
8016
  if (findings.some((finding) => finding.type === "business")) return "opportunity";
7608
8017
  if (findings.some((finding) => finding.type === "artifact")) return "healthy";
7609
8018
  return recurrence > 1 ? "useful_recurrence" : "opportunity";
@@ -7611,35 +8020,93 @@ function trailValenceForFindings(findings, recurrence) {
7611
8020
  function trailShapeForFindings(findings, recurrence) {
7612
8021
  const decisions = findings.filter((finding) => finding.type === "decision").length;
7613
8022
  const artifacts = findings.filter((finding) => finding.type === "artifact").length;
8023
+ if (recurrence <= 1) return "single_signal";
7614
8024
  if (decisions > artifacts && decisions > 0) return "decision_heavy_artifact_light";
7615
8025
  if (artifacts > decisions && artifacts > 0 && decisions === 0) return "artifact_heavy_decision_light";
7616
- if (findings.some((finding) => finding.type === "blocker")) return "accelerating_issue";
8026
+ if (findings.some((finding) => finding.type === "blocker")) return recurrence >= 3 ? "accelerating_issue" : "dense_recent_cluster";
7617
8027
  if (recurrence >= 3) return "chronic_recurrence";
7618
8028
  if (findings.some((finding) => /\b(resurface|again|back|revived|reappeared)\b/i.test(finding.summary))) return "zombie_revival";
7619
- if (findings.some((finding) => /\b(verified|passed|shipped|completed)\b/i.test(finding.summary))) return "healthy_execution";
8029
+ if (findings.some((finding) => finding.type === "outcome" || /\b(verified|passed|shipped|completed)\b/i.test(finding.summary))) return "healthy_execution";
7620
8030
  return "dense_recent_cluster";
7621
8031
  }
8032
+ function findingTimestamp(finding, fallback) {
8033
+ const occurredAt = finding.metadata.occurred_at;
8034
+ return isIsoTimestamp(occurredAt) ? occurredAt : fallback;
8035
+ }
8036
+ function canonicalRelatedToken(finding) {
8037
+ const candidates = [
8038
+ ...arrayOfStrings(finding.metadata.related_source_ids),
8039
+ ...arrayOfStrings(finding.metadata.related_entity_names),
8040
+ finding.source_id,
8041
+ finding.title
8042
+ ];
8043
+ const scored = candidates.map((candidate) => candidate.trim()).filter(Boolean).map((candidate) => {
8044
+ const normalized = candidate.toLowerCase();
8045
+ let score = 0;
8046
+ if (/_/.test(candidate)) score += 6;
8047
+ if (/orgx|mcp|wizard|plugin|runtime|hook|work graph|profile|trace|scaffold|ship|spawn|record|submit|list/i.test(candidate)) score += 5;
8048
+ if (/session|jsonl|rollout|line|current-release/i.test(normalized)) score -= 4;
8049
+ if (candidate.length > 80) score -= 2;
8050
+ return { candidate, score };
8051
+ }).filter((item) => item.score > 0).sort((left, right) => right.score - left.score);
8052
+ return scored[0]?.candidate ?? null;
8053
+ }
8054
+ function groupingKeyForFinding(finding) {
8055
+ const entityType = entityTypeForFinding(finding);
8056
+ const token = canonicalRelatedToken(finding);
8057
+ if (token && ["blocker", "source", "surface", "initiative", "outcome", "task"].includes(entityType)) {
8058
+ return `${entityType}:${slugPart(token)}`;
8059
+ }
8060
+ return `${entityType}:${slugPart(finding.title)}`;
8061
+ }
8062
+ function trailTitleForGroup(entityType, group) {
8063
+ const token = canonicalRelatedToken(group[0]);
8064
+ if (group.length > 1 && token) {
8065
+ const label = token.replace(/[-_]/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
8066
+ if (entityType === "blocker") return `${label} is recurring as an execution blocker`;
8067
+ if (entityType === "source") return `${label} is an attribution coverage gap`;
8068
+ if (entityType === "surface") return `${label} is becoming a product surface trail`;
8069
+ if (entityType === "outcome") return `${label} is becoming outcome evidence`;
8070
+ return `${label} is becoming durable work`;
8071
+ }
8072
+ return group[0].title;
8073
+ }
8074
+ function trailSummaryForGroup(group) {
8075
+ if (group.length === 1) return group[0].summary;
8076
+ const sources = sortedUnique(group.map((finding) => labelForSourceClient(finding.source_client)));
8077
+ const states = sortedUnique(
8078
+ group.map((finding) => typeof finding.metadata.state === "string" ? finding.metadata.state : "").filter(Boolean)
8079
+ );
8080
+ return [
8081
+ `${group.length} evidence events connect this trail across ${sources.join(", ")}.`,
8082
+ states.length > 0 ? `Observed states: ${states.join(", ")}.` : "",
8083
+ group[0]?.summary ?? ""
8084
+ ].filter(Boolean).join(" ");
8085
+ }
7622
8086
  function buildWorkGraphTrails(findings, generatedAt) {
7623
8087
  const grouped = /* @__PURE__ */ new Map();
7624
8088
  for (const finding of findings) {
7625
- const entityType = entityTypeForFinding(finding);
7626
- const key = `${entityType}:${slugPart(finding.title)}`;
8089
+ const key = groupingKeyForFinding(finding);
7627
8090
  grouped.set(key, [...grouped.get(key) ?? [], finding]);
7628
8091
  }
7629
8092
  return [...grouped.entries()].map(([key, group], index) => {
7630
- const first = group[0];
8093
+ const sortedGroup = [...group].sort(
8094
+ (left, right) => findingTimestamp(left, generatedAt).localeCompare(findingTimestamp(right, generatedAt))
8095
+ );
8096
+ const first = sortedGroup[0];
7631
8097
  const entityType = entityTypeForFinding(first);
7632
8098
  const entityId = `${entityType}:${shortHash(key, 12)}`;
7633
- const trailId = `trail:${shortHash({ key, evidence: group.map((finding) => finding.evidence_ref) }, 14)}`;
7634
- const recurrence = group.length;
7635
- const evidenceRefs = sortedUnique(group.map((finding) => finding.evidence_ref));
7636
- const events = group.map((finding, eventIndex) => ({
8099
+ const trailId = `trail:${shortHash({ key, evidence: sortedGroup.map((finding) => finding.evidence_ref) }, 14)}`;
8100
+ const recurrence = sortedGroup.length;
8101
+ const evidenceRefs = sortedUnique(sortedGroup.map((finding) => finding.evidence_ref));
8102
+ const events = sortedGroup.map((finding, eventIndex) => ({
7637
8103
  id: `${trailId}:event:${eventIndex + 1}`,
7638
8104
  trail_id: trailId,
7639
8105
  event_type: eventTypeForFinding(finding),
7640
8106
  entity_id: entityId,
7641
8107
  entity_type: entityType,
7642
- timestamp: generatedAt,
8108
+ timestamp: findingTimestamp(finding, generatedAt),
8109
+ ...typeof finding.metadata.actor_id === "string" ? { actor_id: finding.metadata.actor_id } : {},
7643
8110
  source_id: finding.source_id,
7644
8111
  source_type: finding.source_client,
7645
8112
  redacted_verbatim: finding.summary.slice(0, 320),
@@ -7656,28 +8123,30 @@ function buildWorkGraphTrails(findings, generatedAt) {
7656
8123
  confidence: Math.min(0.92, Math.max(0.58, event.confidence - 0.04)),
7657
8124
  evidence_refs: event.evidence_refs
7658
8125
  }));
7659
- const confidence = group.reduce((total, finding) => total + finding.confidence, 0) / group.length;
8126
+ const confidence = sortedGroup.reduce((total, finding) => total + finding.confidence, 0) / sortedGroup.length;
8127
+ const createdAt = events[0]?.timestamp ?? generatedAt;
8128
+ const updatedAt = events[events.length - 1]?.timestamp ?? generatedAt;
7660
8129
  return {
7661
8130
  id: trailId,
7662
8131
  kind: trailKindForEntity(entityType),
7663
- title: first.title,
7664
- summary: first.summary,
8132
+ title: trailTitleForGroup(entityType, sortedGroup),
8133
+ summary: trailSummaryForGroup(sortedGroup),
7665
8134
  subject_entity_id: entityId,
7666
8135
  subject_entity_type: entityType,
7667
- state: trailStateForFindings(group),
7668
- valence: trailValenceForFindings(group, recurrence),
8136
+ state: trailStateForFindings(sortedGroup),
8137
+ valence: trailValenceForFindings(sortedGroup, recurrence),
7669
8138
  confidence: Number(confidence.toFixed(2)),
7670
- recurrence_score: Math.min(100, recurrence * 28 + evidenceRefs.length * 6),
8139
+ recurrence_score: recurrence <= 1 ? Math.min(24, evidenceRefs.length * 8 + 10) : Math.min(100, recurrence * 24 + evidenceRefs.length * 7),
7671
8140
  impact_score: Math.min(100, Math.round(first.confidence * 70) + recurrence * 8 + (index < 4 ? 10 : 0)),
7672
8141
  privacy_state: "redacted",
7673
8142
  events,
7674
8143
  edges,
7675
8144
  evidence_refs: evidenceRefs,
7676
- blocker_ids: group.filter((finding) => finding.type === "blocker").map((finding) => finding.evidence_ref),
8145
+ blocker_ids: sortedGroup.filter((finding) => finding.type === "blocker").map((finding) => finding.evidence_ref),
7677
8146
  recommendation_ids: [],
7678
- created_at: generatedAt,
7679
- updated_at: generatedAt,
7680
- shape: trailShapeForFindings(group, recurrence)
8147
+ created_at: createdAt,
8148
+ updated_at: updatedAt,
8149
+ shape: trailShapeForFindings(sortedGroup, recurrence)
7681
8150
  };
7682
8151
  });
7683
8152
  }
@@ -7796,9 +8265,12 @@ function buildRecurringPatterns(coverage, findings, trails) {
7796
8265
  });
7797
8266
  }
7798
8267
  if (blockerTrails.length > 0) {
8268
+ const topBlockerTrail = [...blockerTrails].sort(
8269
+ (left, right) => right.events.length - left.events.length || right.impact_score - left.impact_score
8270
+ )[0];
7799
8271
  patterns.push({
7800
8272
  id: "pattern:handoff-friction",
7801
- title: "Blockers are becoming handoff friction",
8273
+ title: topBlockerTrail && topBlockerTrail.events.length > 1 ? `${topBlockerTrail.title} and needs owner-visible resolution` : "Blockers are becoming handoff friction",
7802
8274
  description: `${blockerTrails.length} blocker trail${blockerTrails.length === 1 ? "" : "s"} need owner-visible resolution.`,
7803
8275
  pattern_type: "handoff_friction",
7804
8276
  affected_trail_ids: blockerTrails.map((trail) => trail.id),
@@ -7872,6 +8344,18 @@ function buildTrailRecommendations(patterns, trails) {
7872
8344
  expected_lift: "+continuous attribution",
7873
8345
  confidence: pattern.confidence
7874
8346
  });
8347
+ } else if (pattern.pattern_type === "handoff_friction") {
8348
+ add({
8349
+ id: "recommendation:resolve-blocker-handoff",
8350
+ title: "Resolve the blocker handoff trail",
8351
+ summary: "Promote the recurring blocker path into assigned work with proof requirements and a clear owner.",
8352
+ action_type: "assign_owner",
8353
+ trail_ids: pattern.affected_trail_ids,
8354
+ evidence_refs: evidenceRefs,
8355
+ priority: pattern.severity === "critical" || pattern.severity === "high" ? "p0" : "p1",
8356
+ expected_lift: "+execution continuity",
8357
+ confidence: pattern.confidence
8358
+ });
7875
8359
  } else if (pattern.pattern_type === "business_signal_unclaimed" || pattern.pattern_type === "repeated_work") {
7876
8360
  add({
7877
8361
  id: "recommendation:launch-initiative",
@@ -7900,17 +8384,24 @@ function buildTrailRecommendations(patterns, trails) {
7900
8384
  confidence: topTrail.confidence
7901
8385
  });
7902
8386
  }
7903
- return recommendations.slice(0, 5);
8387
+ const priorityRank = { p0: 0, p1: 1, p2: 2 };
8388
+ return recommendations.sort(
8389
+ (left, right) => priorityRank[left.priority] - priorityRank[right.priority] || right.confidence - left.confidence
8390
+ ).slice(0, 5);
7904
8391
  }
7905
8392
  function buildWorkGraphMirror(input) {
7906
- const { coverage, generatedAt, patterns, recommendations, trails } = input;
8393
+ const { coverage, generatedAt, impact, patterns, recommendations, trails } = input;
7907
8394
  const topTrail = [...trails].sort((a, b) => b.impact_score - a.impact_score)[0];
7908
8395
  const topPattern = [...patterns].sort((a, b) => b.recurrence_count - a.recurrence_count)[0];
7909
8396
  const decisionCount = trailsForType(trails, "decision").length;
7910
8397
  const artifactCount = trailsForType(trails, "artifact").length;
7911
8398
  const blockerCount = trailsForType(trails, "blocker").length;
7912
8399
  const sourceGapCount = coverage.missing.length;
7913
- const headline = topPattern ? topPattern.title : topTrail ? `${topTrail.title} is the clearest work trail` : "Your work is leaving an operating trail";
8400
+ const sourceClients = sortedUnique(
8401
+ trails.flatMap((trail) => trail.events.map((event) => event.source_type))
8402
+ );
8403
+ const sourcePhrase = sourceClients.length > 0 ? sourceClients.map(labelForSourceClient).join(", ") : coverage.connected.join(", ") || "local sources";
8404
+ const headline = blockerCount > 0 ? "Your AI work is shipping, but the execution trail is breaking" : sourceGapCount > 0 ? "Your work leaves trails, but the proof chain is still incomplete" : topPattern ? "Your work has a recurring operating pattern worth preserving" : topTrail ? "Your work is becoming an operating profile" : "Your work is leaving an operating trail";
7914
8405
  const primaryClaimRefs = topTrail?.evidence_refs ?? [];
7915
8406
  const claims = [
7916
8407
  {
@@ -7927,17 +8418,25 @@ function buildWorkGraphMirror(input) {
7927
8418
  },
7928
8419
  {
7929
8420
  id: "mirror:missing-sources",
7930
- text: sourceGapCount > 0 ? `${sourceGapCount} source gap${sourceGapCount === 1 ? "" : "s"} still limit attribution depth.` : "The connected sources are enough for a first operating profile.",
8421
+ text: sourceGapCount > 0 ? `${sourceGapCount} source gap${sourceGapCount === 1 ? "" : "s"} still limit attribution depth.` : "The connected sources are enough for a first operating profile, pending human review.",
7931
8422
  evidence_refs: trailsForType(trails, "source").flatMap((trail) => trail.evidence_refs).slice(0, 4),
7932
8423
  confidence: sourceGapCount > 0 ? 0.76 : 0.66
7933
- }
8424
+ },
8425
+ ...impact ? [{
8426
+ id: "mirror:impact",
8427
+ text: `${impact.time_saved_hours_per_week} hours/week and +${impact.acceleration_percent}% execution acceleration are recoverable if the top trails become durable.`,
8428
+ evidence_refs: trails.flatMap((trail) => trail.evidence_refs).slice(0, 6),
8429
+ confidence: impact.confidence
8430
+ }] : []
7934
8431
  ];
7935
8432
  const body = [
7936
- `OrgX found ${trails.length} evidence trail${trails.length === 1 ? "" : "s"} across ${coverage.connected.join(", ") || "local sources"}.`,
7937
- `The strongest signal is ${topPattern ? topPattern.title.toLowerCase() : topTrail?.title ?? "still forming"}.`,
7938
- blockerCount > 0 ? `${blockerCount} blocker trail${blockerCount === 1 ? "" : "s"} need promotion into owner-visible work.` : "The healthiest trails already connect evidence to action.",
7939
- recommendations[0] ? `The next durable move is: ${recommendations[0].title}.` : "The next move is to inspect the highest-confidence trail before publishing it."
7940
- ].join(" ");
8433
+ `You have real work moving across ${sourcePhrase}: OrgX found ${trails.length} evidence trail${trails.length === 1 ? "" : "s"} without requiring OrgX tool calls to be present.`,
8434
+ topPattern ? `The strongest recurring pattern is ${topPattern.title.toLowerCase()}, with ${topPattern.recurrence_count} appearance${topPattern.recurrence_count === 1 ? "" : "s"} still needing durable writeback.` : topTrail ? `The clearest trail is ${topTrail.title.toLowerCase()}, but it still needs stronger chronology before it becomes durable operating memory.` : "The first profile is forming from sparse evidence and should be reviewed before promotion.",
8435
+ blockerCount > 0 ? `${blockerCount} blocker trail${blockerCount === 1 ? "" : "s"} keep work returning as new work instead of becoming owner-visible resolution.` : `${decisionCount} decision trail${decisionCount === 1 ? "" : "s"} and ${artifactCount} artifact trail${artifactCount === 1 ? "" : "s"} already show where work can be preserved.`,
8436
+ sourceGapCount > 0 ? `${sourceGapCount} source gap${sourceGapCount === 1 ? "" : "s"} still limit trust, especially ownership, handoffs, and later verification.` : "The connected sources are enough for a first public-safe profile, pending human review.",
8437
+ impact ? `Left unresolved, the profile estimates ${impact.time_saved_hours_per_week} recoverable hours/week, +${impact.acceleration_percent}% acceleration, and about $${impact.estimated_monthly_value_usd.toLocaleString("en-US")}/month in operator leverage.` : "",
8438
+ recommendations[0] ? `The next repair is concrete: ${recommendations[0].title}.` : "The next move is to inspect the highest-confidence trail before publishing it."
8439
+ ].filter(Boolean).join(" ");
7941
8440
  return {
7942
8441
  headline,
7943
8442
  body,
@@ -7948,13 +8447,39 @@ function buildWorkGraphMirror(input) {
7948
8447
  };
7949
8448
  }
7950
8449
  function buildTensionMetrics(input) {
7951
- const { coverage, patterns, trails } = input;
8450
+ const { coverage, impact, patterns, quality, trails } = input;
7952
8451
  const decisionTrails = trailsForType(trails, "decision");
7953
8452
  const blockerTrails = trailsForType(trails, "blocker");
7954
8453
  const artifactTrails = trailsForType(trails, "artifact");
7955
8454
  const missingSourceTrails = trailsForType(trails, "source");
7956
8455
  const topReady = trails.filter((trail) => trail.impact_score >= 70 && trail.confidence >= 0.75);
7957
8456
  return [
8457
+ ...quality ? [{
8458
+ id: "tension:quality-score",
8459
+ label: "quality score",
8460
+ value: `${quality.overall}/100`,
8461
+ tone: quality.overall >= 82 ? "good" : quality.overall >= 65 ? "warning" : "danger",
8462
+ trail_ids: trails.slice(0, 8).map((trail) => trail.id),
8463
+ evidence_refs: trails.flatMap((trail) => trail.evidence_refs).slice(0, 8),
8464
+ explanation: "Weighted audit quality across evidence coverage, attribution, trail depth, insight depth, actionability, and impact confidence."
8465
+ }] : [],
8466
+ ...impact ? [{
8467
+ id: "tension:time-saved",
8468
+ label: "hours recoverable",
8469
+ value: `${impact.time_saved_hours_per_week}h/wk`,
8470
+ tone: impact.time_saved_hours_per_week >= 4 ? "warning" : "muted",
8471
+ trail_ids: patterns.flatMap((pattern) => pattern.affected_trail_ids).slice(0, 8),
8472
+ evidence_refs: trails.flatMap((trail) => trail.evidence_refs).slice(0, 8),
8473
+ explanation: "Estimated time recovered by promoting repeated decisions, blockers, artifacts, and writeback gaps into durable operating memory."
8474
+ }, {
8475
+ id: "tension:acceleration",
8476
+ label: "acceleration",
8477
+ value: `+${impact.acceleration_percent}%`,
8478
+ tone: impact.acceleration_percent >= 30 ? "good" : "muted",
8479
+ trail_ids: patterns.flatMap((pattern) => pattern.affected_trail_ids).slice(0, 8),
8480
+ evidence_refs: trails.flatMap((trail) => trail.evidence_refs).slice(0, 8),
8481
+ explanation: "Directional execution lift from connecting source evidence, writeback, owners, and action recommendations."
8482
+ }] : [],
7958
8483
  {
7959
8484
  id: "tension:work-leaks",
7960
8485
  label: "work leaks",
@@ -8011,6 +8536,317 @@ function countFindingsByType(findings) {
8011
8536
  Object.entries(counts).sort(([left], [right]) => left.localeCompare(right))
8012
8537
  );
8013
8538
  }
8539
+ function isIsoTimestamp(value) {
8540
+ return typeof value === "string" && !Number.isNaN(Date.parse(value));
8541
+ }
8542
+ function redactionStateForFinding(finding) {
8543
+ const privacy = typeof finding.metadata.privacy_state === "string" ? finding.metadata.privacy_state : "";
8544
+ if (privacy === "private") return "private";
8545
+ if (privacy === "redacted") return "redacted";
8546
+ return "public_summary";
8547
+ }
8548
+ function attributionKindForFinding(type) {
8549
+ switch (type) {
8550
+ case "action":
8551
+ case "blocker":
8552
+ case "outcome":
8553
+ return "action";
8554
+ case "decision":
8555
+ case "artifact":
8556
+ case "person":
8557
+ case "business":
8558
+ case "product_surface":
8559
+ case "goal":
8560
+ return type;
8561
+ case "initiative_candidate":
8562
+ return "goal";
8563
+ case "missed_orchestration_opportunity":
8564
+ return "source";
8565
+ default:
8566
+ return null;
8567
+ }
8568
+ }
8569
+ function attributionEventTypeForFinding(finding) {
8570
+ if (finding.source_client === "slack") return "slack_message";
8571
+ if (finding.source_client === "github") return "github_event";
8572
+ if (finding.source_client === "linear") return "linear_event";
8573
+ if (finding.source_client === "gmail") return "gmail_message";
8574
+ if (finding.source_client === "calendar") return "calendar_event";
8575
+ if (finding.source_client === "notion" || finding.source_client === "docs") return "doc_signal";
8576
+ if (finding.source_client === "mcp") return "mcp_call";
8577
+ if (finding.type === "artifact") return "repo_artifact";
8578
+ if (finding.type === "missed_orchestration_opportunity") return "source_coverage";
8579
+ if (/\b(mcp|tool|hook|runtime)\b/i.test(`${finding.title} ${finding.summary}`)) return "tool_signal";
8580
+ return "client_extraction";
8581
+ }
8582
+ function nodeBucketForKind(kind) {
8583
+ switch (kind) {
8584
+ case "decision":
8585
+ return "decisions";
8586
+ case "artifact":
8587
+ return "artifacts";
8588
+ case "person":
8589
+ return "people";
8590
+ case "agent":
8591
+ return "agents";
8592
+ case "tool":
8593
+ return "tools";
8594
+ case "business":
8595
+ return "businesses";
8596
+ case "product_surface":
8597
+ return "product_surfaces";
8598
+ case "goal":
8599
+ return "goals";
8600
+ case "source":
8601
+ return "sources";
8602
+ case "action":
8603
+ default:
8604
+ return "actions";
8605
+ }
8606
+ }
8607
+ function nodeIdForFinding(kind, finding) {
8608
+ const dedupeSource = finding.metadata.dedupe_key ?? finding.evidence_ref ?? finding.title;
8609
+ return `${kind}:${slugPart(finding.title)}:${shortHash(dedupeSource, 8)}`;
8610
+ }
8611
+ function buildFindingNode(finding, kind) {
8612
+ const id = nodeIdForFinding(kind, finding);
8613
+ return {
8614
+ id,
8615
+ kind,
8616
+ label: finding.title,
8617
+ summary: finding.summary,
8618
+ source_client: finding.source_client,
8619
+ evidence_refs: [finding.evidence_ref],
8620
+ linked_node_ids: [],
8621
+ confidence: finding.confidence,
8622
+ weight: clampScore2(finding.confidence * 100),
8623
+ review_state: "unreviewed",
8624
+ dedupe_key: `${kind}:${slugPart(finding.title)}`,
8625
+ metadata: {
8626
+ source_id: finding.source_id,
8627
+ finding_type: finding.type,
8628
+ ...finding.metadata
8629
+ }
8630
+ };
8631
+ }
8632
+ function mergeNodes(nodes) {
8633
+ const merged = /* @__PURE__ */ new Map();
8634
+ for (const node of nodes) {
8635
+ const key = node.dedupe_key ?? node.id;
8636
+ const previous = merged.get(key);
8637
+ if (!previous) {
8638
+ merged.set(key, node);
8639
+ continue;
8640
+ }
8641
+ merged.set(key, {
8642
+ ...previous,
8643
+ confidence: Math.max(previous.confidence, node.confidence),
8644
+ evidence_refs: sortedUnique([...previous.evidence_refs, ...node.evidence_refs]),
8645
+ linked_node_ids: sortedUnique([...previous.linked_node_ids, ...node.linked_node_ids]),
8646
+ weight: Math.max(previous.weight, node.weight)
8647
+ });
8648
+ }
8649
+ return [...merged.values()].sort((left, right) => right.weight - left.weight);
8650
+ }
8651
+ function sourceNode(source, connected) {
8652
+ const label = source.trim() || "Unknown source";
8653
+ return {
8654
+ id: `source:${slugPart(label)}:${connected ? "connected" : "missing"}`,
8655
+ kind: "source",
8656
+ label,
8657
+ summary: connected ? `${label} contributed evidence to this Work Graph.` : `${label} was not connected, so related attribution remains incomplete.`,
8658
+ evidence_refs: [],
8659
+ linked_node_ids: [],
8660
+ confidence: connected ? 0.8 : 0.52,
8661
+ weight: connected ? 70 : 62,
8662
+ review_state: connected ? "unreviewed" : "important",
8663
+ dedupe_key: `source:${slugPart(label)}`,
8664
+ metadata: { connected }
8665
+ };
8666
+ }
8667
+ function buildToolNodes(coverage, findings) {
8668
+ const mentionsOrgxTool = coverage.mcpObserved || findings.some((finding) => /\b(orgx[_-]?mcp|orgx_emit|mcp__orgx|tool call)\b/i.test(`${finding.title} ${finding.summary}`));
8669
+ if (!mentionsOrgxTool) return [];
8670
+ return [
8671
+ {
8672
+ id: "tool:orgx-mcp",
8673
+ kind: "tool",
8674
+ label: "OrgX MCP",
8675
+ summary: coverage.orgxMcpCalled ? "OrgX MCP was observed as a durable writeback surface in the audited work." : "OrgX MCP was available or mentioned, but the audit found incomplete durable writeback.",
8676
+ source_client: "mcp",
8677
+ evidence_refs: findings.filter((finding) => /\b(orgx|mcp|tool)\b/i.test(`${finding.title} ${finding.summary}`)).map((finding) => finding.evidence_ref).slice(0, 12),
8678
+ linked_node_ids: [],
8679
+ confidence: coverage.orgxMcpCalled ? 0.86 : 0.72,
8680
+ weight: coverage.orgxMcpCalled ? 88 : 76,
8681
+ review_state: "unreviewed",
8682
+ dedupe_key: "tool:orgx-mcp",
8683
+ metadata: {
8684
+ mcp_observed: coverage.mcpObserved,
8685
+ orgx_observed: coverage.orgxObserved,
8686
+ orgx_mcp_called: coverage.orgxMcpCalled,
8687
+ skill_only_signal: coverage.skillOnlySignal
8688
+ }
8689
+ }
8690
+ ];
8691
+ }
8692
+ function buildAgentNodes(findings) {
8693
+ const actorIds = sortedUnique(
8694
+ findings.map((finding) => typeof finding.metadata.actor_id === "string" ? finding.metadata.actor_id.trim() : "").filter(Boolean)
8695
+ );
8696
+ const sourceAgents = sortedUnique(
8697
+ findings.map(
8698
+ (finding) => ["codex", "claude", "claude-code", "cursor", "openclaw"].includes(finding.source_client) ? finding.source_client : ""
8699
+ ).filter(Boolean)
8700
+ );
8701
+ return [...actorIds, ...sourceAgents].slice(0, 20).map((agent) => ({
8702
+ id: `agent:${slugPart(agent)}`,
8703
+ kind: "agent",
8704
+ label: agent,
8705
+ summary: `${agent} contributed evidence to the Work Graph audit.`,
8706
+ source_client: sourceAgents.includes(agent) ? agent : "manual",
8707
+ evidence_refs: findings.filter((finding) => finding.metadata.actor_id === agent || finding.source_client === agent).map((finding) => finding.evidence_ref).slice(0, 12),
8708
+ linked_node_ids: [],
8709
+ confidence: 0.72,
8710
+ weight: 64,
8711
+ review_state: "unreviewed",
8712
+ dedupe_key: `agent:${slugPart(agent)}`,
8713
+ metadata: {}
8714
+ }));
8715
+ }
8716
+ function buildWorkGraphAttributionSpine(input) {
8717
+ const evidence_refs = input.findings.map((finding) => ({
8718
+ id: finding.evidence_ref,
8719
+ source_client: finding.source_client,
8720
+ source_id: finding.source_id,
8721
+ label: finding.title,
8722
+ summary: finding.summary.slice(0, 1200),
8723
+ ...isIsoTimestamp(finding.metadata.occurred_at) ? { occurred_at: finding.metadata.occurred_at } : {},
8724
+ confidence: finding.confidence,
8725
+ redaction_state: redactionStateForFinding(finding),
8726
+ metadata: {
8727
+ raw_transcript_sent: false,
8728
+ source_label: finding.metadata.source_label ?? null
8729
+ }
8730
+ }));
8731
+ const source_events = [
8732
+ ...input.events.map((event) => ({
8733
+ source_client: event.source_client,
8734
+ source_id: event.source_id,
8735
+ source_label: event.source_label,
8736
+ event_type: event.event_type === "client_extraction" ? "client_extraction" : event.event_type,
8737
+ evidence_ref: event.evidence_ref,
8738
+ confidence: 0.7,
8739
+ metadata: {
8740
+ ...event.metadata,
8741
+ raw_transcript_sent: false
8742
+ }
8743
+ })),
8744
+ ...input.findings.map((finding) => ({
8745
+ source_client: finding.source_client,
8746
+ source_id: finding.source_id,
8747
+ source_label: typeof finding.metadata.source_label === "string" ? finding.metadata.source_label : finding.source_id,
8748
+ event_type: attributionEventTypeForFinding(finding),
8749
+ ...isIsoTimestamp(finding.metadata.occurred_at) ? { occurred_at: finding.metadata.occurred_at } : {},
8750
+ evidence_ref: finding.evidence_ref,
8751
+ ...typeof finding.metadata.actor_id === "string" ? { actor_ref: finding.metadata.actor_id } : {},
8752
+ confidence: finding.confidence,
8753
+ metadata: {
8754
+ finding_type: finding.type,
8755
+ raw_transcript_sent: false
8756
+ }
8757
+ }))
8758
+ ];
8759
+ const buckets = {
8760
+ actions: [],
8761
+ decisions: [],
8762
+ artifacts: [],
8763
+ people: [],
8764
+ agents: buildAgentNodes(input.findings),
8765
+ tools: buildToolNodes(input.coverage, input.findings),
8766
+ businesses: [],
8767
+ product_surfaces: [],
8768
+ goals: [],
8769
+ sources: [
8770
+ ...input.coverage.connected.map((source) => sourceNode(source, true)),
8771
+ ...input.coverage.missing.map((source) => sourceNode(source, false))
8772
+ ],
8773
+ initiative_candidates: []
8774
+ };
8775
+ for (const finding of input.findings) {
8776
+ const kind = attributionKindForFinding(finding.type);
8777
+ if (!kind) continue;
8778
+ const node = buildFindingNode(finding, kind);
8779
+ const bucket = finding.type === "initiative_candidate" ? "initiative_candidates" : nodeBucketForKind(kind);
8780
+ buckets[bucket].push(node);
8781
+ }
8782
+ const normalized = {
8783
+ source_events: source_events.filter(
8784
+ (event, index, all) => all.findIndex(
8785
+ (candidate) => [
8786
+ candidate.source_client,
8787
+ candidate.source_id,
8788
+ candidate.event_type,
8789
+ candidate.evidence_ref
8790
+ ].join(":") === [
8791
+ event.source_client,
8792
+ event.source_id,
8793
+ event.event_type,
8794
+ event.evidence_ref
8795
+ ].join(":")
8796
+ ) === index
8797
+ ).slice(0, 1e3),
8798
+ actions: mergeNodes(buckets.actions).slice(0, 500),
8799
+ decisions: mergeNodes(buckets.decisions).slice(0, 500),
8800
+ artifacts: mergeNodes(buckets.artifacts).slice(0, 500),
8801
+ people: mergeNodes(buckets.people).slice(0, 500),
8802
+ agents: mergeNodes(buckets.agents).slice(0, 200),
8803
+ tools: mergeNodes(buckets.tools).slice(0, 300),
8804
+ businesses: mergeNodes(buckets.businesses).slice(0, 200),
8805
+ product_surfaces: mergeNodes(buckets.product_surfaces).slice(0, 500),
8806
+ goals: mergeNodes(buckets.goals).slice(0, 500),
8807
+ sources: mergeNodes(buckets.sources).slice(0, 300),
8808
+ initiative_candidates: mergeNodes(buckets.initiative_candidates).slice(0, 50),
8809
+ evidence_refs: mergeByEvidenceId(evidence_refs).slice(0, 1e3)
8810
+ };
8811
+ const nodeCount = normalized.actions.length + normalized.decisions.length + normalized.artifacts.length + normalized.people.length + normalized.agents.length + normalized.tools.length + normalized.businesses.length + normalized.product_surfaces.length + normalized.goals.length + normalized.sources.length + normalized.initiative_candidates.length;
8812
+ const confidenceInputs = [
8813
+ ...normalized.evidence_refs.map((evidence) => evidence.confidence),
8814
+ ...normalized.source_events.map((event) => event.confidence)
8815
+ ];
8816
+ const confidence = confidenceInputs.length ? Number((confidenceInputs.reduce((total, value) => total + value, 0) / confidenceInputs.length).toFixed(2)) : 0;
8817
+ return {
8818
+ ...normalized,
8819
+ confidence,
8820
+ dedupe_keys: sortedUnique([
8821
+ ...Object.values(normalized).flat().map((item) => typeof item === "object" && item && "dedupe_key" in item ? String(item.dedupe_key ?? "") : "").filter(Boolean)
8822
+ ]).slice(0, 1e3),
8823
+ privacy: {
8824
+ redaction_state: "public_summary",
8825
+ raw_transcripts_included: false,
8826
+ public_summary_only: true
8827
+ },
8828
+ review: {
8829
+ pending_count: nodeCount,
8830
+ correction_affordances: [
8831
+ "confirm",
8832
+ "merge",
8833
+ "hide",
8834
+ "mark_important",
8835
+ "launch_or_dismiss"
8836
+ ]
8837
+ }
8838
+ };
8839
+ }
8840
+ function mergeByEvidenceId(evidenceRefs) {
8841
+ const merged = /* @__PURE__ */ new Map();
8842
+ for (const evidence of evidenceRefs) {
8843
+ const previous = merged.get(evidence.id);
8844
+ if (!previous || evidence.confidence > previous.confidence) {
8845
+ merged.set(evidence.id, evidence);
8846
+ }
8847
+ }
8848
+ return [...merged.values()].sort((left, right) => right.confidence - left.confidence);
8849
+ }
8014
8850
  function buildWorkGraphFingerprint(input) {
8015
8851
  const sourceClients = sortedUnique(input.findings.map((finding) => finding.source_client));
8016
8852
  const patternHashes = input.findings.map(
@@ -8108,18 +8944,41 @@ function buildSessionReconciliationReport(input) {
8108
8944
  const opportunityScore = scoreOpportunity(coverage, allFindings);
8109
8945
  const initiativeKickoffs = buildKickoffs(allFindings, missed, opportunityScore);
8110
8946
  const recommendations = buildTrailRecommendations(recurringPatterns, trails);
8947
+ const impactProjection = estimateImpactProjection({
8948
+ coverage,
8949
+ findings: allFindings,
8950
+ opportunityScore,
8951
+ patterns: recurringPatterns,
8952
+ trails
8953
+ });
8954
+ const executionQuality = scoreExecutionQuality({
8955
+ coverage,
8956
+ findings: allFindings,
8957
+ impact: impactProjection,
8958
+ patterns: recurringPatterns,
8959
+ recommendations,
8960
+ trails
8961
+ });
8111
8962
  const mirror = buildWorkGraphMirror({
8112
8963
  coverage,
8113
8964
  generatedAt,
8965
+ impact: impactProjection,
8114
8966
  patterns: recurringPatterns,
8115
8967
  recommendations,
8116
8968
  trails
8117
8969
  });
8118
8970
  const tensionMetrics = buildTensionMetrics({
8119
8971
  coverage,
8972
+ impact: impactProjection,
8120
8973
  patterns: recurringPatterns,
8974
+ quality: executionQuality,
8121
8975
  trails
8122
8976
  });
8977
+ const attributionSpine = buildWorkGraphAttributionSpine({
8978
+ coverage,
8979
+ events,
8980
+ findings: allFindings
8981
+ });
8123
8982
  const fingerprint = buildWorkGraphFingerprint({
8124
8983
  connectedSources,
8125
8984
  findings: allFindings,
@@ -8167,7 +9026,10 @@ function buildSessionReconciliationReport(input) {
8167
9026
  recommendations,
8168
9027
  mirror,
8169
9028
  tension_metrics: tensionMetrics,
9029
+ attribution_spine: attributionSpine,
8170
9030
  opportunity_score: opportunityScore,
9031
+ execution_quality: executionQuality,
9032
+ impact_projection: impactProjection,
8171
9033
  initiative_kickoffs: initiativeKickoffs,
8172
9034
  redaction_level: "summary_only",
8173
9035
  raw_transcripts_sent: false
@@ -8254,13 +9116,44 @@ function renderWorkGraphMarkdown(report) {
8254
9116
  lines.push(`Automation potential: ${report.opportunity_score.automation_potential}/100`);
8255
9117
  lines.push(`OrgX fit: ${report.opportunity_score.orgx_fit}/100`);
8256
9118
  lines.push("");
9119
+ lines.push("## Execution Quality");
9120
+ lines.push("");
9121
+ lines.push(`Overall: ${report.execution_quality.overall}/100`);
9122
+ lines.push(`Evidence coverage: ${report.execution_quality.evidence_coverage}/100`);
9123
+ lines.push(`Source attribution: ${report.execution_quality.source_attribution}/100`);
9124
+ lines.push(`Trail depth: ${report.execution_quality.trail_depth}/100`);
9125
+ lines.push(`Insight depth: ${report.execution_quality.insight_depth}/100`);
9126
+ lines.push(`Actionability: ${report.execution_quality.actionability}/100`);
9127
+ lines.push(`Impact confidence: ${report.execution_quality.impact_confidence}/100`);
9128
+ for (const note of report.execution_quality.notes) {
9129
+ lines.push(`- ${note}`);
9130
+ }
9131
+ lines.push("");
9132
+ lines.push("## Impact Projection");
9133
+ lines.push("");
9134
+ lines.push(`Time saved: ${report.impact_projection.time_saved_hours_per_week}h/week`);
9135
+ lines.push(`Acceleration: +${report.impact_projection.acceleration_percent}%`);
9136
+ lines.push(`Estimated monthly value: $${report.impact_projection.estimated_monthly_value_usd.toLocaleString("en-US")}`);
9137
+ lines.push(`Confidence: ${report.impact_projection.confidence}`);
9138
+ for (const item of report.impact_projection.basis) {
9139
+ lines.push(`- ${item}`);
9140
+ }
9141
+ lines.push("");
8257
9142
  lines.push("## Source Coverage");
8258
9143
  lines.push("");
8259
9144
  lines.push(`Connected: ${report.source_coverage.connected.join(", ") || "none"}`);
8260
9145
  lines.push(`Missing: ${report.source_coverage.missing.join(", ") || "none"}`);
9146
+ lines.push(`Coverage score: ${report.source_coverage.coverage_score ?? 0}/100`);
8261
9147
  lines.push(`MCP observed: ${report.source_coverage.mcpObserved ? "yes" : "no"}`);
8262
9148
  lines.push(`OrgX observed: ${report.source_coverage.orgxObserved ? "yes" : "no"}`);
8263
9149
  lines.push(`OrgX MCP called: ${report.source_coverage.orgxMcpCalled ? "yes" : "no"}`);
9150
+ if (report.source_coverage.manifests?.length) {
9151
+ lines.push("");
9152
+ lines.push("Source manifests:");
9153
+ for (const manifest of report.source_coverage.manifests) {
9154
+ lines.push(`- [${manifest.status}] ${manifest.source_label}: ${manifest.finding_count} findings, ${manifest.searched_session_count} searched / ${manifest.skipped_session_count} skipped sessions, confidence ${manifest.confidence}`);
9155
+ }
9156
+ }
8264
9157
  lines.push("");
8265
9158
  lines.push("## Mirror");
8266
9159
  lines.push("");
@@ -8322,6 +9215,30 @@ function renderWorkGraphMarkdown(report) {
8322
9215
  lines.push(`- [${recommendation.priority}] ${recommendation.title}: ${recommendation.summary}`);
8323
9216
  }
8324
9217
  lines.push("");
9218
+ lines.push("## Attribution Spine");
9219
+ lines.push("");
9220
+ lines.push(`Source events: ${report.attribution_spine.source_events.length}`);
9221
+ lines.push(`Evidence refs: ${report.attribution_spine.evidence_refs.length}`);
9222
+ lines.push(`Review nodes: ${report.attribution_spine.review.pending_count}`);
9223
+ lines.push(`Confidence: ${report.attribution_spine.confidence}`);
9224
+ lines.push("");
9225
+ const attributionGroups = [
9226
+ ["Actions", report.attribution_spine.actions],
9227
+ ["Decisions", report.attribution_spine.decisions],
9228
+ ["Artifacts", report.attribution_spine.artifacts],
9229
+ ["People", report.attribution_spine.people],
9230
+ ["Businesses", report.attribution_spine.businesses],
9231
+ ["Surfaces", report.attribution_spine.product_surfaces],
9232
+ ["Sources", report.attribution_spine.sources]
9233
+ ];
9234
+ for (const [label, nodes] of attributionGroups) {
9235
+ if (nodes.length === 0) continue;
9236
+ lines.push(`${label}:`);
9237
+ for (const node of nodes.slice(0, 5)) {
9238
+ lines.push(`- ${node.label} (${node.evidence_refs.join(", ") || "source coverage"})`);
9239
+ }
9240
+ lines.push("");
9241
+ }
8325
9242
  lines.push("## Signup Hydration");
8326
9243
  lines.push("");
8327
9244
  lines.push(`- Strategy: ${report.signup_hydration.strategy}`);
@@ -8337,8 +9254,314 @@ function renderWorkGraphMarkdown(report) {
8337
9254
  return lines.join("\n");
8338
9255
  }
8339
9256
 
9257
+ // src/lib/work-graph-publish.ts
9258
+ async function parseResponse(response) {
9259
+ const text2 = await response.text();
9260
+ if (!text2) return null;
9261
+ try {
9262
+ return JSON.parse(text2);
9263
+ } catch {
9264
+ return text2;
9265
+ }
9266
+ }
9267
+ async function publishWorkGraphReport(report, options = {}) {
9268
+ const auth = await resolveOrgxAuth();
9269
+ if (!auth) {
9270
+ throw new Error("OrgX auth is required to publish a Work Graph. Run `orgx-wizard auth login` or set ORGX_API_KEY.");
9271
+ }
9272
+ const url = buildOrgxApiUrl("/client/work-graph/reports", auth.baseUrl);
9273
+ const response = await fetch(url, {
9274
+ method: "POST",
9275
+ headers: {
9276
+ Authorization: `Bearer ${auth.apiKey}`,
9277
+ "Content-Type": "application/json"
9278
+ },
9279
+ body: JSON.stringify({
9280
+ report,
9281
+ ...options.workspaceId ? { workspace_id: options.workspaceId } : {},
9282
+ ...options.initiativeId ? { initiative_id: options.initiativeId } : {},
9283
+ ...options.entityType ? { entity_type: options.entityType } : {},
9284
+ ...options.entityId ? { entity_id: options.entityId } : {},
9285
+ ...options.artifactUrl ? { artifact_url: options.artifactUrl } : {},
9286
+ attach_artifact: Boolean(options.attachArtifact),
9287
+ public_share: Boolean(options.publicShare)
9288
+ }),
9289
+ signal: options.signal ?? AbortSignal.timeout(15e3)
9290
+ });
9291
+ return {
9292
+ ok: response.ok,
9293
+ status: response.status,
9294
+ url,
9295
+ data: await parseResponse(response)
9296
+ };
9297
+ }
9298
+ async function publishWorkGraphEvents(fingerprint, patch, options = {}) {
9299
+ const auth = await resolveOrgxAuth();
9300
+ if (!auth) {
9301
+ throw new Error("OrgX auth is required to replay Work Graph hook events. Run `orgx-wizard auth login` or set ORGX_API_KEY.");
9302
+ }
9303
+ const url = buildOrgxApiUrl("/client/work-graph/events", auth.baseUrl);
9304
+ const response = await fetch(url, {
9305
+ method: "POST",
9306
+ headers: {
9307
+ Authorization: `Bearer ${auth.apiKey}`,
9308
+ "Content-Type": "application/json"
9309
+ },
9310
+ body: JSON.stringify({
9311
+ work_graph_fingerprint: fingerprint,
9312
+ patch
9313
+ }),
9314
+ signal: options.signal ?? AbortSignal.timeout(15e3)
9315
+ });
9316
+ return {
9317
+ ok: response.ok,
9318
+ status: response.status,
9319
+ url,
9320
+ data: await parseResponse(response)
9321
+ };
9322
+ }
9323
+
9324
+ // src/lib/work-graph-hook-events.ts
9325
+ import { createHash as createHash5 } from "crypto";
9326
+ import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
9327
+ var SOURCE_CLIENTS = [
9328
+ "codex",
9329
+ "claude",
9330
+ "claude-code",
9331
+ "cursor",
9332
+ "openclaw",
9333
+ "slack",
9334
+ "mcp",
9335
+ "github",
9336
+ "linear",
9337
+ "gmail",
9338
+ "calendar",
9339
+ "notion",
9340
+ "docs",
9341
+ "manual",
9342
+ "wizard",
9343
+ "api",
9344
+ "unknown"
9345
+ ];
9346
+ function isRecord2(value) {
9347
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
9348
+ }
9349
+ function asString2(value) {
9350
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
9351
+ }
9352
+ function asNumber(value) {
9353
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
9354
+ }
9355
+ function asStringArray(value) {
9356
+ if (!Array.isArray(value)) return void 0;
9357
+ return value.filter((item) => typeof item === "string" && item.trim().length > 0);
9358
+ }
9359
+ function stableHash(value) {
9360
+ return createHash5("sha256").update(value).digest("hex").slice(0, 20);
9361
+ }
9362
+ function normalizeSourceClient2(value) {
9363
+ const raw = asString2(value)?.toLowerCase();
9364
+ if (!raw) return "unknown";
9365
+ if (SOURCE_CLIENTS.includes(raw)) return raw;
9366
+ if (raw === "claude_code") return "claude-code";
9367
+ return "unknown";
9368
+ }
9369
+ function safeTimestamp(value) {
9370
+ const raw = asString2(value);
9371
+ if (raw && !Number.isNaN(Date.parse(raw))) return new Date(raw).toISOString();
9372
+ return (/* @__PURE__ */ new Date(0)).toISOString();
9373
+ }
9374
+ function sourceLabel(sourceClient) {
9375
+ switch (sourceClient) {
9376
+ case "claude-code":
9377
+ return "Claude Code";
9378
+ case "codex":
9379
+ return "Codex";
9380
+ case "openclaw":
9381
+ return "OpenClaw";
9382
+ case "cursor":
9383
+ return "Cursor";
9384
+ default:
9385
+ return sourceClient.replace(/[-_]/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
9386
+ }
9387
+ }
9388
+ function readHookRecord(line) {
9389
+ try {
9390
+ const parsed = JSON.parse(line);
9391
+ if (!isRecord2(parsed)) return null;
9392
+ const record = {};
9393
+ const schemaVersion = asString2(parsed.schema_version);
9394
+ const source = asString2(parsed.source);
9395
+ const sourceClient = asString2(parsed.source_client);
9396
+ const event = asString2(parsed.event);
9397
+ const sessionId = asString2(parsed.session_id);
9398
+ const turnId = asString2(parsed.turn_id);
9399
+ const cwd = asString2(parsed.cwd);
9400
+ const transcriptPath = asString2(parsed.transcript_path);
9401
+ const timestamp = asString2(parsed.timestamp);
9402
+ if (schemaVersion) record.schema_version = schemaVersion;
9403
+ if (source) record.source = source;
9404
+ if (sourceClient) record.source_client = sourceClient;
9405
+ if (event) record.event = event;
9406
+ if (sessionId) record.session_id = sessionId;
9407
+ if (turnId) record.turn_id = turnId;
9408
+ if (cwd) record.cwd = cwd;
9409
+ if (transcriptPath) record.transcript_path = transcriptPath;
9410
+ if (timestamp) record.timestamp = timestamp;
9411
+ if (isRecord2(parsed.summary)) {
9412
+ const summary = {};
9413
+ const toolName = asString2(parsed.summary.tool_name);
9414
+ const promptChars = asNumber(parsed.summary.prompt_chars);
9415
+ const payloadKeys = asStringArray(parsed.summary.payload_keys);
9416
+ if (toolName) summary.tool_name = toolName;
9417
+ if (promptChars !== void 0) summary.prompt_chars = promptChars;
9418
+ if (payloadKeys) summary.payload_keys = payloadKeys;
9419
+ record.summary = summary;
9420
+ }
9421
+ return record;
9422
+ } catch {
9423
+ return null;
9424
+ }
9425
+ }
9426
+ function readRuntimeHookOutbox(path, limit = 200) {
9427
+ if (!existsSync6(path)) return { path, records: [], skipped: 0 };
9428
+ const lines = readFileSync4(path, "utf8").split(/\r?\n/).filter((line) => line.trim().length > 0);
9429
+ const selected = lines.slice(Math.max(0, lines.length - Math.max(1, limit)));
9430
+ const records = [];
9431
+ let skipped = Math.max(0, lines.length - selected.length);
9432
+ for (const line of selected) {
9433
+ const record = readHookRecord(line);
9434
+ if (record) {
9435
+ records.push(record);
9436
+ } else {
9437
+ skipped += 1;
9438
+ }
9439
+ }
9440
+ return { path, records, skipped };
9441
+ }
9442
+ function buildWorkGraphHookReplayPatch(readResult) {
9443
+ const sourceEvents = [];
9444
+ const evidenceRefs = [];
9445
+ const sources = /* @__PURE__ */ new Map();
9446
+ const agents = /* @__PURE__ */ new Map();
9447
+ const tools = /* @__PURE__ */ new Map();
9448
+ const dedupeKeys = /* @__PURE__ */ new Set();
9449
+ readResult.records.forEach((record, index) => {
9450
+ const sourceClient = normalizeSourceClient2(record.source_client);
9451
+ const event = record.event ?? "runtime_hook";
9452
+ const timestamp = safeTimestamp(record.timestamp);
9453
+ const sourceId = record.session_id ?? record.transcript_path ?? record.cwd ?? `${sourceClient}:hook:${index}`;
9454
+ const evidenceId = `hook:${stableHash(`${sourceClient}:${sourceId}:${event}:${timestamp}:${index}`)}`;
9455
+ const toolName = record.summary?.tool_name;
9456
+ const hasMcpTool = Boolean(toolName && /(mcp|orgx)/i.test(toolName));
9457
+ const label = `${sourceLabel(sourceClient)} ${event}`;
9458
+ const summary = hasMcpTool ? `${sourceLabel(sourceClient)} hook observed tool activity through ${toolName}.` : `${sourceLabel(sourceClient)} hook observed ${event} in the local runtime.`;
9459
+ evidenceRefs.push({
9460
+ id: evidenceId,
9461
+ source_client: sourceClient,
9462
+ source_id: sourceId,
9463
+ label,
9464
+ summary,
9465
+ occurred_at: timestamp,
9466
+ confidence: hasMcpTool ? 0.82 : 0.68,
9467
+ redaction_state: "public_summary",
9468
+ metadata: {
9469
+ raw_transcript_sent: false,
9470
+ hook_source: record.source ?? "runtime_hook",
9471
+ cwd: record.cwd ?? null,
9472
+ transcript_path: record.transcript_path ?? null
9473
+ }
9474
+ });
9475
+ sourceEvents.push({
9476
+ source_client: sourceClient,
9477
+ source_id: sourceId,
9478
+ source_label: sourceLabel(sourceClient),
9479
+ event_type: hasMcpTool ? "mcp_call" : "runtime_hook",
9480
+ occurred_at: timestamp,
9481
+ evidence_ref: evidenceId,
9482
+ confidence: hasMcpTool ? 0.82 : 0.68,
9483
+ metadata: {
9484
+ hook_event: event,
9485
+ raw_transcript_sent: false,
9486
+ prompt_chars: record.summary?.prompt_chars ?? null,
9487
+ payload_keys: record.summary?.payload_keys ?? []
9488
+ }
9489
+ });
9490
+ const sourceEvidence = sources.get(sourceClient) ?? [];
9491
+ sourceEvidence.push(evidenceId);
9492
+ sources.set(sourceClient, sourceEvidence.slice(0, 30));
9493
+ dedupeKeys.add(`hook:${sourceClient}:${sourceId}:${event}`);
9494
+ if (sourceClient !== "unknown") {
9495
+ const agentId = `agent:${sourceClient}`;
9496
+ agents.set(agentId, {
9497
+ id: agentId,
9498
+ kind: "agent",
9499
+ label: sourceLabel(sourceClient),
9500
+ summary: `${sourceLabel(sourceClient)} is emitting passive runtime hook evidence into this Work Graph.`,
9501
+ source_client: sourceClient,
9502
+ evidence_refs: sourceEvidence.slice(0, 30),
9503
+ linked_node_ids: [`source:${sourceClient}`],
9504
+ confidence: 0.7,
9505
+ weight: 62,
9506
+ review_state: "unreviewed",
9507
+ dedupe_key: agentId,
9508
+ metadata: { runtime_hook_replay: true }
9509
+ });
9510
+ }
9511
+ if (toolName) {
9512
+ const toolId = `tool:${stableHash(toolName.toLowerCase())}`;
9513
+ const previous = tools.get(toolId);
9514
+ const previousEvidence = Array.isArray(previous?.evidence_refs) ? previous.evidence_refs.filter((item) => typeof item === "string") : [];
9515
+ tools.set(toolId, {
9516
+ id: toolId,
9517
+ kind: "tool",
9518
+ label: toolName,
9519
+ summary: hasMcpTool ? `Runtime hook evidence shows ${toolName} participating in OrgX/MCP work.` : `Runtime hook evidence shows ${toolName} participating in local work.`,
9520
+ source_client: sourceClient,
9521
+ evidence_refs: [...previousEvidence, evidenceId].slice(0, 30),
9522
+ linked_node_ids: [`source:${sourceClient}`],
9523
+ confidence: hasMcpTool ? 0.82 : 0.66,
9524
+ weight: hasMcpTool ? 78 : 54,
9525
+ review_state: "unreviewed",
9526
+ dedupe_key: toolId,
9527
+ metadata: { runtime_hook_replay: true }
9528
+ });
9529
+ }
9530
+ });
9531
+ const sourceNodes = [...sources.entries()].map(([sourceClient, refs]) => ({
9532
+ id: `source:${sourceClient}`,
9533
+ kind: "source",
9534
+ label: sourceLabel(sourceClient),
9535
+ summary: `${sourceLabel(sourceClient)} hook events have been replayed into this Work Graph profile.`,
9536
+ source_client: sourceClient,
9537
+ evidence_refs: refs.slice(0, 30),
9538
+ linked_node_ids: [],
9539
+ confidence: 0.7,
9540
+ weight: 65,
9541
+ review_state: "unreviewed",
9542
+ dedupe_key: `source:${sourceClient}`,
9543
+ metadata: { runtime_hook_replay: true }
9544
+ }));
9545
+ const patch = {
9546
+ source_events: sourceEvents,
9547
+ evidence_refs: evidenceRefs,
9548
+ sources: sourceNodes,
9549
+ agents: [...agents.values()],
9550
+ tools: [...tools.values()],
9551
+ dedupe_keys: [...dedupeKeys].slice(0, 200),
9552
+ confidence: evidenceRefs.length > 0 ? 0.7 : 0
9553
+ };
9554
+ return {
9555
+ patch,
9556
+ records: readResult.records.length,
9557
+ skipped: readResult.skipped,
9558
+ sources: [...sources.keys()],
9559
+ evidenceRefs: evidenceRefs.length
9560
+ };
9561
+ }
9562
+
8340
9563
  // src/lib/runtime-hooks.ts
8341
- import { copyFileSync, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
9564
+ import { copyFileSync, existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
8342
9565
  import { homedir as homedir2 } from "os";
8343
9566
  import { dirname as dirname4, join as join5 } from "path";
8344
9567
  var HOOK_MARKER = "orgx-session-hook.mjs";
@@ -8364,7 +9587,7 @@ function backupPath(path, now) {
8364
9587
  return `${path}.bak.${timestamp}`;
8365
9588
  }
8366
9589
  function backupExisting(path, now) {
8367
- if (!existsSync6(path)) return null;
9590
+ if (!existsSync7(path)) return null;
8368
9591
  const backup = backupPath(path, now);
8369
9592
  copyFileSync(path, backup);
8370
9593
  return backup;
@@ -8562,7 +9785,7 @@ function inspectRuntimeHooks(options = {}) {
8562
9785
  installed: {
8563
9786
  claudeCode: hasOrgxHook(claudeSettingsRaw),
8564
9787
  codex: hasOrgxHook(codexHooksRaw),
8565
- hookScript: existsSync6(paths.hookScriptPath)
9788
+ hookScript: existsSync7(paths.hookScriptPath)
8566
9789
  },
8567
9790
  codex: {
8568
9791
  configExists: Boolean(codexConfigRaw),
@@ -8762,12 +9985,72 @@ function printRuntimeHookInspection(report) {
8762
9985
  console.log(` ${report.installed.claudeCode ? ICON.ok : ICON.warn} ${pc3.bold("Claude Code ")} ${report.installed.claudeCode ? pc3.green("installed") : pc3.yellow("missing")} ${pc3.dim(report.paths.claudeSettingsPath)}`);
8763
9986
  console.log(` ${ICON.skip} ${pc3.bold("outbox ")} ${pc3.dim(`${report.outboxEvents} event${report.outboxEvents === 1 ? "" : "s"} at ${report.paths.outboxPath}`)}`);
8764
9987
  }
9988
+ function requireHookReplayApproval(options, interactive) {
9989
+ if (options.yes) return true;
9990
+ if (!interactive) {
9991
+ throw new Error("Replaying Work Graph hook events requires --yes in non-interactive mode.");
9992
+ }
9993
+ return clack.confirm({
9994
+ message: "Replay passive hook events into this claimed Work Graph profile?"
9995
+ }).then((value) => {
9996
+ if (clack.isCancel(value) || value !== true) {
9997
+ clack.cancel("Hook replay cancelled.");
9998
+ return false;
9999
+ }
10000
+ return true;
10001
+ });
10002
+ }
10003
+ function parsePositiveInt(value, fallback) {
10004
+ if (!value?.trim()) return fallback;
10005
+ const parsed = Number.parseInt(value.trim(), 10);
10006
+ if (!Number.isFinite(parsed) || parsed < 1) {
10007
+ throw new Error(`Expected a positive integer, got ${value}`);
10008
+ }
10009
+ return parsed;
10010
+ }
10011
+ async function runHookReplayCommand(options) {
10012
+ const fingerprint = options.fingerprint?.trim();
10013
+ if (!fingerprint) {
10014
+ throw new Error("Missing --fingerprint <wgf_...> for hook event replay.");
10015
+ }
10016
+ const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
10017
+ const paths = inspectRuntimeHooks().paths;
10018
+ const outboxPath = resolve(options.outbox?.trim() || paths.outboxPath);
10019
+ const readResult = readRuntimeHookOutbox(outboxPath, parsePositiveInt(options.limit, 200));
10020
+ const replay = buildWorkGraphHookReplayPatch(readResult);
10021
+ if (replay.records === 0) {
10022
+ if (options.json) {
10023
+ console.log(JSON.stringify({ ok: true, published: false, reason: "empty_outbox", ...replay }, null, 2));
10024
+ return;
10025
+ }
10026
+ console.log(` ${ICON.skip} ${pc3.bold("outbox ")} ${pc3.dim(`no hook events found at ${outboxPath}`)}`);
10027
+ return;
10028
+ }
10029
+ const approved = await requireHookReplayApproval(options, interactive);
10030
+ if (!approved) return;
10031
+ const result = await publishWorkGraphEvents(fingerprint, replay.patch);
10032
+ if (!result.ok) process.exitCode = 1;
10033
+ await safeTrackWizardTelemetry("hooks_replay_ran", {
10034
+ command: "hooks replay",
10035
+ fingerprint,
10036
+ records: String(replay.records),
10037
+ sources: replay.sources.join(","),
10038
+ status: String(result.status)
10039
+ });
10040
+ if (options.json) {
10041
+ console.log(JSON.stringify({ ...replay, published: result }, null, 2));
10042
+ return;
10043
+ }
10044
+ console.log(` ${result.ok ? ICON.ok : ICON.warn} ${pc3.bold("replayed ")} ${replay.records} hook event${replay.records === 1 ? "" : "s"} ${pc3.dim(`status ${result.status}`)}`);
10045
+ console.log(` ${ICON.skip} ${pc3.bold("sources ")} ${pc3.dim(replay.sources.join(", ") || "none")}`);
10046
+ console.log(` ${ICON.skip} ${pc3.bold("evidence ")} ${pc3.dim(`${replay.evidenceRefs} public-summary ref${replay.evidenceRefs === 1 ? "" : "s"}`)}`);
10047
+ }
8765
10048
  function readAuditInput(options, interactive) {
8766
10049
  if (options.input?.trim()) {
8767
- return readFileSync5(resolve(options.input.trim()), "utf8");
10050
+ return readFileSync6(resolve(options.input.trim()), "utf8");
8768
10051
  }
8769
10052
  if (!process.stdin.isTTY) {
8770
- return readFileSync5(0, "utf8");
10053
+ return readFileSync6(0, "utf8");
8771
10054
  }
8772
10055
  if (!interactive) {
8773
10056
  throw new Error("Audit input is required. Pass --input <file> or pipe text into wizard audit.");
@@ -8800,7 +10083,7 @@ function collectPathOption(value, previous = []) {
8800
10083
  }
8801
10084
  function parseClientExtractionFile(path) {
8802
10085
  const resolvedPath = resolve(path);
8803
- const parsed = JSON.parse(readFileSync5(resolvedPath, "utf8"));
10086
+ const parsed = JSON.parse(readFileSync6(resolvedPath, "utf8"));
8804
10087
  if (!isRecord(parsed)) {
8805
10088
  throw new Error(`AI-client extraction must be a JSON object: ${resolvedPath}`);
8806
10089
  }
@@ -8902,6 +10185,22 @@ function requireWriteApproval(options, interactive) {
8902
10185
  return true;
8903
10186
  });
8904
10187
  }
10188
+ function requireWorkGraphPublishApproval(options, interactive) {
10189
+ const wantsPublish = Boolean(options.publish || options.publicShare);
10190
+ if (!wantsPublish || options.yes || options.dryRun) return true;
10191
+ if (!interactive) {
10192
+ throw new Error("Publishing a Work Graph requires --yes in non-interactive mode.");
10193
+ }
10194
+ return clack.confirm({
10195
+ message: "Publish this Work Graph to OrgX and create a shareable profile?"
10196
+ }).then((value) => {
10197
+ if (clack.isCancel(value) || value !== true) {
10198
+ clack.cancel("Work Graph publish cancelled.");
10199
+ return false;
10200
+ }
10201
+ return true;
10202
+ });
10203
+ }
8905
10204
  async function resolveAuditWorkspace(options) {
8906
10205
  const explicitId = options.workspaceId?.trim();
8907
10206
  const explicitName = options.workspaceName?.trim();
@@ -9021,18 +10320,27 @@ async function runWorkGraphCommand(options, defaults = {}) {
9021
10320
  };
9022
10321
  const auditInputs = await readWorkGraphInputs(commandOptions, interactive);
9023
10322
  const workspace = await resolveAuditWorkspace(commandOptions);
10323
+ const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
10324
+ const nativeClientExtractions = buildNativeAiClientExtractions(
10325
+ auditInputs.imports,
10326
+ generatedAt
10327
+ );
10328
+ const clientExtractions = [
10329
+ ...auditInputs.clientExtractions,
10330
+ ...nativeClientExtractions
10331
+ ];
9024
10332
  const report = buildSessionReconciliationReport({
9025
- clientExtractions: auditInputs.clientExtractions,
10333
+ clientExtractions,
9026
10334
  connectedSources: [
9027
10335
  ...auditInputs.connectedSources,
9028
10336
  ...workspace.id === "local-workspace" ? [] : ["OrgX workspace"]
9029
10337
  ],
9030
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
9031
10338
  imports: auditInputs.imports,
9032
10339
  missingSources: [
9033
10340
  ...workspace.id === "local-workspace" ? ["OrgX workspace auth"] : [],
9034
10341
  ...auditInputs.missingSources
9035
10342
  ],
10343
+ generatedAt,
9036
10344
  workspace
9037
10345
  });
9038
10346
  const markdown = renderWorkGraphMarkdown(report);
@@ -9042,6 +10350,39 @@ async function runWorkGraphCommand(options, defaults = {}) {
9042
10350
  const markdownPath = resolve(outputDir, `work-graph-report-${timestamp}.md`);
9043
10351
  writeJsonFile(jsonPath, report);
9044
10352
  writeTextFile(markdownPath, markdown);
10353
+ let published = null;
10354
+ const shouldPublish = Boolean(commandOptions.publish || commandOptions.publicShare);
10355
+ if (shouldPublish) {
10356
+ const approved = await requireWorkGraphPublishApproval(commandOptions, interactive);
10357
+ if (approved) {
10358
+ if (commandOptions.dryRun) {
10359
+ published = { ok: true, status: 0 };
10360
+ } else {
10361
+ const publishResult = await publishWorkGraphReport(report, {
10362
+ ...commandOptions.artifactUrl?.trim() ? { artifactUrl: commandOptions.artifactUrl.trim() } : {},
10363
+ attachArtifact: Boolean(commandOptions.attachArtifact || commandOptions.attachToInitiative),
10364
+ ...commandOptions.entityId?.trim() ? { entityId: commandOptions.entityId.trim() } : {},
10365
+ ...commandOptions.entityType ? { entityType: commandOptions.entityType } : {},
10366
+ ...commandOptions.attachToInitiative?.trim() ? { initiativeId: commandOptions.attachToInitiative.trim() } : {},
10367
+ publicShare: Boolean(commandOptions.publicShare || commandOptions.publish),
10368
+ ...workspace.id !== "local-workspace" ? { workspaceId: workspace.id } : {}
10369
+ });
10370
+ if (!publishResult.ok) {
10371
+ throw new Error(`Work Graph publish failed with HTTP ${publishResult.status}: ${JSON.stringify(publishResult.data)}`);
10372
+ }
10373
+ const body = isRecord(publishResult.data) ? publishResult.data : {};
10374
+ const publicShare = isRecord(body.public_share) ? body.public_share : null;
10375
+ const publicUrl = typeof publicShare?.url === "string" ? `${normalizeOrgxBaseUrl(process.env.ORGX_BASE_URL || DEFAULT_ORGX_BASE_URL)}${publicShare.url}` : void 0;
10376
+ const readout = isRecord(body.public_readout) ? body.public_readout : null;
10377
+ published = {
10378
+ ok: true,
10379
+ status: publishResult.status,
10380
+ ...publicUrl ? { publicUrl } : {},
10381
+ ...typeof readout?.review_url === "string" ? { reviewUrl: readout.review_url } : {}
10382
+ };
10383
+ }
10384
+ }
10385
+ }
9045
10386
  if (commandOptions.json) {
9046
10387
  console.log(JSON.stringify({
9047
10388
  jsonPath,
@@ -9051,13 +10392,17 @@ async function runWorkGraphCommand(options, defaults = {}) {
9051
10392
  hydrationKey: report.signup_hydration.hydration_key,
9052
10393
  finalState: report.final_state,
9053
10394
  opportunityScore: report.opportunity_score,
10395
+ executionQuality: report.execution_quality,
10396
+ impactProjection: report.impact_projection,
9054
10397
  missedOrchestration: report.missed_orchestration_opportunities.length,
9055
10398
  clientExtractionCount: report.client_extractions.length,
9056
10399
  kickoffCount: report.initiative_kickoffs.length,
9057
10400
  trailCount: report.trails.length,
9058
10401
  recurringPatternCount: report.recurring_patterns.length,
10402
+ attributionCount: report.attribution_spine.review.pending_count,
9059
10403
  topTrail: report.mirror.primary_trail_id ?? null,
9060
- mirrorHeadline: report.mirror.headline
10404
+ mirrorHeadline: report.mirror.headline,
10405
+ published
9061
10406
  }, null, 2));
9062
10407
  return;
9063
10408
  }
@@ -9067,6 +10412,8 @@ async function runWorkGraphCommand(options, defaults = {}) {
9067
10412
  console.log(` ${ICON.ok} ${pc3.green("state ")} ${pc3.dim(report.final_state)}`);
9068
10413
  console.log(` ${ICON.ok} ${pc3.green("extractions ")} ${pc3.dim(String(report.client_extractions.length))}`);
9069
10414
  console.log(` ${ICON.ok} ${pc3.green("score ")} ${pc3.dim(formatWorkGraphScoreLine(report.opportunity_score))}`);
10415
+ console.log(` ${ICON.ok} ${pc3.green("quality ")} ${pc3.dim(`${report.execution_quality.overall}/100 \xB7 coverage ${report.source_coverage.coverage_score ?? 0}/100`)}`);
10416
+ console.log(` ${ICON.ok} ${pc3.green("impact ")} ${pc3.dim(`${report.impact_projection.time_saved_hours_per_week}h/week \xB7 +${report.impact_projection.acceleration_percent}% acceleration \xB7 ~$${report.impact_projection.estimated_monthly_value_usd.toLocaleString("en-US")}/month`)}`);
9070
10417
  const missed = report.missed_orchestration_opportunities.length;
9071
10418
  const missedColor = missed > 0 ? pc3.yellow : pc3.green;
9072
10419
  console.log(` ${missed > 0 ? ICON.warn : ICON.ok} ${missedColor("missed ")} ${pc3.dim(`${missed} orchestration opportunit${missed === 1 ? "y" : "ies"}`)}`);
@@ -9079,6 +10426,10 @@ async function runWorkGraphCommand(options, defaults = {}) {
9079
10426
  for (const kickoff of report.initiative_kickoffs) {
9080
10427
  console.log(` ${ICON.skip} ${pc3.bold(kickoff.priority.padEnd(3))} ${kickoff.title}`);
9081
10428
  }
10429
+ console.log(` ${ICON.ok} ${pc3.green("attribution ")} ${pc3.dim(`${report.attribution_spine.review.pending_count} nodes \xB7 ${report.attribution_spine.source_events.length} source events`)}`);
10430
+ if (published?.publicUrl) {
10431
+ console.log(` ${ICON.ok} ${pc3.green("profile ")} ${pc3.bold(published.publicUrl)}`);
10432
+ }
9082
10433
  }
9083
10434
  async function checkPluginStatusesCompact() {
9084
10435
  const spinner = createOrgxSpinner("Checking OrgX companion plugin status");
@@ -10038,7 +11389,7 @@ function printDoctorReport(report, assessment) {
10038
11389
  async function main() {
10039
11390
  const program = new Command();
10040
11391
  program.name("orgx-wizard").description("Add OrgX MCP configs, skills/rules, and companion plugins to your local AI tools.").showHelpAfterError();
10041
- const pkgVersion = true ? "0.1.32" : void 0;
11392
+ const pkgVersion = true ? "0.1.36" : void 0;
10042
11393
  program.version(pkgVersion ?? "unknown", "-V, --version");
10043
11394
  program.hook("preAction", (_thisCommand, actionCommand) => {
10044
11395
  if (Boolean(actionCommand.optsWithGlobals().json)) return;
@@ -10753,7 +12104,7 @@ async function main() {
10753
12104
  });
10754
12105
  await runWorkGraphCommand(options);
10755
12106
  });
10756
- workGraph.command("profile").description("Build a local OrgX Profile with Work Graph Trails, Mirror, tensions, and launch 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", "5").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("--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("--json", "emit a JSON command summary").action(async (options) => {
12107
+ workGraph.command("profile").description("Build a local OrgX Profile with Work Graph Trails, Mirror, tensions, and launch 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", "5").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("--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) => {
10757
12108
  await safeTrackWizardTelemetry("work_graph_profile_started", {
10758
12109
  command: "work-graph profile",
10759
12110
  from: options.from ?? "manual"
@@ -10761,7 +12112,7 @@ async function main() {
10761
12112
  await runWorkGraphCommand(options);
10762
12113
  });
10763
12114
  const sessions = program.command("sessions").description("Inspect and reconcile local AI sessions into OrgX-ready Work Graph reports.");
10764
- sessions.command("reconcile").description("Backfill recent Codex and Claude sessions into a redacted Work Graph 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", "5").option("--session-days <days>", "lookback window for local AI-session imports", "7").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("--json", "emit a JSON command summary").action(async (options) => {
12115
+ sessions.command("reconcile").description("Backfill recent Codex and Claude sessions into a redacted Work Graph 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", "5").option("--session-days <days>", "lookback window for local AI-session imports", "7").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) => {
10765
12116
  await safeTrackWizardTelemetry("sessions_reconcile_started", {
10766
12117
  command: "sessions reconcile",
10767
12118
  from: options.from ?? "all"
@@ -10805,6 +12156,9 @@ async function main() {
10805
12156
  }
10806
12157
  }
10807
12158
  });
12159
+ hooks.command("replay").description("Replay passive hook outbox events into a claimed Work Graph profile.").requiredOption("--fingerprint <fingerprint>", "target Work Graph fingerprint, for example wgf_0123...").option("--outbox <path>", "runtime hook JSONL outbox path").option("--limit <count>", "maximum recent hook events to replay", "200").option("--yes", "approve publishing hook evidence without prompting").option("--json", "emit a JSON command summary").action(async (options) => {
12160
+ await runHookReplayCommand(options);
12161
+ });
10808
12162
  program.command("doctor").description("Verify local OrgX surface config and optional remote setup status.").action(async () => {
10809
12163
  const spinner = createOrgxSpinner("Running OrgX health check");
10810
12164
  spinner.start();