@tech-leads-club/harness-toolkit 0.6.0 → 0.7.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/bin/tlc-cli.ts CHANGED
@@ -20,6 +20,7 @@ import {
20
20
  flagsDir,
21
21
  isOnPath,
22
22
  launcherBinDir,
23
+ loopsDir,
23
24
  machineHome,
24
25
  projectConfigPath,
25
26
  projectStateDir,
@@ -193,6 +194,32 @@ export function setPaused(root: string, on: boolean): string {
193
194
  return "gates ACTIVE again";
194
195
  }
195
196
 
197
+ /**
198
+ * why: the operator's escape hatch for a stuck gate — `blockers` and `previous_gaps` are project-wide,
199
+ * so a grind-cap or stagnation signal from one session can block every later subagent until either a
200
+ * clean stop clears it or this runs. Denied from inside a session by `policy-surface-write`.
201
+ */
202
+ export async function resetStuckState(root: string): Promise<string> {
203
+ const cleared = await coreFacade.handoff.clearStuckSignals(root);
204
+ const dir = loopsDir(root);
205
+ let loopFiles = 0;
206
+ if (existsSync(dir)) {
207
+ loopFiles = readdirSync(dir).length;
208
+ rmSync(dir, { recursive: true, force: true });
209
+ }
210
+ if (cleared.length === 0 && loopFiles === 0) {
211
+ return "nothing stuck — no blockers and no grind-loop state to clear";
212
+ }
213
+ const parts: string[] = [];
214
+ if (cleared.length > 0) {
215
+ parts.push(`cleared blockers for: ${cleared.join(", ")}`);
216
+ }
217
+ if (loopFiles > 0) {
218
+ parts.push(`reset ${loopFiles} grind-loop counter(s)`);
219
+ }
220
+ return parts.join("; ");
221
+ }
222
+
196
223
  // hazard: this used to map `focus` onto a second spelling before writing, so the word the operator typed and the
197
224
  // word the config field stored were different — and a config written from the documented word then matched no
198
225
  // branch at all. One word per posture, and nothing translates.
@@ -263,14 +290,9 @@ export function handoffScreen(report: HandoffReport): Screen {
263
290
  rows.push({ label, value: String(value), level });
264
291
  }
265
292
  }
266
- for (const [label, list] of [
267
- ["in progress", slice.in_progress],
268
- ["pending", slice.pending],
269
- ["gaps", slice.previous_gaps?.map((gap) => gap.summary)],
270
- ] as const) {
271
- if (list && list.length > 0) {
272
- rows.push({ label, value: list.slice(0, 6).join(" | ") });
273
- }
293
+ const gaps = slice.previous_gaps?.map((gap) => gap.summary);
294
+ if (gaps && gaps.length > 0) {
295
+ rows.push({ label: "gaps", value: gaps.slice(0, 6).join(" | ") });
274
296
  }
275
297
  sections.push({ title: `${name} (updated ${slice.updated_at})`, rows });
276
298
  }
@@ -863,6 +885,7 @@ TOPICS
863
885
 
864
886
  CONTROL
865
887
  tlc harness grind [on|off] tlc harness pause | resume tlc harness mode solo|paired|focus
888
+ tlc harness reset clear a stuck blocker/grind-loop signal from the handoff
866
889
  tlc harness gate test-command <cmd> [args...] tlc harness gate lint-command <cmd> [args...]
867
890
  tlc harness attest tamper-evident record of what each session ran under
868
891
  tlc harness policy show a policy that changed out of band; accept <path> to clear it
@@ -1128,6 +1151,7 @@ export type Action =
1128
1151
  | { kind: "grind"; on: boolean }
1129
1152
  | { kind: "pause" }
1130
1153
  | { kind: "resume" }
1154
+ | { kind: "reset" }
1131
1155
  | { kind: "mode"; value: string }
1132
1156
  | { kind: "gate"; field: GateField; argv: string[] }
1133
1157
  | { kind: "attest" }
@@ -1193,6 +1217,8 @@ export function route(args: string[]): Action {
1193
1217
  case "resume":
1194
1218
  case "r":
1195
1219
  return { kind: "resume" };
1220
+ case "reset":
1221
+ return { kind: "reset" };
1196
1222
  case "mode":
1197
1223
  case "m": {
1198
1224
  const modeArg = args[1];
@@ -1612,13 +1638,13 @@ function runInstall(toolArgs: string[], root: string): never {
1612
1638
  process.exit(r.status ?? 1);
1613
1639
  }
1614
1640
 
1615
- function main(argv: string[]): void {
1641
+ async function main(argv: string[]): Promise<void> {
1616
1642
  const root = resolveProjectRoot();
1617
1643
  const group = (argv[0] ?? "").toLowerCase();
1618
1644
  if (group !== "harness") {
1619
1645
  console.error(`unknown: ${argv[0] ?? ""}`);
1620
1646
  console.error(
1621
- "usage: tlc harness <status|doctor|help|grind|pause|resume|mode|obs|prices|lessons|init|update|test|build>",
1647
+ "usage: tlc harness <status|doctor|help|grind|pause|resume|reset|mode|obs|prices|lessons|init|update|test|build>",
1622
1648
  );
1623
1649
  process.exit(1);
1624
1650
  }
@@ -1744,6 +1770,9 @@ function main(argv: string[]): void {
1744
1770
  case "resume":
1745
1771
  console.log(setPaused(root, false));
1746
1772
  break;
1773
+ case "reset":
1774
+ console.log(await resetStuckState(root));
1775
+ break;
1747
1776
  case "mode":
1748
1777
  try {
1749
1778
  console.log(setMode(root, action.value));
@@ -1794,5 +1823,8 @@ function main(argv: string[]): void {
1794
1823
  }
1795
1824
 
1796
1825
  if (import.meta.main) {
1797
- main(process.argv.slice(2));
1826
+ main(process.argv.slice(2)).catch((error) => {
1827
+ console.error(error instanceof Error ? error.message : String(error));
1828
+ process.exit(1);
1829
+ });
1798
1830
  }