@tech-leads-club/harness-toolkit 0.16.3 → 0.16.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.
@@ -1,4 +1,4 @@
1
- import{createRequire as _$}from"node:module";var i=_$(import.meta.url);import{existsSync as B,lstatSync as c8,readFileSync as S,readlinkSync as d8,realpathSync as M$,rmSync as l8,statSync as i8,unlinkSync as n8,writeFileSync as a8}from"node:fs";import{homedir as s8}from"node:os";import{basename as r8,dirname as O$,isAbsolute as F$,join as G,relative as o8,resolve as p}from"node:path";import{homedir as L}from"node:os";import{delimiter as FZ,dirname as CZ,join as D,resolve as IZ}from"node:path";function n(){return D(L(),".tlc","harness")}function C($=process.env){return $.TLC_HOME??n()}function T(){let $=process.env.CLAUDE_CONFIG_DIR?.trim();return $&&$.length>0?$:D(L(),".claude")}function A(){let $=process.env.CURSOR_CONFIG_DIR?.trim();return $&&$.length>0?$:D(L(),".cursor")}function b($=process.env){return $.TLC_BIN_DIR?.trim()||D(L(),".local","bin")}function a(){return["tlc","tlc.cmd"]}var s=[{id:"typescript",extensions:[".ts",".tsx",".mts",".cts",".js",".jsx",".mjs",".cjs"],line:["//"],block:[["/*","*/"]],middle:["*"]},{id:"python",extensions:[".py",".pyi",".pyw"],line:["#"],block:[['"""','"""'],["'''","'''"]],middle:[]},{id:"ruby",extensions:[".rb",".rake",".gemspec"],line:["#"],block:[["=begin","=end"]],middle:[]},{id:"shell",extensions:[".sh",".bash",".zsh",".ksh",".fish"],line:["#"],block:[],middle:[]},{id:"go",extensions:[".go"],line:["//"],block:[["/*","*/"]],middle:["*"]},{id:"rust",extensions:[".rs"],line:["//"],block:[["/*","*/"]],middle:["*"]},{id:"java",extensions:[".java"],line:["//"],block:[["/*","*/"]],middle:["*"]},{id:"kotlin",extensions:[".kt",".kts"],line:["//"],block:[["/*","*/"]],middle:["*"]},{id:"swift",extensions:[".swift"],line:["//"],block:[["/*","*/"]],middle:["*"]},{id:"c",extensions:[".c",".h"],line:["//"],block:[["/*","*/"]],middle:["*"]},{id:"cpp",extensions:[".cc",".cpp",".cxx",".hpp",".hh",".hxx"],line:["//"],block:[["/*","*/"]],middle:["*"]},{id:"csharp",extensions:[".cs"],line:["//"],block:[["/*","*/"]],middle:["*"]},{id:"php",extensions:[".php"],line:["//","#"],block:[["/*","*/"]],middle:["*"]},{id:"scala",extensions:[".scala",".sc"],line:["//"],block:[["/*","*/"]],middle:["*"]},{id:"dart",extensions:[".dart"],line:["//"],block:[["/*","*/"]],middle:["*"]},{id:"elixir",extensions:[".ex",".exs"],line:["#"],block:[],middle:[]},{id:"erlang",extensions:[".erl",".hrl"],line:["%"],block:[],middle:[]},{id:"haskell",extensions:[".hs"],line:["--"],block:[["{-","-}"]],middle:[]},{id:"lua",extensions:[".lua"],line:["--"],block:[["--[[","]]"]],middle:[]},{id:"sql",extensions:[".sql"],line:["--"],block:[["/*","*/"]],middle:[]},{id:"css",extensions:[".css",".scss",".sass",".less"],line:["//"],block:[["/*","*/"]],middle:[]},{id:"yaml",extensions:[".yaml",".yml"],line:["#"],block:[],middle:[]},{id:"toml",extensions:[".toml"],line:["#"],block:[],middle:[]},{id:"ini",extensions:[".ini",".cfg",".conf",".properties"],line:[";","#"],block:[],middle:[]},{id:"dockerfile",extensions:[".dockerfile","dockerfile"],line:["#"],block:[],middle:[]},{id:"makefile",extensions:[".mk","makefile"],line:["#"],block:[],middle:[]},{id:"terraform",extensions:[".tf",".tfvars"],line:["#","//"],block:[["/*","*/"]],middle:["*"]},{id:"powershell",extensions:[".ps1",".psm1",".psd1"],line:["#"],block:[["<#","#>"]],middle:[]},{id:"perl",extensions:[".pl",".pm"],line:["#"],block:[],middle:[]},{id:"r",extensions:[".r"],line:["#"],block:[],middle:[]},{id:"julia",extensions:[".jl"],line:["#"],block:[["#=","=#"]],middle:[]},{id:"vue",extensions:[".vue",".svelte"],line:["//"],block:[["/*","*/"],["<!--","-->"]],middle:["*"]},{id:"html",extensions:[".html",".htm",".xml",".xhtml"],line:[],block:[["<!--","-->"]],middle:[]},{id:"graphql",extensions:[".graphql",".gql"],line:["#"],block:[],middle:[]},{id:"protobuf",extensions:[".proto"],line:["//"],block:[["/*","*/"]],middle:["*"]},{id:"zig",extensions:[".zig"],line:["//"],block:[],middle:[]},{id:"clojure",extensions:[".clj",".cljs",".cljc",".edn"],line:[";"],block:[],middle:[]},{id:"ocaml",extensions:[".ml",".mli"],line:[],block:[["(*","*)"]],middle:["*"]},{id:"fsharp",extensions:[".fs",".fsi",".fsx"],line:["//"],block:[["(*","*)"]],middle:["*"]},{id:"vim",extensions:[".vim"],line:['"'],block:[],middle:[]},{id:"tex",extensions:[".tex",".sty",".cls"],line:["%"],block:[],middle:[]}];function D$($){let Z=new Map;for(let J of $){let Q={line:J.line,block:J.block,middle:J.middle};for(let q of J.extensions)Z.set(q.toLowerCase(),Q)}return Z}var T$=D$(s);var A$=T$.size;var v=6;var S$=new Set(["aria2c","curl","fetch","http","httpie","https","wget"]);var L5=new Set([...S$,"nc","ncat","socat","telnet","lwp-request"]);var b$="ts|tsx|mts|cts|js|jsx|mjs|cjs|py|go|rb|rs|java|kt|swift|php|sh|sql",A5=new RegExp(`(?:file://)?((?:[A-Za-z]:)?[\\w./~@+-]*[\\w-]\\.(?:${b$}))(?=[:)\\s,'"\`]|$)`,"g");var R="1970-01-01T00:00:00.000Z";function O($){return{...$,scope:"gate-execution",source:"core",tier:"core",status:"active",confidence:1,hitCount:1,pinned:!1,refs:[],sessionKeys:[],injectedCount:0,gradeableCount:0,helpedCount:0,neutralCount:0,firstSeenAt:R,lastSeenAt:R,lastAccessedAt:R,updatedAt:R}}var r9=[O({id:"core:gate:lint",failedGate:"lint",category:"verification",triggerTokens:["lint","biome","eslint","ruff","format"],instruction:"A lint gate failure means changed files still violate the project lint command. Fix the reported findings without suppressions.",avoid:"Do not add lint suppressions, disable comments, or delete failing files to silence the gate.",prefer:"Apply the smallest fix that clears each finding, then let the stop hook re-check.",preRetryCheck:"Confirm the lint command targets only the intended changed files and still fails for the same codes.",priority:90}),O({id:"core:gate:test",failedGate:"test",category:"verification",triggerTokens:["test","vitest","jest","pytest","failing"],instruction:"A test gate failure means assertions still fail. Fix the behavior or the test under the real contract — do not delete or skip tests.",avoid:"Do not delete failing tests, mark them skipped, or weaken assertions to force green.",prefer:"Reproduce the failure, fix root cause, re-run the same test target.",preRetryCheck:"Identify the failing test name/file from the gate output before editing.",priority:90}),O({id:"core:gate:comments",failedGate:"comments",category:"verification",triggerTokens:["junk comment","TODO","FIXME","banner"],instruction:"Junk-comment policy failed. Delete narrating comments, banners, TODO/FIXME, and commented-out code.",avoid:"Do not keep TODO markers or section banners 'for clarity'.",prefer:"Keep only comments that explain a non-obvious why (invariant, hazard, external constraint).",preRetryCheck:"Scan the listed file:line hits and remove each one.",priority:80}),O({id:"core:gate:ship",failedGate:"ship",category:"ship-evidence",triggerTokens:["ship","evidence","90-verdict","PASS"],instruction:"Ship claim without recent production PASS evidence. Produce real evidence before claiming done.",avoid:"Do not claim shipped based on unit tests alone when runtime paths changed.",prefer:"Run production E2E, write 90-verdict.txt PASS, cite the evidence path.",preRetryCheck:"Confirm evidenceDir and a recent PASS verdict exist for this change.",priority:95}),O({id:"core:gate:empty-diff",failedGate:"empty-diff",category:"ship-evidence",triggerTokens:["empty","diff","no changes","shipped"],instruction:"Done/shipped was claimed with zero file changes. Either implement the work or explain why zero-diff is correct — do not claim shipped on an empty tree.",avoid:"Do not restate 'done' without a real diff or an explicit zero-change justification.",prefer:"Make the missing change, or clearly document why no files should change.",preRetryCheck:"Inspect git status / changed files before the next stop.",priority:92}),O({id:"core:gate:stagnation",failedGate:"stagnation",category:"stagnation",triggerTokens:["stagnation","identical","fingerprint","same fail"],instruction:"Identical validation fingerprint repeated. Change approach — do not re-apply the same failing edit.",avoid:"Do not retry the exact same patch, command, or suppression.",prefer:"Diagnose root cause with a different path, or escalate with BLOCKED / TRIED / NEED.",preRetryCheck:"Diff your last edit against the gate output; ensure the next action is different.",priority:100})];var n$={structure:"#3d3a4a",accent:"#a78bfa",success:"#6ee7b7",warning:"#d4a574",error:"#f87171",info:"#93c5fd",textMain:"#f5f5f7",textMuted:"#9ca3af",textDim:"#6b7280"},V={check:"✔",cross:"✖",warning:"⚠",arrow:"→",arrowRight:"▸",dot:"•",bar:"│",rule:"══",dash:"──"};function a$($){let Z=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec($.trim());if(!Z)return"255;255;255";return[Z[1],Z[2],Z[3]].map((J)=>Number.parseInt(J,16)).join(";")}var X$=String.fromCharCode(27),s$=`${X$}[0m`;function r$($=process.env,Z=process.argv,J=process.stdout.isTTY===!0){if("NO_COLOR"in $)return!1;if(Z.includes("--no-color"))return!1;return J}var o$={ok:"success",warn:"warning",fail:"error",info:"info"},t$={ok:V.check,warn:V.warning,fail:V.cross,info:V.arrowRight},g=16;function E($=r$()){let Z=(Q,q)=>$?`${X$}[${Q}m${q}${s$}`:q,J=(Q,q)=>Z(`38;2;${a$(n$[Q])}`,q);return{enabled:$,paint:J,bold:(Q)=>Z("1",Q),dim:(Q)=>J("textDim",Q),heading:(Q)=>J("accent",`${V.rule} ${Q} ${V.rule}`),footer:(Q)=>J("textDim",`${V.dash} ${Q} ${V.dash}`),kv:(Q,q,X=g)=>` ${J("textMuted",`${Q}:`.padEnd(X))} ${q}`,status:(Q,q)=>`${J(o$[Q],t$[Q])} ${q}`}}var F=E(!1);var e$=2;function $8($=process.stdout.columns){return Math.min(110,Math.max(60,$??100))}function Z8($,Z){if($==="")return[""];let J=[],Q="";for(let q of $.split(/\s+/).filter((X)=>X.length>0)){if(Q===""){Q=q;continue}if(Q.length+1+q.length<=Z){Q=`${Q} ${q}`;continue}J.push(Q),Q=q}if(Q!=="")J.push(Q);return J.length===0?[""]:J}function I($,Z,J=$8()){let Q=[Z.heading($.title.toUpperCase())];if($.summary&&$.summary.length>0)Q.push(` ${$.summary.join(Z.dim(` ${V.bar} `))}`);let q=Math.max(g,...$.sections.flatMap((X)=>(X.rows??[]).map((z)=>z.label.length+1)));for(let X of $.sections){if(Q.push(""),X.title)Q.push(Z.paint("accent",X.title));for(let z of X.rows??[]){let W=z.level?Z.status(z.level,z.value):z.value;Q.push(Z.kv(z.label,W,q))}for(let z of X.lines??[]){if(z===""){Q.push("");continue}let W=X.wrap?Z8(z,J-e$):[z];for(let U of W)Q.push(` ${U}`)}}if($.footer)Q.push("",Z.footer($.footer));return Q.join(`
1
+ import{createRequire as _$}from"node:module";var i=_$(import.meta.url);import{existsSync as B,lstatSync as d8,readFileSync as S,readlinkSync as l8,realpathSync as M$,rmSync as i8,statSync as n8,unlinkSync as a8,writeFileSync as s8}from"node:fs";import{homedir as r8}from"node:os";import{basename as o8,dirname as O$,isAbsolute as F$,join as G,relative as t8,resolve as p}from"node:path";import{homedir as D}from"node:os";import{delimiter as CZ,dirname as IZ,join as T,resolve as _Z}from"node:path";function n(){return T(D(),".tlc","harness")}function C($=process.env){return $.TLC_HOME??n()}function A(){let $=process.env.CLAUDE_CONFIG_DIR?.trim();return $&&$.length>0?$:T(D(),".claude")}function P(){let $=process.env.CURSOR_CONFIG_DIR?.trim();return $&&$.length>0?$:T(D(),".cursor")}function b($=process.env){return $.TLC_BIN_DIR?.trim()||T(D(),".local","bin")}function a(){return["tlc","tlc.cmd"]}var s=[{id:"typescript",extensions:[".ts",".tsx",".mts",".cts",".js",".jsx",".mjs",".cjs"],line:["//"],block:[["/*","*/"]],middle:["*"]},{id:"python",extensions:[".py",".pyi",".pyw"],line:["#"],block:[['"""','"""'],["'''","'''"]],middle:[]},{id:"ruby",extensions:[".rb",".rake",".gemspec"],line:["#"],block:[["=begin","=end"]],middle:[]},{id:"shell",extensions:[".sh",".bash",".zsh",".ksh",".fish"],line:["#"],block:[],middle:[]},{id:"go",extensions:[".go"],line:["//"],block:[["/*","*/"]],middle:["*"]},{id:"rust",extensions:[".rs"],line:["//"],block:[["/*","*/"]],middle:["*"]},{id:"java",extensions:[".java"],line:["//"],block:[["/*","*/"]],middle:["*"]},{id:"kotlin",extensions:[".kt",".kts"],line:["//"],block:[["/*","*/"]],middle:["*"]},{id:"swift",extensions:[".swift"],line:["//"],block:[["/*","*/"]],middle:["*"]},{id:"c",extensions:[".c",".h"],line:["//"],block:[["/*","*/"]],middle:["*"]},{id:"cpp",extensions:[".cc",".cpp",".cxx",".hpp",".hh",".hxx"],line:["//"],block:[["/*","*/"]],middle:["*"]},{id:"csharp",extensions:[".cs"],line:["//"],block:[["/*","*/"]],middle:["*"]},{id:"php",extensions:[".php"],line:["//","#"],block:[["/*","*/"]],middle:["*"]},{id:"scala",extensions:[".scala",".sc"],line:["//"],block:[["/*","*/"]],middle:["*"]},{id:"dart",extensions:[".dart"],line:["//"],block:[["/*","*/"]],middle:["*"]},{id:"elixir",extensions:[".ex",".exs"],line:["#"],block:[],middle:[]},{id:"erlang",extensions:[".erl",".hrl"],line:["%"],block:[],middle:[]},{id:"haskell",extensions:[".hs"],line:["--"],block:[["{-","-}"]],middle:[]},{id:"lua",extensions:[".lua"],line:["--"],block:[["--[[","]]"]],middle:[]},{id:"sql",extensions:[".sql"],line:["--"],block:[["/*","*/"]],middle:[]},{id:"css",extensions:[".css",".scss",".sass",".less"],line:["//"],block:[["/*","*/"]],middle:[]},{id:"yaml",extensions:[".yaml",".yml"],line:["#"],block:[],middle:[]},{id:"toml",extensions:[".toml"],line:["#"],block:[],middle:[]},{id:"ini",extensions:[".ini",".cfg",".conf",".properties"],line:[";","#"],block:[],middle:[]},{id:"dockerfile",extensions:[".dockerfile","dockerfile"],line:["#"],block:[],middle:[]},{id:"makefile",extensions:[".mk","makefile"],line:["#"],block:[],middle:[]},{id:"terraform",extensions:[".tf",".tfvars"],line:["#","//"],block:[["/*","*/"]],middle:["*"]},{id:"powershell",extensions:[".ps1",".psm1",".psd1"],line:["#"],block:[["<#","#>"]],middle:[]},{id:"perl",extensions:[".pl",".pm"],line:["#"],block:[],middle:[]},{id:"r",extensions:[".r"],line:["#"],block:[],middle:[]},{id:"julia",extensions:[".jl"],line:["#"],block:[["#=","=#"]],middle:[]},{id:"vue",extensions:[".vue",".svelte"],line:["//"],block:[["/*","*/"],["<!--","-->"]],middle:["*"]},{id:"html",extensions:[".html",".htm",".xml",".xhtml"],line:[],block:[["<!--","-->"]],middle:[]},{id:"graphql",extensions:[".graphql",".gql"],line:["#"],block:[],middle:[]},{id:"protobuf",extensions:[".proto"],line:["//"],block:[["/*","*/"]],middle:["*"]},{id:"zig",extensions:[".zig"],line:["//"],block:[],middle:[]},{id:"clojure",extensions:[".clj",".cljs",".cljc",".edn"],line:[";"],block:[],middle:[]},{id:"ocaml",extensions:[".ml",".mli"],line:[],block:[["(*","*)"]],middle:["*"]},{id:"fsharp",extensions:[".fs",".fsi",".fsx"],line:["//"],block:[["(*","*)"]],middle:["*"]},{id:"vim",extensions:[".vim"],line:['"'],block:[],middle:[]},{id:"tex",extensions:[".tex",".sty",".cls"],line:["%"],block:[],middle:[]}];function D$($){let Z=new Map;for(let J of $){let Q={line:J.line,block:J.block,middle:J.middle};for(let q of J.extensions)Z.set(q.toLowerCase(),Q)}return Z}var T$=D$(s);var A$=T$.size;var v=6;var S$=new Set(["aria2c","curl","fetch","http","httpie","https","wget"]);var D5=new Set([...S$,"nc","ncat","socat","telnet","lwp-request"]);var b$="ts|tsx|mts|cts|js|jsx|mjs|cjs|py|go|rb|rs|java|kt|swift|php|sh|sql",P5=new RegExp(`(?:file://)?((?:[A-Za-z]:)?[\\w./~@+-]*[\\w-]\\.(?:${b$}))(?=[:)\\s,'"\`]|$)`,"g");var R="1970-01-01T00:00:00.000Z";function O($){return{...$,scope:"gate-execution",source:"core",tier:"core",status:"active",confidence:1,hitCount:1,pinned:!1,refs:[],sessionKeys:[],injectedCount:0,gradeableCount:0,helpedCount:0,neutralCount:0,firstSeenAt:R,lastSeenAt:R,lastAccessedAt:R,updatedAt:R}}var o9=[O({id:"core:gate:lint",failedGate:"lint",category:"verification",triggerTokens:["lint","biome","eslint","ruff","format"],instruction:"A lint gate failure means changed files still violate the project lint command. Fix the reported findings without suppressions.",avoid:"Do not add lint suppressions, disable comments, or delete failing files to silence the gate.",prefer:"Apply the smallest fix that clears each finding, then let the stop hook re-check.",preRetryCheck:"Confirm the lint command targets only the intended changed files and still fails for the same codes.",priority:90}),O({id:"core:gate:test",failedGate:"test",category:"verification",triggerTokens:["test","vitest","jest","pytest","failing"],instruction:"A test gate failure means assertions still fail. Fix the behavior or the test under the real contract — do not delete or skip tests.",avoid:"Do not delete failing tests, mark them skipped, or weaken assertions to force green.",prefer:"Reproduce the failure, fix root cause, re-run the same test target.",preRetryCheck:"Identify the failing test name/file from the gate output before editing.",priority:90}),O({id:"core:gate:comments",failedGate:"comments",category:"verification",triggerTokens:["junk comment","TODO","FIXME","banner"],instruction:"Junk-comment policy failed. Delete narrating comments, banners, TODO/FIXME, and commented-out code.",avoid:"Do not keep TODO markers or section banners 'for clarity'.",prefer:"Keep only comments that explain a non-obvious why (invariant, hazard, external constraint).",preRetryCheck:"Scan the listed file:line hits and remove each one.",priority:80}),O({id:"core:gate:ship",failedGate:"ship",category:"ship-evidence",triggerTokens:["ship","evidence","90-verdict","PASS"],instruction:"Ship claim without recent production PASS evidence. Produce real evidence before claiming done.",avoid:"Do not claim shipped based on unit tests alone when runtime paths changed.",prefer:"Run production E2E, write 90-verdict.txt PASS, cite the evidence path.",preRetryCheck:"Confirm evidenceDir and a recent PASS verdict exist for this change.",priority:95}),O({id:"core:gate:empty-diff",failedGate:"empty-diff",category:"ship-evidence",triggerTokens:["empty","diff","no changes","shipped"],instruction:"Done/shipped was claimed with zero file changes. Either implement the work or explain why zero-diff is correct — do not claim shipped on an empty tree.",avoid:"Do not restate 'done' without a real diff or an explicit zero-change justification.",prefer:"Make the missing change, or clearly document why no files should change.",preRetryCheck:"Inspect git status / changed files before the next stop.",priority:92}),O({id:"core:gate:stagnation",failedGate:"stagnation",category:"stagnation",triggerTokens:["stagnation","identical","fingerprint","same fail"],instruction:"Identical validation fingerprint repeated. Change approach — do not re-apply the same failing edit.",avoid:"Do not retry the exact same patch, command, or suppression.",prefer:"Diagnose root cause with a different path, or escalate with BLOCKED / TRIED / NEED.",preRetryCheck:"Diff your last edit against the gate output; ensure the next action is different.",priority:100})];var n$={structure:"#3d3a4a",accent:"#a78bfa",success:"#6ee7b7",warning:"#d4a574",error:"#f87171",info:"#93c5fd",textMain:"#f5f5f7",textMuted:"#9ca3af",textDim:"#6b7280"},V={check:"✔",cross:"✖",warning:"⚠",arrow:"→",arrowRight:"▸",dot:"•",bar:"│",rule:"══",dash:"──"};function a$($){let Z=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec($.trim());if(!Z)return"255;255;255";return[Z[1],Z[2],Z[3]].map((J)=>Number.parseInt(J,16)).join(";")}var X$=String.fromCharCode(27),s$=`${X$}[0m`;function r$($=process.env,Z=process.argv,J=process.stdout.isTTY===!0){if("NO_COLOR"in $)return!1;if(Z.includes("--no-color"))return!1;return J}var o$={ok:"success",warn:"warning",fail:"error",info:"info"},t$={ok:V.check,warn:V.warning,fail:V.cross,info:V.arrowRight},g=16;function E($=r$()){let Z=(Q,q)=>$?`${X$}[${Q}m${q}${s$}`:q,J=(Q,q)=>Z(`38;2;${a$(n$[Q])}`,q);return{enabled:$,paint:J,bold:(Q)=>Z("1",Q),dim:(Q)=>J("textDim",Q),heading:(Q)=>J("accent",`${V.rule} ${Q} ${V.rule}`),footer:(Q)=>J("textDim",`${V.dash} ${Q} ${V.dash}`),kv:(Q,q,X=g)=>` ${J("textMuted",`${Q}:`.padEnd(X))} ${q}`,status:(Q,q)=>`${J(o$[Q],t$[Q])} ${q}`}}var F=E(!1);var e$=2;function $8($=process.stdout.columns){return Math.min(110,Math.max(60,$??100))}function Z8($,Z){if($==="")return[""];let J=[],Q="";for(let q of $.split(/\s+/).filter((X)=>X.length>0)){if(Q===""){Q=q;continue}if(Q.length+1+q.length<=Z){Q=`${Q} ${q}`;continue}J.push(Q),Q=q}if(Q!=="")J.push(Q);return J.length===0?[""]:J}function _($,Z,J=$8()){let Q=[Z.heading($.title.toUpperCase())];if($.summary&&$.summary.length>0)Q.push(` ${$.summary.join(Z.dim(` ${V.bar} `))}`);let q=Math.max(g,...$.sections.flatMap((X)=>(X.rows??[]).map((W)=>W.label.length+1)));for(let X of $.sections){if(Q.push(""),X.title)Q.push(Z.paint("accent",X.title));for(let W of X.rows??[]){let z=W.level?Z.status(W.level,W.value):W.value;Q.push(Z.kv(W.label,z,q))}for(let W of X.lines??[]){if(W===""){Q.push("");continue}let z=X.wrap?Z8(W,J-e$):[W];for(let U of z)Q.push(` ${U}`)}}if($.footer)Q.push("",Z.footer($.footer));return Q.join(`
2
2
  `)}var H={enabled:!0,signalPath:"obs.jsonl",debugPath:"debug.jsonl",debugEnabled:!1,includePayloads:!1,maxAttrChars:500,sessionCostAlertUsd:5,retentionDays:14,maxSignalEvents:50000,globalSpool:!1};var J8=["No harness decision in this window.","Whatever you just saw was the model, not a rail — the harness allowed everything it was asked about."].join(`
3
- `);var q8={enabled:!1,maxInjectSession:5,maxInjectRetry:8,maxCharsSession:900,maxCharsRetry:1400,promoteHitCount:2,decayLambda:0.02,projectBoost:1.5,syncRulesFile:"auto",gardenOnSessionEnd:!0},z$={version:1,mode:"solo",codePaths:["src","apps","libs","packages"],grind:{enabled:!1,maxLoops:5,lintCommand:null,testCommand:null,appendFiles:"auto"},shipGate:{enabled:!1,runtimePathPrefixes:["src","apps","libs","packages","deploy","scripts"],runtimePathExcludes:[".tlc/","**/node_modules/","**/.git/"],evidenceDir:null,evidenceMaxAgeHours:48,emptyDiffAntiShip:!1,claimWindowMinutes:10},subagents:{enforceAllowlist:!1,requireModel:!1,allowedModels:[],blockedPatterns:["-fast(?:$|[^a-z0-9])","/fast(?:$|[^a-z0-9])"],minEffort:null,blockParentFast:!1,blockMode:"deny",readOnlyTypes:["explore"]},docs:{command:null,severity:"warn"},observe:{enabled:!1,rails:[]},comments:{enabled:!1,onViolation:"followup",mode:"declared"},supplyChain:{enabled:!1},duplication:{enabled:!1,minRun:v},obs:{globalSpool:!1,includePayloads:H.includePayloads,maxAttrChars:H.maxAttrChars,sessionCostAlertUsd:H.sessionCostAlertUsd,retentionDays:H.retentionDays},untrustedContent:{mode:"frame",enabled:!1,extraTools:[],extraCommandPatterns:[]},planGate:{enabled:!1,windowMinutes:120},rules:{enabled:!1},shell:{catastrophicAsk:!0,stallDetection:!1,stallRepeatThreshold:3},secrets:{redactOutput:!0},intelligence:{gapFeedback:!0,failureClassification:!0,progressiveHandoff:!0,progressiveContext:!0,autopilot:!0,idleTurnGate:!1,budgetContinue:!1,budgetContinueAfterLoops:3,lessons:{...q8}},mcpPrime:[],bootstrapExtra:[]};var _="@tech-leads-club/harness-toolkit",k="installed-from-npm";import{existsSync as v8,mkdirSync as oX,readFileSync as m8,realpathSync as tX,writeFileSync as g8}from"node:fs";function W$($){return $!==null&&typeof $==="object"&&!Array.isArray($)}function u8($){return W$($)}var h8="tlc-exec.mjs";function p8($){return JSON.stringify($??null).includes(h8)}function u($){if($===null||$.trim()==="")return{ok:!0,settingsText:"",changed:!1};let Z;try{Z=JSON.parse($)}catch(z){return{ok:!1,error:z instanceof Error?z.message:String(z),block:""}}if(!W$(Z))return{ok:!1,error:"settings.json root is not a JSON object",block:""};let J=u8(Z.hooks)?Z.hooks:{},Q={},q=!1;for(let[z,W]of Object.entries(J)){let U=W.filter((N)=>!p8(N));if(U.length!==W.length)q=!0;if(U.length>0)Q[z]=U}let X={};for(let[z,W]of Object.entries(Z))if(z!=="hooks")X[z]=W;else if(Object.keys(Q).length>0)X.hooks=Q;return{ok:!0,settingsText:JSON.stringify(X,null,2),changed:q}}function Y$($){if(!v8($))return{ok:!0,settingsText:"",changed:!1};let Z=u(m8($,"utf8"));if(Z.ok&&Z.changed)g8($,Z.settingsText,"utf8");return Z}function h($,Z="tlc-exec.mjs"){if($===null||$.trim()==="")return{kind:"absent"};let J;try{J=JSON.parse($)}catch{return{kind:"unparsed"}}if(J===null||typeof J!=="object"||Array.isArray(J))return{kind:"unparsed"};let Q=J,q=Q.hooks!==null&&typeof Q.hooks==="object"&&!Array.isArray(Q.hooks)?Q.hooks:{},X={},z=0,W=0;for(let[U,N]of Object.entries(q)){let l=Array.isArray(N)?N:[],j=l.filter((I$)=>!JSON.stringify(I$??null).includes(Z));if(z+=l.length-j.length,j.length>0)X[U]=j,W+=j.length}if(W===0)return{kind:"empty",removed:z};return{kind:"rewritten",removed:z,text:`${JSON.stringify({...Q,hooks:X},null,2)}
4
- `}}import{join as V$,relative as X4,resolve as z4,sep as W4}from"node:path";var G$=["bin","capabilities","dist","docs","skills","src","tools","config.example.json","package.json"],B$=["config.json","state","flags","rules"],M4=[V$("tools","dev"),V$("tools","__test__")];function t8($=process.env){let Z=s8(),J=b($);return{home:C($),binLinks:a().map((Q)=>G(J,Q)),claudeSettings:G(T(),"settings.json"),cursorHooks:G(A(),"hooks.json"),skillLinks:[G(T(),"skills","harness-init"),G(A(),"skills","harness-init"),G(Z,".tlc","skills","harness-init")]}}function c($){try{return c8($).isSymbolicLink()}catch{return!1}}function d($){let Z=p($),J=[];for(;;)try{return G(M$(Z),...J)}catch{let Q=O$(Z);if(Q===Z)return p($);J.unshift(r8(Z)),Z=Q}}function x($){try{return M$($)}catch{try{let Z=d8($);return d(F$(Z)?Z:p(O$($),Z))}catch{return d($)}}}function e8($,Z,J={relative:o8,isAbsolute:F$}){if($===Z)return!0;let Q=J.relative(Z,$);return Q!==""&&!Q.startsWith("..")&&!J.isAbsolute(Q)}function U$($,Z){return e8(x($),d(Z))}var $Z="tlc-exec.mjs";function ZZ($){try{return i8($).size<4096&&S($,"utf8").includes($Z)}catch{return!1}}function H$($,Z,J,Q,q){if(!B(Z)&&!c(Z))return;if(!c(Z)){if(ZZ(Z)){$.push({action:"remove",target:Z,detail:`${Q}, installed as a copy`});return}$.push({action:"keep",target:Z,detail:`${Q} is a real file the installer did not write`});return}if(q==="target"&&!U$(Z,J)){$.push({action:"keep",target:Z,detail:`points at ${x(Z)} — not ours`});return}let X=q==="location"&&!U$(Z,J);$.push({action:"unlink",target:Z,detail:X?`${Q}, stale — points at ${x(Z)}`:Q})}function QZ($,Z){if(!B(Z))return;let J=u(S(Z,"utf8"));if(!J.ok){$.push({action:"keep",target:Z,detail:`left untouched — it does not parse as JSON: ${J.error}`});return}if(!J.changed)return;$.push({action:"unmerge",target:Z,detail:"drop the harness hook groups, keep every other key and every foreign hook"})}function JZ($,Z){let J=B(Z)?S(Z,"utf8"):null,Q=h(J);switch(Q.kind){case"absent":return;case"unparsed":$.push({action:"keep",target:Z,detail:"left untouched — it does not parse as JSON"});return;case"empty":if(Q.removed>0)$.push({action:"remove",target:Z,detail:`${Q.removed} entries, all ours`});return;default:$.push({action:"unmerge",target:Z,detail:`drop ${Q.removed} harness entries, keep the rest`})}}function qZ($,Z,J){if(c(Z))return $.push({action:"unlink",target:Z,detail:`a link to ${x(Z)} — the checkout it points at is left exactly as it is`}),!0;if(!B(Z))return!1;for(let X of G$){let z=G(Z,X);if(B(z))$.push({action:"remove",target:z,detail:"runtime payload"})}let q=G(Z,k);if(B(q))$.push({action:"remove",target:q,detail:"install marker"});for(let X of B$){let z=G(Z,X);if(!B(z))continue;$.push(J?{action:"remove",target:z,detail:"--purge"}:{action:"keep",target:z,detail:"yours — add --purge to remove it"})}return!1}function XZ($,Z){if(B(G(Z,k)))$.push({action:"manual",target:`npm uninstall -g ${_}`,detail:"the global package is reported, never removed for you"});$.push({action:"manual",target:"rm -rf .tlc/ in each repository",detail:"per-project config and state — this command does not search your disk for them"})}function zZ($,Z={}){let J=Z.purge===!0,Q=[];QZ(Q,$.claudeSettings),JZ(Q,$.cursorHooks);for(let X of $.skillLinks)H$(Q,X,$.home,"skill link","location");for(let X of $.binLinks)H$(Q,X,$.home,"the tlc launcher on PATH","target");let q=qZ(Q,$.home,J);return XZ(Q,$.home),{items:Q,purge:J,homeIsLink:q}}function C$($){return $.items.filter((Z)=>Z.action==="unmerge"||Z.action==="unlink"||Z.action==="remove")}function WZ($,Z){let J=[],Q=[];for(let q of C$($))try{if(q.action==="unmerge"&&q.target===Z.claudeSettings)Y$(q.target);else if(q.action==="unmerge"){let X=h(S(q.target,"utf8"));if(X.kind==="rewritten")a8(q.target,X.text,"utf8")}else if(q.action==="unlink")n8(q.target);else l8(q.target,{recursive:!0,force:!0});J.push(q)}catch(X){Q.push({item:q,reason:X instanceof Error?X.message:String(X)})}return{applied:J,failed:Q}}var YZ={unmerge:"warn",unlink:"warn",remove:"warn",keep:"ok",manual:"info"};function VZ($,Z){let J=C$($),Q=Z!==null;if(J.length===0)return{title:"harness uninstall",summary:["nothing wired"],sections:[{rows:[{label:"state",value:"no harness artefact found — nothing to undo",level:"ok"}]},{title:"STILL YOURS TO DO",rows:K$($)}],footer:`already clean · reinstall with npm i -g ${_}, then \`tlc harness install\``};let q=J.map((W)=>({label:W.action,value:`${W.target} — ${W.detail}`,level:YZ[W.action]})),X=$.items.filter((W)=>W.action==="keep"),z=[{title:Q?"REMOVED":"WOULD REMOVE",rows:q},...X.length>0?[{title:"KEPT",rows:X.map((W)=>({label:"keep",value:`${W.target} — ${W.detail}`,level:"ok"}))}]:[],{title:"STILL YOURS TO DO",rows:K$($)},...Z!==null&&Z.failed.length>0?[{title:"FAILED",rows:Z.failed.map((W)=>({label:"error",value:`${W.item.target} — ${W.reason}`,level:"fail"}))}]:[]];return{title:"harness uninstall",summary:[Q?`${Z.applied.length} applied`:`${J.length} pending`,$.purge?"purge: state included":"purge: state kept",$.homeIsLink?"runtime: linked checkout, unlinked only":"runtime: owned by the installer"],sections:z,footer:Q?`reinstall any time with npm i -g ${_}, then \`tlc harness install\``:"nothing was changed · re-run with --yes to apply this plan"}}function K$($){return $.items.filter((Z)=>Z.action==="manual").map((Z)=>({label:"run",value:`${Z.target} — ${Z.detail}`,level:"info"}))}function GZ($,Z,J=F){return I(VZ($,Z),J)}function BZ($=process.argv.slice(2)){let Z=t8(),J=zZ(Z,{purge:$.includes("--purge")}),Q=$.includes("--yes")?WZ(J,Z):null;return console.log(GZ(J,Q,E())),Q!==null&&Q.failed.length>0?1:0}if(i.main==i.module)process.exitCode=BZ();export{t8 as uninstallTargets,VZ as uninstallScreen,GZ as uninstallReportText,zZ as planUninstall,C$ as pendingItems,BZ as main,e8 as isInsideRoot,d as canonicalise,WZ as applyUninstall};
3
+ `);var q8={enabled:!1,maxInjectSession:5,maxInjectRetry:8,maxCharsSession:900,maxCharsRetry:1400,promoteHitCount:2,decayLambda:0.02,projectBoost:1.5,syncRulesFile:"auto",gardenOnSessionEnd:!0},W$={version:1,mode:"solo",codePaths:["src","apps","libs","packages"],grind:{enabled:!1,maxLoops:5,lintCommand:null,testCommand:null,appendFiles:"auto"},shipGate:{enabled:!1,runtimePathPrefixes:["src","apps","libs","packages","deploy","scripts"],runtimePathExcludes:[".tlc/","**/node_modules/","**/.git/"],evidenceDir:null,evidenceMaxAgeHours:48,emptyDiffAntiShip:!1,claimWindowMinutes:10},subagents:{enforceAllowlist:!1,requireModel:!1,allowedModels:[],blockedPatterns:["-fast(?:$|[^a-z0-9])","/fast(?:$|[^a-z0-9])"],minEffort:null,blockParentFast:!1,blockMode:"deny",readOnlyTypes:["explore"]},docs:{command:null,severity:"warn"},observe:{enabled:!1,rails:[]},comments:{enabled:!1,onViolation:"followup",mode:"declared"},supplyChain:{enabled:!1},duplication:{enabled:!1,minRun:v},obs:{globalSpool:!1,includePayloads:H.includePayloads,maxAttrChars:H.maxAttrChars,sessionCostAlertUsd:H.sessionCostAlertUsd,retentionDays:H.retentionDays},untrustedContent:{mode:"frame",enabled:!1,extraTools:[],extraCommandPatterns:[]},planGate:{enabled:!1,windowMinutes:120},rules:{enabled:!1},shell:{catastrophicAsk:!0,stallDetection:!1,stallRepeatThreshold:3},secrets:{redactOutput:!0},intelligence:{gapFeedback:!0,failureClassification:!0,progressiveHandoff:!0,progressiveContext:!0,autopilot:!0,idleTurnGate:!1,budgetContinue:!1,budgetContinueAfterLoops:3,lessons:{...q8}},mcpPrime:[],bootstrapExtra:[]};var N="@tech-leads-club/harness-toolkit",k="installed-from-npm";import{existsSync as m8,mkdirSync as X4,readFileSync as g8,realpathSync as W4,writeFileSync as u8}from"node:fs";function z$($){return $!==null&&typeof $==="object"&&!Array.isArray($)}function h8($){return z$($)}var p8="tlc-exec.mjs";function c8($){return JSON.stringify($??null).includes(p8)}function u($){if($===null||$.trim()==="")return{ok:!0,settingsText:"",changed:!1};let Z;try{Z=JSON.parse($)}catch(W){return{ok:!1,error:W instanceof Error?W.message:String(W),block:""}}if(!z$(Z))return{ok:!1,error:"settings.json root is not a JSON object",block:""};let J=h8(Z.hooks)?Z.hooks:{},Q={},q=!1;for(let[W,z]of Object.entries(J)){let U=z.filter((j)=>!c8(j));if(U.length!==z.length)q=!0;if(U.length>0)Q[W]=U}let X={};for(let[W,z]of Object.entries(Z))if(W!=="hooks")X[W]=z;else if(Object.keys(Q).length>0)X.hooks=Q;return{ok:!0,settingsText:JSON.stringify(X,null,2),changed:q}}function Y$($){if(!m8($))return{ok:!0,settingsText:"",changed:!1};let Z=u(g8($,"utf8"));if(Z.ok&&Z.changed)u8($,Z.settingsText,"utf8");return Z}function h($,Z="tlc-exec.mjs"){if($===null||$.trim()==="")return{kind:"absent"};let J;try{J=JSON.parse($)}catch{return{kind:"unparsed"}}if(J===null||typeof J!=="object"||Array.isArray(J))return{kind:"unparsed"};let Q=J,q=Q.hooks!==null&&typeof Q.hooks==="object"&&!Array.isArray(Q.hooks)?Q.hooks:{},X={},W=0,z=0;for(let[U,j]of Object.entries(q)){let l=Array.isArray(j)?j:[],L=l.filter((I$)=>!JSON.stringify(I$??null).includes(Z));if(W+=l.length-L.length,L.length>0)X[U]=L,z+=L.length}if(z===0)return{kind:"empty",removed:W};return{kind:"rewritten",removed:W,text:`${JSON.stringify({...Q,hooks:X},null,2)}
4
+ `}}import{join as V$,relative as H4,resolve as K4,sep as M4}from"node:path";var G$=["bin","capabilities","dist","docs","skills","src","tools","config.example.json","package.json"],B$=["config.json","state","flags","rules"],L4=[V$("tools","dev"),V$("tools","__test__")];function e8($=process.env){let Z=r8(),J=b($);return{home:C($),binLinks:a().map((Q)=>G(J,Q)),claudeSettings:G(A(),"settings.json"),cursorHooks:G(P(),"hooks.json"),skillLinks:[G(A(),"skills","harness-init"),G(P(),"skills","harness-init"),G(Z,".tlc","skills","harness-init")]}}function c($){try{return d8($).isSymbolicLink()}catch{return!1}}function d($){let Z=p($),J=[];for(;;)try{return G(M$(Z),...J)}catch{let Q=O$(Z);if(Q===Z)return p($);J.unshift(o8(Z)),Z=Q}}function x($){try{return M$($)}catch{try{let Z=l8($);return d(F$(Z)?Z:p(O$($),Z))}catch{return d($)}}}function $Z($,Z,J={relative:t8,isAbsolute:F$}){if($===Z)return!0;let Q=J.relative(Z,$);return Q!==""&&!Q.startsWith("..")&&!J.isAbsolute(Q)}function U$($,Z){return $Z(x($),d(Z))}var ZZ="tlc-exec.mjs";function QZ($){try{return n8($).size<4096&&S($,"utf8").includes(ZZ)}catch{return!1}}function H$($,Z,J,Q,q){if(!B(Z)&&!c(Z))return;if(!c(Z)){if(QZ(Z)){$.push({action:"remove",target:Z,detail:`${Q}, installed as a copy`});return}$.push({action:"keep",target:Z,detail:`${Q} is a real file the installer did not write`});return}if(q==="target"&&!U$(Z,J)){$.push({action:"keep",target:Z,detail:`points at ${x(Z)} — not ours`});return}let X=q==="location"&&!U$(Z,J);$.push({action:"unlink",target:Z,detail:X?`${Q}, stale — points at ${x(Z)}`:Q})}function JZ($,Z){if(!B(Z))return;let J=u(S(Z,"utf8"));if(!J.ok){$.push({action:"keep",target:Z,detail:`left untouched — it does not parse as JSON: ${J.error}`});return}if(!J.changed)return;$.push({action:"unmerge",target:Z,detail:"drop the harness hook groups, keep every other key and every foreign hook"})}function qZ($,Z){let J=B(Z)?S(Z,"utf8"):null,Q=h(J);switch(Q.kind){case"absent":return;case"unparsed":$.push({action:"keep",target:Z,detail:"left untouched — it does not parse as JSON"});return;case"empty":if(Q.removed>0)$.push({action:"remove",target:Z,detail:`${Q.removed} entries, all ours`});return;default:$.push({action:"unmerge",target:Z,detail:`drop ${Q.removed} harness entries, keep the rest`})}}function XZ($,Z,J){if(c(Z))return $.push({action:"unlink",target:Z,detail:`a link to ${x(Z)} — the checkout it points at is left exactly as it is`}),!0;if(!B(Z))return!1;for(let X of G$){let W=G(Z,X);if(B(W))$.push({action:"remove",target:W,detail:"runtime payload"})}let q=G(Z,k);if(B(q))$.push({action:"remove",target:q,detail:"install marker"});for(let X of B$){let W=G(Z,X);if(!B(W))continue;$.push(J?{action:"remove",target:W,detail:"--purge"}:{action:"keep",target:W,detail:"yours — add --purge to remove it"})}return!1}function WZ($,Z){if(B(G(Z,k)))$.push({action:"manual",target:`npm uninstall -g ${N}`,detail:"the global package is reported, never removed for you"});$.push({action:"manual",target:"rm -rf .tlc/ in each repository",detail:"per-project config and state — this command does not search your disk for them"})}function zZ($,Z={}){let J=Z.purge===!0,Q=[];JZ(Q,$.claudeSettings),qZ(Q,$.cursorHooks);for(let X of $.skillLinks)H$(Q,X,$.home,"skill link","location");for(let X of $.binLinks)H$(Q,X,$.home,"the tlc launcher on PATH","target");let q=XZ(Q,$.home,J);return WZ(Q,$.home),{items:Q,purge:J,homeIsLink:q}}function C$($){return $.items.filter((Z)=>Z.action==="unmerge"||Z.action==="unlink"||Z.action==="remove")}function YZ($,Z){let J=[],Q=[];for(let q of C$($))try{if(q.action==="unmerge"&&q.target===Z.claudeSettings)Y$(q.target);else if(q.action==="unmerge"){let X=h(S(q.target,"utf8"));if(X.kind==="rewritten")s8(q.target,X.text,"utf8")}else if(q.action==="unlink")a8(q.target);else i8(q.target,{recursive:!0,force:!0});J.push(q)}catch(X){Q.push({item:q,reason:X instanceof Error?X.message:String(X)})}return{applied:J,failed:Q}}var VZ={unmerge:"warn",unlink:"warn",remove:"warn",keep:"ok",manual:"info"};function GZ($,Z){let J=C$($),Q=Z!==null;if(J.length===0)return{title:"harness uninstall",summary:["nothing wired"],sections:[{rows:[{label:"state",value:"no harness artefact found — nothing to undo",level:"ok"}]},{title:"STILL YOURS TO DO",rows:K$($)}],footer:`already clean · reinstall with npm i -g ${N}, then \`tlc harness install\``};let q=J.map((z)=>({label:z.action,value:`${z.target} — ${z.detail}`,level:VZ[z.action]})),X=$.items.filter((z)=>z.action==="keep"),W=[{title:Q?"REMOVED":"WOULD REMOVE",rows:q},...X.length>0?[{title:"KEPT",rows:X.map((z)=>({label:"keep",value:`${z.target} — ${z.detail}`,level:"ok"}))}]:[],{title:"STILL YOURS TO DO",rows:K$($)},...Z!==null&&Z.failed.length>0?[{title:"FAILED",rows:Z.failed.map((z)=>({label:"error",value:`${z.item.target} — ${z.reason}`,level:"fail"}))}]:[]];return{title:"harness uninstall",summary:[Q?`${Z.applied.length} applied`:`${J.length} pending`,$.purge?"purge: state included":"purge: state kept",$.homeIsLink?"runtime: linked checkout, unlinked only":"runtime: owned by the installer"],sections:W,footer:Q?`reinstall any time with npm i -g ${N}, then \`tlc harness install\``:"nothing was changed · re-run with --yes to apply this plan"}}function K$($){return $.items.filter((Z)=>Z.action==="manual").map((Z)=>({label:"run",value:`${Z.target} — ${Z.detail}`,level:"info"}))}function BZ($,Z,J=F){return _(GZ($,Z),J)}function UZ($=process.argv.slice(2)){let Z=e8(),J=zZ(Z,{purge:$.includes("--purge")}),Q=$.includes("--yes")?YZ(J,Z):null;return console.log(BZ(J,Q,E())),Q!==null&&Q.failed.length>0?1:0}if(i.main==i.module)process.exitCode=UZ();export{e8 as uninstallTargets,GZ as uninstallScreen,BZ as uninstallReportText,zZ as planUninstall,C$ as pendingItems,UZ as main,$Z as isInsideRoot,d as canonicalise,YZ as applyUninstall};
package/docs/concepts.md CHANGED
@@ -132,8 +132,10 @@ findings without deleting tests, which on a malformed command sends it to edit h
132
132
 
133
133
  ## pause / resume
134
134
 
135
- `tlc harness pause` disables stop checks (grind + ship challenge). Use when exploring or mid-refactor.
136
- `tlc harness resume` turns them back on.
135
+ `tlc harness pause` is the circuit breaker: every hook steps aside and answers at once, reading no policy and
136
+ writing no state. Only the floor still runs, so a paused session still cannot rewrite the policy it will resume
137
+ under. Pausing a repository also pauses the agents working in its linked worktrees. `tlc harness resume` turns
138
+ the harness back on.
137
139
 
138
140
  Run both from your own terminal. Inside an agent session they are denied by the floor rule
139
141
  `policy-surface-write`: policy is the operator's to change, and a stop check the agent can switch off is not a
package/docs/diagnose.md CHANGED
@@ -51,8 +51,9 @@ a green light nobody earned. So a silent harness shows up as a healthy-looking s
51
51
  tlc harness doctor
52
52
  ```
53
53
 
54
- There is deliberately no switch that turns the harness off. `tlc harness pause` disables the stop checks; the floor
55
- and the action-time rails keep running, because a limit an agent can turn off is not a limit.
54
+ `tlc harness pause` is the operator's circuit breaker: every hook steps aside without reading policy or writing
55
+ state, and only the floor keeps running, because a limit an agent can turn off is not a limit. A pause flag that
56
+ appears without the CLI is a policy change, not a pause, and is ignored.
56
57
 
57
58
  ## Node vs Bun
58
59
 
@@ -113,7 +113,7 @@ For a rail, the operator changes it from their own terminal, outside the agent s
113
113
 
114
114
  ```bash
115
115
  tlc harness mode solo # leave the paired posture
116
- tlc harness pause # disable stop checks while exploring
116
+ tlc harness pause # circuit breaker: every hook steps aside, only the floor runs
117
117
  tlc harness policy accept --all # after you edited config mid-session
118
118
  ```
119
119
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tech-leads-club/harness-toolkit",
3
- "version": "0.16.3",
3
+ "version": "0.16.4",
4
4
  "type": "module",
5
5
  "description": "Multi-provider agent steering: gates, follow-up, handoff, policy",
6
6
  "keywords": [
@@ -146,6 +146,7 @@ import {
146
146
  resolveProjectSyncMode,
147
147
  } from "./policy/policy.loader.ts";
148
148
  import { operatorBootstrapLines } from "./policy/policy.operator.ts";
149
+ import { isPaused, pauseFlagPath } from "./policy/policy.pause.ts";
149
150
  import { isOperatorMode, OPERATOR_MODES } from "./policy/policy.posture.ts";
150
151
  import { activeRails } from "./policy/policy.rails.ts";
151
152
  import { pruneShadowed, shadowedKeys, typeMismatches, unknownKeys } from "./policy/policy.shadow.ts";
@@ -438,6 +439,8 @@ export const coreFacade = {
438
439
  acceptPolicySources,
439
440
  divergedPaths,
440
441
  allDivergedPaths,
442
+ isPaused,
443
+ pauseFlagPath,
441
444
  activeRails,
442
445
  operatorBootstrapLines,
443
446
  loadPolicy,
@@ -0,0 +1,21 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { linkedWorktreeMainRoot } from "../../platform/git.ts";
4
+ import { flagsDir } from "../../platform/paths.ts";
5
+ import { divergedPaths } from "./policy.integrity.ts";
6
+
7
+ export const PAUSE_FLAG = "skip-verify";
8
+
9
+ export function pauseFlagPath(root: string): string {
10
+ return join(flagsDir(root), PAUSE_FLAG);
11
+ }
12
+
13
+ export function isPaused(root: string, sessionKey: string): boolean {
14
+ const roots = [root, linkedWorktreeMainRoot(root)].filter(
15
+ (candidate): candidate is string => candidate !== null,
16
+ );
17
+ return roots.some((candidate) => {
18
+ const flag = pauseFlagPath(candidate);
19
+ return existsSync(flag) && !divergedPaths(candidate, sessionKey).includes(flag);
20
+ });
21
+ }
@@ -23,6 +23,8 @@ export type HandlerContext = {
23
23
 
24
24
  export type Handler = (event: HarnessEvent, ctx: HandlerContext) => Decision | Promise<Decision>;
25
25
 
26
+ export type PausedHandler = (event: HarnessEvent, protectedPaths: string[]) => Decision;
27
+
26
28
  export type RunIo = {
27
29
  readStdin?: () => Promise<string>;
28
30
  now?: () => Date;
@@ -151,7 +153,33 @@ function asRecord(value: unknown): Record<string, unknown> {
151
153
  : {};
152
154
  }
153
155
 
154
- export async function runHandler(handler: Handler, io: RunIo = {}): Promise<RunOutcome> {
156
+ function isPausedSafely(event: HarnessEvent): boolean {
157
+ try {
158
+ return coreFacade.policy.isPaused(event.projectDir, event.sessionKey);
159
+ } catch {
160
+ return false;
161
+ }
162
+ }
163
+
164
+ function pausedDecision(event: HarnessEvent, whilePaused: PausedHandler | undefined): Decision {
165
+ if (!whilePaused) {
166
+ return { kind: "abstain" };
167
+ }
168
+ try {
169
+ return whilePaused(
170
+ event,
171
+ providerRegistry.flatMap((p) => p.wiringTargets()),
172
+ );
173
+ } catch {
174
+ return { kind: "abstain" };
175
+ }
176
+ }
177
+
178
+ export async function runHandler(
179
+ handler: Handler,
180
+ io: RunIo = {},
181
+ whilePaused?: PausedHandler,
182
+ ): Promise<RunOutcome> {
155
183
  const readStdin = io.readStdin ?? readStdinText;
156
184
  const now = io.now ? io.now() : new Date();
157
185
  const abstainRendered: Rendered = { stdout: "{}", exitCode: 0 };
@@ -191,6 +219,12 @@ export async function runHandler(handler: Handler, io: RunIo = {}): Promise<RunO
191
219
  }
192
220
 
193
221
  const capabilities = provider.capabilities();
222
+ if (isPausedSafely(event)) {
223
+ const decision = degrade(pausedDecision(event, whilePaused), event, capabilities, {
224
+ contextBudgetChars: CONTEXT_BUDGET_CHARS,
225
+ });
226
+ return { event, decision, rendered: provider.render(decision, event) };
227
+ }
194
228
  recordHookEnter(event);
195
229
 
196
230
  try {
@@ -239,8 +273,8 @@ export async function runHandler(handler: Handler, io: RunIo = {}): Promise<RunO
239
273
  }
240
274
  }
241
275
 
242
- export async function main(handler: Handler): Promise<void> {
243
- const outcome = await runHandler(handler);
276
+ export async function main(handler: Handler, whilePaused?: PausedHandler): Promise<void> {
277
+ const outcome = await runHandler(handler, {}, whilePaused);
244
278
  if (outcome.rendered.stdout !== null) {
245
279
  const text = outcome.rendered.stdout;
246
280
  process.stdout.write(text.endsWith("\n") ? text : `${text}\n`);
@@ -5,7 +5,7 @@ import { coreFacade } from "../core/index.ts";
5
5
  import type { AddedLine } from "../platform/git.ts";
6
6
  import { diffProposedAgainstDisk, diffTwoStrings, localRepoRemote } from "../platform/git.ts";
7
7
  import { normalizeSeparators } from "../platform/sanitize.ts";
8
- import type { Handler, HandlerContext } from "./run.ts";
8
+ import type { Handler, HandlerContext, PausedHandler } from "./run.ts";
9
9
  import { main } from "./run.ts";
10
10
  import { shipGateVerdict } from "./ship-gate.ts";
11
11
  import {
@@ -321,6 +321,16 @@ async function recordShellCwd(event: HarnessEvent): Promise<void> {
321
321
  });
322
322
  }
323
323
 
324
+ export const floorVerdict: PausedHandler = (event, protectedPaths) =>
325
+ coreFacade.floor.evaluateFloor({
326
+ projectDir: event.projectDir,
327
+ toolName: event.toolName,
328
+ filePath: filePathOf(event),
329
+ command: event.command,
330
+ isReadEvent: event.event === "read.before",
331
+ protectedPaths,
332
+ });
333
+
324
334
  export const toolBeforeHandler: Handler = async (
325
335
  event: HarnessEvent,
326
336
  ctx: HandlerContext,
@@ -331,14 +341,7 @@ export const toolBeforeHandler: Handler = async (
331
341
 
332
342
  // invariant: the floor runs first and reads no policy, so no config value and no agent edit can
333
343
  // reach a decision before it.
334
- const floor = coreFacade.floor.evaluateFloor({
335
- projectDir: event.projectDir,
336
- toolName: event.toolName,
337
- filePath: filePathOf(event),
338
- command: event.command,
339
- isReadEvent: event.event === "read.before",
340
- protectedPaths: ctx.protectedPaths,
341
- });
344
+ const floor = floorVerdict(event, ctx.protectedPaths);
342
345
  if (floor.kind !== "allow") {
343
346
  // invariant: one rail owns the record of every shell decision. The floor short-circuits before the shell
344
347
  // rail runs, so without this a floor denial of a shell command was recorded by nothing at all — and the
@@ -441,5 +444,5 @@ export const toolBeforeHandler: Handler = async (
441
444
  };
442
445
 
443
446
  if (import.meta.main) {
444
- await main(toolBeforeHandler);
447
+ await main(toolBeforeHandler, floorVerdict);
445
448
  }
@@ -58,6 +58,21 @@ export async function gitCommonDirOf(dir: string): Promise<string | null> {
58
58
  }
59
59
  }
60
60
 
61
+ export function linkedWorktreeMainRoot(root: string): string | null {
62
+ let text: string;
63
+ try {
64
+ text = readFileSync(join(root, ".git"), "utf8");
65
+ } catch {
66
+ return null;
67
+ }
68
+ const pointer = /^gitdir:\s*(.+)$/m.exec(text)?.[1]?.trim();
69
+ if (!pointer) {
70
+ return null;
71
+ }
72
+ const main = /^(.*)[\\/]\.git[\\/]worktrees[\\/][^\\/]+[\\/]?$/.exec(resolve(root, pointer))?.[1];
73
+ return main ? main : null;
74
+ }
75
+
61
76
  async function gitLines(projectDir: string, args: string[]): Promise<string[]> {
62
77
  const result = await runProcess({ command: ["git", ...args], cwd: projectDir });
63
78
  if (result.exitCode !== 0) {