@tech-leads-club/harness-toolkit 0.16.4 → 0.16.6
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/bin/tlc-cli.ts +4 -1
- package/bin/tlc-exec.d.mts +18 -0
- package/bin/tlc-exec.mjs +94 -2
- package/dist/compact-before.mjs +91 -91
- package/dist/doctor.mjs +98 -98
- package/dist/init-project.mjs +85 -85
- package/dist/install-runtime.mjs +82 -82
- package/dist/lessons-cli.mjs +91 -91
- package/dist/obs-cli.mjs +88 -88
- package/dist/prompt-submit.mjs +91 -91
- package/dist/refresh-model-prices.mjs +84 -84
- package/dist/response-after.mjs +91 -91
- package/dist/run.mjs +91 -91
- package/dist/session-end.mjs +87 -87
- package/dist/session-start.mjs +91 -91
- package/dist/shim.mjs +82 -82
- package/dist/ship-gate.mjs +95 -94
- package/dist/stop.mjs +94 -93
- package/dist/subagent-start.mjs +91 -91
- package/dist/subagent-stop.mjs +92 -92
- package/dist/support.mjs +88 -88
- package/dist/tlc-cli.mjs +107 -106
- package/dist/tool-after.mjs +87 -87
- package/dist/tool-before.mjs +106 -105
- package/dist/tool-failure.mjs +91 -91
- package/dist/uninstall-runtime.mjs +4 -4
- package/dist/verify.mjs +150 -0
- package/docs/concepts.md +20 -0
- package/docs/diagnose.md +21 -0
- package/package.json +1 -1
- package/src/core/core.facade.ts +4 -0
- package/src/core/gate/gate.verdicts.ts +95 -0
- package/src/core/index.ts +1 -0
- package/src/entrypoints/ship-gate.ts +68 -79
- package/src/entrypoints/stop.ts +12 -0
- package/src/platform/git.ts +59 -1
- package/tools/verify.ts +145 -0
package/README.md
CHANGED
|
@@ -485,6 +485,7 @@ See `tlc harness help architecture` or [`docs/architecture.md`](docs/architectur
|
|
|
485
485
|
| `tlc harness help [topic]` | Docs |
|
|
486
486
|
| `tlc harness build` | Compile `dist/` for the Node fallback path |
|
|
487
487
|
| `tlc harness test` | Run the full local gate |
|
|
488
|
+
| `tlc harness verify` | Run lint and tests on the current tree and record the verdict that push, PR open and merge read |
|
|
488
489
|
| `tlc harness grind [on\|off]` | Lint/test follow-ups on stop |
|
|
489
490
|
| `tlc harness pause` / `resume` | Circuit breaker: every hook steps aside except the floor / back on |
|
|
490
491
|
| `tlc harness mode solo\|paired\|focus` | Operator posture |
|
package/bin/tlc-cli.ts
CHANGED
|
@@ -881,6 +881,7 @@ QUICK
|
|
|
881
881
|
tlc harness uninstall print what would be undone; --yes applies it, --purge includes state
|
|
882
882
|
tlc harness build compile dist/ for Node
|
|
883
883
|
tlc harness test run the full local gate
|
|
884
|
+
tlc harness verify run lint/tests on this tree; push and PR read the recorded verdict
|
|
884
885
|
tlc harness help <topic> documentation
|
|
885
886
|
|
|
886
887
|
TOPICS
|
|
@@ -1203,6 +1204,8 @@ export function route(args: string[]): Action {
|
|
|
1203
1204
|
return { kind: "version" };
|
|
1204
1205
|
case "test":
|
|
1205
1206
|
return { kind: "test" };
|
|
1207
|
+
case "verify":
|
|
1208
|
+
return { kind: "entry", entry: "verify", args: args.slice(1) };
|
|
1206
1209
|
case "grind":
|
|
1207
1210
|
case "g": {
|
|
1208
1211
|
const arg = (args[1] ?? "on").toLowerCase();
|
|
@@ -1661,7 +1664,7 @@ async function main(argv: string[]): Promise<void> {
|
|
|
1661
1664
|
if (group !== "harness") {
|
|
1662
1665
|
console.error(`unknown: ${argv[0] ?? ""}`);
|
|
1663
1666
|
console.error(
|
|
1664
|
-
"usage: tlc harness <status|doctor|help|grind|pause|resume|reset|mode|obs|prices|lessons|init|update|test|build>",
|
|
1667
|
+
"usage: tlc harness <status|doctor|help|grind|pause|resume|reset|mode|obs|prices|lessons|init|update|test|verify|build>",
|
|
1665
1668
|
);
|
|
1666
1669
|
process.exit(1);
|
|
1667
1670
|
}
|
package/bin/tlc-exec.d.mts
CHANGED
|
@@ -59,3 +59,21 @@ export function decideRuntime(input: RuntimeDecisionInput): RuntimeDecision;
|
|
|
59
59
|
|
|
60
60
|
export function main(argv?: string[]): void;
|
|
61
61
|
export const HOOK_ENTRIES: Set<string>;
|
|
62
|
+
|
|
63
|
+
export const HOOK_TRACE_MAX_BYTES: number;
|
|
64
|
+
|
|
65
|
+
export function hookTracePath(harnessHome: string): string;
|
|
66
|
+
|
|
67
|
+
export function appendHookTrace(harnessHome: string, record: Record<string, unknown>): void;
|
|
68
|
+
|
|
69
|
+
export type HookIdentity =
|
|
70
|
+
| {
|
|
71
|
+
event?: string;
|
|
72
|
+
tool?: string;
|
|
73
|
+
tool_use_id?: string;
|
|
74
|
+
generation_id?: string;
|
|
75
|
+
conversation_id?: string;
|
|
76
|
+
}
|
|
77
|
+
| { parse_error: true };
|
|
78
|
+
|
|
79
|
+
export function hookIdentity(input: Buffer): HookIdentity;
|
package/bin/tlc-exec.mjs
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
appendFileSync,
|
|
5
|
+
existsSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
realpathSync,
|
|
9
|
+
renameSync,
|
|
10
|
+
statSync,
|
|
11
|
+
writeFileSync,
|
|
12
|
+
writeSync,
|
|
13
|
+
} from "node:fs";
|
|
4
14
|
import { homedir } from "node:os";
|
|
5
15
|
import { delimiter, dirname, join } from "node:path";
|
|
6
16
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
@@ -191,6 +201,52 @@ function carryOn() {
|
|
|
191
201
|
process.exit(0);
|
|
192
202
|
}
|
|
193
203
|
|
|
204
|
+
export const HOOK_TRACE_MAX_BYTES = 5 * 1024 * 1024;
|
|
205
|
+
|
|
206
|
+
export function hookTracePath(harnessHome) {
|
|
207
|
+
return join(harnessHome, "state", "hook-trace.jsonl");
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function appendHookTrace(harnessHome, record) {
|
|
211
|
+
if (isPackagedCopy(harnessHome)) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
const path = hookTracePath(harnessHome);
|
|
216
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
217
|
+
try {
|
|
218
|
+
if (statSync(path).size > HOOK_TRACE_MAX_BYTES) {
|
|
219
|
+
renameSync(path, `${path}.1`);
|
|
220
|
+
}
|
|
221
|
+
} catch {}
|
|
222
|
+
appendFileSync(path, `${JSON.stringify({ ts: new Date().toISOString(), pid: process.pid, ...record })}\n`);
|
|
223
|
+
} catch {}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function hookIdentity(input) {
|
|
227
|
+
try {
|
|
228
|
+
const parsed = JSON.parse(input.toString("utf8"));
|
|
229
|
+
const pick = (key) => (typeof parsed?.[key] === "string" ? parsed[key] : undefined);
|
|
230
|
+
return {
|
|
231
|
+
event: pick("hook_event_name"),
|
|
232
|
+
tool: pick("tool_name"),
|
|
233
|
+
tool_use_id: pick("tool_use_id"),
|
|
234
|
+
generation_id: pick("generation_id"),
|
|
235
|
+
conversation_id: pick("conversation_id") ?? pick("session_id"),
|
|
236
|
+
};
|
|
237
|
+
} catch {
|
|
238
|
+
return { parse_error: true };
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function readHookInput() {
|
|
243
|
+
try {
|
|
244
|
+
return { input: readFileSync(0) };
|
|
245
|
+
} catch (error) {
|
|
246
|
+
return { input: null, error: error instanceof Error ? error.message : String(error) };
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
194
250
|
export function decideRuntime({ harnessHome, entry, bunPath, nodeMajor, distExists, srcPath }) {
|
|
195
251
|
const distPath = join(harnessHome, "dist", `${entry}.mjs`);
|
|
196
252
|
if (bunPath && srcPath && distExists) {
|
|
@@ -236,8 +292,24 @@ export function decideRuntime({ harnessHome, entry, bunPath, nodeMajor, distExis
|
|
|
236
292
|
|
|
237
293
|
function run(harnessHome, command, commandArgs, origin = harnessHome, entry = "") {
|
|
238
294
|
const isHook = HOOK_ENTRIES.has(entry);
|
|
295
|
+
const started = Date.now();
|
|
296
|
+
const hookInput = isHook ? readHookInput() : { input: null };
|
|
297
|
+
if (isHook) {
|
|
298
|
+
appendHookTrace(harnessHome, {
|
|
299
|
+
phase: "start",
|
|
300
|
+
entry,
|
|
301
|
+
ppid: process.ppid,
|
|
302
|
+
cwd: process.cwd(),
|
|
303
|
+
node: process.version,
|
|
304
|
+
uptime_ms: Math.round(process.uptime() * 1000),
|
|
305
|
+
stdin_bytes: hookInput.input?.length ?? null,
|
|
306
|
+
stdin_error: hookInput.error,
|
|
307
|
+
...(hookInput.input ? hookIdentity(hookInput.input) : {}),
|
|
308
|
+
});
|
|
309
|
+
}
|
|
239
310
|
const result = spawnSync(command, commandArgs, {
|
|
240
|
-
|
|
311
|
+
...(hookInput.input ? { input: hookInput.input } : {}),
|
|
312
|
+
stdio: isHook ? [hookInput.input ? "pipe" : "inherit", "pipe", "inherit"] : "inherit",
|
|
241
313
|
maxBuffer: 64 * 1024 * 1024,
|
|
242
314
|
// why: `TLC_ORIGIN` is where this copy physically lives, which is not `TLC_HOME` once an npm-installed shim
|
|
243
315
|
// is driving the runtime installed under the conventional path. `tlc harness install` needs the former as
|
|
@@ -254,6 +326,18 @@ function run(harnessHome, command, commandArgs, origin = harnessHome, entry = ""
|
|
|
254
326
|
},
|
|
255
327
|
shell: false,
|
|
256
328
|
});
|
|
329
|
+
if (isHook) {
|
|
330
|
+
appendHookTrace(harnessHome, {
|
|
331
|
+
phase: "end",
|
|
332
|
+
entry,
|
|
333
|
+
ms: Date.now() - started,
|
|
334
|
+
status: result.status,
|
|
335
|
+
signal: result.signal,
|
|
336
|
+
spawn_error: result.error?.message,
|
|
337
|
+
stdout_bytes: result.stdout?.length ?? 0,
|
|
338
|
+
verdict: result.stdout?.toString("utf8").trim().slice(0, 60),
|
|
339
|
+
});
|
|
340
|
+
}
|
|
257
341
|
if (result.error) {
|
|
258
342
|
console.error(`tlc: failed to start ${command}: ${result.error.message}`);
|
|
259
343
|
if (HOOK_ENTRIES.has(entry)) {
|
|
@@ -284,6 +368,14 @@ export function main(argv = process.argv) {
|
|
|
284
368
|
console.error(" entry: session-start | tool-before | stop | doctor | ...");
|
|
285
369
|
process.exit(2);
|
|
286
370
|
}
|
|
371
|
+
if (HOOK_ENTRIES.has(entry)) {
|
|
372
|
+
process.on("uncaughtException", (error) => {
|
|
373
|
+
const detail = error instanceof Error ? (error.stack ?? error.message) : String(error);
|
|
374
|
+
appendHookTrace(harnessHome, { phase: "crash", entry, error: detail.slice(0, 500) });
|
|
375
|
+
console.error(`tlc: ${entry} launcher failed (${detail.split("\n")[0]}); carrying on without a verdict.`);
|
|
376
|
+
carryOn();
|
|
377
|
+
});
|
|
378
|
+
}
|
|
287
379
|
const args = argv.slice(3);
|
|
288
380
|
|
|
289
381
|
const nodeMajor = Number.parseInt(process.versions.node.split(".")[0] ?? "0", 10);
|