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

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 +15 -29
  2. package/bin/tlc-cli.ts +63 -2
  3. package/dist/compact-before.mjs +7961 -13
  4. package/dist/doctor.mjs +8480 -33
  5. package/dist/help-topic.mjs +14 -6
  6. package/dist/init-project.mjs +793 -36
  7. package/dist/install-runtime.mjs +7268 -17
  8. package/dist/lessons-cli.mjs +7044 -26
  9. package/dist/obs-cli.mjs +7037 -27
  10. package/dist/price-lookup.mjs +201 -11
  11. package/dist/prompt-submit.mjs +7975 -15
  12. package/dist/refresh-model-prices.mjs +7060 -25
  13. package/dist/response-after.mjs +7962 -12
  14. package/dist/run.mjs +7960 -10
  15. package/dist/session-end.mjs +8030 -15
  16. package/dist/session-start.mjs +8076 -21
  17. package/dist/shim.mjs +7024 -18
  18. package/dist/stop.mjs +8042 -29
  19. package/dist/subagent-start.mjs +7983 -13
  20. package/dist/subagent-stop.mjs +7962 -12
  21. package/dist/support.mjs +7168 -21
  22. package/dist/tlc-cli.mjs +8235 -65
  23. package/dist/tool-after.mjs +8177 -21
  24. package/dist/tool-before.mjs +7991 -16
  25. package/dist/tool-failure.mjs +7962 -15
  26. package/dist/uninstall-runtime.mjs +1008 -61
  27. package/docs/log.md +1 -1
  28. package/package.json +1 -1
  29. package/dist/chunks/compact-before-1e4qg1qt.mjs +0 -1185
  30. package/dist/chunks/compact-before-2hpbfxm5.mjs +0 -5782
  31. package/dist/chunks/compact-before-49j320yp.mjs +0 -1283
  32. package/dist/chunks/compact-before-4jrq0sqs.mjs +0 -61
  33. package/dist/chunks/compact-before-6w8n1vh1.mjs +0 -186
  34. package/dist/chunks/compact-before-7sdmwswh.mjs +0 -52
  35. package/dist/chunks/compact-before-beqpmqrm.mjs +0 -187
  36. package/dist/chunks/compact-before-j9y4jgn4.mjs +0 -845
  37. package/dist/chunks/compact-before-pk86tqx2.mjs +0 -118
  38. package/dist/chunks/compact-before-pkqk5v29.mjs +0 -137
  39. package/dist/chunks/compact-before-w1293m4n.mjs +0 -315
  40. package/dist/chunks/compact-before-wnnds45y.mjs +0 -26
  41. package/dist/chunks/compact-before-wt2c3nh4.mjs +0 -551
package/bin/tlc-build.mjs CHANGED
@@ -38,32 +38,21 @@ function sourcesIn(dir) {
38
38
  }
39
39
 
40
40
  /**
41
- * why one invocation with every entry: `--splitting` can only share a chunk between entries it sees together.
42
- * Built one at a time, each bundle inlined the whole core measured, 24 bundles of ~257 KB each where more than
43
- * half of every one was byte-identical to its neighbour: 5.0 MB of `dist/` to carry 548 KB of distinct code
41
+ * hazard: this built every entry in ONE invocation with `--splitting`, which cut `dist/` from 5.0 MB to 548 KB
42
+ * and shipped a broken CLI. A shared chunk carries the module body of anything two entries both import, including
43
+ * `bin/tlc-cli.ts`, and its `if (import.meta.main)` guard evaluates **true** inside that chunk. So running
44
+ * `install-runtime`, which imports the CLI for `NPM_MARKER` and `wireRuntime`, ran the CLI's `main` instead:
45
+ * `tlc harness install` printed `unknown:` and installed nothing. Published as 0.3.2 and caught by installing it
44
46
  * ([/decisions/ad-098.md](/decisions/ad-098.md)).
45
47
  *
46
- * invariant: the entry names stay flat and keep `.mjs`, because the launcher resolves `dist/<entry>.mjs` and the
47
- * hooks on every installed machine name the launcher. Chunks go in a subdirectory, so pruning an orphan entry
48
- * never has to tell the two apart.
48
+ * invariant: one bundle per entry, so a module that self-executes behind `import.meta.main` is inlined into the
49
+ * one program that is allowed to run it. Splitting can come back when no library module carries that guard, and
50
+ * not before the size win is real and it is not worth a CLI that cannot install.
49
51
  */
50
- const CHUNK_DIR = "chunks";
51
-
52
- function build(targets) {
52
+ function buildOne(source, out) {
53
53
  const result = spawnSync(
54
54
  "bun",
55
- [
56
- "build",
57
- "--target=node",
58
- "--format=esm",
59
- "--splitting",
60
- `--outdir=${dist}`,
61
- "--entry-naming",
62
- "[name].mjs",
63
- "--chunk-naming",
64
- `${CHUNK_DIR}/[name]-[hash].mjs`,
65
- ...targets.map((target) => target.source),
66
- ],
55
+ ["build", "--target=node", "--format=esm", `--outfile=${out}`, source],
67
56
  { stdio: "inherit" },
68
57
  );
69
58
  if (result.error?.code === "ENOENT") {
@@ -85,14 +74,11 @@ const targets = [
85
74
  mkdirSync(dist, { recursive: true });
86
75
  console.log(`tlc-build → ${dist}`);
87
76
 
88
- /**
89
- * hazard: chunk names carry a content hash, so a rebuild after a source change writes new ones and leaves the old
90
- * ones behind. Nothing references them and they ship anyway, growing the package with every build.
91
- *
92
- * invariant: the chunk directory is derived, so it is replaced rather than merged.
93
- */
94
- rmSync(join(dist, CHUNK_DIR), { recursive: true, force: true });
95
- build(targets);
77
+ // invariant: a leftover chunk directory from the split build would ship for ever, referenced by nothing.
78
+ rmSync(join(dist, "chunks"), { recursive: true, force: true });
79
+ for (const target of targets) {
80
+ buildOne(target.source, join(dist, `${target.name}.mjs`));
81
+ }
96
82
 
97
83
  // invariant: the launcher stays executable on a filesystem that tracks the bit. A no-op where it does not.
98
84
  try {
package/bin/tlc-cli.ts CHANGED
@@ -904,6 +904,61 @@ export function resolveHarnessRoot(): string {
904
904
  }
905
905
  }
906
906
 
907
+ /**
908
+ * Where npm put the package this command was installed from.
909
+ *
910
+ * hazard: `update` spawned `install-runtime` through the **runtime home's** launcher, so the tool resolved its
911
+ * source and its destination to the same directory and reported "already at … — nothing to copy". Measured on a
912
+ * scratch machine: `npm i -g` moved the package from 0.3.0 to 0.3.2 and the runtime the hooks execute stayed on
913
+ * 0.3.0. Every npm install that ever ran `update` bumped a package and kept its old code, while `doctor` said
914
+ * update "re-materialises this directory" ([/decisions/ad-098.md](/decisions/ad-098.md)).
915
+ *
916
+ * invariant: asked of npm rather than derived from this process. The CLI can be running from the runtime home,
917
+ * from the package, or from a linked clone, and only npm knows where it installs globally.
918
+ */
919
+ export function globalPackageRoot(
920
+ probe = {
921
+ npmRoot: () => spawnSync("npm", ["root", "-g"], { encoding: "utf8", shell: true }).stdout ?? "",
922
+ exists: existsSync,
923
+ },
924
+ ): string | null {
925
+ // invariant: trimmed here rather than in the probe. `npm root -g` ends in a newline, and a path with a newline
926
+ // in it fails as a directory while reading as a plausible string in an error message.
927
+ const root = probe.npmRoot().trim();
928
+ if (root.length === 0) {
929
+ return null;
930
+ }
931
+ const candidate = join(root, ...NPM_PACKAGE.split("/"));
932
+ return probe.exists(candidate) ? candidate : null;
933
+ }
934
+
935
+ /**
936
+ * invariant: the *package's* launcher runs the materialisation, not the runtime home's. A release that fixes
937
+ * `install` has to be able to deliver that fix, and the old code cannot do it.
938
+ *
939
+ * invariant: both ends are named explicitly — `TLC_ORIGIN` is where the code comes from and `TLC_INSTALL_DEST` is
940
+ * where it goes — because each of them defaults to the same conventional home when left unsaid, which is exactly
941
+ * how this became a no-op.
942
+ */
943
+ export function npmSyncPlan(
944
+ packageRoot: string,
945
+ dest: string,
946
+ ): { command: string; args: string[]; env: Record<string, string> } {
947
+ return {
948
+ command: process.execPath,
949
+ args: [join(packageRoot, "bin", "tlc-exec.mjs"), "install-runtime"],
950
+ env: { TLC_ORIGIN: packageRoot, TLC_INSTALL_DEST: dest },
951
+ };
952
+ }
953
+
954
+ export function npmRootFailureMessage(home: string): string {
955
+ return [
956
+ `update: npm reported no global root, so the package it just installed cannot be found.`,
957
+ ` The runtime at ${home} is unchanged — nothing was half-written.`,
958
+ ` Run \`npm root -g\` yourself; then \`npm i -g ${NPM_PACKAGE}@latest\` and \`tlc harness install\`.`,
959
+ ].join("\n");
960
+ }
961
+
907
962
  /**
908
963
  * Everything an install has to put in place outside the runtime directory itself: the init skill where each
909
964
  * provider reads it, the user-level hooks, and a seeded config.
@@ -1293,9 +1348,15 @@ function runUpdate(root: string): never {
1293
1348
  console.error(npmUpdateFailureMessage());
1294
1349
  process.exit(bump.status ?? 1);
1295
1350
  }
1296
- const sync = spawnSync(process.execPath, [execBinPath(), "install-runtime"], {
1351
+ const packageRoot = globalPackageRoot();
1352
+ if (packageRoot === null) {
1353
+ console.error(npmRootFailureMessage(home));
1354
+ process.exit(1);
1355
+ }
1356
+ const plan = npmSyncPlan(packageRoot, home);
1357
+ const sync = spawnSync(plan.command, plan.args, {
1297
1358
  stdio: "inherit",
1298
- env: process.env,
1359
+ env: { ...process.env, ...plan.env },
1299
1360
  });
1300
1361
  if ((sync.status ?? 1) !== 0) {
1301
1362
  process.exit(sync.status ?? 1);