@tech-leads-club/harness-toolkit 0.2.1 → 0.3.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 +22 -26
- package/bin/tlc-build.mjs +93 -0
- package/bin/tlc-cli.ts +117 -58
- package/bin/tlc-exec.mjs +16 -13
- package/dist/compact-before.mjs +151 -15
- package/dist/doctor.mjs +288 -45
- package/dist/help-topic.mjs +0 -0
- package/dist/init-project.mjs +15 -14
- package/dist/install-runtime.mjs +100 -17
- package/dist/lessons-cli.mjs +152 -8
- package/dist/obs-cli.mjs +149 -8
- package/dist/price-lookup.mjs +45 -23
- package/dist/prompt-submit.mjs +151 -15
- package/dist/refresh-model-prices.mjs +7190 -46
- package/dist/response-after.mjs +151 -15
- package/dist/run.mjs +151 -15
- package/dist/session-end.mjs +151 -15
- package/dist/session-start.mjs +151 -15
- package/dist/shim.mjs +7040 -16
- package/dist/stop.mjs +151 -15
- package/dist/subagent-start.mjs +151 -15
- package/dist/subagent-stop.mjs +151 -15
- package/dist/support.mjs +149 -8
- package/dist/tlc-cli.mjs +289 -89
- package/dist/tool-after.mjs +196 -38
- package/dist/tool-before.mjs +151 -15
- package/dist/tool-failure.mjs +151 -15
- package/dist/uninstall-runtime.mjs +9 -10
- package/docs/log.md +7 -0
- package/docs/measure.md +35 -31
- package/package.json +6 -5
- package/src/core/core.facade.ts +18 -0
- package/src/core/index.ts +3 -0
- package/src/core/pricing/pricing.freshness.ts +118 -0
- package/src/core/release/release.version.ts +147 -0
- package/src/core/shim/shim.precedence.ts +72 -0
- package/src/core/skill/skill.link.ts +92 -0
- package/src/entrypoints/shim.ts +49 -11
- package/src/platform/fs-atomic.ts +61 -23
- package/src/platform/links.ts +73 -0
- package/src/platform/paths.ts +27 -0
- package/src/platform/pricing.ts +139 -31
- package/src/providers/cursor/cursor.wiring.ts +11 -8
- package/tools/doctor.ts +127 -10
- package/tools/init-project.ts +34 -14
- package/tools/install-runtime.ts +89 -6
- package/tools/refresh-model-prices.ts +242 -75
- package/tools/uninstall-runtime.ts +23 -19
- package/CHANGELOG.md +0 -95
- package/bin/tlc-build +0 -80
- package/bin/tlc-exec +0 -10
- package/bin/tlc-exec.cmd +0 -4
- package/docs/decisions/ad-001.md +0 -32
- package/docs/decisions/ad-002.md +0 -51
- package/docs/decisions/ad-003.md +0 -30
- package/docs/decisions/ad-004.md +0 -37
- package/docs/decisions/ad-005.md +0 -36
- package/docs/decisions/ad-006.md +0 -49
- package/docs/decisions/ad-007.md +0 -36
- package/docs/decisions/ad-008.md +0 -54
- package/docs/decisions/ad-009.md +0 -61
- package/docs/decisions/ad-010.md +0 -45
- package/docs/decisions/ad-011.md +0 -59
- package/docs/decisions/ad-012.md +0 -71
- package/docs/decisions/ad-013.md +0 -87
- package/docs/decisions/ad-014.md +0 -56
- package/docs/decisions/ad-015.md +0 -33
- package/docs/decisions/ad-016.md +0 -98
- package/docs/decisions/ad-017.md +0 -65
- package/docs/decisions/ad-018.md +0 -77
- package/docs/decisions/ad-019.md +0 -75
- package/docs/decisions/ad-020.md +0 -88
- package/docs/decisions/ad-021.md +0 -57
- package/docs/decisions/ad-022.md +0 -120
- package/docs/decisions/ad-023.md +0 -87
- package/docs/decisions/ad-024.md +0 -85
- package/docs/decisions/ad-025.md +0 -92
- package/docs/decisions/ad-026.md +0 -131
- package/docs/decisions/ad-027.md +0 -90
- package/docs/decisions/ad-028.md +0 -90
- package/docs/decisions/ad-029.md +0 -73
- package/docs/decisions/ad-030.md +0 -97
- package/docs/decisions/ad-031.md +0 -94
- package/docs/decisions/ad-032.md +0 -83
- package/docs/decisions/ad-033.md +0 -89
- package/docs/decisions/ad-034.md +0 -86
- package/docs/decisions/ad-035.md +0 -86
- package/docs/decisions/ad-036.md +0 -68
- package/docs/decisions/ad-037.md +0 -47
- package/docs/decisions/ad-038.md +0 -52
- package/docs/decisions/ad-039.md +0 -69
- package/docs/decisions/ad-040.md +0 -89
- package/docs/decisions/ad-041.md +0 -98
- package/docs/decisions/ad-042.md +0 -82
- package/docs/decisions/ad-043.md +0 -79
- package/docs/decisions/ad-044.md +0 -61
- package/docs/decisions/ad-045.md +0 -94
- package/docs/decisions/ad-046.md +0 -111
- package/docs/decisions/ad-047.md +0 -96
- package/docs/decisions/ad-048.md +0 -85
- package/docs/decisions/ad-049.md +0 -66
- package/docs/decisions/ad-050.md +0 -94
- package/docs/decisions/ad-051.md +0 -69
- package/docs/decisions/ad-052.md +0 -69
- package/docs/decisions/ad-053.md +0 -78
- package/docs/decisions/ad-054.md +0 -98
- package/docs/decisions/ad-055.md +0 -74
- package/docs/decisions/ad-056.md +0 -85
- package/docs/decisions/ad-057.md +0 -68
- package/docs/decisions/ad-058.md +0 -97
- package/docs/decisions/ad-059.md +0 -82
- package/docs/decisions/ad-060.md +0 -75
- package/docs/decisions/ad-061.md +0 -68
- package/docs/decisions/ad-062.md +0 -72
- package/docs/decisions/ad-063.md +0 -84
- package/docs/decisions/ad-064.md +0 -79
- package/docs/decisions/ad-065.md +0 -81
- package/docs/decisions/ad-066.md +0 -111
- package/docs/decisions/ad-067.md +0 -64
- package/docs/decisions/ad-068.md +0 -79
- package/docs/decisions/ad-069.md +0 -74
- package/docs/decisions/ad-070.md +0 -86
- package/docs/decisions/ad-071.md +0 -93
- package/docs/decisions/ad-072.md +0 -82
- package/docs/decisions/ad-073.md +0 -102
- package/docs/decisions/ad-074.md +0 -91
- package/docs/decisions/ad-075.md +0 -79
- package/docs/decisions/ad-076.md +0 -102
- package/docs/decisions/ad-077.md +0 -94
- package/docs/decisions/ad-078.md +0 -84
- package/docs/decisions/ad-079.md +0 -73
- package/docs/decisions/ad-080.md +0 -86
- package/docs/decisions/ad-081.md +0 -70
- package/docs/decisions/ad-082.md +0 -79
- package/docs/decisions/ad-083.md +0 -88
- package/docs/decisions/index.md +0 -111
- package/model-aliases.json +0 -12
- package/model-prices.cursor.json +0 -410
- package/model-prices.json +0 -1
- package/tools/test-env.mjs +0 -28
- package/tools/test-env.names.d.mts +0 -1
- package/tools/test-env.names.mjs +0 -14
package/dist/support.mjs
CHANGED
|
@@ -269,20 +269,27 @@ function readJson(path) {
|
|
|
269
269
|
return null;
|
|
270
270
|
}
|
|
271
271
|
}
|
|
272
|
-
|
|
272
|
+
function isContention(error) {
|
|
273
|
+
return errorCode(error) === "EEXIST" || isRetryableFsError(error);
|
|
274
|
+
}
|
|
275
|
+
async function withFileLock(lockPath, fn, options = {}) {
|
|
276
|
+
const {
|
|
277
|
+
openLock = (path) => closeSync(openSync(path, "wx")),
|
|
278
|
+
lockSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
279
|
+
lockAttempts = 200
|
|
280
|
+
} = options;
|
|
273
281
|
mkdirSync2(dirname2(lockPath), { recursive: true });
|
|
274
|
-
const attempts = 200;
|
|
275
282
|
let acquired = false;
|
|
276
|
-
for (let attempt = 0;attempt <
|
|
283
|
+
for (let attempt = 0;attempt < lockAttempts; attempt++) {
|
|
277
284
|
try {
|
|
278
|
-
|
|
285
|
+
openLock(lockPath);
|
|
279
286
|
acquired = true;
|
|
280
287
|
break;
|
|
281
288
|
} catch (error) {
|
|
282
|
-
if (
|
|
289
|
+
if (!isContention(error)) {
|
|
283
290
|
throw error;
|
|
284
291
|
}
|
|
285
|
-
await
|
|
292
|
+
await lockSleep(nextDelay({ attempt, baseMs: 10, capMs: 200 }));
|
|
286
293
|
}
|
|
287
294
|
}
|
|
288
295
|
if (!acquired) {
|
|
@@ -297,14 +304,14 @@ async function withFileLock(lockPath, fn) {
|
|
|
297
304
|
}
|
|
298
305
|
}
|
|
299
306
|
async function updateJsonAtomic(path, mutator, options) {
|
|
300
|
-
const { lockPath, afterWrite, ...atomicOptions } = options;
|
|
307
|
+
const { lockPath, afterWrite, openLock, lockSleep, lockAttempts, ...atomicOptions } = options;
|
|
301
308
|
return withFileLock(lockPath, async () => {
|
|
302
309
|
const current = readJson(path);
|
|
303
310
|
const next = mutator(current);
|
|
304
311
|
await writeJsonAtomic(path, next, atomicOptions);
|
|
305
312
|
afterWrite?.(path);
|
|
306
313
|
return next;
|
|
307
|
-
});
|
|
314
|
+
}, { openLock, lockSleep, lockAttempts });
|
|
308
315
|
}
|
|
309
316
|
|
|
310
317
|
// src/core/capability/capability.store.ts
|
|
@@ -5252,6 +5259,62 @@ function release(root, provider, session) {
|
|
|
5252
5259
|
deletePresenceRecord(root, provider, session);
|
|
5253
5260
|
}
|
|
5254
5261
|
|
|
5262
|
+
// src/core/pricing/pricing.freshness.ts
|
|
5263
|
+
var DEFAULT_TTL_DAYS = 7;
|
|
5264
|
+
var MS_PER_DAY = 86400000;
|
|
5265
|
+
function freshness(meta, now, ttlDays = DEFAULT_TTL_DAYS) {
|
|
5266
|
+
if (meta === null) {
|
|
5267
|
+
return { state: "absent" };
|
|
5268
|
+
}
|
|
5269
|
+
const stamp = meta.refreshedAt;
|
|
5270
|
+
if (stamp === undefined || Number.isNaN(Date.parse(stamp))) {
|
|
5271
|
+
return { state: "undated" };
|
|
5272
|
+
}
|
|
5273
|
+
const ageMs = now.getTime() - Date.parse(stamp);
|
|
5274
|
+
const ageDays = Math.max(0, ageMs / MS_PER_DAY);
|
|
5275
|
+
return ageDays > ttlDays ? { state: "stale", ageDays, refreshedAt: stamp } : { state: "fresh", ageDays, refreshedAt: stamp };
|
|
5276
|
+
}
|
|
5277
|
+
function shouldRefetch(state) {
|
|
5278
|
+
return state.state === "absent" || state.state === "undated" || state.state === "stale";
|
|
5279
|
+
}
|
|
5280
|
+
function freshnessMessage(state, catalogue) {
|
|
5281
|
+
switch (state.state) {
|
|
5282
|
+
case "absent":
|
|
5283
|
+
return `${catalogue}: not on this machine — run \`tlc harness prices refresh\``;
|
|
5284
|
+
case "undated":
|
|
5285
|
+
return `${catalogue}: present but carries no date — it will be refetched`;
|
|
5286
|
+
case "fresh":
|
|
5287
|
+
return `${catalogue}: ${describeAge(state.ageDays)} old`;
|
|
5288
|
+
default:
|
|
5289
|
+
return `${catalogue}: ${describeAge(state.ageDays)} old — run \`tlc harness prices refresh\``;
|
|
5290
|
+
}
|
|
5291
|
+
}
|
|
5292
|
+
var MIN_RETAINED_RATIO = 0.5;
|
|
5293
|
+
function mayReplace(existingCount, incomingCount, minRatio = MIN_RETAINED_RATIO) {
|
|
5294
|
+
if (incomingCount === 0) {
|
|
5295
|
+
return { replace: false, reason: "parsed no entries at all — the upstream format has changed" };
|
|
5296
|
+
}
|
|
5297
|
+
if (existingCount === 0) {
|
|
5298
|
+
return { replace: true, reason: `first catalogue, ${incomingCount} entries` };
|
|
5299
|
+
}
|
|
5300
|
+
if (incomingCount >= existingCount) {
|
|
5301
|
+
return { replace: true, reason: `${existingCount} → ${incomingCount} entries` };
|
|
5302
|
+
}
|
|
5303
|
+
const retained = incomingCount / existingCount;
|
|
5304
|
+
return retained >= minRatio ? { replace: true, reason: `${existingCount} → ${incomingCount} entries` } : {
|
|
5305
|
+
replace: false,
|
|
5306
|
+
reason: `would drop from ${existingCount} to ${incomingCount} entries, keeping the existing catalogue — the upstream format has probably changed`
|
|
5307
|
+
};
|
|
5308
|
+
}
|
|
5309
|
+
function describeAge(ageDays) {
|
|
5310
|
+
if (ageDays < 1) {
|
|
5311
|
+
const hours = Math.max(1, Math.round(ageDays * 24));
|
|
5312
|
+
return `${hours} hour${hours === 1 ? "" : "s"}`;
|
|
5313
|
+
}
|
|
5314
|
+
const days = Math.round(ageDays);
|
|
5315
|
+
return `${days} day${days === 1 ? "" : "s"}`;
|
|
5316
|
+
}
|
|
5317
|
+
|
|
5255
5318
|
// src/core/release/release.decisions.ts
|
|
5256
5319
|
import { existsSync as existsSync17, readdirSync as readdirSync5, readFileSync as readFileSync19 } from "node:fs";
|
|
5257
5320
|
import { join as join18 } from "node:path";
|
|
@@ -5555,6 +5618,69 @@ function evaluateShellCommand(args) {
|
|
|
5555
5618
|
return { kind: "allow" };
|
|
5556
5619
|
}
|
|
5557
5620
|
|
|
5621
|
+
// src/core/shim/shim.precedence.ts
|
|
5622
|
+
var LAUNCHER = "tlc-exec";
|
|
5623
|
+
function invocationText(entry) {
|
|
5624
|
+
return [entry.command ?? "", ...entry.args ?? []].join(" ");
|
|
5625
|
+
}
|
|
5626
|
+
function coversHandler(settings, handler) {
|
|
5627
|
+
for (const matchers of Object.values(settings.hooks ?? {})) {
|
|
5628
|
+
for (const matcher of matchers) {
|
|
5629
|
+
for (const entry of matcher.hooks ?? []) {
|
|
5630
|
+
const text = invocationText(entry);
|
|
5631
|
+
if (text.includes(LAUNCHER) && new RegExp(`(^|\\s)${handler}(\\s|$)`).test(text)) {
|
|
5632
|
+
return true;
|
|
5633
|
+
}
|
|
5634
|
+
}
|
|
5635
|
+
}
|
|
5636
|
+
}
|
|
5637
|
+
return false;
|
|
5638
|
+
}
|
|
5639
|
+
function decideShim(userSettings, handler) {
|
|
5640
|
+
if (userSettings === null) {
|
|
5641
|
+
return { run: true, reason: "no user-level settings — this shim is the only hook for this event" };
|
|
5642
|
+
}
|
|
5643
|
+
return coversHandler(userSettings, handler) ? {
|
|
5644
|
+
run: false,
|
|
5645
|
+
reason: `a user-level hook already runs ${handler} — standing down to avoid a second run`
|
|
5646
|
+
} : { run: true, reason: `no user-level hook runs ${handler}` };
|
|
5647
|
+
}
|
|
5648
|
+
|
|
5649
|
+
// src/core/skill/skill.link.ts
|
|
5650
|
+
var SKILL_NAME = "harness-init";
|
|
5651
|
+
function skillLinks(runtimeHome2, providerDirs, present) {
|
|
5652
|
+
const source = `${runtimeHome2}/skills/${SKILL_NAME}`;
|
|
5653
|
+
return providerDirs.filter((dir) => present(dir)).map((providerDir) => ({
|
|
5654
|
+
providerDir,
|
|
5655
|
+
source,
|
|
5656
|
+
target: `${providerDir}/skills/${SKILL_NAME}`
|
|
5657
|
+
}));
|
|
5658
|
+
}
|
|
5659
|
+
function linkHealth(target, runtimeHome2, probe) {
|
|
5660
|
+
const resolved = probe.linkTarget(target);
|
|
5661
|
+
if (resolved === null) {
|
|
5662
|
+
return { state: "absent", target };
|
|
5663
|
+
}
|
|
5664
|
+
if (!probe.exists(resolved)) {
|
|
5665
|
+
return { state: "dangling", target, resolved };
|
|
5666
|
+
}
|
|
5667
|
+
const resolveHome = probe.realpath ?? ((path) => path);
|
|
5668
|
+
const home = resolveHome(runtimeHome2).replace(/\/+$/, "");
|
|
5669
|
+
return resolved === home || resolved.startsWith(`${home}/`) ? { state: "ok", target, resolved } : { state: "outside-runtime", target, resolved };
|
|
5670
|
+
}
|
|
5671
|
+
function linkHealthMessage(health) {
|
|
5672
|
+
switch (health.state) {
|
|
5673
|
+
case "ok":
|
|
5674
|
+
return `linked → ${health.resolved}`;
|
|
5675
|
+
case "dangling":
|
|
5676
|
+
return `points at ${health.resolved}, which does not exist — re-run \`tlc harness install\``;
|
|
5677
|
+
case "outside-runtime":
|
|
5678
|
+
return `points at ${health.resolved}, outside the runtime — it will break when that path goes`;
|
|
5679
|
+
default:
|
|
5680
|
+
return "not linked — the provider cannot see the init skill";
|
|
5681
|
+
}
|
|
5682
|
+
}
|
|
5683
|
+
|
|
5558
5684
|
// src/core/stagnation/stagnation.resolution.ts
|
|
5559
5685
|
import { existsSync as existsSync20, mkdirSync as mkdirSync11, readFileSync as readFileSync22, writeFileSync as writeFileSync10 } from "node:fs";
|
|
5560
5686
|
import { join as join21 } from "node:path";
|
|
@@ -6682,6 +6808,21 @@ var coreFacade = {
|
|
|
6682
6808
|
readHandoffFile,
|
|
6683
6809
|
readForeignSlices
|
|
6684
6810
|
},
|
|
6811
|
+
shim: {
|
|
6812
|
+
coversHandler,
|
|
6813
|
+
decideShim
|
|
6814
|
+
},
|
|
6815
|
+
pricing: {
|
|
6816
|
+
freshness,
|
|
6817
|
+
freshnessMessage,
|
|
6818
|
+
mayReplace,
|
|
6819
|
+
shouldRefetch
|
|
6820
|
+
},
|
|
6821
|
+
skill: {
|
|
6822
|
+
linkHealth,
|
|
6823
|
+
linkHealthMessage,
|
|
6824
|
+
skillLinks
|
|
6825
|
+
},
|
|
6685
6826
|
lesson: {
|
|
6686
6827
|
projectLessonsInjectable,
|
|
6687
6828
|
recordLessonFromFailure,
|