worktrust 0.8.9 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/package.json +1 -1
- package/preserve-lines.mjs +8 -1
- package/stretch-evidence.mjs +49 -2
- package/worktrust.mjs +1 -1
package/README.md
CHANGED
|
@@ -174,6 +174,7 @@ nothing it does not:
|
|
|
174
174
|
| `autonomy`, `authorship` | the permission mode the AI app ran in, on one scale (ask, edits, plan, auto, full), how often it changed and the turns in plan mode; of the stretch's commits, how many name an AI as co-author (the trailer is read here, its names never leave) (0.8.7) |
|
|
175
175
|
| `framing`, `quality`, `risk`, `tool_mix`, `reads`, `context_files` | how the work was framed (your turns before the agent's first action, a plan first or not), checked (a check passing after the last change, the failures before a check passed, the change looked at before delivery; security scans count as checks), risked (destructive commands proposed, refused, run), which kinds of tools were used, files read again unchanged, and which of the AI app's own context files (instructions, skills, agents, commands, settings, MCP) the commits changed; kinds and counts only (0.8.8) |
|
|
176
176
|
| `quality.change_blocks`, `checked_blocks`, `rework_cycles`, `stage` | how many change blocks (edits with no check between them) a check followed, how often a failed check was followed by another change before it ran again (rework), and the furthest step the stretch reached here: attempted, verified, committed, pushed, pr, deployed; counts and one word only (0.8.9) |
|
|
177
|
+
| `timing` | how fast the stretch moved: seconds from your first turn to the first action and from the first change to the first check, the actions and seconds from a failure to the change that answered it, the calls from a failed check to it passing, and the calls back to work after a compaction; medians from the order and the clocks, never what was said (0.9.0) |
|
|
177
178
|
| `collector_version` | the CLI version that measured it |
|
|
178
179
|
| `seq`, `prev`, `hash` | the line's place, the hash of the line before it, and its own hash |
|
|
179
180
|
|
package/package.json
CHANGED
package/preserve-lines.mjs
CHANGED
|
@@ -111,6 +111,11 @@ export function archiveLine(entry, behaviour = null, { ids = {}, collector, salt
|
|
|
111
111
|
if (whole(entry.quality.change_blocks) && whole(entry.quality.checked_blocks) && whole(entry.quality.rework_cycles) && entry.quality.checked_blocks <= entry.quality.change_blocks) Object.assign(line.quality, { change_blocks: entry.quality.change_blocks, checked_blocks: entry.quality.checked_blocks, rework_cycles: entry.quality.rework_cycles });
|
|
112
112
|
}
|
|
113
113
|
if (STAGES.includes(entry.stage)) line.stage = entry.stage;
|
|
114
|
+
// 0.9.0: how fast the stretch moved; whole seconds and counts, each key optional.
|
|
115
|
+
if (entry.timing && typeof entry.timing === "object") {
|
|
116
|
+
const timing = Object.fromEntries(TIMING_KEYS.filter((key) => whole(entry.timing[key])).map((key) => [key, entry.timing[key]]));
|
|
117
|
+
if (Object.keys(timing).length > 0) line.timing = timing;
|
|
118
|
+
}
|
|
114
119
|
const risk = pick(entry.risk, ["proposed", "refused", "run"]);
|
|
115
120
|
if (risk && risk.proposed > 0 && risk.refused + risk.run <= risk.proposed) line.risk = risk;
|
|
116
121
|
const keyed = (record, keys) => { const kept = Object.entries(record && typeof record === "object" ? record : {}).filter(([key, n]) => keys.includes(key) && whole(n) && n > 0).sort(); return kept.length ? Object.fromEntries(kept) : null; };
|
|
@@ -169,7 +174,9 @@ export function archiveEntries(dir, ownKey) {
|
|
|
169
174
|
*/
|
|
170
175
|
/** The furthest step a stretch reached on the computer (0.8.9). */
|
|
171
176
|
const STAGES = ["attempted", "verified", "committed", "pushed", "pr", "deployed"];
|
|
172
|
-
|
|
177
|
+
/** How fast a stretch moved (0.9.0). */
|
|
178
|
+
const TIMING_KEYS = ["first_action_s", "first_check_s", "detect_actions", "detect_s", "recovery_calls", "resume_actions"];
|
|
179
|
+
export const DERIVED_KEYS = ["signals", "analyzer_version", "verification", "unconfirmed", "delivery", "recovery", "delegation", "context", "routing", "steering", "tools", "complexity", "oversight", "planning", "changes", "autonomy", "authorship", "framing", "quality", "risk", "tool_mix", "reads", "context_files", "stage", "timing", "interrupts", "steers", "utc_offset"];
|
|
173
180
|
export function supplementFor(line, earlier) {
|
|
174
181
|
const missing = DERIVED_KEYS.filter((key) => line[key] !== undefined && !earlier.some((old) => old[key] !== undefined));
|
|
175
182
|
if (missing.length === 0) return null;
|
package/stretch-evidence.mjs
CHANGED
|
@@ -334,9 +334,56 @@ function practiceOf(messages, { isHumanTurn }) {
|
|
|
334
334
|
};
|
|
335
335
|
}
|
|
336
336
|
|
|
337
|
+
/**
|
|
338
|
+
* HOW FAST A STRETCH MOVED (0.9.0): seconds from the person's first turn to the agent's first action (an edit, a write or
|
|
339
|
+
* a shell command) and from the first change to the first check; per failure, the actions and seconds until the next
|
|
340
|
+
* change that answered it (detection), and the calls from a failed check to that check passing (recovery depth); after
|
|
341
|
+
* a resume, a clear or a compaction, the calls until the next change or check (back to work). Medians, from the order
|
|
342
|
+
* and the clocks alone; nothing of what was said or run.
|
|
343
|
+
*/
|
|
344
|
+
const median = (values) => { if (values.length === 0) return null; const sorted = [...values].sort((a, b) => a - b); const middle = Math.floor(sorted.length / 2); return Math.round(sorted.length % 2 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2); };
|
|
345
|
+
function timingOf(messages, { isHumanTurn, textOf }) {
|
|
346
|
+
let firstTurn = null, firstAction = null, firstChange = null, firstCheck = null, index = 0, resumedAt = null;
|
|
347
|
+
const kindsById = new Map(), open = [], openChecks = new Map(), detect = [], detectSeconds = [], depth = [], resume = [];
|
|
348
|
+
for (const message of messages) {
|
|
349
|
+
if (message.bridge || !Number.isFinite(message.at)) continue;
|
|
350
|
+
const text = message.type === "user" ? textOf(message.content) : "";
|
|
351
|
+
if (message.compacted || ["compact", "clear", "resume"].includes(COMMAND.exec(text)?.[1] ?? "")) resumedAt = index;
|
|
352
|
+
if (message.type === "user" && !message.meta && message.kind !== "tool_result" && isHumanTurn(message.content) && firstTurn === null) firstTurn = message.at;
|
|
353
|
+
for (const result of message.results ?? []) {
|
|
354
|
+
const kinds = kindsById.get(result.id) ?? [];
|
|
355
|
+
if (result.failed !== true) {
|
|
356
|
+
for (const kind of kinds.filter((item) => CHECK_KINDS.includes(item) && result.failed === false)) { const at = openChecks.get(kind); if (at !== undefined) { depth.push(index - at); openChecks.delete(kind); } }
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
open.push({ index, at: message.at });
|
|
360
|
+
for (const kind of kinds.filter((item) => CHECK_KINDS.includes(item))) if (!openChecks.has(kind)) openChecks.set(kind, index);
|
|
361
|
+
}
|
|
362
|
+
for (const call of message.calls ?? []) {
|
|
363
|
+
index += 1;
|
|
364
|
+
kindsById.set(call.id, call.kinds);
|
|
365
|
+
const tool = call.family.split("|")[0];
|
|
366
|
+
if (firstAction === null && ACTION_TOOLS.has(tool)) firstAction = message.at;
|
|
367
|
+
if (EDIT_TOOLS.has(tool)) {
|
|
368
|
+
if (firstChange === null) firstChange = message.at;
|
|
369
|
+
for (const failure of open.splice(0)) { detect.push(index - failure.index); detectSeconds.push(Math.max(0, (message.at - failure.at) / 1000)); }
|
|
370
|
+
}
|
|
371
|
+
if (firstChange !== null && firstCheck === null && call.kinds.some((kind) => CHECK_KINDS.includes(kind))) firstCheck = message.at;
|
|
372
|
+
if (resumedAt !== null && (EDIT_TOOLS.has(tool) || call.kinds.some((kind) => CHECK_KINDS.includes(kind)))) { resume.push(index - resumedAt); resumedAt = null; }
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
const out = {
|
|
376
|
+
first_action_s: firstTurn !== null && firstAction !== null && firstAction >= firstTurn ? Math.round((firstAction - firstTurn) / 1000) : null,
|
|
377
|
+
first_check_s: firstChange !== null && firstCheck !== null ? Math.round((firstCheck - firstChange) / 1000) : null,
|
|
378
|
+
detect_actions: median(detect), detect_s: median(detectSeconds), recovery_calls: median(depth), resume_actions: median(resume),
|
|
379
|
+
};
|
|
380
|
+
const kept = Object.fromEntries(Object.entries(out).filter(([, value]) => value !== null));
|
|
381
|
+
return Object.keys(kept).length > 0 ? kept : null;
|
|
382
|
+
}
|
|
383
|
+
|
|
337
384
|
/** The stretch's whole derived record; `helpers` are the hook's own readers of a human turn, so both read it the same way. */
|
|
338
385
|
export function deriveStretch(messages, helpers) {
|
|
339
386
|
const context = contextOf(messages, helpers), routing = routingOf(messages), steering = steeringOf(messages, helpers);
|
|
340
|
-
const tools = toolsOf(messages), oversight = oversightOf(messages), planning = planningOf(messages), autonomy = autonomyOf(messages), practice = practiceOf(messages, helpers);
|
|
341
|
-
return { ...(practice ?? {}), ...(oversight ? { oversight } : {}), ...(planning ? { planning } : {}), ...(autonomy ? { autonomy } : {}), ...verificationOf(messages, helpers), ...(context ? { context } : {}), ...(routing ? { routing } : {}), ...(steering ? { steering } : {}), ...(tools ? { tools } : {}) };
|
|
387
|
+
const timing = timingOf(messages, helpers), tools = toolsOf(messages), oversight = oversightOf(messages), planning = planningOf(messages), autonomy = autonomyOf(messages), practice = practiceOf(messages, helpers);
|
|
388
|
+
return { ...(practice ?? {}), ...(oversight ? { oversight } : {}), ...(planning ? { planning } : {}), ...(autonomy ? { autonomy } : {}), ...verificationOf(messages, helpers), ...(context ? { context } : {}), ...(routing ? { routing } : {}), ...(steering ? { steering } : {}), ...(tools ? { tools } : {}), ...(timing ? { timing } : {}) };
|
|
342
389
|
}
|
package/worktrust.mjs
CHANGED
|
@@ -73,7 +73,7 @@ const command = args.find((arg, at) => !arg.startsWith("--") && !(at > 0 && VALU
|
|
|
73
73
|
const flag = (name) => { const at = args.indexOf(`--${name}`); return at >= 0 ? args[at + 1] : undefined; };
|
|
74
74
|
const has = (name) => args.includes(`--${name}`);
|
|
75
75
|
/** This CLI's version, said to the door so the app can tell which computer runs an old one (check-cli-package holds it equal to package.json). */
|
|
76
|
-
const CLI_VERSION = "0.
|
|
76
|
+
const CLI_VERSION = "0.9.0";
|
|
77
77
|
const ORIGIN = (flag("origin") ?? process.env.WORKTRUST_ORIGIN ?? "https://app.worktrust.io").replace(/\/$/, "");
|
|
78
78
|
const MCP = flag("url") ?? process.env.WORKTRUST_MCP_URL ?? `${ORIGIN}/api/mcp`;
|
|
79
79
|
const HOME_DIR = join(homedir(), ".worktrust");
|