worktrust 0.9.3 → 0.9.5
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 +2 -0
- package/package.json +1 -1
- package/preserve-lines.mjs +9 -1
- package/stretch-evidence.mjs +45 -4
- package/worktrust.mjs +1 -1
package/README.md
CHANGED
|
@@ -178,6 +178,8 @@ nothing it does not:
|
|
|
178
178
|
| `hygiene` | risk and hygiene: rollbacks, privileged commands (sudo, a forced push), hooks skipped (--no-verify), secrets touched (a .env printed, a private key read), destructive commands and how many a look or a check came before, the answer to a guard (retried, changed, stopped), and whether a check followed delivery; counts only, never the command or the path (0.9.1) |
|
|
179
179
|
| `route` | the route the work took: branches made, pushes straight to the default branch, and of the stretch's own commits those over twenty files, a fix that followed another, those naming an issue, and the changed CI, script and docs files; counts only, never a subject, a branch name or a path (0.9.2) |
|
|
180
180
|
| `adaptation` | evidence and adaptation: what the stretch read by kind (docs, tests, logs, config, source, the web), a plan written again after a failure, a model changed right after a failure, two or more agents whose work a check met, and outages (overloaded, rate-limited, timed out) and how many the work went on past; counts only, never a path or a message (0.9.3) |
|
|
181
|
+
| `reach` | the work's reach and its plan: commands on infrastructure (terraform, kubectl, helm, docker), data (psql, sqlite, bq, dbt) and cloud (aws, gcloud, az, supabase, vercel); plan or roadmap documents written and whether one came before the first code change; reviews approved, sent back or commented and issues opened or closed from the terminal; counts and one flag, never a command, a file name or a path (0.9.4) |
|
|
182
|
+
| `hygiene.secrets` | counts a secret touched only when a file that holds one (a .env, a private key, credentials.json, .aws/credentials) is shown or copied (cat, head, tail, base64, cp); a search for a word (grep, sed, awk) is no read (0.9.5) |
|
|
181
183
|
| `collector_version` | the CLI version that measured it |
|
|
182
184
|
| `seq`, `prev`, `hash` | the line's place, the hash of the line before it, and its own hash |
|
|
183
185
|
|
package/package.json
CHANGED
package/preserve-lines.mjs
CHANGED
|
@@ -134,6 +134,12 @@ export function archiveLine(entry, behaviour = null, { ids = {}, collector, salt
|
|
|
134
134
|
if (Object.keys(sources).length > 0) adaptation.sources = sources;
|
|
135
135
|
if (Object.keys(adaptation).length > 0) line.adaptation = adaptation;
|
|
136
136
|
}
|
|
137
|
+
// 0.9.4: the work's reach and its plan; whole counts, and whether a plan came first.
|
|
138
|
+
if (entry.reach && typeof entry.reach === "object") {
|
|
139
|
+
const reach = Object.fromEntries(REACH_KEYS.filter((key) => whole(entry.reach[key])).map((key) => [key, entry.reach[key]]));
|
|
140
|
+
if (typeof entry.reach.plan_first === "boolean" && reach.plan_docs) reach.plan_first = entry.reach.plan_first;
|
|
141
|
+
if (Object.keys(reach).length > 0) line.reach = reach;
|
|
142
|
+
}
|
|
137
143
|
const risk = pick(entry.risk, ["proposed", "refused", "run"]);
|
|
138
144
|
if (risk && risk.proposed > 0 && risk.refused + risk.run <= risk.proposed) line.risk = risk;
|
|
139
145
|
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; };
|
|
@@ -193,6 +199,8 @@ export function archiveEntries(dir, ownKey) {
|
|
|
193
199
|
/** The furthest step a stretch reached on the computer (0.8.9). */
|
|
194
200
|
const STAGES = ["attempted", "verified", "committed", "pushed", "pr", "deployed"];
|
|
195
201
|
/** How fast a stretch moved (0.9.0). */
|
|
202
|
+
/** The work's reach and its plan (0.9.4). */
|
|
203
|
+
const REACH_KEYS = ["infra", "data", "cloud", "plan_docs", "reviews_approved", "reviews_changes", "reviews_commented", "issues_opened", "issues_closed"];
|
|
196
204
|
/** Evidence read and adaptation (0.9.3). */
|
|
197
205
|
const ADAPTATION_KEYS = ["replans", "escalations", "reconciled", "outages", "outages_continued"];
|
|
198
206
|
const READ_KINDS = ["docs", "tests", "logs", "config", "source", "web"];
|
|
@@ -201,7 +209,7 @@ const ROUTE_KEYS = ["branches", "pushes_to_main", "commits_large", "fix_followup
|
|
|
201
209
|
/** Risk and hygiene (0.9.1). */
|
|
202
210
|
const HYGIENE_KEYS = ["rollbacks", "privileged", "bypasses", "secrets", "destructive", "destructive_checked", "guard_retried", "guard_changed", "guard_stopped"];
|
|
203
211
|
const TIMING_KEYS = ["first_action_s", "first_check_s", "detect_actions", "detect_s", "recovery_calls", "resume_actions"];
|
|
204
|
-
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", "hygiene", "route", "adaptation", "interrupts", "steers", "utc_offset"];
|
|
212
|
+
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", "hygiene", "route", "adaptation", "reach", "interrupts", "steers", "utc_offset"];
|
|
205
213
|
export function supplementFor(line, earlier) {
|
|
206
214
|
const missing = DERIVED_KEYS.filter((key) => line[key] !== undefined && !earlier.some((old) => old[key] !== undefined));
|
|
207
215
|
if (missing.length === 0) return null;
|
package/stretch-evidence.mjs
CHANGED
|
@@ -49,15 +49,31 @@ const MARKS = [
|
|
|
49
49
|
// 0.9.2: the route the work took: a branch of its own, or a push straight to the default branch.
|
|
50
50
|
["branch", /\bgit\s+(checkout\s+-b|switch\s+-c)\s|\bgit\s+branch\s+(?!-)[\w./-]+/],
|
|
51
51
|
["push_main", /\bgit\s+push\b[^\n;&|]*\s(main|master)\b/],
|
|
52
|
+
// 0.9.4: the kind of system a command worked on, and reviews and issues handled from the terminal.
|
|
53
|
+
["infra", /(^|[\s;&|(])(terraform|tofu|pulumi|kubectl|helm|kustomize|ansible(-playbook)?|docker(\s+compose)?|docker-compose|podman)\s/],
|
|
54
|
+
["data", /(^|[\s;&|(])(psql|pg_dump|pg_restore|mysql|sqlite3|duckdb|mongosh|redis-cli|bq|snowsql|dbt|clickhouse(-client)?)\b/],
|
|
55
|
+
["cloud", /(^|[\s;&|(])(aws|gcloud|gsutil|az|vercel|netlify|fly|flyctl|wrangler|supabase|firebase|heroku|doctl)\s/],
|
|
56
|
+
["review_approve", /\bgh\s+pr\s+review\b[^\n;&|]*\s(-a|--approve)\b/],
|
|
57
|
+
["review_changes", /\bgh\s+pr\s+review\b[^\n;&|]*\s(-r|--request-changes)\b/],
|
|
58
|
+
["review_comment", /\bgh\s+pr\s+review\b[^\n;&|]*\s(-c|--comment)\b/],
|
|
59
|
+
["issue_open", /\bgh\s+issue\s+create\b/],
|
|
60
|
+
["issue_close", /\bgh\s+issue\s+close\b/],
|
|
52
61
|
];
|
|
53
|
-
|
|
54
|
-
const
|
|
62
|
+
/** A plan or roadmap the work wrote down (0.9.4), by the file's name; the name never leaves. */
|
|
63
|
+
const PLAN_DOC = /(^|[\\/])(PLAN|ROADMAP|DESIGN|SPEC|RFC|ADR)[\w.-]*\.(md|mdx|txt)$|(^|[\\/])(plans?|roadmaps?|rfcs?|adrs?|specs?)[\\/][^\\/]+\.(md|mdx)$|\.plan\.md$/i;
|
|
64
|
+
const WRITE_TOOLS = new Set(["Write", "Edit", "MultiEdit", "write_file", "edit_file", "create_file"]);
|
|
65
|
+
// 0.9.5: a file that holds a secret by its name, as a path token; "credentials" only as a file (a search for the word is no read).
|
|
66
|
+
const SECRET_FILE = /(^|[\/\s'"])(\.env(\.[\w-]+)?|id_(rsa|ed25519|ecdsa)|[\w.-]+\.(pem|p12|pfx|key)|credentials\.json|\.aws[\/]credentials|\.npmrc|\.netrc|\.pgpass)(['"\s]|$)/;
|
|
67
|
+
// 0.9.5: a command that shows or copies a file's content; a search (grep, sed, awk) names a word and is no read.
|
|
68
|
+
const PRINTS = /(^|[\s;&|(])(cat|less|more|head|tail|bat|type|Get-Content|base64|xxd|strings|cp|scp)\s/;
|
|
55
69
|
const READ_TOOLS = new Set(["Read", "read_file", "view", "open_file"]);
|
|
56
70
|
export const marksOf = (block) => {
|
|
57
71
|
const name = String(block?.name ?? "");
|
|
58
72
|
const command = SHELL_TOOLS.has(name) ? block.input?.command ?? block.input?.CommandLine ?? block.input?.cmd : null;
|
|
59
73
|
const path = READ_TOOLS.has(name) ? block.input?.file_path ?? block.input?.path ?? null : null;
|
|
60
74
|
const marks = typeof command === "string" ? MARKS.filter(([, pattern]) => pattern.test(command)).map(([mark]) => mark) : [];
|
|
75
|
+
const written = WRITE_TOOLS.has(name) ? block.input?.file_path ?? block.input?.path ?? null : null;
|
|
76
|
+
if (typeof written === "string" && PLAN_DOC.test(written)) marks.push("plan_doc");
|
|
61
77
|
if ((typeof path === "string" && SECRET_FILE.test(path)) || (typeof command === "string" && PRINTS.test(command) && SECRET_FILE.test(command))) marks.push("secret");
|
|
62
78
|
return marks;
|
|
63
79
|
};
|
|
@@ -509,9 +525,34 @@ function adaptationOf(messages) {
|
|
|
509
525
|
return Object.keys(kept).length > 0 ? kept : null;
|
|
510
526
|
}
|
|
511
527
|
|
|
528
|
+
/**
|
|
529
|
+
* THE WORK'S REACH AND ITS PLAN (0.9.4): commands on infrastructure, data and cloud; plan or roadmap documents written,
|
|
530
|
+
* and whether one came before the first code change; reviews approved, sent back or commented and issues opened or closed
|
|
531
|
+
* from the terminal. Counts, and one flag.
|
|
532
|
+
*/
|
|
533
|
+
function reachOf(messages) {
|
|
534
|
+
const results = new Map();
|
|
535
|
+
for (const message of messages) for (const result of message.results ?? []) results.set(result.id, result);
|
|
536
|
+
const out = { infra: 0, data: 0, cloud: 0, plan_docs: 0, reviews_approved: 0, reviews_changes: 0, reviews_commented: 0, issues_opened: 0, issues_closed: 0 };
|
|
537
|
+
const KEY = { infra: "infra", data: "data", cloud: "cloud", plan_doc: "plan_docs", review_approve: "reviews_approved", review_changes: "reviews_changes", review_comment: "reviews_commented", issue_open: "issues_opened", issue_close: "issues_closed" };
|
|
538
|
+
let changed = false, planFirst = null;
|
|
539
|
+
for (const message of messages) {
|
|
540
|
+
if (message.bridge) continue;
|
|
541
|
+
for (const call of message.calls ?? []) {
|
|
542
|
+
const tool = call.family.split("|")[0], marks = call.marks ?? [], result = results.get(call.id);
|
|
543
|
+
if (marks.includes("plan_doc") && !changed && planFirst === null) planFirst = true;
|
|
544
|
+
if (EDIT_TOOLS.has(tool) && !marks.includes("plan_doc")) { if (!changed && planFirst === null) planFirst = false; changed = true; }
|
|
545
|
+
for (const mark of marks) if (KEY[mark] && result?.failed !== true) out[KEY[mark]] += 1;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
const kept = Object.fromEntries(Object.entries(out).filter(([, value]) => value > 0));
|
|
549
|
+
if (out.plan_docs > 0) kept.plan_first = planFirst === true;
|
|
550
|
+
return Object.keys(kept).length > 0 ? kept : null;
|
|
551
|
+
}
|
|
552
|
+
|
|
512
553
|
/** The stretch's whole derived record; `helpers` are the hook's own readers of a human turn, so both read it the same way. */
|
|
513
554
|
export function deriveStretch(messages, helpers) {
|
|
514
555
|
const context = contextOf(messages, helpers), routing = routingOf(messages), steering = steeringOf(messages, helpers);
|
|
515
|
-
const timing = timingOf(messages, helpers), hygiene = hygieneOf(messages), route = routeOf(messages), adaptation = adaptationOf(messages), tools = toolsOf(messages), oversight = oversightOf(messages), planning = planningOf(messages), autonomy = autonomyOf(messages), practice = practiceOf(messages, helpers);
|
|
516
|
-
return { ...(practice ?? {}), ...(oversight ? { oversight } : {}), ...(planning ? { planning } : {}), ...(autonomy ? { autonomy } : {}), ...verificationOf(messages, helpers), ...(context ? { context } : {}), ...(routing ? { routing } : {}), ...(steering ? { steering } : {}), ...(tools ? { tools } : {}), ...(timing ? { timing } : {}), ...(hygiene ? { hygiene } : {}), ...(route ? { route } : {}), ...(adaptation ? { adaptation } : {}) };
|
|
556
|
+
const timing = timingOf(messages, helpers), hygiene = hygieneOf(messages), route = routeOf(messages), adaptation = adaptationOf(messages), reach = reachOf(messages), tools = toolsOf(messages), oversight = oversightOf(messages), planning = planningOf(messages), autonomy = autonomyOf(messages), practice = practiceOf(messages, helpers);
|
|
557
|
+
return { ...(practice ?? {}), ...(oversight ? { oversight } : {}), ...(planning ? { planning } : {}), ...(autonomy ? { autonomy } : {}), ...verificationOf(messages, helpers), ...(context ? { context } : {}), ...(routing ? { routing } : {}), ...(steering ? { steering } : {}), ...(tools ? { tools } : {}), ...(timing ? { timing } : {}), ...(hygiene ? { hygiene } : {}), ...(route ? { route } : {}), ...(adaptation ? { adaptation } : {}), ...(reach ? { reach } : {}) };
|
|
517
558
|
}
|
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.9.
|
|
76
|
+
const CLI_VERSION = "0.9.5";
|
|
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");
|