@tech-leads-club/harness-toolkit 0.2.1 → 0.2.4
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/bin/tlc-cli.ts +20 -5
- package/dist/compact-before.mjs +86 -8
- package/dist/doctor.mjs +116 -10
- package/dist/init-project.mjs +13 -7
- package/dist/lessons-cli.mjs +86 -8
- package/dist/obs-cli.mjs +86 -8
- package/dist/prompt-submit.mjs +86 -8
- package/dist/response-after.mjs +86 -8
- package/dist/run.mjs +86 -8
- package/dist/session-end.mjs +86 -8
- package/dist/session-start.mjs +86 -8
- package/dist/shim.mjs +6976 -15
- package/dist/stop.mjs +86 -8
- package/dist/subagent-start.mjs +86 -8
- package/dist/subagent-stop.mjs +86 -8
- package/dist/support.mjs +86 -8
- package/dist/tlc-cli.mjs +106 -12
- package/dist/tool-after.mjs +86 -8
- package/dist/tool-before.mjs +86 -8
- package/dist/tool-failure.mjs +86 -8
- package/docs/log.md +5 -0
- package/package.json +3 -2
- package/src/core/core.facade.ts +11 -0
- package/src/core/index.ts +2 -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 +81 -0
- package/src/entrypoints/shim.ts +41 -9
- package/src/platform/fs-atomic.ts +61 -23
- package/src/platform/paths.ts +27 -0
- package/tools/doctor.ts +50 -3
- package/tools/init-project.ts +27 -7
- package/CHANGELOG.md +0 -95
- 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/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/obs-cli.mjs
CHANGED
|
@@ -272,20 +272,27 @@ function readJson(path) {
|
|
|
272
272
|
return null;
|
|
273
273
|
}
|
|
274
274
|
}
|
|
275
|
-
|
|
275
|
+
function isContention(error) {
|
|
276
|
+
return errorCode(error) === "EEXIST" || isRetryableFsError(error);
|
|
277
|
+
}
|
|
278
|
+
async function withFileLock(lockPath, fn, options = {}) {
|
|
279
|
+
const {
|
|
280
|
+
openLock = (path) => closeSync(openSync(path, "wx")),
|
|
281
|
+
lockSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
282
|
+
lockAttempts = 200
|
|
283
|
+
} = options;
|
|
276
284
|
mkdirSync2(dirname2(lockPath), { recursive: true });
|
|
277
|
-
const attempts = 200;
|
|
278
285
|
let acquired = false;
|
|
279
|
-
for (let attempt = 0;attempt <
|
|
286
|
+
for (let attempt = 0;attempt < lockAttempts; attempt++) {
|
|
280
287
|
try {
|
|
281
|
-
|
|
288
|
+
openLock(lockPath);
|
|
282
289
|
acquired = true;
|
|
283
290
|
break;
|
|
284
291
|
} catch (error) {
|
|
285
|
-
if (
|
|
292
|
+
if (!isContention(error)) {
|
|
286
293
|
throw error;
|
|
287
294
|
}
|
|
288
|
-
await
|
|
295
|
+
await lockSleep(nextDelay({ attempt, baseMs: 10, capMs: 200 }));
|
|
289
296
|
}
|
|
290
297
|
}
|
|
291
298
|
if (!acquired) {
|
|
@@ -300,14 +307,14 @@ async function withFileLock(lockPath, fn) {
|
|
|
300
307
|
}
|
|
301
308
|
}
|
|
302
309
|
async function updateJsonAtomic(path, mutator, options) {
|
|
303
|
-
const { lockPath, afterWrite, ...atomicOptions } = options;
|
|
310
|
+
const { lockPath, afterWrite, openLock, lockSleep, lockAttempts, ...atomicOptions } = options;
|
|
304
311
|
return withFileLock(lockPath, async () => {
|
|
305
312
|
const current = readJson(path);
|
|
306
313
|
const next = mutator(current);
|
|
307
314
|
await writeJsonAtomic(path, next, atomicOptions);
|
|
308
315
|
afterWrite?.(path);
|
|
309
316
|
return next;
|
|
310
|
-
});
|
|
317
|
+
}, { openLock, lockSleep, lockAttempts });
|
|
311
318
|
}
|
|
312
319
|
|
|
313
320
|
// src/core/capability/capability.store.ts
|
|
@@ -5558,6 +5565,68 @@ function evaluateShellCommand(args) {
|
|
|
5558
5565
|
return { kind: "allow" };
|
|
5559
5566
|
}
|
|
5560
5567
|
|
|
5568
|
+
// src/core/shim/shim.precedence.ts
|
|
5569
|
+
var LAUNCHER = "tlc-exec";
|
|
5570
|
+
function invocationText(entry) {
|
|
5571
|
+
return [entry.command ?? "", ...entry.args ?? []].join(" ");
|
|
5572
|
+
}
|
|
5573
|
+
function coversHandler(settings, handler) {
|
|
5574
|
+
for (const matchers of Object.values(settings.hooks ?? {})) {
|
|
5575
|
+
for (const matcher of matchers) {
|
|
5576
|
+
for (const entry of matcher.hooks ?? []) {
|
|
5577
|
+
const text = invocationText(entry);
|
|
5578
|
+
if (text.includes(LAUNCHER) && new RegExp(`(^|\\s)${handler}(\\s|$)`).test(text)) {
|
|
5579
|
+
return true;
|
|
5580
|
+
}
|
|
5581
|
+
}
|
|
5582
|
+
}
|
|
5583
|
+
}
|
|
5584
|
+
return false;
|
|
5585
|
+
}
|
|
5586
|
+
function decideShim(userSettings, handler) {
|
|
5587
|
+
if (userSettings === null) {
|
|
5588
|
+
return { run: true, reason: "no user-level settings — this shim is the only hook for this event" };
|
|
5589
|
+
}
|
|
5590
|
+
return coversHandler(userSettings, handler) ? {
|
|
5591
|
+
run: false,
|
|
5592
|
+
reason: `a user-level hook already runs ${handler} — standing down to avoid a second run`
|
|
5593
|
+
} : { run: true, reason: `no user-level hook runs ${handler}` };
|
|
5594
|
+
}
|
|
5595
|
+
|
|
5596
|
+
// src/core/skill/skill.link.ts
|
|
5597
|
+
var SKILL_NAME = "harness-init";
|
|
5598
|
+
function skillLinks(runtimeHome2, providerDirs, present) {
|
|
5599
|
+
const source = `${runtimeHome2}/skills/${SKILL_NAME}`;
|
|
5600
|
+
return providerDirs.filter((dir) => present(dir)).map((providerDir) => ({
|
|
5601
|
+
providerDir,
|
|
5602
|
+
source,
|
|
5603
|
+
target: `${providerDir}/skills/${SKILL_NAME}`
|
|
5604
|
+
}));
|
|
5605
|
+
}
|
|
5606
|
+
function linkHealth(target, runtimeHome2, probe) {
|
|
5607
|
+
const resolved = probe.linkTarget(target);
|
|
5608
|
+
if (resolved === null) {
|
|
5609
|
+
return { state: "absent", target };
|
|
5610
|
+
}
|
|
5611
|
+
if (!probe.exists(resolved)) {
|
|
5612
|
+
return { state: "dangling", target, resolved };
|
|
5613
|
+
}
|
|
5614
|
+
const home = runtimeHome2.replace(/\/+$/, "");
|
|
5615
|
+
return resolved === home || resolved.startsWith(`${home}/`) ? { state: "ok", target, resolved } : { state: "outside-runtime", target, resolved };
|
|
5616
|
+
}
|
|
5617
|
+
function linkHealthMessage(health) {
|
|
5618
|
+
switch (health.state) {
|
|
5619
|
+
case "ok":
|
|
5620
|
+
return `linked → ${health.resolved}`;
|
|
5621
|
+
case "dangling":
|
|
5622
|
+
return `points at ${health.resolved}, which does not exist — re-run \`tlc harness install\``;
|
|
5623
|
+
case "outside-runtime":
|
|
5624
|
+
return `points at ${health.resolved}, outside the runtime — it will break when that path goes`;
|
|
5625
|
+
default:
|
|
5626
|
+
return "not linked — the provider cannot see the init skill";
|
|
5627
|
+
}
|
|
5628
|
+
}
|
|
5629
|
+
|
|
5561
5630
|
// src/core/stagnation/stagnation.resolution.ts
|
|
5562
5631
|
import { existsSync as existsSync20, mkdirSync as mkdirSync11, readFileSync as readFileSync22, writeFileSync as writeFileSync10 } from "node:fs";
|
|
5563
5632
|
import { join as join21 } from "node:path";
|
|
@@ -6685,6 +6754,15 @@ var coreFacade = {
|
|
|
6685
6754
|
readHandoffFile,
|
|
6686
6755
|
readForeignSlices
|
|
6687
6756
|
},
|
|
6757
|
+
shim: {
|
|
6758
|
+
coversHandler,
|
|
6759
|
+
decideShim
|
|
6760
|
+
},
|
|
6761
|
+
skill: {
|
|
6762
|
+
linkHealth,
|
|
6763
|
+
linkHealthMessage,
|
|
6764
|
+
skillLinks
|
|
6765
|
+
},
|
|
6688
6766
|
lesson: {
|
|
6689
6767
|
projectLessonsInjectable,
|
|
6690
6768
|
recordLessonFromFailure,
|
package/dist/prompt-submit.mjs
CHANGED
|
@@ -276,20 +276,27 @@ function readJson(path) {
|
|
|
276
276
|
return null;
|
|
277
277
|
}
|
|
278
278
|
}
|
|
279
|
-
|
|
279
|
+
function isContention(error) {
|
|
280
|
+
return errorCode(error) === "EEXIST" || isRetryableFsError(error);
|
|
281
|
+
}
|
|
282
|
+
async function withFileLock(lockPath, fn, options = {}) {
|
|
283
|
+
const {
|
|
284
|
+
openLock = (path) => closeSync(openSync(path, "wx")),
|
|
285
|
+
lockSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
286
|
+
lockAttempts = 200
|
|
287
|
+
} = options;
|
|
280
288
|
mkdirSync2(dirname2(lockPath), { recursive: true });
|
|
281
|
-
const attempts = 200;
|
|
282
289
|
let acquired = false;
|
|
283
|
-
for (let attempt = 0;attempt <
|
|
290
|
+
for (let attempt = 0;attempt < lockAttempts; attempt++) {
|
|
284
291
|
try {
|
|
285
|
-
|
|
292
|
+
openLock(lockPath);
|
|
286
293
|
acquired = true;
|
|
287
294
|
break;
|
|
288
295
|
} catch (error) {
|
|
289
|
-
if (
|
|
296
|
+
if (!isContention(error)) {
|
|
290
297
|
throw error;
|
|
291
298
|
}
|
|
292
|
-
await
|
|
299
|
+
await lockSleep(nextDelay({ attempt, baseMs: 10, capMs: 200 }));
|
|
293
300
|
}
|
|
294
301
|
}
|
|
295
302
|
if (!acquired) {
|
|
@@ -304,14 +311,14 @@ async function withFileLock(lockPath, fn) {
|
|
|
304
311
|
}
|
|
305
312
|
}
|
|
306
313
|
async function updateJsonAtomic(path, mutator, options) {
|
|
307
|
-
const { lockPath, afterWrite, ...atomicOptions } = options;
|
|
314
|
+
const { lockPath, afterWrite, openLock, lockSleep, lockAttempts, ...atomicOptions } = options;
|
|
308
315
|
return withFileLock(lockPath, async () => {
|
|
309
316
|
const current = readJson(path);
|
|
310
317
|
const next = mutator(current);
|
|
311
318
|
await writeJsonAtomic(path, next, atomicOptions);
|
|
312
319
|
afterWrite?.(path);
|
|
313
320
|
return next;
|
|
314
|
-
});
|
|
321
|
+
}, { openLock, lockSleep, lockAttempts });
|
|
315
322
|
}
|
|
316
323
|
|
|
317
324
|
// src/core/capability/capability.store.ts
|
|
@@ -5569,6 +5576,68 @@ function evaluateShellCommand(args) {
|
|
|
5569
5576
|
return { kind: "allow" };
|
|
5570
5577
|
}
|
|
5571
5578
|
|
|
5579
|
+
// src/core/shim/shim.precedence.ts
|
|
5580
|
+
var LAUNCHER = "tlc-exec";
|
|
5581
|
+
function invocationText(entry) {
|
|
5582
|
+
return [entry.command ?? "", ...entry.args ?? []].join(" ");
|
|
5583
|
+
}
|
|
5584
|
+
function coversHandler(settings, handler) {
|
|
5585
|
+
for (const matchers of Object.values(settings.hooks ?? {})) {
|
|
5586
|
+
for (const matcher of matchers) {
|
|
5587
|
+
for (const entry of matcher.hooks ?? []) {
|
|
5588
|
+
const text = invocationText(entry);
|
|
5589
|
+
if (text.includes(LAUNCHER) && new RegExp(`(^|\\s)${handler}(\\s|$)`).test(text)) {
|
|
5590
|
+
return true;
|
|
5591
|
+
}
|
|
5592
|
+
}
|
|
5593
|
+
}
|
|
5594
|
+
}
|
|
5595
|
+
return false;
|
|
5596
|
+
}
|
|
5597
|
+
function decideShim(userSettings, handler) {
|
|
5598
|
+
if (userSettings === null) {
|
|
5599
|
+
return { run: true, reason: "no user-level settings — this shim is the only hook for this event" };
|
|
5600
|
+
}
|
|
5601
|
+
return coversHandler(userSettings, handler) ? {
|
|
5602
|
+
run: false,
|
|
5603
|
+
reason: `a user-level hook already runs ${handler} — standing down to avoid a second run`
|
|
5604
|
+
} : { run: true, reason: `no user-level hook runs ${handler}` };
|
|
5605
|
+
}
|
|
5606
|
+
|
|
5607
|
+
// src/core/skill/skill.link.ts
|
|
5608
|
+
var SKILL_NAME = "harness-init";
|
|
5609
|
+
function skillLinks(runtimeHome2, providerDirs, present) {
|
|
5610
|
+
const source = `${runtimeHome2}/skills/${SKILL_NAME}`;
|
|
5611
|
+
return providerDirs.filter((dir) => present(dir)).map((providerDir) => ({
|
|
5612
|
+
providerDir,
|
|
5613
|
+
source,
|
|
5614
|
+
target: `${providerDir}/skills/${SKILL_NAME}`
|
|
5615
|
+
}));
|
|
5616
|
+
}
|
|
5617
|
+
function linkHealth(target, runtimeHome2, probe) {
|
|
5618
|
+
const resolved = probe.linkTarget(target);
|
|
5619
|
+
if (resolved === null) {
|
|
5620
|
+
return { state: "absent", target };
|
|
5621
|
+
}
|
|
5622
|
+
if (!probe.exists(resolved)) {
|
|
5623
|
+
return { state: "dangling", target, resolved };
|
|
5624
|
+
}
|
|
5625
|
+
const home = runtimeHome2.replace(/\/+$/, "");
|
|
5626
|
+
return resolved === home || resolved.startsWith(`${home}/`) ? { state: "ok", target, resolved } : { state: "outside-runtime", target, resolved };
|
|
5627
|
+
}
|
|
5628
|
+
function linkHealthMessage(health) {
|
|
5629
|
+
switch (health.state) {
|
|
5630
|
+
case "ok":
|
|
5631
|
+
return `linked → ${health.resolved}`;
|
|
5632
|
+
case "dangling":
|
|
5633
|
+
return `points at ${health.resolved}, which does not exist — re-run \`tlc harness install\``;
|
|
5634
|
+
case "outside-runtime":
|
|
5635
|
+
return `points at ${health.resolved}, outside the runtime — it will break when that path goes`;
|
|
5636
|
+
default:
|
|
5637
|
+
return "not linked — the provider cannot see the init skill";
|
|
5638
|
+
}
|
|
5639
|
+
}
|
|
5640
|
+
|
|
5572
5641
|
// src/core/stagnation/stagnation.resolution.ts
|
|
5573
5642
|
import { existsSync as existsSync20, mkdirSync as mkdirSync11, readFileSync as readFileSync22, writeFileSync as writeFileSync10 } from "node:fs";
|
|
5574
5643
|
import { join as join21 } from "node:path";
|
|
@@ -6696,6 +6765,15 @@ var coreFacade = {
|
|
|
6696
6765
|
readHandoffFile,
|
|
6697
6766
|
readForeignSlices
|
|
6698
6767
|
},
|
|
6768
|
+
shim: {
|
|
6769
|
+
coversHandler,
|
|
6770
|
+
decideShim
|
|
6771
|
+
},
|
|
6772
|
+
skill: {
|
|
6773
|
+
linkHealth,
|
|
6774
|
+
linkHealthMessage,
|
|
6775
|
+
skillLinks
|
|
6776
|
+
},
|
|
6699
6777
|
lesson: {
|
|
6700
6778
|
projectLessonsInjectable,
|
|
6701
6779
|
recordLessonFromFailure,
|
package/dist/response-after.mjs
CHANGED
|
@@ -276,20 +276,27 @@ function readJson(path) {
|
|
|
276
276
|
return null;
|
|
277
277
|
}
|
|
278
278
|
}
|
|
279
|
-
|
|
279
|
+
function isContention(error) {
|
|
280
|
+
return errorCode(error) === "EEXIST" || isRetryableFsError(error);
|
|
281
|
+
}
|
|
282
|
+
async function withFileLock(lockPath, fn, options = {}) {
|
|
283
|
+
const {
|
|
284
|
+
openLock = (path) => closeSync(openSync(path, "wx")),
|
|
285
|
+
lockSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
286
|
+
lockAttempts = 200
|
|
287
|
+
} = options;
|
|
280
288
|
mkdirSync2(dirname2(lockPath), { recursive: true });
|
|
281
|
-
const attempts = 200;
|
|
282
289
|
let acquired = false;
|
|
283
|
-
for (let attempt = 0;attempt <
|
|
290
|
+
for (let attempt = 0;attempt < lockAttempts; attempt++) {
|
|
284
291
|
try {
|
|
285
|
-
|
|
292
|
+
openLock(lockPath);
|
|
286
293
|
acquired = true;
|
|
287
294
|
break;
|
|
288
295
|
} catch (error) {
|
|
289
|
-
if (
|
|
296
|
+
if (!isContention(error)) {
|
|
290
297
|
throw error;
|
|
291
298
|
}
|
|
292
|
-
await
|
|
299
|
+
await lockSleep(nextDelay({ attempt, baseMs: 10, capMs: 200 }));
|
|
293
300
|
}
|
|
294
301
|
}
|
|
295
302
|
if (!acquired) {
|
|
@@ -304,14 +311,14 @@ async function withFileLock(lockPath, fn) {
|
|
|
304
311
|
}
|
|
305
312
|
}
|
|
306
313
|
async function updateJsonAtomic(path, mutator, options) {
|
|
307
|
-
const { lockPath, afterWrite, ...atomicOptions } = options;
|
|
314
|
+
const { lockPath, afterWrite, openLock, lockSleep, lockAttempts, ...atomicOptions } = options;
|
|
308
315
|
return withFileLock(lockPath, async () => {
|
|
309
316
|
const current = readJson(path);
|
|
310
317
|
const next = mutator(current);
|
|
311
318
|
await writeJsonAtomic(path, next, atomicOptions);
|
|
312
319
|
afterWrite?.(path);
|
|
313
320
|
return next;
|
|
314
|
-
});
|
|
321
|
+
}, { openLock, lockSleep, lockAttempts });
|
|
315
322
|
}
|
|
316
323
|
|
|
317
324
|
// src/core/capability/capability.store.ts
|
|
@@ -5569,6 +5576,68 @@ function evaluateShellCommand(args) {
|
|
|
5569
5576
|
return { kind: "allow" };
|
|
5570
5577
|
}
|
|
5571
5578
|
|
|
5579
|
+
// src/core/shim/shim.precedence.ts
|
|
5580
|
+
var LAUNCHER = "tlc-exec";
|
|
5581
|
+
function invocationText(entry) {
|
|
5582
|
+
return [entry.command ?? "", ...entry.args ?? []].join(" ");
|
|
5583
|
+
}
|
|
5584
|
+
function coversHandler(settings, handler) {
|
|
5585
|
+
for (const matchers of Object.values(settings.hooks ?? {})) {
|
|
5586
|
+
for (const matcher of matchers) {
|
|
5587
|
+
for (const entry of matcher.hooks ?? []) {
|
|
5588
|
+
const text = invocationText(entry);
|
|
5589
|
+
if (text.includes(LAUNCHER) && new RegExp(`(^|\\s)${handler}(\\s|$)`).test(text)) {
|
|
5590
|
+
return true;
|
|
5591
|
+
}
|
|
5592
|
+
}
|
|
5593
|
+
}
|
|
5594
|
+
}
|
|
5595
|
+
return false;
|
|
5596
|
+
}
|
|
5597
|
+
function decideShim(userSettings, handler) {
|
|
5598
|
+
if (userSettings === null) {
|
|
5599
|
+
return { run: true, reason: "no user-level settings — this shim is the only hook for this event" };
|
|
5600
|
+
}
|
|
5601
|
+
return coversHandler(userSettings, handler) ? {
|
|
5602
|
+
run: false,
|
|
5603
|
+
reason: `a user-level hook already runs ${handler} — standing down to avoid a second run`
|
|
5604
|
+
} : { run: true, reason: `no user-level hook runs ${handler}` };
|
|
5605
|
+
}
|
|
5606
|
+
|
|
5607
|
+
// src/core/skill/skill.link.ts
|
|
5608
|
+
var SKILL_NAME = "harness-init";
|
|
5609
|
+
function skillLinks(runtimeHome2, providerDirs, present) {
|
|
5610
|
+
const source = `${runtimeHome2}/skills/${SKILL_NAME}`;
|
|
5611
|
+
return providerDirs.filter((dir) => present(dir)).map((providerDir) => ({
|
|
5612
|
+
providerDir,
|
|
5613
|
+
source,
|
|
5614
|
+
target: `${providerDir}/skills/${SKILL_NAME}`
|
|
5615
|
+
}));
|
|
5616
|
+
}
|
|
5617
|
+
function linkHealth(target, runtimeHome2, probe) {
|
|
5618
|
+
const resolved = probe.linkTarget(target);
|
|
5619
|
+
if (resolved === null) {
|
|
5620
|
+
return { state: "absent", target };
|
|
5621
|
+
}
|
|
5622
|
+
if (!probe.exists(resolved)) {
|
|
5623
|
+
return { state: "dangling", target, resolved };
|
|
5624
|
+
}
|
|
5625
|
+
const home = runtimeHome2.replace(/\/+$/, "");
|
|
5626
|
+
return resolved === home || resolved.startsWith(`${home}/`) ? { state: "ok", target, resolved } : { state: "outside-runtime", target, resolved };
|
|
5627
|
+
}
|
|
5628
|
+
function linkHealthMessage(health) {
|
|
5629
|
+
switch (health.state) {
|
|
5630
|
+
case "ok":
|
|
5631
|
+
return `linked → ${health.resolved}`;
|
|
5632
|
+
case "dangling":
|
|
5633
|
+
return `points at ${health.resolved}, which does not exist — re-run \`tlc harness install\``;
|
|
5634
|
+
case "outside-runtime":
|
|
5635
|
+
return `points at ${health.resolved}, outside the runtime — it will break when that path goes`;
|
|
5636
|
+
default:
|
|
5637
|
+
return "not linked — the provider cannot see the init skill";
|
|
5638
|
+
}
|
|
5639
|
+
}
|
|
5640
|
+
|
|
5572
5641
|
// src/core/stagnation/stagnation.resolution.ts
|
|
5573
5642
|
import { existsSync as existsSync20, mkdirSync as mkdirSync11, readFileSync as readFileSync22, writeFileSync as writeFileSync10 } from "node:fs";
|
|
5574
5643
|
import { join as join21 } from "node:path";
|
|
@@ -6696,6 +6765,15 @@ var coreFacade = {
|
|
|
6696
6765
|
readHandoffFile,
|
|
6697
6766
|
readForeignSlices
|
|
6698
6767
|
},
|
|
6768
|
+
shim: {
|
|
6769
|
+
coversHandler,
|
|
6770
|
+
decideShim
|
|
6771
|
+
},
|
|
6772
|
+
skill: {
|
|
6773
|
+
linkHealth,
|
|
6774
|
+
linkHealthMessage,
|
|
6775
|
+
skillLinks
|
|
6776
|
+
},
|
|
6699
6777
|
lesson: {
|
|
6700
6778
|
projectLessonsInjectable,
|
|
6701
6779
|
recordLessonFromFailure,
|
package/dist/run.mjs
CHANGED
|
@@ -276,20 +276,27 @@ function readJson(path) {
|
|
|
276
276
|
return null;
|
|
277
277
|
}
|
|
278
278
|
}
|
|
279
|
-
|
|
279
|
+
function isContention(error) {
|
|
280
|
+
return errorCode(error) === "EEXIST" || isRetryableFsError(error);
|
|
281
|
+
}
|
|
282
|
+
async function withFileLock(lockPath, fn, options = {}) {
|
|
283
|
+
const {
|
|
284
|
+
openLock = (path) => closeSync(openSync(path, "wx")),
|
|
285
|
+
lockSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
286
|
+
lockAttempts = 200
|
|
287
|
+
} = options;
|
|
280
288
|
mkdirSync2(dirname2(lockPath), { recursive: true });
|
|
281
|
-
const attempts = 200;
|
|
282
289
|
let acquired = false;
|
|
283
|
-
for (let attempt = 0;attempt <
|
|
290
|
+
for (let attempt = 0;attempt < lockAttempts; attempt++) {
|
|
284
291
|
try {
|
|
285
|
-
|
|
292
|
+
openLock(lockPath);
|
|
286
293
|
acquired = true;
|
|
287
294
|
break;
|
|
288
295
|
} catch (error) {
|
|
289
|
-
if (
|
|
296
|
+
if (!isContention(error)) {
|
|
290
297
|
throw error;
|
|
291
298
|
}
|
|
292
|
-
await
|
|
299
|
+
await lockSleep(nextDelay({ attempt, baseMs: 10, capMs: 200 }));
|
|
293
300
|
}
|
|
294
301
|
}
|
|
295
302
|
if (!acquired) {
|
|
@@ -304,14 +311,14 @@ async function withFileLock(lockPath, fn) {
|
|
|
304
311
|
}
|
|
305
312
|
}
|
|
306
313
|
async function updateJsonAtomic(path, mutator, options) {
|
|
307
|
-
const { lockPath, afterWrite, ...atomicOptions } = options;
|
|
314
|
+
const { lockPath, afterWrite, openLock, lockSleep, lockAttempts, ...atomicOptions } = options;
|
|
308
315
|
return withFileLock(lockPath, async () => {
|
|
309
316
|
const current = readJson(path);
|
|
310
317
|
const next = mutator(current);
|
|
311
318
|
await writeJsonAtomic(path, next, atomicOptions);
|
|
312
319
|
afterWrite?.(path);
|
|
313
320
|
return next;
|
|
314
|
-
});
|
|
321
|
+
}, { openLock, lockSleep, lockAttempts });
|
|
315
322
|
}
|
|
316
323
|
|
|
317
324
|
// src/core/capability/capability.store.ts
|
|
@@ -5569,6 +5576,68 @@ function evaluateShellCommand(args) {
|
|
|
5569
5576
|
return { kind: "allow" };
|
|
5570
5577
|
}
|
|
5571
5578
|
|
|
5579
|
+
// src/core/shim/shim.precedence.ts
|
|
5580
|
+
var LAUNCHER = "tlc-exec";
|
|
5581
|
+
function invocationText(entry) {
|
|
5582
|
+
return [entry.command ?? "", ...entry.args ?? []].join(" ");
|
|
5583
|
+
}
|
|
5584
|
+
function coversHandler(settings, handler) {
|
|
5585
|
+
for (const matchers of Object.values(settings.hooks ?? {})) {
|
|
5586
|
+
for (const matcher of matchers) {
|
|
5587
|
+
for (const entry of matcher.hooks ?? []) {
|
|
5588
|
+
const text = invocationText(entry);
|
|
5589
|
+
if (text.includes(LAUNCHER) && new RegExp(`(^|\\s)${handler}(\\s|$)`).test(text)) {
|
|
5590
|
+
return true;
|
|
5591
|
+
}
|
|
5592
|
+
}
|
|
5593
|
+
}
|
|
5594
|
+
}
|
|
5595
|
+
return false;
|
|
5596
|
+
}
|
|
5597
|
+
function decideShim(userSettings, handler) {
|
|
5598
|
+
if (userSettings === null) {
|
|
5599
|
+
return { run: true, reason: "no user-level settings — this shim is the only hook for this event" };
|
|
5600
|
+
}
|
|
5601
|
+
return coversHandler(userSettings, handler) ? {
|
|
5602
|
+
run: false,
|
|
5603
|
+
reason: `a user-level hook already runs ${handler} — standing down to avoid a second run`
|
|
5604
|
+
} : { run: true, reason: `no user-level hook runs ${handler}` };
|
|
5605
|
+
}
|
|
5606
|
+
|
|
5607
|
+
// src/core/skill/skill.link.ts
|
|
5608
|
+
var SKILL_NAME = "harness-init";
|
|
5609
|
+
function skillLinks(runtimeHome2, providerDirs, present) {
|
|
5610
|
+
const source = `${runtimeHome2}/skills/${SKILL_NAME}`;
|
|
5611
|
+
return providerDirs.filter((dir) => present(dir)).map((providerDir) => ({
|
|
5612
|
+
providerDir,
|
|
5613
|
+
source,
|
|
5614
|
+
target: `${providerDir}/skills/${SKILL_NAME}`
|
|
5615
|
+
}));
|
|
5616
|
+
}
|
|
5617
|
+
function linkHealth(target, runtimeHome2, probe) {
|
|
5618
|
+
const resolved = probe.linkTarget(target);
|
|
5619
|
+
if (resolved === null) {
|
|
5620
|
+
return { state: "absent", target };
|
|
5621
|
+
}
|
|
5622
|
+
if (!probe.exists(resolved)) {
|
|
5623
|
+
return { state: "dangling", target, resolved };
|
|
5624
|
+
}
|
|
5625
|
+
const home = runtimeHome2.replace(/\/+$/, "");
|
|
5626
|
+
return resolved === home || resolved.startsWith(`${home}/`) ? { state: "ok", target, resolved } : { state: "outside-runtime", target, resolved };
|
|
5627
|
+
}
|
|
5628
|
+
function linkHealthMessage(health) {
|
|
5629
|
+
switch (health.state) {
|
|
5630
|
+
case "ok":
|
|
5631
|
+
return `linked → ${health.resolved}`;
|
|
5632
|
+
case "dangling":
|
|
5633
|
+
return `points at ${health.resolved}, which does not exist — re-run \`tlc harness install\``;
|
|
5634
|
+
case "outside-runtime":
|
|
5635
|
+
return `points at ${health.resolved}, outside the runtime — it will break when that path goes`;
|
|
5636
|
+
default:
|
|
5637
|
+
return "not linked — the provider cannot see the init skill";
|
|
5638
|
+
}
|
|
5639
|
+
}
|
|
5640
|
+
|
|
5572
5641
|
// src/core/stagnation/stagnation.resolution.ts
|
|
5573
5642
|
import { existsSync as existsSync20, mkdirSync as mkdirSync11, readFileSync as readFileSync22, writeFileSync as writeFileSync10 } from "node:fs";
|
|
5574
5643
|
import { join as join21 } from "node:path";
|
|
@@ -6696,6 +6765,15 @@ var coreFacade = {
|
|
|
6696
6765
|
readHandoffFile,
|
|
6697
6766
|
readForeignSlices
|
|
6698
6767
|
},
|
|
6768
|
+
shim: {
|
|
6769
|
+
coversHandler,
|
|
6770
|
+
decideShim
|
|
6771
|
+
},
|
|
6772
|
+
skill: {
|
|
6773
|
+
linkHealth,
|
|
6774
|
+
linkHealthMessage,
|
|
6775
|
+
skillLinks
|
|
6776
|
+
},
|
|
6699
6777
|
lesson: {
|
|
6700
6778
|
projectLessonsInjectable,
|
|
6701
6779
|
recordLessonFromFailure,
|
package/dist/session-end.mjs
CHANGED
|
@@ -276,20 +276,27 @@ function readJson(path) {
|
|
|
276
276
|
return null;
|
|
277
277
|
}
|
|
278
278
|
}
|
|
279
|
-
|
|
279
|
+
function isContention(error) {
|
|
280
|
+
return errorCode(error) === "EEXIST" || isRetryableFsError(error);
|
|
281
|
+
}
|
|
282
|
+
async function withFileLock(lockPath, fn, options = {}) {
|
|
283
|
+
const {
|
|
284
|
+
openLock = (path) => closeSync(openSync(path, "wx")),
|
|
285
|
+
lockSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
286
|
+
lockAttempts = 200
|
|
287
|
+
} = options;
|
|
280
288
|
mkdirSync2(dirname2(lockPath), { recursive: true });
|
|
281
|
-
const attempts = 200;
|
|
282
289
|
let acquired = false;
|
|
283
|
-
for (let attempt = 0;attempt <
|
|
290
|
+
for (let attempt = 0;attempt < lockAttempts; attempt++) {
|
|
284
291
|
try {
|
|
285
|
-
|
|
292
|
+
openLock(lockPath);
|
|
286
293
|
acquired = true;
|
|
287
294
|
break;
|
|
288
295
|
} catch (error) {
|
|
289
|
-
if (
|
|
296
|
+
if (!isContention(error)) {
|
|
290
297
|
throw error;
|
|
291
298
|
}
|
|
292
|
-
await
|
|
299
|
+
await lockSleep(nextDelay({ attempt, baseMs: 10, capMs: 200 }));
|
|
293
300
|
}
|
|
294
301
|
}
|
|
295
302
|
if (!acquired) {
|
|
@@ -304,14 +311,14 @@ async function withFileLock(lockPath, fn) {
|
|
|
304
311
|
}
|
|
305
312
|
}
|
|
306
313
|
async function updateJsonAtomic(path, mutator, options) {
|
|
307
|
-
const { lockPath, afterWrite, ...atomicOptions } = options;
|
|
314
|
+
const { lockPath, afterWrite, openLock, lockSleep, lockAttempts, ...atomicOptions } = options;
|
|
308
315
|
return withFileLock(lockPath, async () => {
|
|
309
316
|
const current = readJson(path);
|
|
310
317
|
const next = mutator(current);
|
|
311
318
|
await writeJsonAtomic(path, next, atomicOptions);
|
|
312
319
|
afterWrite?.(path);
|
|
313
320
|
return next;
|
|
314
|
-
});
|
|
321
|
+
}, { openLock, lockSleep, lockAttempts });
|
|
315
322
|
}
|
|
316
323
|
|
|
317
324
|
// src/core/capability/capability.store.ts
|
|
@@ -5569,6 +5576,68 @@ function evaluateShellCommand(args) {
|
|
|
5569
5576
|
return { kind: "allow" };
|
|
5570
5577
|
}
|
|
5571
5578
|
|
|
5579
|
+
// src/core/shim/shim.precedence.ts
|
|
5580
|
+
var LAUNCHER = "tlc-exec";
|
|
5581
|
+
function invocationText(entry) {
|
|
5582
|
+
return [entry.command ?? "", ...entry.args ?? []].join(" ");
|
|
5583
|
+
}
|
|
5584
|
+
function coversHandler(settings, handler) {
|
|
5585
|
+
for (const matchers of Object.values(settings.hooks ?? {})) {
|
|
5586
|
+
for (const matcher of matchers) {
|
|
5587
|
+
for (const entry of matcher.hooks ?? []) {
|
|
5588
|
+
const text = invocationText(entry);
|
|
5589
|
+
if (text.includes(LAUNCHER) && new RegExp(`(^|\\s)${handler}(\\s|$)`).test(text)) {
|
|
5590
|
+
return true;
|
|
5591
|
+
}
|
|
5592
|
+
}
|
|
5593
|
+
}
|
|
5594
|
+
}
|
|
5595
|
+
return false;
|
|
5596
|
+
}
|
|
5597
|
+
function decideShim(userSettings, handler) {
|
|
5598
|
+
if (userSettings === null) {
|
|
5599
|
+
return { run: true, reason: "no user-level settings — this shim is the only hook for this event" };
|
|
5600
|
+
}
|
|
5601
|
+
return coversHandler(userSettings, handler) ? {
|
|
5602
|
+
run: false,
|
|
5603
|
+
reason: `a user-level hook already runs ${handler} — standing down to avoid a second run`
|
|
5604
|
+
} : { run: true, reason: `no user-level hook runs ${handler}` };
|
|
5605
|
+
}
|
|
5606
|
+
|
|
5607
|
+
// src/core/skill/skill.link.ts
|
|
5608
|
+
var SKILL_NAME = "harness-init";
|
|
5609
|
+
function skillLinks(runtimeHome2, providerDirs, present) {
|
|
5610
|
+
const source = `${runtimeHome2}/skills/${SKILL_NAME}`;
|
|
5611
|
+
return providerDirs.filter((dir) => present(dir)).map((providerDir) => ({
|
|
5612
|
+
providerDir,
|
|
5613
|
+
source,
|
|
5614
|
+
target: `${providerDir}/skills/${SKILL_NAME}`
|
|
5615
|
+
}));
|
|
5616
|
+
}
|
|
5617
|
+
function linkHealth(target, runtimeHome2, probe) {
|
|
5618
|
+
const resolved = probe.linkTarget(target);
|
|
5619
|
+
if (resolved === null) {
|
|
5620
|
+
return { state: "absent", target };
|
|
5621
|
+
}
|
|
5622
|
+
if (!probe.exists(resolved)) {
|
|
5623
|
+
return { state: "dangling", target, resolved };
|
|
5624
|
+
}
|
|
5625
|
+
const home = runtimeHome2.replace(/\/+$/, "");
|
|
5626
|
+
return resolved === home || resolved.startsWith(`${home}/`) ? { state: "ok", target, resolved } : { state: "outside-runtime", target, resolved };
|
|
5627
|
+
}
|
|
5628
|
+
function linkHealthMessage(health) {
|
|
5629
|
+
switch (health.state) {
|
|
5630
|
+
case "ok":
|
|
5631
|
+
return `linked → ${health.resolved}`;
|
|
5632
|
+
case "dangling":
|
|
5633
|
+
return `points at ${health.resolved}, which does not exist — re-run \`tlc harness install\``;
|
|
5634
|
+
case "outside-runtime":
|
|
5635
|
+
return `points at ${health.resolved}, outside the runtime — it will break when that path goes`;
|
|
5636
|
+
default:
|
|
5637
|
+
return "not linked — the provider cannot see the init skill";
|
|
5638
|
+
}
|
|
5639
|
+
}
|
|
5640
|
+
|
|
5572
5641
|
// src/core/stagnation/stagnation.resolution.ts
|
|
5573
5642
|
import { existsSync as existsSync20, mkdirSync as mkdirSync11, readFileSync as readFileSync22, writeFileSync as writeFileSync10 } from "node:fs";
|
|
5574
5643
|
import { join as join21 } from "node:path";
|
|
@@ -6696,6 +6765,15 @@ var coreFacade = {
|
|
|
6696
6765
|
readHandoffFile,
|
|
6697
6766
|
readForeignSlices
|
|
6698
6767
|
},
|
|
6768
|
+
shim: {
|
|
6769
|
+
coversHandler,
|
|
6770
|
+
decideShim
|
|
6771
|
+
},
|
|
6772
|
+
skill: {
|
|
6773
|
+
linkHealth,
|
|
6774
|
+
linkHealthMessage,
|
|
6775
|
+
skillLinks
|
|
6776
|
+
},
|
|
6699
6777
|
lesson: {
|
|
6700
6778
|
projectLessonsInjectable,
|
|
6701
6779
|
recordLessonFromFailure,
|