@tech-leads-club/harness-toolkit 0.3.1 → 0.3.2

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.
Files changed (41) hide show
  1. package/bin/tlc-build.mjs +35 -6
  2. package/dist/chunks/compact-before-1e4qg1qt.mjs +1185 -0
  3. package/dist/chunks/compact-before-2hpbfxm5.mjs +5782 -0
  4. package/dist/chunks/compact-before-49j320yp.mjs +1283 -0
  5. package/dist/chunks/compact-before-4jrq0sqs.mjs +61 -0
  6. package/dist/chunks/compact-before-6w8n1vh1.mjs +186 -0
  7. package/dist/chunks/compact-before-7sdmwswh.mjs +52 -0
  8. package/dist/chunks/compact-before-beqpmqrm.mjs +187 -0
  9. package/dist/chunks/compact-before-j9y4jgn4.mjs +845 -0
  10. package/dist/chunks/compact-before-pk86tqx2.mjs +118 -0
  11. package/dist/chunks/compact-before-pkqk5v29.mjs +137 -0
  12. package/dist/chunks/compact-before-w1293m4n.mjs +315 -0
  13. package/dist/chunks/compact-before-wnnds45y.mjs +26 -0
  14. package/dist/chunks/compact-before-wt2c3nh4.mjs +551 -0
  15. package/dist/compact-before.mjs +13 -7961
  16. package/dist/doctor.mjs +33 -8480
  17. package/dist/help-topic.mjs +6 -14
  18. package/dist/init-project.mjs +36 -793
  19. package/dist/install-runtime.mjs +17 -7268
  20. package/dist/lessons-cli.mjs +25 -7043
  21. package/dist/obs-cli.mjs +27 -7037
  22. package/dist/price-lookup.mjs +11 -201
  23. package/dist/prompt-submit.mjs +14 -7974
  24. package/dist/refresh-model-prices.mjs +26 -7061
  25. package/dist/response-after.mjs +11 -7961
  26. package/dist/run.mjs +10 -7960
  27. package/dist/session-end.mjs +14 -8029
  28. package/dist/session-start.mjs +20 -8075
  29. package/dist/shim.mjs +18 -7024
  30. package/dist/stop.mjs +29 -8042
  31. package/dist/subagent-start.mjs +13 -7983
  32. package/dist/subagent-stop.mjs +11 -7961
  33. package/dist/support.mjs +21 -7168
  34. package/dist/tlc-cli.mjs +65 -8200
  35. package/dist/tool-after.mjs +20 -8176
  36. package/dist/tool-before.mjs +15 -7990
  37. package/dist/tool-failure.mjs +14 -7961
  38. package/dist/uninstall-runtime.mjs +61 -1008
  39. package/docs/log.md +4 -0
  40. package/package.json +1 -1
  41. package/src/core/release/release.version.ts +10 -4
@@ -0,0 +1,1185 @@
1
+ import {
2
+ JSON_FLAG,
3
+ emitJson,
4
+ takeJsonFlag,
5
+ unknownFlags
6
+ } from "./compact-before-wnnds45y.mjs";
7
+ import {
8
+ coreFacade
9
+ } from "./compact-before-2hpbfxm5.mjs";
10
+ import {
11
+ PLAIN,
12
+ createStyle,
13
+ render
14
+ } from "./compact-before-49j320yp.mjs";
15
+ import {
16
+ __require,
17
+ flagsDir,
18
+ projectConfigPath,
19
+ projectStateDir,
20
+ providerConfigDirs,
21
+ runtimeHome
22
+ } from "./compact-before-4jrq0sqs.mjs";
23
+
24
+ // bin/tlc-cli.ts
25
+ import { spawnSync } from "node:child_process";
26
+ import {
27
+ existsSync as existsSync2,
28
+ lstatSync as lstatSync2,
29
+ mkdirSync as mkdirSync2,
30
+ readdirSync,
31
+ readFileSync,
32
+ realpathSync,
33
+ rmSync as rmSync2,
34
+ writeFileSync
35
+ } from "node:fs";
36
+ import { delimiter, join as join2 } from "node:path";
37
+
38
+ // src/platform/links.ts
39
+ import { copyFileSync, existsSync, lstatSync, mkdirSync, rmSync, symlinkSync } from "node:fs";
40
+ import { dirname, join } from "node:path";
41
+ var LINK_TYPE = "junction";
42
+ function linkDir(source, target) {
43
+ let replaced = false;
44
+ if (isLink(target)) {
45
+ rmSync(target, { recursive: true, force: true });
46
+ replaced = true;
47
+ } else if (existsSync(target)) {
48
+ return {
49
+ kind: "refused",
50
+ target,
51
+ reason: `${target} exists and is not a link — move it aside and re-run`
52
+ };
53
+ }
54
+ mkdirSync(dirname(target), { recursive: true });
55
+ symlinkSync(source, target, LINK_TYPE);
56
+ return { kind: replaced ? "relinked" : "linked", target, source };
57
+ }
58
+ function isLink(path) {
59
+ try {
60
+ return lstatSync(path).isSymbolicLink();
61
+ } catch {
62
+ return false;
63
+ }
64
+ }
65
+ function seedConfig(dest) {
66
+ const path = join(dest, "config.json");
67
+ const example = join(dest, "config.example.json");
68
+ if (existsSync(path) || !existsSync(example)) {
69
+ return { seeded: false, path };
70
+ }
71
+ copyFileSync(example, path);
72
+ return { seeded: true, path };
73
+ }
74
+
75
+ // bin/tlc-cli.ts
76
+ class UsageError extends Error {
77
+ }
78
+ function resolveProjectRoot() {
79
+ return process.env.TLC_PROJECT_DIR ?? process.cwd();
80
+ }
81
+ function modeFilePath(root) {
82
+ return join2(projectStateDir(root), "harness-mode");
83
+ }
84
+ function grindFlagPath(root) {
85
+ return join2(flagsDir(root), "grind-on");
86
+ }
87
+ function skipFlagPath(root) {
88
+ return join2(flagsDir(root), "skip-verify");
89
+ }
90
+ function focusFlagPath(root) {
91
+ return join2(flagsDir(root), "focus");
92
+ }
93
+ function pairedFlagPath(root) {
94
+ return join2(flagsDir(root), "paired");
95
+ }
96
+ function ensureFlagsDir(root) {
97
+ mkdirSync2(flagsDir(root), { recursive: true });
98
+ }
99
+ function readMode(root) {
100
+ return coreFacade.policy.loadPolicy(root).mode;
101
+ }
102
+ function grindOn(root) {
103
+ return coreFacade.policy.loadPolicy(root).grind.enabled;
104
+ }
105
+ function gatesPaused(root) {
106
+ return existsSync2(skipFlagPath(root));
107
+ }
108
+ function acceptedModes() {
109
+ return coreFacade.policy.OPERATOR_MODES.join(" | ");
110
+ }
111
+ function statusScreen(root) {
112
+ const report = statusJson(root);
113
+ const origin = report.modeInvalid === undefined ? `from ${report.modeOrigin}` : `${report.modeOrigin} — \`${report.modeInvalid}\` is not a posture; accepted: ${acceptedModes()}`;
114
+ return {
115
+ title: "harness status",
116
+ summary: [root],
117
+ sections: [
118
+ {
119
+ rows: [
120
+ { label: "mode", value: `${report.mode} [${origin}]`, level: "info" },
121
+ {
122
+ label: "grind",
123
+ value: report.grind ? "ON — stop hook re-runs lint/tests and auto-retries on fail" : "OFF — no auto fix loops",
124
+ level: report.grind ? "ok" : "info"
125
+ },
126
+ {
127
+ label: "gates",
128
+ value: report.gatesPaused ? "PAUSED — stop checks disabled" : "active",
129
+ level: report.gatesPaused ? "warn" : "ok"
130
+ }
131
+ ]
132
+ },
133
+ {
134
+ title: "Postures",
135
+ lines: [
136
+ "paired explains as it goes, and asks before any sizable move",
137
+ "solo works on its own; a destructive action, a dead-end or real ambiguity reaches you",
138
+ "focus only a destructive action or a dead-end reaches you; it settles ambiguity itself"
139
+ ]
140
+ }
141
+ ],
142
+ footer: "verification is identical at all three postures · tlc harness why · tlc harness doctor"
143
+ };
144
+ }
145
+ function statusText(root, style = PLAIN) {
146
+ return render(statusScreen(root), style);
147
+ }
148
+ function statusJson(root) {
149
+ const policy = coreFacade.policy.loadPolicy(root);
150
+ const posture = coreFacade.policy.resolveProjectPosture(root);
151
+ return {
152
+ root,
153
+ mode: posture.mode,
154
+ modeOrigin: posture.origin,
155
+ ...posture.invalid === undefined ? {} : { modeInvalid: posture.invalid },
156
+ grind: policy.grind.enabled,
157
+ gatesPaused: gatesPaused(root)
158
+ };
159
+ }
160
+ function setGrind(root, on) {
161
+ ensureFlagsDir(root);
162
+ const path = grindFlagPath(root);
163
+ if (on) {
164
+ writeFileSync(path, "");
165
+ coreFacade.policy.refreshPolicyBaselines(root);
166
+ return "grind ON — stop hook will lint/test and auto-retry on failure";
167
+ }
168
+ if (existsSync2(path)) {
169
+ rmSync2(path);
170
+ }
171
+ coreFacade.policy.refreshPolicyBaselines(root);
172
+ return "grind OFF — no auto fix loops";
173
+ }
174
+ function setPaused(root, on) {
175
+ ensureFlagsDir(root);
176
+ const path = skipFlagPath(root);
177
+ if (on) {
178
+ writeFileSync(path, "");
179
+ coreFacade.policy.refreshPolicyBaselines(root);
180
+ return "gates PAUSED — stop checks disabled until `tlc harness resume`";
181
+ }
182
+ if (existsSync2(path)) {
183
+ rmSync2(path);
184
+ }
185
+ coreFacade.policy.refreshPolicyBaselines(root);
186
+ return "gates ACTIVE again";
187
+ }
188
+ var MODE_CONFIRMATION = {
189
+ paired: "mode paired — explains as it goes, and asks before any sizable move",
190
+ solo: "mode solo — a destructive action, a dead-end or real ambiguity reaches you",
191
+ focus: "mode focus — only a destructive action or a dead-end reaches you; ambiguity is settled for you"
192
+ };
193
+ function setMode(root, raw) {
194
+ const mode = raw.toLowerCase();
195
+ if (!coreFacade.policy.isOperatorMode(mode)) {
196
+ throw new UsageError(`mode must be: ${acceptedModes()}`);
197
+ }
198
+ ensureFlagsDir(root);
199
+ writeFileSync(modeFilePath(root), `${mode}
200
+ `);
201
+ coreFacade.policy.refreshPolicyBaselines(root);
202
+ return MODE_CONFIRMATION[mode];
203
+ }
204
+ function handoffJson(root) {
205
+ const file = coreFacade.handoff.readHandoffFile(root);
206
+ const providers = {};
207
+ for (const provider of Object.keys(file.by_provider)) {
208
+ providers[provider] = coreFacade.handoff.readHandoff(root, provider);
209
+ }
210
+ return { root, providers };
211
+ }
212
+ function handoffScreen(report) {
213
+ const names = Object.keys(report.providers).sort();
214
+ if (names.length === 0) {
215
+ return {
216
+ title: "handoff",
217
+ summary: [report.root],
218
+ sections: [{ lines: ["nothing recorded yet — this is a fresh start, not a missing file"] }]
219
+ };
220
+ }
221
+ const sections = [];
222
+ for (const name of names) {
223
+ const slice = report.providers[name];
224
+ if (!slice) {
225
+ continue;
226
+ }
227
+ const rows = [];
228
+ for (const [label, value, level] of [
229
+ ["blockers", slice.blockers, "warn"],
230
+ ["next", slice.next_action, "info"],
231
+ ["last gate", slice.last_gate_result, slice.last_gate_result === "pass" ? "ok" : "warn"],
232
+ ["last failure", slice.last_failure_category, "fail"]
233
+ ]) {
234
+ if (value) {
235
+ rows.push({ label, value: String(value), level });
236
+ }
237
+ }
238
+ for (const [label, list] of [
239
+ ["in progress", slice.in_progress],
240
+ ["pending", slice.pending],
241
+ ["gaps", slice.previous_gaps?.map((gap) => gap.summary)]
242
+ ]) {
243
+ if (list && list.length > 0) {
244
+ rows.push({ label, value: list.slice(0, 6).join(" | ") });
245
+ }
246
+ }
247
+ sections.push({ title: `${name} (updated ${slice.updated_at})`, rows });
248
+ }
249
+ return { title: "handoff", summary: [report.root], sections };
250
+ }
251
+ function handoffText(report, style = PLAIN) {
252
+ return render(handoffScreen(report), style);
253
+ }
254
+ function attestScreen(root) {
255
+ const records = coreFacade.attest.readAttestations(root);
256
+ const verdict = coreFacade.attest.verifyChain(records);
257
+ const head = verdict.ok ? { label: "chain", value: `attestation chain OK — ${verdict.length} session(s)`, level: "ok" } : {
258
+ label: "chain",
259
+ value: `attestation chain BROKEN at record ${verdict.brokenAt} (${verdict.reason})`,
260
+ level: "fail"
261
+ };
262
+ if (records.length === 0) {
263
+ return {
264
+ title: "attestation",
265
+ summary: [root],
266
+ sections: [{ rows: [head] }, { lines: ["no sessions recorded yet"] }]
267
+ };
268
+ }
269
+ const sections = [{ rows: [head] }];
270
+ for (const record of records.slice(-10).reverse()) {
271
+ const rules = Object.entries(record.decisionsByRule).map(([rule, count]) => `${rule}=${count}`).join(" ");
272
+ sections.push({
273
+ title: `${record.ts} ${record.provider}/${record.session}`,
274
+ rows: [
275
+ {
276
+ label: "policy",
277
+ value: `${record.policyFingerprint}${record.policyDiverged ? " (DIVERGED mid-session)" : ""}`,
278
+ level: record.policyDiverged ? "warn" : "ok"
279
+ },
280
+ { label: "rails", value: record.railsActive.join(", ") || "none" },
281
+ {
282
+ label: "gates",
283
+ value: `${record.gates.pass} pass / ${record.gates.fail} fail${rules ? ` | ${rules}` : ""}`
284
+ }
285
+ ]
286
+ });
287
+ }
288
+ return {
289
+ title: "attestation",
290
+ summary: [root],
291
+ sections,
292
+ footer: "chained, not signed — it detects a rewritten record and proves nothing about authorship"
293
+ };
294
+ }
295
+ function attestText(root, style = PLAIN) {
296
+ return render(attestScreen(root), style);
297
+ }
298
+ function attestJson(root) {
299
+ const records = coreFacade.attest.readAttestations(root);
300
+ const verdict = coreFacade.attest.verifyChain(records);
301
+ return verdict.ok ? { ok: true, sessions: verdict.length, records } : { ok: false, brokenAt: verdict.brokenAt, reason: verdict.reason, sessions: records.length, records };
302
+ }
303
+ function acceptPolicy(root, paths, interactive) {
304
+ if (!interactive) {
305
+ throw new UsageError("tlc harness policy accept needs an interactive terminal — clearing a policy divergence is the operator's call, not a script's.");
306
+ }
307
+ const requested = paths.includes("--all") ? coreFacade.policy.allDivergedPaths(root) : paths;
308
+ if (paths.includes("--all") && requested.length === 0) {
309
+ return `nothing to accept — no policy source diverged in ${root}`;
310
+ }
311
+ if (requested.length === 0) {
312
+ throw new UsageError([
313
+ "usage: tlc harness policy accept <path> [path...]",
314
+ " tlc harness policy accept --all accept everything `tlc harness policy` lists here"
315
+ ].join(`
316
+ `));
317
+ }
318
+ const blocked = coreFacade.policy.allDivergedPaths(root);
319
+ const notHere = requested.filter((path) => !blocked.includes(path));
320
+ const outcome = coreFacade.policy.acceptPolicySources(root, requested);
321
+ if (outcome.kind === "not-a-source") {
322
+ throw new UsageError([
323
+ `not a policy source: ${outcome.paths.join(", ")}`,
324
+ "The sources the loader reads are:",
325
+ ...outcome.sources.map((source) => ` ${source}`)
326
+ ].join(`
327
+ `));
328
+ }
329
+ if (outcome.kind === "nothing-to-accept") {
330
+ return [
331
+ `nothing to accept — ${root} has no recorded session baseline.`,
332
+ "Acceptance is written per project. Run this from the repository whose session is blocked:",
333
+ ` cd <that repo> && tlc harness policy accept ${requested.join(" ")}`
334
+ ].join(`
335
+ `);
336
+ }
337
+ const lines = [
338
+ `accepted: ${outcome.paths.join(", ")}`,
339
+ ` for sessions in ${root} — acceptance is per project, not machine-wide`
340
+ ];
341
+ if (notHere.length > 0) {
342
+ lines.push(` note: ${notHere.join(", ")} was not diverging here. If a session elsewhere is blocked, run this in that repository too.`);
343
+ }
344
+ return lines.join(`
345
+ `);
346
+ }
347
+ function policyScreen(root) {
348
+ const diverged = coreFacade.policy.allDivergedPaths(root);
349
+ if (diverged.length === 0) {
350
+ return {
351
+ title: "policy baseline",
352
+ sections: [
353
+ {
354
+ rows: [
355
+ {
356
+ label: "baseline",
357
+ value: "matches — nothing changed out of band during any live session",
358
+ level: "ok"
359
+ }
360
+ ]
361
+ }
362
+ ]
363
+ };
364
+ }
365
+ return {
366
+ title: "policy baseline",
367
+ summary: [`policy changed out of band during a live session (${diverged.length})`],
368
+ sections: [
369
+ { rows: diverged.map((path) => ({ label: "changed", value: path, level: "warn" })) },
370
+ {
371
+ title: "If that was you, accept it from your own terminal with",
372
+ lines: [`tlc harness policy accept ${diverged.join(" ")}`, "", "or: tlc harness policy accept --all"]
373
+ }
374
+ ],
375
+ footer: "accepting is per path, so anything you leave out keeps blocking"
376
+ };
377
+ }
378
+ function policyText(root, style = PLAIN) {
379
+ return render(policyScreen(root), style);
380
+ }
381
+ function policyJson(root) {
382
+ const diverged = coreFacade.policy.allDivergedPaths(root);
383
+ return { diverged, ok: diverged.length === 0 };
384
+ }
385
+ function upstreamRef(dest) {
386
+ const read = (args) => {
387
+ const r = spawnSync("git", ["-C", dest, ...args], { encoding: "utf8", env: process.env });
388
+ return (r.status ?? 1) === 0 ? (r.stdout ?? "").trim() : "";
389
+ };
390
+ const tracked = read(["rev-parse", "--abbrev-ref", "@{u}"]);
391
+ if (tracked !== "") {
392
+ return tracked;
393
+ }
394
+ return `origin/${read(["rev-parse", "--abbrev-ref", "HEAD"]) || "main"}`;
395
+ }
396
+ var NPM_PACKAGE = "@tech-leads-club/harness-toolkit";
397
+ var NPM_MARKER = "installed-from-npm";
398
+ function classifyRuntimePath(dest, probe) {
399
+ if (probe.isSymlink(dest)) {
400
+ return "linked";
401
+ }
402
+ if (!probe.exists(dest)) {
403
+ return "absent";
404
+ }
405
+ if (probe.exists(join2(dest, ".git"))) {
406
+ return "managed";
407
+ }
408
+ return probe.exists(join2(dest, NPM_MARKER)) ? "npm" : "unmanaged";
409
+ }
410
+ function runtimePathKind(dest) {
411
+ return classifyRuntimePath(dest, {
412
+ isSymlink: (path) => {
413
+ try {
414
+ return lstatSync2(path).isSymbolicLink();
415
+ } catch {
416
+ return false;
417
+ }
418
+ },
419
+ exists: existsSync2
420
+ });
421
+ }
422
+ function missingBundles(dest) {
423
+ const entrypoints = join2(dest, "src", "entrypoints");
424
+ if (!existsSync2(entrypoints)) {
425
+ return [];
426
+ }
427
+ const expected = readdirSync(entrypoints).filter((name) => name.endsWith(".ts") && !name.endsWith(".test.ts")).map((name) => `${name.slice(0, -3)}.mjs`);
428
+ return expected.filter((bundle) => !existsSync2(join2(dest, "dist", bundle)));
429
+ }
430
+ function linkedRuntimeMessage(dest, target) {
431
+ return [
432
+ `update: ${dest} is a link to a working clone${target ? ` → ${target}` : ""}.`,
433
+ "Nothing in it is touched by this command — updating that clone is your own `git pull`.",
434
+ "Refreshing the machine-local parts only: CLI link, init skill, provider hooks."
435
+ ].join(`
436
+ `);
437
+ }
438
+ function fetchFailureMessage(dest) {
439
+ return [
440
+ `update: git fetch failed in ${dest}.`,
441
+ ` The published package needs no clone: npm i -g ${NPM_PACKAGE}@latest, then \`tlc harness install\`.`,
442
+ " If this runtime predates the move to tech-leads-club/harness-toolkit, it is still pointing at the old",
443
+ " repository, and that install is what the package replaces.",
444
+ " For a private fork, this needs a GitHub credential: `gh auth login`, then `gh auth setup-git`."
445
+ ].join(`
446
+ `);
447
+ }
448
+ function unmanagedRuntimeMessage(dest) {
449
+ return [
450
+ `update: ${dest} is not a git checkout, so there is nothing to pull.`,
451
+ `Install the package to get a runtime update can move: npm i -g ${NPM_PACKAGE}@latest, then \`tlc harness install\`.`
452
+ ].join(`
453
+ `);
454
+ }
455
+ function npmUpdateFailureMessage() {
456
+ return [
457
+ `update: npm could not install ${NPM_PACKAGE}@latest.`,
458
+ " permissions — a global prefix owned by root needs sudo, or an npm prefix you own:",
459
+ " npm config set prefix ~/.local",
460
+ " not found — the package is published; check the network and any registry proxy in ~/.npmrc",
461
+ " offline — nothing was changed; the runtime you have still works."
462
+ ].join(`
463
+ `);
464
+ }
465
+ function resetFailureMessage(dest, mergeRef, gitOutput) {
466
+ return [
467
+ `update: could not move the runtime to ${mergeRef}.`,
468
+ ` path: ${dest} (managed checkout)`,
469
+ gitOutput.trim() ? ` git: ${gitOutput.trim().split(`
470
+ `).slice(-3).join(" / ")}` : "",
471
+ `Nothing was changed. If this persists, install the package instead: npm i -g ${NPM_PACKAGE}@latest, then \`tlc harness install\`.`
472
+ ].filter(Boolean).join(`
473
+ `);
474
+ }
475
+ function runtimeRevision(dest) {
476
+ if (!existsSync2(join2(dest, ".git"))) {
477
+ return { revision: null, date: null };
478
+ }
479
+ const read = (args) => {
480
+ const r = spawnSync("git", ["-C", dest, ...args], { encoding: "utf8", env: process.env });
481
+ const out = (r.stdout ?? "").trim();
482
+ return (r.status ?? 1) === 0 && out !== "" ? out : null;
483
+ };
484
+ return { revision: read(["rev-parse", "--short", "HEAD"]), date: read(["log", "-1", "--format=%cs"]) };
485
+ }
486
+ function versionJson(root) {
487
+ const dest = resolveHarnessRoot();
488
+ const { revision, date } = runtimeRevision(dest);
489
+ return {
490
+ runtime: dest,
491
+ revision,
492
+ date,
493
+ seenRevision: coreFacade.release.readReleaseSeen(root)?.revision ?? null
494
+ };
495
+ }
496
+ function versionScreen(root) {
497
+ const report = versionJson(root);
498
+ const rows = report.revision === null ? [
499
+ { label: "runtime", value: report.runtime },
500
+ {
501
+ label: "revision",
502
+ value: "unknown — the runtime path is not a git checkout, so `update` cannot pull either",
503
+ level: "warn"
504
+ }
505
+ ] : [
506
+ { label: "runtime", value: report.runtime },
507
+ { label: "revision", value: `${report.revision} (${report.date ?? "date unknown"})`, level: "ok" },
508
+ {
509
+ label: "project last saw",
510
+ value: report.seenRevision ?? "nothing yet — the next update will announce what landed"
511
+ }
512
+ ];
513
+ return { title: "harness version", sections: [{ rows }] };
514
+ }
515
+ function versionText(root, style = PLAIN) {
516
+ return render(versionScreen(root), style);
517
+ }
518
+ function pendingUpdate(dest, mergeRef) {
519
+ if (!existsSync2(join2(dest, ".git"))) {
520
+ return { ok: false, reason: "the runtime path is not a git checkout", commits: 0, decisions: [] };
521
+ }
522
+ const fetch = spawnSync("git", ["-C", dest, "fetch", "origin"], { stdio: "inherit", env: process.env });
523
+ if ((fetch.status ?? 1) !== 0) {
524
+ return { ok: false, reason: "git fetch failed", commits: 0, decisions: [] };
525
+ }
526
+ const count = spawnSync("git", ["-C", dest, "rev-list", "--count", `HEAD..${mergeRef}`], {
527
+ encoding: "utf8",
528
+ env: process.env
529
+ });
530
+ const commits = Number.parseInt((count.stdout ?? "0").trim(), 10) || 0;
531
+ const added = spawnSync("git", ["-C", dest, "diff", "--name-only", "--diff-filter=A", `HEAD..${mergeRef}`, "--", "docs/decisions"], { encoding: "utf8", env: process.env });
532
+ const files = (added.stdout ?? "").split(`
533
+ `).map((line) => line.trim().split("/").pop() ?? "").filter(Boolean);
534
+ return { ok: true, commits, decisions: coreFacade.release.readDecisions(dest, files) };
535
+ }
536
+ function pendingScreen(report) {
537
+ if (!report.ok) {
538
+ return {
539
+ title: "update --check",
540
+ sections: [
541
+ {
542
+ rows: [{ label: "status", value: `${report.reason} — nothing to compare against`, level: "warn" }]
543
+ }
544
+ ]
545
+ };
546
+ }
547
+ if (report.commits === 0) {
548
+ return {
549
+ title: "update --check",
550
+ sections: [
551
+ { rows: [{ label: "status", value: "the runtime is current — nothing to pull", level: "ok" }] }
552
+ ]
553
+ };
554
+ }
555
+ const digest = coreFacade.release.formatDecisionDigest(report.decisions);
556
+ return {
557
+ title: "update --check",
558
+ summary: [`${report.commits} commit(s) would be pulled`, "Nothing has changed yet."],
559
+ sections: [{ lines: digest === "" ? ["no decisions landed in that range"] : digest.split(`
560
+ `) }]
561
+ };
562
+ }
563
+ function pendingText(report, style = PLAIN) {
564
+ return render(pendingScreen(report), style);
565
+ }
566
+ var GATE_FIELDS = {
567
+ "test-command": "test",
568
+ "lint-command": "lint"
569
+ };
570
+ var EXECUTABLE_EXTENSIONS = ["", ".exe", ".cmd", ".bat", ".ps1"];
571
+ function resolveExecutable(name, env = process.env) {
572
+ const candidates = (base) => EXECUTABLE_EXTENSIONS.map((ext) => `${base}${ext}`);
573
+ if (name.includes("/") || name.includes("\\")) {
574
+ return candidates(name).find((candidate) => existsSync2(candidate)) ?? null;
575
+ }
576
+ for (const dir of (env.PATH ?? "").split(delimiter)) {
577
+ if (!dir) {
578
+ continue;
579
+ }
580
+ const found = candidates(join2(dir, name)).find((candidate) => existsSync2(candidate));
581
+ if (found) {
582
+ return found;
583
+ }
584
+ }
585
+ return null;
586
+ }
587
+ function setGateCommand(root, field, argv, interactive) {
588
+ if (argv.length === 0) {
589
+ throw new UsageError(`usage: tlc harness gate ${field}-command <command> [args...]`);
590
+ }
591
+ if (!interactive) {
592
+ throw new UsageError(`tlc harness gate ${field}-command needs an interactive terminal — harness policy is the operator's to set, not a script's.`);
593
+ }
594
+ const binary = argv[0];
595
+ if (resolveExecutable(binary) === null) {
596
+ throw new UsageError(`\`${binary}\` was not found on PATH, and a gate command that cannot run is a config fault ([/decisions/ad-021.md](/decisions/ad-021.md)).`);
597
+ }
598
+ const path = projectConfigPath(root);
599
+ const parsed = existsSync2(path) ? JSON.parse(readFileSync(path, "utf8")) : {};
600
+ const grind = { ...parsed.grind ?? {} };
601
+ grind[field === "test" ? "testCommand" : "lintCommand"] = argv;
602
+ parsed.grind = grind;
603
+ mkdirSync2(join2(root, ".tlc", "harness"), { recursive: true });
604
+ writeFileSync(path, `${JSON.stringify(parsed, null, 2)}
605
+ `, "utf8");
606
+ coreFacade.policy.refreshPolicyBaselines(root);
607
+ return `grind.${field}Command = ${JSON.stringify(argv)}`;
608
+ }
609
+ function helpScreen() {
610
+ return {
611
+ title: "tlc harness",
612
+ sections: [
613
+ {
614
+ lines: `Requires Node.js 24+ (Active LTS 24 or Current 26).
615
+
616
+ Read commands accept --json: status, doctor, obs, lessons, prices lookup, attest, policy.
617
+
618
+ QUICK
619
+ tlc harness status mode / grind / gates
620
+ tlc harness version runtime revision, and what this project last saw
621
+ tlc harness update --check what an update would pull, without pulling it
622
+ tlc harness update pull runtime + refresh skill/CLI, then doctor
623
+ tlc harness doctor health checklist
624
+ tlc harness why [n] the last n decisions this tool made, with the rule behind each
625
+ tlc harness install put the runtime in place from the installed npm package
626
+ tlc harness uninstall print what would be undone; --yes applies it, --purge includes state
627
+ tlc harness build compile dist/ for Node
628
+ tlc harness test run the full local gate
629
+ tlc harness help <topic> documentation
630
+
631
+ TOPICS
632
+ architecture | concepts | lessons | measure | prices | diagnose | init
633
+
634
+ CONTROL
635
+ tlc harness grind [on|off] tlc harness pause | resume tlc harness mode solo|paired|focus
636
+ tlc harness gate test-command <cmd> [args...] tlc harness gate lint-command <cmd> [args...]
637
+ tlc harness attest tamper-evident record of what each session ran under
638
+ tlc harness policy show a policy that changed out of band; accept <path> to clear it
639
+
640
+ MEASURE
641
+ tlc harness obs live|events|report|prune
642
+ tlc harness prices refresh [all|cursor|litellm]
643
+ tlc harness prices lookup <model-id>
644
+ tlc harness lessons list|show|garden|sync-rules
645
+
646
+ PROJECT
647
+ tlc harness init --minimal | tlc harness init --write --stdin-json`.split(`
648
+ `)
649
+ }
650
+ ],
651
+ footer: "tlc harness help <topic> for a document · tlc harness why to see what it decided"
652
+ };
653
+ }
654
+ function helpText(style = PLAIN) {
655
+ return render(helpScreen(), style);
656
+ }
657
+ function pricesHelpScreen() {
658
+ return {
659
+ title: "prices",
660
+ sections: [
661
+ {
662
+ lines: ` tlc harness prices refresh [all|cursor|litellm] [--if-stale]
663
+ tlc harness prices lookup <model-id>
664
+
665
+ refresh / refresh all both planes of model-prices.json
666
+ refresh cursor the provider's own rates
667
+ refresh litellm the vendors' list prices
668
+ --if-stale fetch only past the 7-day TTL
669
+ lookup <model-id> catalog key, pool, USD for 1M in + 1M out
670
+
671
+ Catalogue: <runtime home>/model-prices.json — fetched per machine, never versioned
672
+ Overrides: <runtime home>/model-prices.local.json — yours, hand-written
673
+ Documentation: tlc harness help prices`.split(`
674
+ `)
675
+ }
676
+ ],
677
+ footer: "resolution: your overrides → the asking provider's plane → the vendor plane → null"
678
+ };
679
+ }
680
+ function pricesHelpText(style = PLAIN) {
681
+ return render(pricesHelpScreen(), style);
682
+ }
683
+ function resolveHarnessRoot() {
684
+ const home = runtimeHome();
685
+ try {
686
+ return realpathSync(home);
687
+ } catch {
688
+ return home;
689
+ }
690
+ }
691
+ function wireRuntime(dest, home) {
692
+ const lines = [];
693
+ const seeded = seedConfig(dest);
694
+ if (seeded.seeded) {
695
+ lines.push(`config seeded → ${seeded.path}`);
696
+ }
697
+ if (!existsSync2(join2(dest, "skills", "harness-init"))) {
698
+ return { lines, missingSkill: true };
699
+ }
700
+ const links = coreFacade.skill.skillLinks(dest, providerConfigDirs(), existsSync2);
701
+ if (links.length === 0) {
702
+ lines.push("no provider config dir found — skill not linked");
703
+ }
704
+ for (const link of links) {
705
+ const outcome = linkDir(link.source, link.target);
706
+ lines.push(outcome.kind === "refused" ? `skill not linked — ${outcome.reason}` : `skill → ${outcome.target}`);
707
+ }
708
+ const hooks = spawnSync(process.execPath, [join2(dest, "bin", "write-user-hooks.mjs")], {
709
+ stdio: "inherit",
710
+ env: { ...process.env, TLC_HOME: home }
711
+ });
712
+ if ((hooks.status ?? 1) !== 0) {
713
+ lines.push("hooks unchanged (merge manually or: node bin/write-user-hooks.mjs --force)");
714
+ }
715
+ return { lines, missingSkill: false };
716
+ }
717
+ function execBinPath() {
718
+ return join2(resolveHarnessRoot(), "bin", "tlc-exec.mjs");
719
+ }
720
+ function buildBinPath() {
721
+ return join2(resolveHarnessRoot(), "bin", "tlc-build.mjs");
722
+ }
723
+ function route(args) {
724
+ const cmd = (args[0] ?? "status").toLowerCase();
725
+ switch (cmd) {
726
+ case "status":
727
+ case "st":
728
+ case "s":
729
+ return { kind: "status" };
730
+ case "build":
731
+ case "rebuild":
732
+ return { kind: "build" };
733
+ case "update":
734
+ case "upgrade": {
735
+ const flags = args.slice(1);
736
+ if (flags.includes("--check")) {
737
+ return { kind: "update-check" };
738
+ }
739
+ const leftover = unknownFlags(flags);
740
+ if (leftover.length > 0) {
741
+ throw new UsageError(leftover[0] === "--force" ? `update takes no --force: a managed runtime is already reset to upstream, and a linked clone is never written to. If update cannot move it, install the package instead: npm i -g ${NPM_PACKAGE}@latest.` : `unknown flag: ${leftover[0]}
742
+ usage: tlc harness update [--check]`);
743
+ }
744
+ return { kind: "update" };
745
+ }
746
+ case "version":
747
+ case "--version":
748
+ return { kind: "version" };
749
+ case "test":
750
+ return { kind: "test" };
751
+ case "grind":
752
+ case "g": {
753
+ const arg = (args[1] ?? "on").toLowerCase();
754
+ if (arg === "on" || arg === "1" || arg === "true") {
755
+ return { kind: "grind", on: true };
756
+ }
757
+ if (arg === "off" || arg === "0" || arg === "false") {
758
+ return { kind: "grind", on: false };
759
+ }
760
+ throw new UsageError("usage: tlc harness grind [on|off]");
761
+ }
762
+ case "pause":
763
+ case "p":
764
+ return { kind: "pause" };
765
+ case "resume":
766
+ case "r":
767
+ return { kind: "resume" };
768
+ case "mode":
769
+ case "m": {
770
+ const modeArg = args[1];
771
+ if (!modeArg) {
772
+ throw new UsageError("usage: tlc harness mode <solo|paired|focus>");
773
+ }
774
+ return { kind: "mode", value: modeArg };
775
+ }
776
+ case "attest":
777
+ return { kind: "attest" };
778
+ case "handoff":
779
+ return { kind: "handoff" };
780
+ case "policy": {
781
+ const sub = (args[1] ?? "").toLowerCase();
782
+ if (!sub) {
783
+ return { kind: "policy", accept: [] };
784
+ }
785
+ if (sub !== "accept") {
786
+ throw new UsageError("usage: tlc harness policy [accept <path> [path...]]");
787
+ }
788
+ return { kind: "policy", accept: args.slice(2) };
789
+ }
790
+ case "gate": {
791
+ const field = GATE_FIELDS[(args[1] ?? "").toLowerCase()];
792
+ if (!field) {
793
+ throw new UsageError("usage: tlc harness gate <test-command|lint-command> <command> [args...]");
794
+ }
795
+ return { kind: "gate", field, argv: args.slice(2) };
796
+ }
797
+ case "prices": {
798
+ const sub = (args[1] ?? "").toLowerCase();
799
+ if (!sub || sub === "help" || sub === "-h" || sub === "--help") {
800
+ return { kind: "prices-help" };
801
+ }
802
+ if (sub === "refresh") {
803
+ return { kind: "prices-refresh", scope: args[2] ?? "all" };
804
+ }
805
+ if (sub === "lookup" || sub === "get") {
806
+ const modelId = args[2];
807
+ if (!modelId) {
808
+ throw new UsageError(`usage: tlc harness prices lookup <model-id>
809
+ detail: tlc harness help prices`);
810
+ }
811
+ return { kind: "prices-lookup", modelId };
812
+ }
813
+ throw new UsageError(`usage: tlc harness prices refresh [all|cursor|litellm] | tlc harness prices lookup <model>
814
+ detail: tlc harness help prices`);
815
+ }
816
+ case "obs":
817
+ case "o":
818
+ return { kind: "entry", entry: "obs-cli", args: args.slice(1) };
819
+ case "doctor":
820
+ case "doc":
821
+ return { kind: "entry", entry: "doctor", args: args.slice(1) };
822
+ case "lessons":
823
+ case "lesson":
824
+ return { kind: "entry", entry: "lessons-cli", args: args.slice(1) };
825
+ case "init":
826
+ return { kind: "entry", entry: "init-project", args: args.slice(1) };
827
+ case "install":
828
+ return { kind: "entry", entry: "install-runtime", args: args.slice(1) };
829
+ case "uninstall":
830
+ return { kind: "entry", entry: "uninstall-runtime", args: args.slice(1) };
831
+ case "why":
832
+ return { kind: "entry", entry: "obs-cli", args: ["why", ...args.slice(1)] };
833
+ case "help":
834
+ case "-h":
835
+ case "--help": {
836
+ const topic = args[1];
837
+ if (!topic) {
838
+ return { kind: "help" };
839
+ }
840
+ return { kind: "entry", entry: "help-topic", args: [topic] };
841
+ }
842
+ default:
843
+ return { kind: "unknown", cmd };
844
+ }
845
+ }
846
+ var TEST_ENV_IMPORT = ["--import", "./tools/test-env.mjs"];
847
+ function buildTestSteps() {
848
+ return [
849
+ { label: "biome check", bin: "npx", args: ["biome", "check", "--error-on-warnings"] },
850
+ { label: "tsc --noEmit", bin: "npx", args: ["tsc", "--noEmit"] },
851
+ { label: "src suite", bin: "node", args: [...TEST_ENV_IMPORT, "--test", "src/**/__test__/*.test.ts"] },
852
+ { label: "tools suite", bin: "node", args: [...TEST_ENV_IMPORT, "--test", "tools/__test__/*.test.ts"] },
853
+ { label: "check-boundaries", bin: "node", args: ["tools/dev/check-boundaries.ts"] },
854
+ { label: "check-suppressions", bin: "node", args: ["tools/dev/check-suppressions.ts"] },
855
+ { label: "check-wiring", bin: "node", args: ["tools/dev/check-wiring.ts"] },
856
+ { label: "check-docs-bundle", bin: "node", args: ["tools/dev/check-docs-bundle.ts"] },
857
+ { label: "check-decisions", bin: "node", args: ["tools/dev/check-decisions.ts"] },
858
+ { label: "check-screens", bin: "node", args: ["tools/dev/check-screens.ts"] },
859
+ { label: "check-obs-contract", bin: "node", args: ["tools/dev/check-obs-contract.ts"] },
860
+ { label: "check-manifest", bin: "node", args: ["tools/dev/check-manifest.ts"] },
861
+ { label: "capabilities in sync", bin: "node", args: ["tools/dev/render-capabilities.ts", "--check"] },
862
+ { label: "changelog in sync", bin: "node", args: ["tools/dev/render-changelog.ts", "--check"] },
863
+ { label: "log in sync", bin: "node", args: ["tools/dev/render-log.ts", "--check"] },
864
+ { label: "coverage in sync", bin: "node", args: ["tools/dev/render-coverage.ts", "--check"] }
865
+ ];
866
+ }
867
+ function runTestSteps(steps, cwd, spawner = (bin, spawnArgs, spawnCwd) => spawnSync(bin, spawnArgs, { cwd: spawnCwd, stdio: "inherit" })) {
868
+ for (const step of steps) {
869
+ console.log(`tlc harness test: running ${step.label}`);
870
+ const result = spawner(step.bin, step.args, cwd);
871
+ const status = result.status ?? 1;
872
+ if (status !== 0) {
873
+ console.error(`tlc harness test: FAILED at "${step.label}" (exit ${status})`);
874
+ return status;
875
+ }
876
+ }
877
+ console.log("tlc harness test: all steps passed");
878
+ return 0;
879
+ }
880
+ function announceNewCapabilities(root, runtimeRoot) {
881
+ const catalog = coreFacade.capability.loadCatalog(runtimeRoot);
882
+ const policy = coreFacade.capability.readProjectPolicyRaw(root);
883
+ if (!catalog || !policy) {
884
+ return;
885
+ }
886
+ const seen = coreFacade.capability.readRuntimeSeen(root);
887
+ const fresh = coreFacade.capability.listNewlyAnnounceable(policy, catalog, seen.catalogVersion);
888
+ if (fresh.length === 0) {
889
+ return;
890
+ }
891
+ console.log("");
892
+ console.log(coreFacade.capability.formatCapabilityDigest(fresh));
893
+ console.log("");
894
+ coreFacade.capability.writeRuntimeSeen(root, catalog.catalogVersion);
895
+ }
896
+ function announceLandedDecisions(root, dest, before) {
897
+ const now = runtimeRevision(dest).revision;
898
+ if (now === null) {
899
+ return;
900
+ }
901
+ const seen = coreFacade.release.readReleaseSeen(root)?.revision ?? before;
902
+ if (seen === null || seen === now) {
903
+ coreFacade.release.writeReleaseSeen(root, now);
904
+ return;
905
+ }
906
+ const added = spawnSync("git", ["-C", dest, "diff", "--name-only", "--diff-filter=A", `${seen}..${now}`, "--", "docs/decisions"], { encoding: "utf8", env: process.env });
907
+ if ((added.status ?? 1) !== 0) {
908
+ console.log(`update: cannot list what landed since ${seen} — that revision is no longer in the checkout`);
909
+ coreFacade.release.writeReleaseSeen(root, now);
910
+ return;
911
+ }
912
+ const files = (added.stdout ?? "").split(`
913
+ `).map((line) => line.trim().split("/").pop() ?? "").filter(Boolean);
914
+ const digest = coreFacade.release.formatDecisionDigest(coreFacade.release.readDecisions(dest, files));
915
+ if (digest !== "") {
916
+ console.log("");
917
+ console.log(digest);
918
+ console.log("");
919
+ }
920
+ coreFacade.release.writeReleaseSeen(root, now);
921
+ }
922
+ function runUpdate(root) {
923
+ const dest = resolveHarnessRoot();
924
+ const revisionBefore = runtimeRevision(dest).revision;
925
+ const home = runtimeHome();
926
+ console.log(`update: runtime → ${dest}`);
927
+ if (!existsSync2(join2(dest, "bin", "tlc-exec.mjs"))) {
928
+ console.error(`update: missing install at ${home}`);
929
+ console.error(`update: install once with \`npm i -g ${NPM_PACKAGE}\`, then \`tlc harness install\`, then retry.`);
930
+ process.exit(1);
931
+ }
932
+ const kind = runtimePathKind(home);
933
+ if (kind === "linked") {
934
+ console.log(linkedRuntimeMessage(home, dest === home ? null : dest));
935
+ } else if (kind === "npm") {
936
+ const bump = spawnSync("npm", ["install", "-g", `${NPM_PACKAGE}@latest`], {
937
+ stdio: "inherit",
938
+ env: process.env,
939
+ shell: true
940
+ });
941
+ if ((bump.status ?? 1) !== 0) {
942
+ console.error(npmUpdateFailureMessage());
943
+ process.exit(bump.status ?? 1);
944
+ }
945
+ const sync = spawnSync(process.execPath, [execBinPath(), "install-runtime"], {
946
+ stdio: "inherit",
947
+ env: process.env
948
+ });
949
+ if ((sync.status ?? 1) !== 0) {
950
+ process.exit(sync.status ?? 1);
951
+ }
952
+ } else if (kind === "unmanaged") {
953
+ console.log(unmanagedRuntimeMessage(dest));
954
+ } else {
955
+ const fetch = spawnSync("git", ["-C", dest, "fetch", "origin"], {
956
+ stdio: "inherit",
957
+ env: process.env
958
+ });
959
+ if ((fetch.status ?? 1) !== 0) {
960
+ console.error(fetchFailureMessage(dest));
961
+ process.exit(fetch.status ?? 1);
962
+ }
963
+ const mergeRef = upstreamRef(dest);
964
+ const reset = spawnSync("git", ["-C", dest, "reset", "--hard", mergeRef], {
965
+ encoding: "utf8",
966
+ env: process.env
967
+ });
968
+ if ((reset.status ?? 1) !== 0) {
969
+ console.error(resetFailureMessage(dest, mergeRef, `${reset.stderr ?? ""}${reset.stdout ?? ""}`));
970
+ process.exit(reset.status ?? 1);
971
+ }
972
+ const after = runtimeRevision(dest).revision;
973
+ console.log(revisionBefore === after ? `update: runtime already at ${after ?? "unknown"} — nothing to move` : `update: runtime ${revisionBefore ?? "unknown"} → ${after ?? "unknown"}`);
974
+ }
975
+ const wired = wireRuntime(dest, home);
976
+ for (const line of wired.lines) {
977
+ console.log(`update: ${line}`);
978
+ }
979
+ if (wired.missingSkill) {
980
+ console.error(`update: missing skill at ${join2(dest, "skills", "harness-init")}`);
981
+ process.exit(1);
982
+ }
983
+ const missing = missingBundles(dest);
984
+ if (missing.length === 0) {
985
+ console.log("update: dist/ complete — no rebuild, so the runtime path stays clean");
986
+ } else if (existsSync2(buildBinPath())) {
987
+ console.log(`update: ${missing.length} bundle(s) missing — building`);
988
+ const build = spawnSync(process.execPath, [buildBinPath()], { stdio: "inherit", env: process.env });
989
+ if ((build.status ?? 1) !== 0) {
990
+ console.log(`update: build failed — ${missing.length} bundle(s) still missing from dist/`);
991
+ }
992
+ }
993
+ announceNewCapabilities(root, dest);
994
+ announceLandedDecisions(root, dest, revisionBefore);
995
+ spawnSync(process.execPath, [execBinPath(), "refresh-model-prices", "all", "--if-stale"], {
996
+ stdio: "inherit",
997
+ env: { ...process.env, TLC_PROJECT_DIR: root }
998
+ });
999
+ console.log("update: running doctor…");
1000
+ const doctor = spawnSync(process.execPath, [execBinPath(), "doctor"], {
1001
+ stdio: "inherit",
1002
+ env: { ...process.env, TLC_PROJECT_DIR: root }
1003
+ });
1004
+ console.log("update: ok — reload if hooks/skill should refresh");
1005
+ process.exit(doctor.status ?? 0);
1006
+ }
1007
+ function runEntry(entry, toolArgs, root) {
1008
+ const r = spawnSync(process.execPath, [execBinPath(), entry, ...toolArgs], {
1009
+ stdio: "inherit",
1010
+ env: { ...process.env, TLC_PROJECT_DIR: root }
1011
+ });
1012
+ process.exit(r.status ?? 1);
1013
+ }
1014
+ function main(argv) {
1015
+ const root = resolveProjectRoot();
1016
+ const group = (argv[0] ?? "").toLowerCase();
1017
+ if (group !== "harness") {
1018
+ console.error(`unknown: ${argv[0] ?? ""}`);
1019
+ console.error("usage: tlc harness <status|doctor|help|grind|pause|resume|mode|obs|prices|lessons|init|update|test|build>");
1020
+ process.exit(1);
1021
+ }
1022
+ const { json, rest: args } = takeJsonFlag(argv.slice(1));
1023
+ let action;
1024
+ try {
1025
+ action = route(args);
1026
+ } catch (error) {
1027
+ if (error instanceof UsageError) {
1028
+ console.error(error.message);
1029
+ process.exit(1);
1030
+ }
1031
+ throw error;
1032
+ }
1033
+ switch (action.kind) {
1034
+ case "status": {
1035
+ const leftover = unknownFlags(args.slice(1));
1036
+ if (leftover.length > 0) {
1037
+ console.error(`unknown flag: ${leftover[0]}`);
1038
+ console.error("usage: tlc harness status [--json]");
1039
+ process.exit(1);
1040
+ }
1041
+ if (json) {
1042
+ emitJson(statusJson(root));
1043
+ } else {
1044
+ console.log(statusText(root, createStyle()));
1045
+ }
1046
+ break;
1047
+ }
1048
+ case "handoff": {
1049
+ const leftover = unknownFlags(args.slice(1));
1050
+ if (leftover.length > 0) {
1051
+ console.error(`unknown flag: ${leftover[0]}`);
1052
+ console.error("usage: tlc harness handoff [--json]");
1053
+ process.exit(1);
1054
+ }
1055
+ const report = handoffJson(root);
1056
+ if (json) {
1057
+ emitJson(report);
1058
+ } else {
1059
+ console.log(handoffText(report));
1060
+ }
1061
+ break;
1062
+ }
1063
+ case "attest": {
1064
+ const leftover = unknownFlags(args.slice(1));
1065
+ if (leftover.length > 0) {
1066
+ console.error(`unknown flag: ${leftover[0]}`);
1067
+ console.error("usage: tlc harness attest [--json]");
1068
+ process.exit(1);
1069
+ }
1070
+ const report = attestJson(root);
1071
+ if (json) {
1072
+ emitJson(report);
1073
+ } else {
1074
+ console.log(attestText(root, createStyle()));
1075
+ }
1076
+ process.exit(report.ok ? 0 : 1);
1077
+ break;
1078
+ }
1079
+ case "policy": {
1080
+ if (action.accept.length === 0 && !args.includes("accept")) {
1081
+ if (json) {
1082
+ emitJson(policyJson(root));
1083
+ } else {
1084
+ console.log(policyText(root, createStyle()));
1085
+ }
1086
+ break;
1087
+ }
1088
+ try {
1089
+ console.log(acceptPolicy(root, action.accept, Boolean(process.stdin.isTTY)));
1090
+ } catch (error) {
1091
+ if (error instanceof UsageError) {
1092
+ console.error(error.message);
1093
+ process.exit(1);
1094
+ }
1095
+ throw error;
1096
+ }
1097
+ break;
1098
+ }
1099
+ case "help":
1100
+ console.log(helpText(createStyle()));
1101
+ break;
1102
+ case "build": {
1103
+ const r = spawnSync(process.execPath, [buildBinPath()], { stdio: "inherit", env: process.env });
1104
+ process.exit(r.status ?? 1);
1105
+ break;
1106
+ }
1107
+ case "version":
1108
+ if (json) {
1109
+ emitJson(versionJson(root));
1110
+ } else {
1111
+ console.log(versionText(root, createStyle()));
1112
+ }
1113
+ break;
1114
+ case "update-check": {
1115
+ const dest = resolveHarnessRoot();
1116
+ const report = pendingUpdate(dest, upstreamRef(dest));
1117
+ if (json) {
1118
+ emitJson(report);
1119
+ } else {
1120
+ console.log(pendingText(report, createStyle()));
1121
+ }
1122
+ break;
1123
+ }
1124
+ case "update":
1125
+ runUpdate(root);
1126
+ break;
1127
+ case "test": {
1128
+ const status = runTestSteps(buildTestSteps(), process.cwd());
1129
+ process.exit(status);
1130
+ break;
1131
+ }
1132
+ case "grind":
1133
+ console.log(setGrind(root, action.on));
1134
+ break;
1135
+ case "pause":
1136
+ console.log(setPaused(root, true));
1137
+ break;
1138
+ case "resume":
1139
+ console.log(setPaused(root, false));
1140
+ break;
1141
+ case "mode":
1142
+ try {
1143
+ console.log(setMode(root, action.value));
1144
+ } catch (error) {
1145
+ if (error instanceof UsageError) {
1146
+ console.error(error.message);
1147
+ process.exit(1);
1148
+ }
1149
+ throw error;
1150
+ }
1151
+ break;
1152
+ case "gate":
1153
+ try {
1154
+ console.log(setGateCommand(root, action.field, action.argv, process.stdin.isTTY === true));
1155
+ } catch (error) {
1156
+ if (error instanceof UsageError) {
1157
+ console.error(error.message);
1158
+ process.exit(1);
1159
+ }
1160
+ throw error;
1161
+ }
1162
+ break;
1163
+ case "prices-help":
1164
+ console.log(pricesHelpText(createStyle()));
1165
+ break;
1166
+ case "prices-refresh":
1167
+ runEntry("refresh-model-prices", [action.scope], root);
1168
+ break;
1169
+ case "prices-lookup":
1170
+ runEntry("price-lookup", json ? [action.modelId, JSON_FLAG] : [action.modelId], root);
1171
+ break;
1172
+ case "entry":
1173
+ runEntry(action.entry, json ? [...action.args, JSON_FLAG] : action.args, root);
1174
+ break;
1175
+ case "unknown":
1176
+ console.error(`unknown: ${action.cmd}`);
1177
+ console.log(helpText(createStyle()));
1178
+ process.exit(1);
1179
+ }
1180
+ }
1181
+ if (__require.main == __require.module) {
1182
+ main(process.argv.slice(2));
1183
+ }
1184
+
1185
+ export { linkDir, UsageError, resolveProjectRoot, modeFilePath, grindFlagPath, skipFlagPath, focusFlagPath, pairedFlagPath, ensureFlagsDir, readMode, grindOn, gatesPaused, acceptedModes, statusScreen, statusText, statusJson, setGrind, setPaused, setMode, handoffJson, handoffScreen, handoffText, attestScreen, attestText, attestJson, acceptPolicy, policyScreen, policyText, policyJson, upstreamRef, NPM_PACKAGE, NPM_MARKER, classifyRuntimePath, runtimePathKind, missingBundles, linkedRuntimeMessage, fetchFailureMessage, unmanagedRuntimeMessage, npmUpdateFailureMessage, resetFailureMessage, runtimeRevision, versionJson, versionScreen, versionText, pendingUpdate, pendingScreen, pendingText, resolveExecutable, setGateCommand, helpScreen, helpText, pricesHelpScreen, pricesHelpText, resolveHarnessRoot, wireRuntime, execBinPath, buildBinPath, route, TEST_ENV_IMPORT, buildTestSteps, runTestSteps };