@tech-leads-club/harness-toolkit 0.15.2 → 0.15.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 +13 -1
- package/dist/compact-before.mjs +91 -80
- package/dist/doctor.mjs +88 -77
- package/dist/init-project.mjs +56 -56
- package/dist/install-runtime.mjs +60 -60
- package/dist/lessons-cli.mjs +53 -53
- package/dist/obs-cli.mjs +62 -62
- package/dist/prompt-submit.mjs +91 -80
- package/dist/refresh-model-prices.mjs +54 -54
- package/dist/response-after.mjs +91 -80
- package/dist/run.mjs +91 -80
- package/dist/session-end.mjs +85 -85
- package/dist/session-start.mjs +85 -85
- package/dist/shim.mjs +57 -57
- package/dist/ship-gate.mjs +70 -70
- package/dist/stop.mjs +94 -83
- package/dist/subagent-start.mjs +91 -80
- package/dist/subagent-stop.mjs +92 -81
- package/dist/support.mjs +86 -86
- package/dist/tlc-cli.mjs +69 -69
- package/dist/tool-after.mjs +93 -82
- package/dist/tool-before.mjs +94 -83
- package/dist/tool-failure.mjs +91 -80
- package/docs/log.md +5 -0
- package/package.json +1 -1
- package/src/core/floor/floor.service.ts +31 -11
- package/src/entrypoints/support.ts +15 -13
- package/src/providers/claude/index.ts +2 -0
- package/src/providers/cursor/index.ts +2 -0
- package/src/providers/index.ts +0 -2
- package/src/providers/provider.port.ts +1 -0
package/docs/log.md
CHANGED
|
@@ -13,6 +13,11 @@ Generated from `docs/decisions/` — do not edit by hand. Run `node tools/render
|
|
|
13
13
|
A reserved file of the [OKF v0.1](/decisions/ad-013.md) bundle: entries grouped under ISO 8601 headings,
|
|
14
14
|
newest first. For what landed in which npm release, see `CHANGELOG.md` at the repository root.
|
|
15
15
|
|
|
16
|
+
## 2026-09-12
|
|
17
|
+
|
|
18
|
+
- **AD-139** — lessonsView joins ProviderPort, closing the one dispatcher that bypassed it ([/decisions/ad-139.md](/decisions/ad-139.md))
|
|
19
|
+
- **AD-140** — an unrecognized shell wrapper can no longer hide a forced push ([/decisions/ad-140.md](/decisions/ad-140.md))
|
|
20
|
+
|
|
16
21
|
## 2026-09-11
|
|
17
22
|
|
|
18
23
|
- **AD-137** — Gate artifacts are scoped per session, the same fix AD-122 already proved for handoff ([/decisions/ad-137.md](/decisions/ad-137.md))
|
package/package.json
CHANGED
|
@@ -145,6 +145,26 @@ function pathArgs(args: ShellWord[]): ShellWord[] {
|
|
|
145
145
|
return args.filter((word) => !word.text.startsWith("-") && word.text !== "");
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
+
// hazard: `verbOf` strips a path down to its basename before comparing (`/usr/bin/git` → `git`) — a
|
|
149
|
+
// literal-text scan that skips the same normalization would deny `git push --force` but allow the identical
|
|
150
|
+
// command run as `/usr/bin/git push --force`, which is exactly the shape a path-qualifying wrapper produces.
|
|
151
|
+
function namesGit(text: string): boolean {
|
|
152
|
+
return (text.split("/").pop() ?? text) === "git";
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// why: `verbOf` only sees through a wrapper this gate already knows by name, and `WRAPPERS` cannot list
|
|
156
|
+
// every proxy or shim an operator's shell might inject ahead of the real command — discovered live when one
|
|
157
|
+
// such wrapper carried a force push straight past this rule. Matching the whole segment, not the resolved
|
|
158
|
+
// head verb, means an unrecognized wrapper can delay `git` but not hide it.
|
|
159
|
+
function forcedGitPush(segment: ShellSegment): boolean {
|
|
160
|
+
const texts = segment.words.map((word) => word.text);
|
|
161
|
+
return (
|
|
162
|
+
texts.some(namesGit) &&
|
|
163
|
+
texts.includes("push") &&
|
|
164
|
+
texts.some((text) => text === "--force" || text === "-f")
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
148
168
|
function checkShell(input: FloorInput): Decision {
|
|
149
169
|
const command = input.command;
|
|
150
170
|
if (!command) {
|
|
@@ -165,6 +185,17 @@ function checkShell(input: FloorInput): Decision {
|
|
|
165
185
|
}
|
|
166
186
|
|
|
167
187
|
for (const segment of segments) {
|
|
188
|
+
// why: checked before `verbOf` resolves anything — a wrapper this gate does not recognize can delay
|
|
189
|
+
// which word `verbOf` treats as the head, or exhaust the segment before a head is ever found, but it
|
|
190
|
+
// cannot remove `git`/`push`/`--force` from the segment's own words.
|
|
191
|
+
if (forcedGitPush(segment)) {
|
|
192
|
+
return denial(
|
|
193
|
+
"history-rewrite",
|
|
194
|
+
"`git push --force` discards remote commits that are not in your history. Use --force-with-lease, which refuses when the remote moved.",
|
|
195
|
+
"force push",
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
168
199
|
const head = verbOf(segment.words);
|
|
169
200
|
if (!head) {
|
|
170
201
|
continue;
|
|
@@ -187,17 +218,6 @@ function checkShell(input: FloorInput): Decision {
|
|
|
187
218
|
return denial("machine-control", `\`${verb}\` controls the machine, not the project.`, verb);
|
|
188
219
|
}
|
|
189
220
|
|
|
190
|
-
if (verb === "git" && args.some((word) => word.text === "push")) {
|
|
191
|
-
const forced = args.some((word) => word.text === "--force" || word.text === "-f");
|
|
192
|
-
if (forced) {
|
|
193
|
-
return denial(
|
|
194
|
-
"history-rewrite",
|
|
195
|
-
"`git push --force` discards remote commits that are not in your history. Use --force-with-lease, which refuses when the remote moved.",
|
|
196
|
-
"force push",
|
|
197
|
-
);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
221
|
const destructive = DESTRUCTIVE_VERBS.has(verb) || isMkfs(verb);
|
|
202
222
|
if (!destructive) {
|
|
203
223
|
continue;
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from "../core/index.ts";
|
|
11
11
|
import { filterCodeTargets, filterTestTargets, gitRootOf, listChangedRepoFiles } from "../platform/git.ts";
|
|
12
12
|
import { runProcess } from "../platform/process.ts";
|
|
13
|
-
import { type ProviderPort,
|
|
13
|
+
import { type ProviderPort, providers } from "../providers/index.ts";
|
|
14
14
|
|
|
15
15
|
// invariant: one definition, taken from core rather than restated.
|
|
16
16
|
export const OBS_CONFIG = coreFacade.observability.DEFAULT_OBS;
|
|
@@ -248,18 +248,20 @@ export function renderLessonLine(lesson: HarnessLesson): string {
|
|
|
248
248
|
return coreFacade.lesson.renderLessonBlock(lesson);
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
251
|
+
// invariant: a single dispatcher, called from both session entrypoints — the durable view is written at
|
|
252
|
+
// session start and again at session end, so a second copy of this logic in either one would repeat the
|
|
253
|
+
// AD-042 defect.
|
|
254
|
+
|
|
255
|
+
// why: a registry lookup, not a name-checking chain — adding a third adapter to `providers` gets it
|
|
256
|
+
// dispatched here for free, exactly as it already gets `detect`/`capabilities`/`render` for free —
|
|
257
|
+
// `ProviderPort` itself, not this function, is what requires `lessonsView` to exist ([/decisions/ad-139.md](/decisions/ad-139.md)).
|
|
258
|
+
export function renderProviderLessonsView(
|
|
259
|
+
providerName: string,
|
|
260
|
+
root: string,
|
|
261
|
+
registry: readonly ProviderPort[] = providers,
|
|
262
|
+
): string | null {
|
|
263
|
+
const provider = registry.find((candidate) => candidate.name === providerName);
|
|
264
|
+
return provider?.lessonsView(root) ?? null;
|
|
263
265
|
}
|
|
264
266
|
|
|
265
267
|
/**
|
|
@@ -2,6 +2,7 @@ import type { ProviderPort } from "../provider.port.ts";
|
|
|
2
2
|
import { claudeCapabilities } from "./claude.capabilities.ts";
|
|
3
3
|
import { detectClaude } from "./claude.detect.ts";
|
|
4
4
|
import { claudeToEvent } from "./claude.inbound.ts";
|
|
5
|
+
import { renderClaudeLessonsView } from "./claude.lessons-view.ts";
|
|
5
6
|
import { claudeRender } from "./claude.outbound.ts";
|
|
6
7
|
import { claudePolicyDefaults } from "./claude.policy-defaults.ts";
|
|
7
8
|
import { claudeWiring, claudeWiringTargets } from "./claude.wiring.ts";
|
|
@@ -15,4 +16,5 @@ export const claudeProvider: ProviderPort = {
|
|
|
15
16
|
render: claudeRender,
|
|
16
17
|
wiring: claudeWiring,
|
|
17
18
|
wiringTargets: claudeWiringTargets,
|
|
19
|
+
lessonsView: renderClaudeLessonsView,
|
|
18
20
|
};
|
|
@@ -2,6 +2,7 @@ import type { ProviderPort } from "../provider.port.ts";
|
|
|
2
2
|
import { cursorCapabilities } from "./cursor.capabilities.ts";
|
|
3
3
|
import { detectCursor } from "./cursor.detect.ts";
|
|
4
4
|
import { cursorToEvent } from "./cursor.inbound.ts";
|
|
5
|
+
import { renderCursorLessonsView } from "./cursor.lessons-view.ts";
|
|
5
6
|
import { cursorRender } from "./cursor.outbound.ts";
|
|
6
7
|
import { cursorPolicyDefaults } from "./cursor.policy-defaults.ts";
|
|
7
8
|
import { cursorWiring, cursorWiringTargets } from "./cursor.wiring.ts";
|
|
@@ -15,4 +16,5 @@ export const cursorProvider: ProviderPort = {
|
|
|
15
16
|
render: cursorRender,
|
|
16
17
|
wiring: cursorWiring,
|
|
17
18
|
wiringTargets: cursorWiringTargets,
|
|
19
|
+
lessonsView: renderCursorLessonsView,
|
|
18
20
|
};
|
package/src/providers/index.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
export { renderClaudeLessonsView } from "./claude/claude.lessons-view.ts";
|
|
2
1
|
export type { TranscriptUsage } from "./claude/claude.transcript.ts";
|
|
3
2
|
export { readClaudeUsage } from "./claude/claude.transcript.ts";
|
|
4
3
|
export { claudeWiring, mergeClaudeSettings } from "./claude/claude.wiring.ts";
|
|
5
|
-
export { renderCursorLessonsView } from "./cursor/cursor.lessons-view.ts";
|
|
6
4
|
export type { DegradeOptions } from "./provider.degrade.ts";
|
|
7
5
|
export { degrade, truncateContext } from "./provider.degrade.ts";
|
|
8
6
|
export type { ProviderPort } from "./provider.port.ts";
|