@tech-leads-club/harness-toolkit 0.15.1 → 0.15.2

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
1
  import{createRequire as I$}from"node:module";var i=I$(import.meta.url);import{existsSync as B,lstatSync as c8,readFileSync as S,readlinkSync as d8,realpathSync as K$,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 M$,isAbsolute as C$,join as G,relative as o8,resolve as p}from"node:path";import{homedir as L}from"node:os";import{delimiter as OZ,dirname as FZ,join as D,resolve as IZ}from"node:path";function n(){return D(L(),".tlc","harness")}function F($=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 L$($){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 D$=L$(s);var T$=D$.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 C($){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=[C({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}),C({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}),C({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}),C({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}),C({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}),C({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 q$=String.fromCharCode(27),s$=`${q$}[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)=>$?`${q$}[${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 O=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(`
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},X$={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 nX,readFileSync as m8,realpathSync as aX,writeFileSync as g8}from"node:fs";function z$($){return $!==null&&typeof $==="object"&&!Array.isArray($)}function u8($){return z$($)}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(!z$(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 W$($){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((F$)=>!JSON.stringify(F$??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 Y$,relative as Z4,resolve as Q4,sep as J4}from"node:path";var V$=["bin","capabilities","dist","docs","skills","src","tools","config.example.json","package.json"],G$=["config.json","state","flags","rules"],B4=[Y$("tools","dev"),Y$("tools","__test__")];function t8($=process.env){let Z=s8(),J=b($);return{home:F($),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(K$(Z),...J)}catch{let Q=M$(Z);if(Q===Z)return p($);J.unshift(r8(Z)),Z=Q}}function x($){try{return K$($)}catch{try{let Z=d8($);return d(C$(Z)?Z:p(M$($),Z))}catch{return d($)}}}function e8($,Z,J={relative:o8,isAbsolute:C$}){if($===Z)return!0;let Q=J.relative(Z,$);return Q!==""&&!Q.startsWith("..")&&!J.isAbsolute(Q)}function B$($,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 U$($,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"&&!B$(Z,J)){$.push({action:"keep",target:Z,detail:`points at ${x(Z)} — not ours`});return}let X=q==="location"&&!B$(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 V$){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 G$){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)U$(Q,X,$.home,"skill link","location");for(let X of $.binLinks)U$(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 O$($){return $.items.filter((Z)=>Z.action==="unmerge"||Z.action==="unlink"||Z.action==="remove")}function WZ($,Z){let J=[],Q=[];for(let q of O$($))try{if(q.action==="unmerge"&&q.target===Z.claudeSettings)W$(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=O$($),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:H$($)}],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:H$($)},...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 H$($){return $.items.filter((Z)=>Z.action==="manual").map((Z)=>({label:"run",value:`${Z.target} — ${Z.detail}`,level:"info"}))}function GZ($,Z,J=O){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,O$ 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},X$={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 sX,readFileSync as m8,realpathSync as rX,writeFileSync as g8}from"node:fs";function z$($){return $!==null&&typeof $==="object"&&!Array.isArray($)}function u8($){return z$($)}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(!z$(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 W$($){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((F$)=>!JSON.stringify(F$??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 Y$,relative as J4,resolve as q4,sep as X4}from"node:path";var V$=["bin","capabilities","dist","docs","skills","src","tools","config.example.json","package.json"],G$=["config.json","state","flags","rules"],H4=[Y$("tools","dev"),Y$("tools","__test__")];function t8($=process.env){let Z=s8(),J=b($);return{home:F($),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(K$(Z),...J)}catch{let Q=M$(Z);if(Q===Z)return p($);J.unshift(r8(Z)),Z=Q}}function x($){try{return K$($)}catch{try{let Z=d8($);return d(C$(Z)?Z:p(M$($),Z))}catch{return d($)}}}function e8($,Z,J={relative:o8,isAbsolute:C$}){if($===Z)return!0;let Q=J.relative(Z,$);return Q!==""&&!Q.startsWith("..")&&!J.isAbsolute(Q)}function B$($,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 U$($,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"&&!B$(Z,J)){$.push({action:"keep",target:Z,detail:`points at ${x(Z)} — not ours`});return}let X=q==="location"&&!B$(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 V$){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 G$){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)U$(Q,X,$.home,"skill link","location");for(let X of $.binLinks)U$(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 O$($){return $.items.filter((Z)=>Z.action==="unmerge"||Z.action==="unlink"||Z.action==="remove")}function WZ($,Z){let J=[],Q=[];for(let q of O$($))try{if(q.action==="unmerge"&&q.target===Z.claudeSettings)W$(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=O$($),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:H$($)}],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:H$($)},...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 H$($){return $.items.filter((Z)=>Z.action==="manual").map((Z)=>({label:"run",value:`${Z.target} — ${Z.detail}`,level:"info"}))}function GZ($,Z,J=O){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,O$ as pendingItems,BZ as main,e8 as isInsideRoot,d as canonicalise,WZ as applyUninstall};
package/docs/log.md CHANGED
@@ -16,6 +16,7 @@ newest first. For what landed in which npm release, see `CHANGELOG.md` at the re
16
16
  ## 2026-09-11
17
17
 
18
18
  - **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))
19
+ - **AD-138** — a pr-open rule's own denial names its draft escape hatch ([/decisions/ad-138.md](/decisions/ad-138.md))
19
20
 
20
21
  ## 2026-09-10
21
22
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tech-leads-club/harness-toolkit",
3
- "version": "0.15.1",
3
+ "version": "0.15.2",
4
4
  "type": "module",
5
5
  "description": "Multi-provider agent steering: gates, follow-up, handoff, policy",
6
6
  "keywords": [
@@ -9,6 +9,7 @@ import type { Decision } from "../../contracts/decision.ts";
9
9
  import { WHY_POINTER } from "../diagnostics/diagnostics.message.ts";
10
10
  import type { OperatorMode } from "../policy/policy.types.ts";
11
11
  import { missingProofs, type Observation, type ProofContext, proofLabel } from "./rules.proof.ts";
12
+ import { TRIGGER_ESCAPE_HATCH_HINT } from "./rules.trigger.ts";
12
13
  import type { Rule, RuleVerdict } from "./rules.types.ts";
13
14
 
14
15
  /**
@@ -28,6 +29,13 @@ export function effectiveVerdict(declared: RuleVerdict, mode: OperatorMode): Rul
28
29
  * genuine miss, and nothing in the message said which directory was checked
29
30
  * ([/decisions/ad-120.md](/decisions/ad-120.md)). The body stays verbatim beneath it either way.
30
31
  */
32
+ /**
33
+ * why the escape-hatch hint sits here, generated, rather than in the operator's own body: an operator who
34
+ * writes a `pr-open` rule and never thinks to mention the draft escape hatch (`SHELL_SHAPES`, `rules.trigger.ts`)
35
+ * leaves every agent that hits the denial with no way to discover it except a human explaining it by hand,
36
+ * confirmed live — generated once, here, it reaches every operator's rule without asking any of them to write
37
+ * it ([/decisions/ad-138.md](/decisions/ad-138.md)).
38
+ */
31
39
  export function ruleMessage(
32
40
  rule: Rule,
33
41
  missing: readonly ReturnType<typeof proofLabel>[],
@@ -35,7 +43,9 @@ export function ruleMessage(
35
43
  sha: string | null,
36
44
  ): string {
37
45
  const head = `rule ${rule.name} (${rule.tier}): missing ${missing.join(", ")} — checked ${shaRoot} at ${sha ?? "no HEAD"}\n${WHY_POINTER}`;
38
- return rule.body.trim() === "" ? head : `${head}\n\n${rule.body.trim()}`;
46
+ const hint = TRIGGER_ESCAPE_HATCH_HINT[rule.on.kind];
47
+ const withHint = hint === undefined ? head : `${head}\n\n${hint}`;
48
+ return rule.body.trim() === "" ? withHint : `${withHint}\n\n${rule.body.trim()}`;
39
49
  }
40
50
 
41
51
  export type RuleOutcome = {
@@ -61,6 +61,17 @@ const SHELL_SHAPES: Record<"pr-open" | "commit" | "push" | "pr-merge", readonly
61
61
  "pr-merge": [{ prefix: ["gh", "pr", "merge"] }],
62
62
  };
63
63
 
64
+ /**
65
+ * why colocated with `SHELL_SHAPES`, not left to `rules.decide.ts`: this is the human-readable half of the
66
+ * `excludeIfAny` fact above it. Only `pr-open` carries one today — a future trigger gaining its own
67
+ * `excludeIfAny` shape has this constant sitting right beside the reminder that its own message may need one
68
+ * too ([/decisions/ad-138.md](/decisions/ad-138.md)).
69
+ */
70
+ export const TRIGGER_ESCAPE_HATCH_HINT: Partial<Record<RuleTrigger["kind"], string>> = {
71
+ "pr-open":
72
+ "A draft PR (`gh pr create --draft` or `-d`) is not gated by any pr-open rule — open it as a draft, satisfy this rule's proof, then `gh pr ready`, which is itself gated the normal way.",
73
+ };
74
+
64
75
  /**
65
76
  * invariant: `tokenizeShell` already declines to emit segments from a heredoc body, so a body is never mistaken
66
77
  * for a command and a command after one is still seen. Measured both ways on