@useorgx/orgx-opencode-plugin 0.1.0-alpha.1 → 0.1.0-alpha.10
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/LICENSE +21 -0
- package/README.md +122 -3
- package/dist/OpenCodeDriver.d.ts +5 -2
- package/dist/OpenCodeDriver.d.ts.map +1 -1
- package/dist/OpenCodeDriver.js +12 -1
- package/dist/OpenCodeDriver.js.map +1 -1
- package/dist/attentionBridge.d.ts +38 -0
- package/dist/attentionBridge.d.ts.map +1 -0
- package/dist/attentionBridge.js +230 -0
- package/dist/attentionBridge.js.map +1 -0
- package/dist/childProcessEnv.d.ts +5 -0
- package/dist/childProcessEnv.d.ts.map +1 -0
- package/dist/childProcessEnv.js +17 -0
- package/dist/childProcessEnv.js.map +1 -0
- package/dist/cli.js +6 -3
- package/dist/cli.js.map +1 -1
- package/dist/contextPackHydration.d.ts +14 -0
- package/dist/contextPackHydration.d.ts.map +1 -0
- package/dist/contextPackHydration.js +57 -0
- package/dist/contextPackHydration.js.map +1 -0
- package/dist/continuityHealth.d.ts +48 -0
- package/dist/continuityHealth.d.ts.map +1 -0
- package/dist/continuityHealth.js +118 -0
- package/dist/continuityHealth.js.map +1 -0
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -3
- package/dist/index.js.map +1 -1
- package/dist/peer.d.ts +14 -0
- package/dist/peer.d.ts.map +1 -1
- package/dist/peer.js +142 -13
- package/dist/peer.js.map +1 -1
- package/dist/plugin.d.ts +17 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +124 -0
- package/dist/plugin.js.map +1 -0
- package/dist/sentry.d.ts +4 -0
- package/dist/sentry.d.ts.map +1 -0
- package/dist/sentry.js +97 -0
- package/dist/sentry.js.map +1 -0
- package/dist/sessionSummaryBridge.d.ts +20 -0
- package/dist/sessionSummaryBridge.d.ts.map +1 -0
- package/dist/sessionSummaryBridge.js +120 -0
- package/dist/sessionSummaryBridge.js.map +1 -0
- package/dist/v2Canary.d.ts +13 -0
- package/dist/v2Canary.d.ts.map +1 -0
- package/dist/v2Canary.js +27 -0
- package/dist/v2Canary.js.map +1 -0
- package/dist/workGraphOutbox.js +2 -2
- package/dist/workGraphOutbox.js.map +1 -1
- package/dist/workGraphReplay.d.ts +9 -0
- package/dist/workGraphReplay.d.ts.map +1 -0
- package/dist/workGraphReplay.js +41 -0
- package/dist/workGraphReplay.js.map +1 -0
- package/package.json +30 -9
- package/plugin.manifest.json +8 -2
- package/scripts/orgx-work-graph-reconcile.mjs +994 -0
- package/scripts/orgx-work-graph-reconcile.node-test.mjs +131 -0
|
@@ -0,0 +1,994 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createReadStream,
|
|
5
|
+
existsSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
realpathSync,
|
|
8
|
+
writeFileSync,
|
|
9
|
+
} from "node:fs";
|
|
10
|
+
import { createInterface } from "node:readline";
|
|
11
|
+
import { createHash } from "node:crypto";
|
|
12
|
+
import { basename, dirname, join } from "node:path";
|
|
13
|
+
import { homedir } from "node:os";
|
|
14
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
15
|
+
|
|
16
|
+
const WORK_GRAPH_SCHEMA_VERSION = "2.0.0";
|
|
17
|
+
const WORK_GRAPH_FINGERPRINT_VERSION = "wgf_v1";
|
|
18
|
+
const DEFAULT_OUTBOX = join(
|
|
19
|
+
homedir(),
|
|
20
|
+
".config",
|
|
21
|
+
"useorgx",
|
|
22
|
+
"wizard",
|
|
23
|
+
"hooks",
|
|
24
|
+
"events.jsonl"
|
|
25
|
+
);
|
|
26
|
+
const SOURCE_CLIENTS = new Set([
|
|
27
|
+
"codex",
|
|
28
|
+
"claude",
|
|
29
|
+
"claude-code",
|
|
30
|
+
"cursor",
|
|
31
|
+
"opencode",
|
|
32
|
+
"goose",
|
|
33
|
+
"openclaw",
|
|
34
|
+
"slack",
|
|
35
|
+
"mcp",
|
|
36
|
+
"orgx_runtime_hook",
|
|
37
|
+
"github",
|
|
38
|
+
"linear",
|
|
39
|
+
"gmail",
|
|
40
|
+
"calendar",
|
|
41
|
+
"notion",
|
|
42
|
+
"docs",
|
|
43
|
+
"manual",
|
|
44
|
+
"wizard",
|
|
45
|
+
"api",
|
|
46
|
+
"unknown",
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
export function parseArgs(argv) {
|
|
50
|
+
const args = {};
|
|
51
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
52
|
+
const arg = argv[index];
|
|
53
|
+
if (!arg.startsWith("--")) continue;
|
|
54
|
+
const [rawKey, ...rest] = arg.slice(2).split("=");
|
|
55
|
+
const key = rawKey.trim();
|
|
56
|
+
if (!key) continue;
|
|
57
|
+
if (rest.length > 0) {
|
|
58
|
+
args[key] = rest.join("=");
|
|
59
|
+
} else if (argv[index + 1] && !argv[index + 1].startsWith("--")) {
|
|
60
|
+
args[key] = argv[index + 1];
|
|
61
|
+
index += 1;
|
|
62
|
+
} else {
|
|
63
|
+
args[key] = "true";
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return args;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function pickString(...values) {
|
|
70
|
+
for (const value of values) {
|
|
71
|
+
if (typeof value !== "string") continue;
|
|
72
|
+
const trimmed = value.trim();
|
|
73
|
+
if (trimmed) return trimmed;
|
|
74
|
+
}
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function stableJson(value) {
|
|
79
|
+
if (Array.isArray(value)) {
|
|
80
|
+
return `[${value.map((item) => stableJson(item)).join(",")}]`;
|
|
81
|
+
}
|
|
82
|
+
if (value && typeof value === "object") {
|
|
83
|
+
return `{${Object.keys(value)
|
|
84
|
+
.sort()
|
|
85
|
+
.map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`)
|
|
86
|
+
.join(",")}}`;
|
|
87
|
+
}
|
|
88
|
+
return JSON.stringify(value);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function hashString(value, length = 24) {
|
|
92
|
+
return createHash("sha256").update(value).digest("hex").slice(0, length);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function stableHash(value, length = 24) {
|
|
96
|
+
return hashString(stableJson(value), length);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function slug(value, fallback = "unknown") {
|
|
100
|
+
return (
|
|
101
|
+
pickString(value)
|
|
102
|
+
?.toLowerCase()
|
|
103
|
+
.replace(/[^a-z0-9._:-]+/g, "-")
|
|
104
|
+
.replace(/^-+|-+$/g, "")
|
|
105
|
+
.slice(0, 120) || fallback
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function toIsoTimestamp(value, fallback) {
|
|
110
|
+
const parsed = Date.parse(String(value || ""));
|
|
111
|
+
if (Number.isFinite(parsed)) return new Date(parsed).toISOString();
|
|
112
|
+
return fallback;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function normalizeSourceClient(value) {
|
|
116
|
+
const raw = pickString(value)?.toLowerCase() || "unknown";
|
|
117
|
+
if (raw === "claude_code") return "claude-code";
|
|
118
|
+
if (raw === "claudecode") return "claude-code";
|
|
119
|
+
if (raw === "open-claw") return "openclaw";
|
|
120
|
+
return SOURCE_CLIENTS.has(raw) ? raw : "unknown";
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function sourceLabel(sourceClient) {
|
|
124
|
+
return {
|
|
125
|
+
codex: "Codex",
|
|
126
|
+
"claude-code": "Claude Code",
|
|
127
|
+
claude: "Claude",
|
|
128
|
+
openclaw: "OpenClaw",
|
|
129
|
+
orgx_runtime_hook: "OrgX runtime hook",
|
|
130
|
+
mcp: "MCP",
|
|
131
|
+
wizard: "OrgX wizard",
|
|
132
|
+
}[sourceClient] || sourceClient;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function eventPhase(event) {
|
|
136
|
+
const normalized = String(event || "").toLowerCase();
|
|
137
|
+
if (normalized.includes("stop") || normalized.includes("complete")) {
|
|
138
|
+
return "completed";
|
|
139
|
+
}
|
|
140
|
+
if (normalized.includes("block") || normalized.includes("error")) {
|
|
141
|
+
return "blocked";
|
|
142
|
+
}
|
|
143
|
+
if (normalized.includes("permission")) return "blocked";
|
|
144
|
+
return "in_progress";
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function recordSortKey(record) {
|
|
148
|
+
return [
|
|
149
|
+
record.timestamp || "",
|
|
150
|
+
record.source_client || "",
|
|
151
|
+
record.run_id || "",
|
|
152
|
+
record.session_id || "",
|
|
153
|
+
record.event || "",
|
|
154
|
+
record.turn_id || "",
|
|
155
|
+
].join("\u0000");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function normalizeHookRecord(record, index = 0) {
|
|
159
|
+
if (!record || typeof record !== "object" || Array.isArray(record)) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
const sourceClient = normalizeSourceClient(record.source_client);
|
|
163
|
+
const sessionId = pickString(record.session_id, record.sessionId);
|
|
164
|
+
const runId = pickString(record.run_id, record.runId);
|
|
165
|
+
const cwd = pickString(record.cwd, record.workspace, process.cwd());
|
|
166
|
+
const timestamp = toIsoTimestamp(record.timestamp, "1970-01-01T00:00:00.000Z");
|
|
167
|
+
const summary =
|
|
168
|
+
record.summary && typeof record.summary === "object" && !Array.isArray(record.summary)
|
|
169
|
+
? record.summary
|
|
170
|
+
: {};
|
|
171
|
+
return {
|
|
172
|
+
schema_version: pickString(record.schema_version, "2026-05-07"),
|
|
173
|
+
source: pickString(record.source, "orgx_runtime_hook"),
|
|
174
|
+
source_client: sourceClient,
|
|
175
|
+
event: pickString(record.event, "unknown"),
|
|
176
|
+
run_id: runId,
|
|
177
|
+
session_id: sessionId || `unknown-session-${index + 1}`,
|
|
178
|
+
turn_id: pickString(record.turn_id, record.turnId),
|
|
179
|
+
cwd,
|
|
180
|
+
transcript_path: pickString(record.transcript_path, record.transcriptPath),
|
|
181
|
+
timestamp,
|
|
182
|
+
summary: {
|
|
183
|
+
tool_name: pickString(summary.tool_name, summary.toolName),
|
|
184
|
+
prompt_chars:
|
|
185
|
+
typeof summary.prompt_chars === "number" && Number.isFinite(summary.prompt_chars)
|
|
186
|
+
? summary.prompt_chars
|
|
187
|
+
: undefined,
|
|
188
|
+
payload_keys: Array.isArray(summary.payload_keys)
|
|
189
|
+
? summary.payload_keys
|
|
190
|
+
.filter((item) => typeof item === "string")
|
|
191
|
+
.slice(0, 40)
|
|
192
|
+
: [],
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export async function loadHookOutboxRecords(outboxPath, { maxRecords = 5000 } = {}) {
|
|
198
|
+
if (!existsSync(outboxPath)) {
|
|
199
|
+
return { records: [], skipped: 0, missing: true };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const records = [];
|
|
203
|
+
let skipped = 0;
|
|
204
|
+
const rl = createInterface({
|
|
205
|
+
input: createReadStream(outboxPath, { encoding: "utf8" }),
|
|
206
|
+
crlfDelay: Infinity,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
for await (const line of rl) {
|
|
210
|
+
const trimmed = line.trim();
|
|
211
|
+
if (!trimmed) continue;
|
|
212
|
+
try {
|
|
213
|
+
const parsed = JSON.parse(trimmed);
|
|
214
|
+
const normalized = normalizeHookRecord(parsed, records.length + skipped);
|
|
215
|
+
if (normalized) records.push(normalized);
|
|
216
|
+
else skipped += 1;
|
|
217
|
+
} catch {
|
|
218
|
+
skipped += 1;
|
|
219
|
+
}
|
|
220
|
+
if (records.length >= maxRecords) break;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return { records, skipped, missing: false };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function toolNames(records) {
|
|
227
|
+
return [
|
|
228
|
+
...new Set(
|
|
229
|
+
records
|
|
230
|
+
.map((record) => pickString(record.summary?.tool_name))
|
|
231
|
+
.filter(Boolean)
|
|
232
|
+
.sort()
|
|
233
|
+
),
|
|
234
|
+
];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function hasOrgxSignal(records) {
|
|
238
|
+
return records.some((record) => {
|
|
239
|
+
const haystack = [
|
|
240
|
+
record.source,
|
|
241
|
+
record.source_client,
|
|
242
|
+
record.event,
|
|
243
|
+
record.summary?.tool_name,
|
|
244
|
+
...(record.summary?.payload_keys || []),
|
|
245
|
+
]
|
|
246
|
+
.filter(Boolean)
|
|
247
|
+
.join(" ")
|
|
248
|
+
.toLowerCase();
|
|
249
|
+
return haystack.includes("orgx") || haystack.includes("useorgx");
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function hasMcpSignal(records) {
|
|
254
|
+
return records.some((record) => {
|
|
255
|
+
const haystack = [
|
|
256
|
+
record.event,
|
|
257
|
+
record.summary?.tool_name,
|
|
258
|
+
...(record.summary?.payload_keys || []),
|
|
259
|
+
]
|
|
260
|
+
.filter(Boolean)
|
|
261
|
+
.join(" ")
|
|
262
|
+
.toLowerCase();
|
|
263
|
+
return haystack.includes("mcp");
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function hasOrgxMcpSignal(records) {
|
|
268
|
+
return records.some((record) => {
|
|
269
|
+
const haystack = [
|
|
270
|
+
record.event,
|
|
271
|
+
record.summary?.tool_name,
|
|
272
|
+
...(record.summary?.payload_keys || []),
|
|
273
|
+
]
|
|
274
|
+
.filter(Boolean)
|
|
275
|
+
.join(" ")
|
|
276
|
+
.toLowerCase();
|
|
277
|
+
return (
|
|
278
|
+
haystack.includes("mcp") &&
|
|
279
|
+
(haystack.includes("orgx") || haystack.includes("useorgx"))
|
|
280
|
+
);
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function buildEvidenceRef(record, ordinal) {
|
|
285
|
+
const sourceId = `${record.source_client}:${record.session_id}`;
|
|
286
|
+
const id = `${sourceId}:${ordinal}`;
|
|
287
|
+
const tool = pickString(record.summary?.tool_name);
|
|
288
|
+
const summaryBits = [
|
|
289
|
+
`${sourceLabel(record.source_client)} ${record.event} hook observed.`,
|
|
290
|
+
tool ? `Tool signal: ${tool}.` : undefined,
|
|
291
|
+
typeof record.summary?.prompt_chars === "number"
|
|
292
|
+
? `Prompt length: ${record.summary.prompt_chars} characters.`
|
|
293
|
+
: undefined,
|
|
294
|
+
].filter(Boolean);
|
|
295
|
+
return {
|
|
296
|
+
id,
|
|
297
|
+
source_client: record.source_client,
|
|
298
|
+
source_id: sourceId,
|
|
299
|
+
label: `${sourceLabel(record.source_client)} ${record.event}`,
|
|
300
|
+
summary: summaryBits.join(" "),
|
|
301
|
+
occurred_at: record.timestamp,
|
|
302
|
+
confidence: 0.72,
|
|
303
|
+
redaction_state: "public_summary",
|
|
304
|
+
metadata: {
|
|
305
|
+
event: record.event,
|
|
306
|
+
cwd_hash: hashString(record.cwd, 16),
|
|
307
|
+
has_transcript_path: Boolean(record.transcript_path),
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function buildAttributionNode({
|
|
313
|
+
id,
|
|
314
|
+
kind,
|
|
315
|
+
label,
|
|
316
|
+
summary,
|
|
317
|
+
sourceClient,
|
|
318
|
+
evidenceRefs,
|
|
319
|
+
linkedNodeIds = [],
|
|
320
|
+
confidence = 0.72,
|
|
321
|
+
weight = 50,
|
|
322
|
+
metadata = {},
|
|
323
|
+
}) {
|
|
324
|
+
return {
|
|
325
|
+
id,
|
|
326
|
+
kind,
|
|
327
|
+
label,
|
|
328
|
+
summary,
|
|
329
|
+
source_client: sourceClient,
|
|
330
|
+
evidence_refs: evidenceRefs,
|
|
331
|
+
linked_node_ids: linkedNodeIds,
|
|
332
|
+
confidence,
|
|
333
|
+
weight,
|
|
334
|
+
review_state: "unreviewed",
|
|
335
|
+
dedupe_key: id,
|
|
336
|
+
metadata,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function finalStateFor(records) {
|
|
341
|
+
if (records.some((record) => eventPhase(record.event) === "blocked")) {
|
|
342
|
+
return "blocked";
|
|
343
|
+
}
|
|
344
|
+
if (records.some((record) => eventPhase(record.event) === "completed")) {
|
|
345
|
+
return "completed";
|
|
346
|
+
}
|
|
347
|
+
if (records.length > 0) return "in_progress";
|
|
348
|
+
return "unknown";
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function findingTypeCounts(findings) {
|
|
352
|
+
const counts = {};
|
|
353
|
+
for (const finding of findings) {
|
|
354
|
+
counts[finding.type] = (counts[finding.type] || 0) + 1;
|
|
355
|
+
}
|
|
356
|
+
return counts;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function emptyAttributionSpine(evidenceRefs = []) {
|
|
360
|
+
return {
|
|
361
|
+
source_events: [],
|
|
362
|
+
actions: [],
|
|
363
|
+
decisions: [],
|
|
364
|
+
artifacts: [],
|
|
365
|
+
people: [],
|
|
366
|
+
agents: [],
|
|
367
|
+
tools: [],
|
|
368
|
+
businesses: [],
|
|
369
|
+
product_surfaces: [],
|
|
370
|
+
goals: [],
|
|
371
|
+
sources: [],
|
|
372
|
+
initiative_candidates: [],
|
|
373
|
+
evidence_refs: evidenceRefs,
|
|
374
|
+
confidence: evidenceRefs.length ? 0.7 : 0,
|
|
375
|
+
dedupe_keys: [],
|
|
376
|
+
privacy: {
|
|
377
|
+
redaction_state: "public_summary",
|
|
378
|
+
raw_transcripts_included: false,
|
|
379
|
+
public_summary_only: true,
|
|
380
|
+
},
|
|
381
|
+
review: {
|
|
382
|
+
pending_count: 0,
|
|
383
|
+
correction_affordances: [
|
|
384
|
+
"confirm",
|
|
385
|
+
"merge",
|
|
386
|
+
"hide",
|
|
387
|
+
"mark_important",
|
|
388
|
+
"launch_or_dismiss",
|
|
389
|
+
],
|
|
390
|
+
},
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export function buildWorkGraphReport(recordsInput, options = {}) {
|
|
395
|
+
const generatedAt = toIsoTimestamp(
|
|
396
|
+
options.generatedAt,
|
|
397
|
+
new Date().toISOString()
|
|
398
|
+
);
|
|
399
|
+
const records = recordsInput
|
|
400
|
+
.map((record, index) => normalizeHookRecord(record, index))
|
|
401
|
+
.filter(Boolean)
|
|
402
|
+
.sort((a, b) => recordSortKey(a).localeCompare(recordSortKey(b)));
|
|
403
|
+
const firstRecord = records[0];
|
|
404
|
+
const workspaceCwd = pickString(options.workspaceCwd, firstRecord?.cwd, process.cwd());
|
|
405
|
+
const workspaceName = pickString(
|
|
406
|
+
options.workspaceName,
|
|
407
|
+
basename(workspaceCwd) || workspaceCwd,
|
|
408
|
+
"Local workspace"
|
|
409
|
+
);
|
|
410
|
+
const workspaceHash = `sha256:${hashString(workspaceCwd, 32)}`;
|
|
411
|
+
const workspace = {
|
|
412
|
+
id: pickString(options.workspaceId, `local:${hashString(workspaceCwd, 16)}`),
|
|
413
|
+
name: workspaceName,
|
|
414
|
+
};
|
|
415
|
+
const sourceClients = [
|
|
416
|
+
...new Set(records.map((record) => record.source_client).filter(Boolean)),
|
|
417
|
+
].sort();
|
|
418
|
+
const sessions = [
|
|
419
|
+
...new Set(
|
|
420
|
+
records
|
|
421
|
+
.map((record) => `${record.source_client}:${record.session_id}`)
|
|
422
|
+
.filter(Boolean)
|
|
423
|
+
),
|
|
424
|
+
].sort();
|
|
425
|
+
const observedToolNames = toolNames(records);
|
|
426
|
+
const orgxMcpCalled = hasOrgxMcpSignal(records);
|
|
427
|
+
const evidenceRefs = records.slice(0, 200).map(buildEvidenceRef);
|
|
428
|
+
const primaryEvidenceRef = evidenceRefs[0]?.id || "work-graph:hook-outbox:empty";
|
|
429
|
+
const sourceEvents = records.slice(0, 200).map((record, index) => ({
|
|
430
|
+
source_client: record.source_client,
|
|
431
|
+
source_id: `${record.source_client}:${record.session_id}`,
|
|
432
|
+
source_label: `${sourceLabel(record.source_client)} session`,
|
|
433
|
+
event_type: "runtime_hook",
|
|
434
|
+
occurred_at: record.timestamp,
|
|
435
|
+
evidence_ref: evidenceRefs[index].id,
|
|
436
|
+
confidence: 0.72,
|
|
437
|
+
metadata: {
|
|
438
|
+
event: record.event,
|
|
439
|
+
tool_name: record.summary?.tool_name,
|
|
440
|
+
prompt_chars: record.summary?.prompt_chars,
|
|
441
|
+
},
|
|
442
|
+
}));
|
|
443
|
+
const reportEvents = sourceEvents.map((event) => ({
|
|
444
|
+
source_client: event.source_client,
|
|
445
|
+
source_id: event.source_id,
|
|
446
|
+
source_label: event.source_label,
|
|
447
|
+
event_type: event.metadata?.tool_name ? "tool_signal" : "client_extraction",
|
|
448
|
+
evidence_ref: event.evidence_ref,
|
|
449
|
+
metadata: {
|
|
450
|
+
...event.metadata,
|
|
451
|
+
occurred_at: event.occurred_at,
|
|
452
|
+
attribution_event_type: event.event_type,
|
|
453
|
+
},
|
|
454
|
+
}));
|
|
455
|
+
|
|
456
|
+
const findings = [];
|
|
457
|
+
if (records.length > 0) {
|
|
458
|
+
findings.push({
|
|
459
|
+
type: "action",
|
|
460
|
+
title: "AI client lifecycle activity observed",
|
|
461
|
+
summary:
|
|
462
|
+
"OrgX captured summary-only hook events from local AI client sessions.",
|
|
463
|
+
source_client: records[0].source_client,
|
|
464
|
+
source_id: `${records[0].source_client}:${records[0].session_id}`,
|
|
465
|
+
evidence_ref: primaryEvidenceRef,
|
|
466
|
+
confidence: 0.72,
|
|
467
|
+
metadata: {
|
|
468
|
+
event_count: records.length,
|
|
469
|
+
session_count: sessions.length,
|
|
470
|
+
source_clients: sourceClients,
|
|
471
|
+
},
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
if (observedToolNames.length > 0) {
|
|
475
|
+
findings.push({
|
|
476
|
+
type: "artifact",
|
|
477
|
+
title: "Tool-use trail is available for work graph hydration",
|
|
478
|
+
summary:
|
|
479
|
+
"Hook metadata includes compact tool names that can seed OrgX sources, tools, and evidence refs without raw transcript upload.",
|
|
480
|
+
source_client: records[0]?.source_client || "unknown",
|
|
481
|
+
source_id: records[0]
|
|
482
|
+
? `${records[0].source_client}:${records[0].session_id}`
|
|
483
|
+
: "unknown:session",
|
|
484
|
+
evidence_ref: primaryEvidenceRef,
|
|
485
|
+
confidence: 0.7,
|
|
486
|
+
metadata: {
|
|
487
|
+
tool_names: observedToolNames.slice(0, 20),
|
|
488
|
+
},
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const missedOrchestration = [];
|
|
493
|
+
if (records.length > 0 && !orgxMcpCalled) {
|
|
494
|
+
const missed = {
|
|
495
|
+
type: "missed_orchestration_opportunity",
|
|
496
|
+
title: "Session activity was captured without durable OrgX writeback",
|
|
497
|
+
summary:
|
|
498
|
+
"The hook outbox observed client lifecycle events, but no OrgX MCP tool signal was detected in the compact hook metadata.",
|
|
499
|
+
source_client: "wizard",
|
|
500
|
+
source_id: "work-graph:hook-outbox",
|
|
501
|
+
evidence_ref: primaryEvidenceRef,
|
|
502
|
+
confidence: 0.76,
|
|
503
|
+
metadata: {
|
|
504
|
+
event_count: records.length,
|
|
505
|
+
session_count: sessions.length,
|
|
506
|
+
},
|
|
507
|
+
};
|
|
508
|
+
findings.push(missed);
|
|
509
|
+
missedOrchestration.push(missed);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const sourceNodes = sourceClients.map((sourceClient) =>
|
|
513
|
+
buildAttributionNode({
|
|
514
|
+
id: `source:${sourceClient}`,
|
|
515
|
+
kind: "source",
|
|
516
|
+
label: sourceLabel(sourceClient),
|
|
517
|
+
summary: `${sourceLabel(sourceClient)} hook events were present in the outbox.`,
|
|
518
|
+
sourceClient,
|
|
519
|
+
evidenceRefs: evidenceRefs
|
|
520
|
+
.filter((ref) => ref.source_client === sourceClient)
|
|
521
|
+
.slice(0, 10)
|
|
522
|
+
.map((ref) => ref.id),
|
|
523
|
+
linkedNodeIds: ["action:hook-lifecycle-captured"],
|
|
524
|
+
confidence: 0.72,
|
|
525
|
+
weight: 70,
|
|
526
|
+
metadata: { connected: true },
|
|
527
|
+
})
|
|
528
|
+
);
|
|
529
|
+
const toolNodes = observedToolNames.slice(0, 50).map((name) =>
|
|
530
|
+
buildAttributionNode({
|
|
531
|
+
id: `tool:${slug(name)}`,
|
|
532
|
+
kind: "tool",
|
|
533
|
+
label: name.slice(0, 120),
|
|
534
|
+
summary: `Tool signal observed from hook metadata: ${name}.`,
|
|
535
|
+
sourceClient: name.toLowerCase().includes("orgx") ? "mcp" : records[0]?.source_client || "unknown",
|
|
536
|
+
evidenceRefs: evidenceRefs
|
|
537
|
+
.filter((ref) => records[evidenceRefs.indexOf(ref)]?.summary?.tool_name === name)
|
|
538
|
+
.slice(0, 10)
|
|
539
|
+
.map((ref) => ref.id),
|
|
540
|
+
linkedNodeIds: ["action:hook-lifecycle-captured"],
|
|
541
|
+
confidence: 0.68,
|
|
542
|
+
weight: name.toLowerCase().includes("orgx") ? 84 : 60,
|
|
543
|
+
metadata: {},
|
|
544
|
+
})
|
|
545
|
+
);
|
|
546
|
+
const actionNode = records.length
|
|
547
|
+
? buildAttributionNode({
|
|
548
|
+
id: "action:hook-lifecycle-captured",
|
|
549
|
+
kind: "action",
|
|
550
|
+
label: "Capture AI client lifecycle",
|
|
551
|
+
summary:
|
|
552
|
+
"Convert local hook outbox events into OrgX Work Graph source events, evidence refs, and reviewable initiative candidates.",
|
|
553
|
+
sourceClient: "wizard",
|
|
554
|
+
evidenceRefs: evidenceRefs.slice(0, 20).map((ref) => ref.id),
|
|
555
|
+
linkedNodeIds: [...sourceNodes.map((node) => node.id), ...toolNodes.map((node) => node.id)],
|
|
556
|
+
confidence: 0.74,
|
|
557
|
+
weight: 82,
|
|
558
|
+
metadata: { raw_transcripts_sent: false },
|
|
559
|
+
})
|
|
560
|
+
: null;
|
|
561
|
+
const initiativeNode = records.length
|
|
562
|
+
? buildAttributionNode({
|
|
563
|
+
id: "initiative:continuous-orgx-writeback",
|
|
564
|
+
kind: "goal",
|
|
565
|
+
label: "Install continuous OrgX writeback",
|
|
566
|
+
summary:
|
|
567
|
+
"Review hook-derived session evidence and promote real decisions, blockers, artifacts, and goals into OrgX.",
|
|
568
|
+
sourceClient: "wizard",
|
|
569
|
+
evidenceRefs: evidenceRefs.slice(0, 20).map((ref) => ref.id),
|
|
570
|
+
linkedNodeIds: actionNode ? [actionNode.id] : [],
|
|
571
|
+
confidence: orgxMcpCalled ? 0.64 : 0.78,
|
|
572
|
+
weight: orgxMcpCalled ? 62 : 88,
|
|
573
|
+
metadata: { priority: orgxMcpCalled ? "p2" : "p0" },
|
|
574
|
+
})
|
|
575
|
+
: null;
|
|
576
|
+
|
|
577
|
+
const attributionSpine = emptyAttributionSpine(evidenceRefs);
|
|
578
|
+
attributionSpine.source_events = sourceEvents;
|
|
579
|
+
attributionSpine.actions = actionNode ? [actionNode] : [];
|
|
580
|
+
attributionSpine.sources = sourceNodes;
|
|
581
|
+
attributionSpine.tools = toolNodes;
|
|
582
|
+
attributionSpine.initiative_candidates = initiativeNode ? [initiativeNode] : [];
|
|
583
|
+
attributionSpine.confidence = records.length ? 0.72 : 0;
|
|
584
|
+
attributionSpine.dedupe_keys = [
|
|
585
|
+
...attributionSpine.actions,
|
|
586
|
+
...attributionSpine.sources,
|
|
587
|
+
...attributionSpine.tools,
|
|
588
|
+
...attributionSpine.initiative_candidates,
|
|
589
|
+
].map((node) => node.dedupe_key);
|
|
590
|
+
attributionSpine.review.pending_count =
|
|
591
|
+
attributionSpine.actions.length +
|
|
592
|
+
attributionSpine.tools.length +
|
|
593
|
+
attributionSpine.initiative_candidates.length;
|
|
594
|
+
|
|
595
|
+
const counts = findingTypeCounts(findings);
|
|
596
|
+
const fingerprintBasis = {
|
|
597
|
+
schema_version: WORK_GRAPH_SCHEMA_VERSION,
|
|
598
|
+
fingerprint_version: WORK_GRAPH_FINGERPRINT_VERSION,
|
|
599
|
+
workspace_hash: workspaceHash,
|
|
600
|
+
source_clients: sourceClients.length ? sourceClients : ["unknown"],
|
|
601
|
+
connected_source_hashes: sessions.map((session) => `sha256:${hashString(session, 32)}`),
|
|
602
|
+
missing_source_hashes: orgxMcpCalled ? [] : [`sha256:${hashString("orgx-mcp", 32)}`],
|
|
603
|
+
finding_type_counts: counts,
|
|
604
|
+
pattern_hashes: [
|
|
605
|
+
...records.map((record) =>
|
|
606
|
+
`sha256:${stableHash({
|
|
607
|
+
source_client: record.source_client,
|
|
608
|
+
event: record.event,
|
|
609
|
+
tool_name: record.summary?.tool_name,
|
|
610
|
+
}, 32)}`
|
|
611
|
+
),
|
|
612
|
+
].sort(),
|
|
613
|
+
trail_shape_hashes: [],
|
|
614
|
+
recurring_pattern_hashes: [],
|
|
615
|
+
kickoff_hashes: records.length
|
|
616
|
+
? [`sha256:${hashString("continuous-orgx-writeback", 32)}`]
|
|
617
|
+
: [],
|
|
618
|
+
raw_transcripts_included: false,
|
|
619
|
+
};
|
|
620
|
+
const workGraphFingerprint = `wgf_${stableHash(fingerprintBasis, 24)}`;
|
|
621
|
+
const hydrationKey = `orgx:work-graph:${workGraphFingerprint}`;
|
|
622
|
+
const reportId = `report_${stableHash({ workGraphFingerprint, generatedAt }, 24)}`;
|
|
623
|
+
|
|
624
|
+
return {
|
|
625
|
+
schema_version: WORK_GRAPH_SCHEMA_VERSION,
|
|
626
|
+
report_id: reportId,
|
|
627
|
+
idempotency_key: `work-graph:hook-outbox:${workGraphFingerprint}`,
|
|
628
|
+
work_graph_fingerprint: workGraphFingerprint,
|
|
629
|
+
fingerprint_version: WORK_GRAPH_FINGERPRINT_VERSION,
|
|
630
|
+
fingerprint_basis: fingerprintBasis,
|
|
631
|
+
signup_hydration: {
|
|
632
|
+
strategy: "work_graph_fingerprint_claim",
|
|
633
|
+
hydration_key: hydrationKey,
|
|
634
|
+
eligible: records.length > 0,
|
|
635
|
+
notes: records.length
|
|
636
|
+
? ["claim after signup", "summary-only hook outbox hydration"]
|
|
637
|
+
: ["no hook events found"],
|
|
638
|
+
},
|
|
639
|
+
generated_at: generatedAt,
|
|
640
|
+
source_client: "wizard",
|
|
641
|
+
session_id: sessions[0] || "hook-outbox-empty",
|
|
642
|
+
workspace,
|
|
643
|
+
audit_method: {
|
|
644
|
+
mode: "ai_client_session_search",
|
|
645
|
+
searched_session_files: records.length > 0 ? 1 : 0,
|
|
646
|
+
skipped_session_files: 0,
|
|
647
|
+
searched_message_count: records.length,
|
|
648
|
+
retained_evidence_lines: evidenceRefs.length,
|
|
649
|
+
searched_source_groups: sourceClients.length,
|
|
650
|
+
extraction_lenses: ["runtime_hooks", "summary_only_work_graph"],
|
|
651
|
+
client_native_packs: sourceClients.map((sourceClient) => ({
|
|
652
|
+
source_client: sourceClient,
|
|
653
|
+
source_label: sourceLabel(sourceClient),
|
|
654
|
+
searched_session_count: records.filter(
|
|
655
|
+
(record) => record.source_client === sourceClient
|
|
656
|
+
).length,
|
|
657
|
+
finding_count: findings.filter(
|
|
658
|
+
(finding) => finding.source_client === sourceClient
|
|
659
|
+
).length,
|
|
660
|
+
confidence: 0.72,
|
|
661
|
+
})),
|
|
662
|
+
privacy_contract: [
|
|
663
|
+
"raw transcripts are excluded",
|
|
664
|
+
"hook payload values are summarized before persistence",
|
|
665
|
+
"workspace paths are hashed in public evidence metadata",
|
|
666
|
+
],
|
|
667
|
+
notes: [],
|
|
668
|
+
},
|
|
669
|
+
domain_coverage: [
|
|
670
|
+
{
|
|
671
|
+
id: "runtime-hooks",
|
|
672
|
+
label: "Runtime hooks",
|
|
673
|
+
summary: `${records.length} summary-only lifecycle events captured.`,
|
|
674
|
+
finding_count: findings.length,
|
|
675
|
+
source_clients: sourceClients.length ? sourceClients : ["unknown"],
|
|
676
|
+
evidence_refs: evidenceRefs.slice(0, 20).map((ref) => ref.id),
|
|
677
|
+
confidence: records.length ? 0.72 : 0,
|
|
678
|
+
},
|
|
679
|
+
],
|
|
680
|
+
skill_tool_signals: observedToolNames.slice(0, 50).map((name) => ({
|
|
681
|
+
id: `tool:${slug(name)}`,
|
|
682
|
+
label: name.slice(0, 120),
|
|
683
|
+
kind: name.toLowerCase().includes("orgx") ? "mcp_tool" : "client_tool",
|
|
684
|
+
mention_count: records.filter((record) => record.summary?.tool_name === name).length,
|
|
685
|
+
source_clients: sourceClients.length ? sourceClients : ["unknown"],
|
|
686
|
+
evidence_refs: evidenceRefs
|
|
687
|
+
.filter((_, index) => records[index]?.summary?.tool_name === name)
|
|
688
|
+
.slice(0, 20)
|
|
689
|
+
.map((ref) => ref.id),
|
|
690
|
+
confidence: 0.68,
|
|
691
|
+
})),
|
|
692
|
+
source_coverage: {
|
|
693
|
+
connected: sourceClients.map(sourceLabel),
|
|
694
|
+
missing: orgxMcpCalled ? [] : ["OrgX MCP writeback"],
|
|
695
|
+
mcpObserved: hasMcpSignal(records),
|
|
696
|
+
orgxObserved: hasOrgxSignal(records),
|
|
697
|
+
orgxMcpCalled,
|
|
698
|
+
skillOnlySignal: records.length > 0 && !orgxMcpCalled,
|
|
699
|
+
coverage_score: orgxMcpCalled ? 80 : records.length ? 45 : 0,
|
|
700
|
+
manifests: sourceClients.map((sourceClient) => ({
|
|
701
|
+
source_client: sourceClient,
|
|
702
|
+
source_label: sourceLabel(sourceClient),
|
|
703
|
+
status: "connected",
|
|
704
|
+
searched_sources: ["hook_outbox"],
|
|
705
|
+
searched_session_count: records.filter(
|
|
706
|
+
(record) => record.source_client === sourceClient
|
|
707
|
+
).length,
|
|
708
|
+
skipped_session_count: 0,
|
|
709
|
+
query_count: 0,
|
|
710
|
+
finding_count: findings.filter(
|
|
711
|
+
(finding) => finding.source_client === sourceClient
|
|
712
|
+
).length,
|
|
713
|
+
confidence: 0.72,
|
|
714
|
+
notes: [],
|
|
715
|
+
})),
|
|
716
|
+
notes: orgxMcpCalled
|
|
717
|
+
? ["OrgX MCP signal detected in hook metadata."]
|
|
718
|
+
: ["No OrgX MCP writeback signal detected in compact hook metadata."],
|
|
719
|
+
},
|
|
720
|
+
final_state: finalStateFor(records),
|
|
721
|
+
events: reportEvents,
|
|
722
|
+
findings,
|
|
723
|
+
missed_orchestration_opportunities: missedOrchestration,
|
|
724
|
+
trails: [],
|
|
725
|
+
recurring_patterns: [],
|
|
726
|
+
recommendations: records.length && !orgxMcpCalled
|
|
727
|
+
? [
|
|
728
|
+
{
|
|
729
|
+
id: "recommendation:promote-hook-outbox",
|
|
730
|
+
title: "Promote hook outbox evidence",
|
|
731
|
+
summary:
|
|
732
|
+
"Review summary-only hook records and promote real decisions, blockers, artifacts, and goals into OrgX.",
|
|
733
|
+
action_type: "connect_source",
|
|
734
|
+
trail_ids: [],
|
|
735
|
+
evidence_refs: [primaryEvidenceRef],
|
|
736
|
+
priority: "p0",
|
|
737
|
+
expected_lift: "+work graph durability",
|
|
738
|
+
confidence: 0.76,
|
|
739
|
+
},
|
|
740
|
+
]
|
|
741
|
+
: [],
|
|
742
|
+
mirror: {
|
|
743
|
+
headline: records.length
|
|
744
|
+
? "AI work is ready for OrgX review"
|
|
745
|
+
: "No AI hook activity found",
|
|
746
|
+
body: records.length
|
|
747
|
+
? "OrgX found summary-only lifecycle evidence that can hydrate a work graph without sending raw transcripts."
|
|
748
|
+
: "The hook outbox did not contain records to hydrate.",
|
|
749
|
+
lens: "all",
|
|
750
|
+
generated_at: generatedAt,
|
|
751
|
+
claims: records.length
|
|
752
|
+
? [
|
|
753
|
+
{
|
|
754
|
+
id: "mirror:hook-outbox",
|
|
755
|
+
text: `${records.length} lifecycle events were captured across ${sessions.length} session(s).`,
|
|
756
|
+
evidence_refs: [primaryEvidenceRef],
|
|
757
|
+
confidence: 0.72,
|
|
758
|
+
},
|
|
759
|
+
]
|
|
760
|
+
: [],
|
|
761
|
+
},
|
|
762
|
+
tension_metrics: records.length && !orgxMcpCalled
|
|
763
|
+
? [
|
|
764
|
+
{
|
|
765
|
+
id: "tension:work-without-writeback",
|
|
766
|
+
label: "work without writeback",
|
|
767
|
+
value: String(records.length),
|
|
768
|
+
tone: "warning",
|
|
769
|
+
trail_ids: [],
|
|
770
|
+
evidence_refs: [primaryEvidenceRef],
|
|
771
|
+
explanation:
|
|
772
|
+
"Hook events exist, but compact metadata did not prove durable OrgX MCP writeback.",
|
|
773
|
+
},
|
|
774
|
+
]
|
|
775
|
+
: [],
|
|
776
|
+
opportunity_score: {
|
|
777
|
+
overall: records.length ? (orgxMcpCalled ? 58 : 76) : 0,
|
|
778
|
+
value_potential: records.length ? 78 : 0,
|
|
779
|
+
evidence_quality: records.length ? 62 : 0,
|
|
780
|
+
urgency: records.length && !orgxMcpCalled ? 80 : 35,
|
|
781
|
+
owner_clarity: records.length ? 55 : 0,
|
|
782
|
+
automation_potential: records.length ? 86 : 0,
|
|
783
|
+
orgx_fit: records.length ? 90 : 0,
|
|
784
|
+
},
|
|
785
|
+
execution_quality: {
|
|
786
|
+
overall: records.length ? 64 : 0,
|
|
787
|
+
evidence_coverage: records.length ? 58 : 0,
|
|
788
|
+
source_attribution: records.length ? 70 : 0,
|
|
789
|
+
trail_depth: 0,
|
|
790
|
+
insight_depth: records.length ? 45 : 0,
|
|
791
|
+
actionability: records.length ? 72 : 0,
|
|
792
|
+
impact_confidence: records.length ? 52 : 0,
|
|
793
|
+
notes: [
|
|
794
|
+
"This producer intentionally emits summary-only evidence.",
|
|
795
|
+
"Durable entities should be confirmed before promotion.",
|
|
796
|
+
],
|
|
797
|
+
},
|
|
798
|
+
impact_projection: {
|
|
799
|
+
time_saved_hours_per_week: records.length ? 2 : 0,
|
|
800
|
+
acceleration_percent: records.length ? 8 : 0,
|
|
801
|
+
estimated_monthly_value_usd: 0,
|
|
802
|
+
confidence: records.length ? 0.35 : 0,
|
|
803
|
+
basis: ["hook outbox event count", "source coverage"],
|
|
804
|
+
assumptions: ["manual review promotes only confirmed entities"],
|
|
805
|
+
},
|
|
806
|
+
investigation: {
|
|
807
|
+
schema_version: WORK_GRAPH_SCHEMA_VERSION,
|
|
808
|
+
audit_id: `hook-outbox:${stableHash({ workspaceHash, generatedAt }, 16)}`,
|
|
809
|
+
fingerprint: workGraphFingerprint,
|
|
810
|
+
generated_at: generatedAt,
|
|
811
|
+
raw_events_summary: {
|
|
812
|
+
event_count: records.length,
|
|
813
|
+
source_clients: sourceClients,
|
|
814
|
+
sessions: sessions.length,
|
|
815
|
+
},
|
|
816
|
+
corpus_manifest: {
|
|
817
|
+
outbox_path: options.outboxPath ? "<local-hook-outbox>" : undefined,
|
|
818
|
+
raw_transcripts_excluded: true,
|
|
819
|
+
},
|
|
820
|
+
work_loops: [],
|
|
821
|
+
loop_families: [],
|
|
822
|
+
usage_catalogue: {
|
|
823
|
+
tools: observedToolNames,
|
|
824
|
+
},
|
|
825
|
+
source_confidence: {
|
|
826
|
+
hook_outbox: records.length ? 0.72 : 0,
|
|
827
|
+
},
|
|
828
|
+
why_not_100: orgxMcpCalled
|
|
829
|
+
? [
|
|
830
|
+
{
|
|
831
|
+
code: "compact_hook_records",
|
|
832
|
+
summary: "Hook records are compact and need entity confirmation.",
|
|
833
|
+
},
|
|
834
|
+
]
|
|
835
|
+
: [
|
|
836
|
+
{
|
|
837
|
+
code: "missing_orgx_mcp_writeback",
|
|
838
|
+
summary: "No OrgX MCP writeback signal was detected.",
|
|
839
|
+
},
|
|
840
|
+
],
|
|
841
|
+
counterfactuals: [],
|
|
842
|
+
verification_log: [
|
|
843
|
+
{
|
|
844
|
+
step: "load_hook_outbox",
|
|
845
|
+
status: "passed",
|
|
846
|
+
records_read: records.length,
|
|
847
|
+
records_skipped: options.recordsSkipped || 0,
|
|
848
|
+
},
|
|
849
|
+
],
|
|
850
|
+
critic_log: [],
|
|
851
|
+
verification_log_summary: {
|
|
852
|
+
total: records.length,
|
|
853
|
+
passed: records.length,
|
|
854
|
+
dropped: 0,
|
|
855
|
+
},
|
|
856
|
+
critic_log_summary: {
|
|
857
|
+
total: 0,
|
|
858
|
+
survived: 0,
|
|
859
|
+
dropped_or_demoted: 0,
|
|
860
|
+
},
|
|
861
|
+
mirror_paragraph: {},
|
|
862
|
+
repair_plan: [],
|
|
863
|
+
impact_projection: {},
|
|
864
|
+
redaction_log: [
|
|
865
|
+
{
|
|
866
|
+
rule: "exclude_raw_transcripts",
|
|
867
|
+
status: "passed",
|
|
868
|
+
},
|
|
869
|
+
],
|
|
870
|
+
raw_transcripts_excluded: true,
|
|
871
|
+
claimable: records.length > 0,
|
|
872
|
+
},
|
|
873
|
+
initiative_kickoffs: records.length
|
|
874
|
+
? [
|
|
875
|
+
{
|
|
876
|
+
title: "Install continuous OrgX writeback",
|
|
877
|
+
summary:
|
|
878
|
+
"Turn hook-derived lifecycle evidence into reviewed OrgX entities and runtime progress.",
|
|
879
|
+
reason: orgxMcpCalled
|
|
880
|
+
? "Hook evidence exists and can be attached to the active graph."
|
|
881
|
+
: "Work happened in AI clients without a proven OrgX MCP writeback signal.",
|
|
882
|
+
finding_refs: [primaryEvidenceRef],
|
|
883
|
+
priority: orgxMcpCalled ? "p2" : "p0",
|
|
884
|
+
},
|
|
885
|
+
]
|
|
886
|
+
: [],
|
|
887
|
+
attribution_spine: attributionSpine,
|
|
888
|
+
redaction_level: "summary_only",
|
|
889
|
+
raw_transcripts_sent: false,
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
export async function postWorkGraphReport({
|
|
894
|
+
report,
|
|
895
|
+
baseUrl,
|
|
896
|
+
apiKey,
|
|
897
|
+
fetchImpl = fetch,
|
|
898
|
+
}) {
|
|
899
|
+
const normalizedBaseUrl = pickString(baseUrl, "https://www.useorgx.com").replace(/\/+$/, "");
|
|
900
|
+
const token = pickString(apiKey);
|
|
901
|
+
if (!token) {
|
|
902
|
+
throw new Error("ORGX_API_KEY is required when posting a Work Graph report");
|
|
903
|
+
}
|
|
904
|
+
const response = await fetchImpl(`${normalizedBaseUrl}/api/client/work-graph/reports`, {
|
|
905
|
+
method: "POST",
|
|
906
|
+
headers: {
|
|
907
|
+
"Content-Type": "application/json",
|
|
908
|
+
Authorization: `Bearer ${token}`,
|
|
909
|
+
},
|
|
910
|
+
body: JSON.stringify({
|
|
911
|
+
report,
|
|
912
|
+
public_share: false,
|
|
913
|
+
attach_artifact: false,
|
|
914
|
+
}),
|
|
915
|
+
});
|
|
916
|
+
const body = await response.json().catch(async () => ({
|
|
917
|
+
text: await response.text().catch(() => ""),
|
|
918
|
+
}));
|
|
919
|
+
if (!response.ok) {
|
|
920
|
+
throw new Error(`Work Graph report post failed with HTTP ${response.status}`);
|
|
921
|
+
}
|
|
922
|
+
return body;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
export async function main({
|
|
926
|
+
argv = process.argv.slice(2),
|
|
927
|
+
env = process.env,
|
|
928
|
+
now = () => new Date(),
|
|
929
|
+
fetchImpl = fetch,
|
|
930
|
+
} = {}) {
|
|
931
|
+
const args = parseArgs(argv);
|
|
932
|
+
const outboxPath = pickString(args.outbox, env.ORGX_WIZARD_HOOK_OUTBOX, DEFAULT_OUTBOX);
|
|
933
|
+
const maxRecords = Number.parseInt(pickString(args.max_records, args["max-records"], "5000"), 10);
|
|
934
|
+
const loaded = await loadHookOutboxRecords(outboxPath, {
|
|
935
|
+
maxRecords: Number.isFinite(maxRecords) && maxRecords > 0 ? maxRecords : 5000,
|
|
936
|
+
});
|
|
937
|
+
const report = buildWorkGraphReport(loaded.records, {
|
|
938
|
+
outboxPath,
|
|
939
|
+
generatedAt: now().toISOString(),
|
|
940
|
+
workspaceCwd: pickString(args.cwd, env.ORGX_WORKSPACE_CWD),
|
|
941
|
+
workspaceName: pickString(args.workspace_name, args["workspace-name"]),
|
|
942
|
+
workspaceId: pickString(args.workspace_id, args["workspace-id"]),
|
|
943
|
+
recordsSkipped: loaded.skipped,
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
const result = {
|
|
947
|
+
ok: true,
|
|
948
|
+
outbox_path: outboxPath,
|
|
949
|
+
records_read: loaded.records.length,
|
|
950
|
+
records_skipped: loaded.skipped,
|
|
951
|
+
outbox_missing: loaded.missing,
|
|
952
|
+
work_graph_fingerprint: report.work_graph_fingerprint,
|
|
953
|
+
hydration_key: report.signup_hydration.hydration_key,
|
|
954
|
+
report,
|
|
955
|
+
};
|
|
956
|
+
|
|
957
|
+
if (args.post === "true") {
|
|
958
|
+
result.posted = await postWorkGraphReport({
|
|
959
|
+
report,
|
|
960
|
+
baseUrl: pickString(args.base_url, args["base-url"], env.ORGX_BASE_URL),
|
|
961
|
+
apiKey: pickString(args.api_key, args["api-key"], env.ORGX_API_KEY),
|
|
962
|
+
fetchImpl,
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
const outputPath = pickString(args.output);
|
|
967
|
+
if (outputPath) {
|
|
968
|
+
mkdirSync(dirname(outputPath), { recursive: true, mode: 0o700 });
|
|
969
|
+
writeFileSync(outputPath, `${JSON.stringify(result, null, 2)}\n`, {
|
|
970
|
+
encoding: "utf8",
|
|
971
|
+
mode: 0o600,
|
|
972
|
+
});
|
|
973
|
+
} else {
|
|
974
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
return result;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
export function isDirectRun({ invokedPath = process.argv[1], moduleUrl = import.meta.url } = {}) {
|
|
981
|
+
if (!invokedPath) return false;
|
|
982
|
+
try {
|
|
983
|
+
return realpathSync(invokedPath) === realpathSync(fileURLToPath(moduleUrl));
|
|
984
|
+
} catch {
|
|
985
|
+
return moduleUrl === pathToFileURL(invokedPath).href;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
if (isDirectRun()) {
|
|
990
|
+
main().catch((error) => {
|
|
991
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
992
|
+
process.exit(1);
|
|
993
|
+
});
|
|
994
|
+
}
|