@tech-leads-club/harness-toolkit 0.11.0 → 0.11.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.
- package/bin/tlc-cli.ts +0 -57
- package/dist/compact-before.mjs +68 -68
- package/dist/doctor.mjs +79 -79
- package/dist/init-project.mjs +81 -81
- package/dist/install-runtime.mjs +76 -76
- package/dist/lessons-cli.mjs +76 -76
- package/dist/obs-cli.mjs +69 -69
- package/dist/prompt-submit.mjs +68 -68
- package/dist/refresh-model-prices.mjs +67 -67
- package/dist/response-after.mjs +68 -68
- package/dist/run.mjs +67 -67
- package/dist/session-end.mjs +70 -70
- package/dist/session-start.mjs +72 -72
- package/dist/shim.mjs +67 -67
- package/dist/ship-gate.mjs +67 -67
- package/dist/stop.mjs +73 -73
- package/dist/subagent-start.mjs +68 -68
- package/dist/subagent-stop.mjs +69 -69
- package/dist/support.mjs +71 -71
- package/dist/tlc-cli.mjs +103 -230
- package/dist/tool-after.mjs +69 -69
- package/dist/tool-before.mjs +68 -68
- package/dist/tool-failure.mjs +68 -68
- package/dist/uninstall-runtime.mjs +3 -3
- package/docs/log.md +10 -0
- package/docs/providers/index.md +3 -1
- package/package.json +1 -1
- package/src/core/rules/rules.trigger.ts +20 -15
- package/dist/new-provider.mjs +0 -126
- package/tools/new-provider.ts +0 -251
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{createRequire as I$}from"node:module";var l=I$(import.meta.url);import{existsSync as B,lstatSync as d8,readFileSync as S,readlinkSync as l8,realpathSync as K$,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 M$,isAbsolute as O$,join as G,relative as t8,resolve as h}from"node:path";import{homedir as D}from"node:os";import{delimiter as FZ,dirname as IZ,join as j,resolve as NZ}from"node:path";function i(){return j(D(),".tlc","harness")}function C($=process.env){return $.TLC_HOME??i()}function T(){let $=process.env.CLAUDE_CONFIG_DIR?.trim();return $&&$.length>0?$:j(D(),".claude")}function A(){let $=process.env.CURSOR_CONFIG_DIR?.trim();return $&&$.length>0?$:j(D(),".cursor")}function b($=process.env){return $.TLC_BIN_DIR?.trim()||j(D(),".local","bin")}function n(){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 j$=D$(s);var T$=j$.size;var y=6;var x$=new Set(["aria2c","curl","fetch","http","httpie","https","wget"]);var D5=new Set([...x$,"nc","ncat","socat","telnet","lwp-request"]);var S$="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-]\\.(?:${S$}))(?=[:)\\s,'"\`]|$)`,"g");var R="1970-01-01T00:00:00.000Z";function M($){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 d9=[M({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}),M({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}),M({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}),M({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}),M({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}),M({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 i$={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 n$($){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),a$=`${q$}[0m`;function s$($=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 r$={ok:"success",warn:"warning",fail:"error",info:"info"},o$={ok:V.check,warn:V.warning,fail:V.cross,info:V.arrowRight},g=16;function E($=s$()){let Z=(Q,q)=>$?`${q$}[${Q}m${q}${a$}`:q,J=(Q,q)=>Z(`38;2;${n$(i$[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(r$[Q],o$[Q])} ${q}`}}var O=E(!1);var t$=2;function e$($=process.stdout.columns){return Math.min(110,Math.max(60,$??100))}function $8($,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=e$()){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?$8(z,J-t$):[z];for(let U of W)Q.push(` ${U}`)}}if($.footer)Q.push("",Z.footer($.footer));return Q.join(`
|
|
1
|
+
import{createRequire as F$}from"node:module";var l=F$(import.meta.url);import{existsSync as B,lstatSync as h8,readFileSync as S,readlinkSync as p8,realpathSync as K$,rmSync as c8,statSync as d8,unlinkSync as l8,writeFileSync as i8}from"node:fs";import{homedir as n8}from"node:os";import{basename as a8,dirname as M$,isAbsolute as O$,join as G,relative as s8,resolve as h}from"node:path";import{homedir as j}from"node:os";import{delimiter as MZ,dirname as OZ,join as D,resolve as CZ}from"node:path";function i(){return D(j(),".tlc","harness")}function C($=process.env){return $.TLC_HOME??i()}function T(){let $=process.env.CLAUDE_CONFIG_DIR?.trim();return $&&$.length>0?$:D(j(),".claude")}function A(){let $=process.env.CURSOR_CONFIG_DIR?.trim();return $&&$.length>0?$:D(j(),".cursor")}function b($=process.env){return $.TLC_BIN_DIR?.trim()||D(j(),".local","bin")}function n(){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 j$($){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$=j$(s);var T$=D$.size;var y=6;var x$=new Set(["aria2c","curl","fetch","http","httpie","https","wget"]);var N5=new Set([...x$,"nc","ncat","socat","telnet","lwp-request"]);var S$="ts|tsx|mts|cts|js|jsx|mjs|cjs|py|go|rb|rs|java|kt|swift|php|sh|sql",j5=new RegExp(`(?:file://)?((?:[A-Za-z]:)?[\\w./~@+-]*[\\w-]\\.(?:${S$}))(?=[:)\\s,'"\`]|$)`,"g");var R="1970-01-01T00:00:00.000Z";function M($){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 h9=[M({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}),M({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}),M({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}),M({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}),M({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}),M({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 i$={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 n$($){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),a$=`${q$}[0m`;function s$($=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 r$={ok:"success",warn:"warning",fail:"error",info:"info"},o$={ok:V.check,warn:V.warning,fail:V.cross,info:V.arrowRight},m=16;function E($=s$()){let Z=(Q,q)=>$?`${q$}[${Q}m${q}${a$}`:q,J=(Q,q)=>Z(`38;2;${n$(i$[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=m)=>` ${J("textMuted",`${Q}:`.padEnd(X))} ${q}`,status:(Q,q)=>`${J(r$[Q],o$[Q])} ${q}`}}var O=E(!1);var t$=2;function e$($=process.stdout.columns){return Math.min(110,Math.max(60,$??100))}function $8($,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 F($,Z,J=e$()){let Q=[Z.heading($.title.toUpperCase())];if($.summary&&$.summary.length>0)Q.push(` ${$.summary.join(Z.dim(` ${V.bar} `))}`);let q=Math.max(m,...$.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?$8(z,J-t$):[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 Q8=["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 J8={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:y},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:{...J8}},mcpPrime:[],bootstrapExtra:[]};
|
|
4
|
-
`}}import{join as Y$,relative as
|
|
3
|
+
`);var J8={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:y},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:{...J8}},mcpPrime:[],bootstrapExtra:[]};var N="@tech-leads-club/harness-toolkit",k="installed-from-npm";import{existsSync as f8,mkdirSync as Eq,readFileSync as y8,realpathSync as kq,writeFileSync as v8}from"node:fs";function z$($){return $!==null&&typeof $==="object"&&!Array.isArray($)}function m8($){return z$($)}var g8="tlc-exec.mjs";function u8($){return JSON.stringify($??null).includes(g8)}function g($){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=m8(Z.hooks)?Z.hooks:{},Q={},q=!1;for(let[z,W]of Object.entries(J)){let U=W.filter((_)=>!u8(_));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(!f8($))return{ok:!0,settingsText:"",changed:!1};let Z=g(y8($,"utf8"));if(Z.ok&&Z.changed)v8($,Z.settingsText,"utf8");return Z}function u($,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,_]of Object.entries(q)){let d=Array.isArray(_)?_:[],L=d.filter((I$)=>!JSON.stringify(I$??null).includes(Z));if(z+=d.length-L.length,L.length>0)X[U]=L,W+=L.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 vq,resolve as mq,sep as gq}from"node:path";var V$=["bin","capabilities","dist","docs","skills","src","tools","config.example.json","package.json"],G$=["config.json","state","flags","rules"],nq=[Y$("tools","dev"),Y$("tools","__test__")];function r8($=process.env){let Z=n8(),J=b($);return{home:C($),binLinks:n().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 p($){try{return h8($).isSymbolicLink()}catch{return!1}}function c($){let Z=h($),J=[];for(;;)try{return G(K$(Z),...J)}catch{let Q=M$(Z);if(Q===Z)return h($);J.unshift(a8(Z)),Z=Q}}function x($){try{return K$($)}catch{try{let Z=p8($);return c(O$(Z)?Z:h(M$($),Z))}catch{return c($)}}}function o8($,Z,J={relative:s8,isAbsolute:O$}){if($===Z)return!0;let Q=J.relative(Z,$);return Q!==""&&!Q.startsWith("..")&&!J.isAbsolute(Q)}function B$($,Z){return o8(x($),c(Z))}var t8="tlc-exec.mjs";function e8($){try{return d8($).size<4096&&S($,"utf8").includes(t8)}catch{return!1}}function U$($,Z,J,Q,q){if(!B(Z)&&!p(Z))return;if(!p(Z)){if(e8(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 $Z($,Z){if(!B(Z))return;let J=g(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 ZZ($,Z){let J=B(Z)?S(Z,"utf8"):null,Q=u(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(p(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 JZ($,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 qZ($,Z={}){let J=Z.purge===!0,Q=[];$Z(Q,$.claudeSettings),ZZ(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 JZ(Q,$.home),{items:Q,purge:J,homeIsLink:q}}function C$($){return $.items.filter((Z)=>Z.action==="unmerge"||Z.action==="unlink"||Z.action==="remove")}function XZ($,Z){let J=[],Q=[];for(let q of C$($))try{if(q.action==="unmerge"&&q.target===Z.claudeSettings)W$(q.target);else if(q.action==="unmerge"){let X=u(S(q.target,"utf8"));if(X.kind==="rewritten")i8(q.target,X.text,"utf8")}else if(q.action==="unlink")l8(q.target);else c8(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 zZ={unmerge:"warn",unlink:"warn",remove:"warn",keep:"ok",manual:"info"};function WZ($,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:H$($)}],footer:`already clean · reinstall with npm i -g ${N}, then \`tlc harness install\``};let q=J.map((W)=>({label:W.action,value:`${W.target} — ${W.detail}`,level:zZ[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 ${N}, 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 YZ($,Z,J=O){return F(WZ($,Z),J)}function VZ($=process.argv.slice(2)){let Z=r8(),J=qZ(Z,{purge:$.includes("--purge")}),Q=$.includes("--yes")?XZ(J,Z):null;return console.log(YZ(J,Q,E())),Q!==null&&Q.failed.length>0?1:0}if(l.main==l.module)process.exitCode=VZ();export{r8 as uninstallTargets,WZ as uninstallScreen,YZ as uninstallReportText,qZ as planUninstall,C$ as pendingItems,VZ as main,o8 as isInsideRoot,c as canonicalise,XZ as applyUninstall};
|
package/docs/log.md
CHANGED
|
@@ -13,6 +13,16 @@ 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-08
|
|
17
|
+
|
|
18
|
+
- **AD-127** — A rule's shell triggers match the act at any word position, not only at word 0 ([/decisions/ad-127.md](/decisions/ad-127.md))
|
|
19
|
+
|
|
20
|
+
## 2026-09-07
|
|
21
|
+
|
|
22
|
+
- **AD-124** — A provider's wiring target is a floor surface; secret-shaped tool output is a rail ([/decisions/ad-124.md](/decisions/ad-124.md))
|
|
23
|
+
- **AD-125** — A provider's docs and file scaffold are generated from its own code, never hand-maintained ([/decisions/ad-125.md](/decisions/ad-125.md))
|
|
24
|
+
- **AD-126** — The provider scaffold is a contributor tool; it never belonged on the published CLI ([/decisions/ad-126.md](/decisions/ad-126.md))
|
|
25
|
+
|
|
16
26
|
## 2026-09-06
|
|
17
27
|
|
|
18
28
|
- **AD-123** — Policy stays declarative; no custom executable policy code, ever ([/decisions/ad-123.md](/decisions/ad-123.md))
|
package/docs/providers/index.md
CHANGED
|
@@ -68,4 +68,6 @@ kind's absence.
|
|
|
68
68
|
|
|
69
69
|
A new provider is a new directory under `src/providers/<name>/` plus one line in
|
|
70
70
|
`src/providers/provider.registry.ts` (see [/decisions/ad-004.md](/decisions/ad-004.md) and
|
|
71
|
-
[/decisions/ad-009.md](/decisions/ad-009.md) item 7) — never a change to `core/`.
|
|
71
|
+
[/decisions/ad-009.md](/decisions/ad-009.md) item 7) — never a change to `core/`. `node
|
|
72
|
+
tools/dev/new-provider.ts <name>`, run from a clone, scaffolds the stub files and a doc skeleton — a
|
|
73
|
+
contributor-only tool, not a published CLI command ([/decisions/ad-126.md](/decisions/ad-126.md)).
|
package/package.json
CHANGED
|
@@ -63,18 +63,6 @@ function subCommands(command: string): string[][] {
|
|
|
63
63
|
.filter((words) => words.length > 0);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
/** why prefix rather than equality: `gh pr create --fill --base main` is the same act as `gh pr create`. */
|
|
67
|
-
function startsWithShape(words: readonly string[], prefix: readonly string[]): boolean {
|
|
68
|
-
return prefix.every((token, index) => words[index] === token);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function matchesShape(words: readonly string[], shape: ShellShape): boolean {
|
|
72
|
-
if (!startsWithShape(words, shape.prefix)) {
|
|
73
|
-
return false;
|
|
74
|
-
}
|
|
75
|
-
return !shape.excludeIfAny?.some((flag) => words.includes(flag));
|
|
76
|
-
}
|
|
77
|
-
|
|
78
66
|
/**
|
|
79
67
|
* why a basename fallback: a token with no `/` of its own names an act, not a location — `build.sh` is the
|
|
80
68
|
* same script whether it runs as `build.sh`, `./scripts/build.sh` or `/home/user/tools/scripts/build.sh`.
|
|
@@ -93,16 +81,33 @@ function tokenMatches(word: string | undefined, token: string): boolean {
|
|
|
93
81
|
return word === token || (!token.includes("/") && word.endsWith(`/${token}`));
|
|
94
82
|
}
|
|
95
83
|
|
|
84
|
+
/**
|
|
85
|
+
* why any starting index, not only 0: a wrapper in front of the real command — a proxy, `sudo`, `time`, `env
|
|
86
|
+
* FOO=bar`, or one nobody has written yet — must not hide the act behind it. An anchored check goes silently
|
|
87
|
+
* dead the moment anything sits in front of the verb it expects at word 0; a real rule stayed dead for exactly
|
|
88
|
+
* that reason before this existed ([/decisions/ad-127.md](/decisions/ad-127.md)). Trailing words past the
|
|
89
|
+
* phrase don't matter either: `gh pr create --fill --base main` is the same act as `gh pr create`.
|
|
90
|
+
*/
|
|
91
|
+
function containsPhrase(words: readonly string[], tokens: readonly string[]): boolean {
|
|
92
|
+
if (tokens.length === 0) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
return words.some((_, start) => tokens.every((token, index) => tokenMatches(words[start + index], token)));
|
|
96
|
+
}
|
|
97
|
+
|
|
96
98
|
/**
|
|
97
99
|
* why a phrase and not a word: an operator writes `command(gh pr review)`, meaning those words in that order.
|
|
98
100
|
* Matching the raw string against the whole command would let a heredoc or an unrelated argument satisfy it.
|
|
99
101
|
*/
|
|
100
102
|
export function matchesPhrase(words: readonly string[], pattern: string): boolean {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
+
return containsPhrase(words, pattern.trim().split(/\s+/));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function matchesShape(words: readonly string[], shape: ShellShape): boolean {
|
|
107
|
+
if (!containsPhrase(words, shape.prefix)) {
|
|
103
108
|
return false;
|
|
104
109
|
}
|
|
105
|
-
return
|
|
110
|
+
return !shape.excludeIfAny?.some((flag) => words.includes(flag));
|
|
106
111
|
}
|
|
107
112
|
|
|
108
113
|
export function triggerMatches(trigger: RuleTrigger, context: TriggerContext): boolean {
|
package/dist/new-provider.mjs
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import{createRequire as Z}from"node:module";var W=Z(import.meta.url);import{existsSync as $,mkdirSync as q,writeFileSync as C}from"node:fs";import{dirname as X,join as H}from"node:path";import{fileURLToPath as L}from"node:url";var Y=H(X(L(import.meta.url)),".."),V=/[\s/\\]/,k=new Set(["index","provider"]);function x(B){if(B.length===0)return{ok:!1,reason:"provider name must not be empty"};if(V.test(B))return{ok:!1,reason:"provider name must not contain whitespace or a slash"};if(k.has(B.toLowerCase()))return{ok:!1,reason:`"${B}" is a reserved name`};return{ok:!0}}function K(B){return B.length===0?B:`${B[0]?.toUpperCase()}${B.slice(1)}`}function M(B){return`// why: always false until real detection logic replaces it, so the provider stays inert.
|
|
2
|
-
export function detect${K(B)}(_raw: unknown): boolean {
|
|
3
|
-
return false;
|
|
4
|
-
}
|
|
5
|
-
`}function T(B){return`import type { HarnessEventKind, ProviderCapabilities } from "../../contracts/index.ts";
|
|
6
|
-
|
|
7
|
-
export function ${B}Capabilities(): ProviderCapabilities {
|
|
8
|
-
return {
|
|
9
|
-
enforcesHooks: true,
|
|
10
|
-
askSupportedOn: [],
|
|
11
|
-
sessionEnv: false,
|
|
12
|
-
nativeLoopCounter: false,
|
|
13
|
-
dedicatedShellEvent: false,
|
|
14
|
-
toolInputRewrite: false,
|
|
15
|
-
// why: a placeholder, deliberately not a real HarnessEventKind — fails assertSatisfiesContract until replaced.
|
|
16
|
-
toolOutputRewriteOn: ["TODO-fill-in-real-capabilities" as unknown as HarnessEventKind],
|
|
17
|
-
contextAtToolBefore: false,
|
|
18
|
-
contextAtToolAfter: false,
|
|
19
|
-
contextAtStop: false,
|
|
20
|
-
sessionStartContextReliable: false,
|
|
21
|
-
toolOutputAtAfter: false,
|
|
22
|
-
usageInPayload: false,
|
|
23
|
-
effortSignal: false,
|
|
24
|
-
thoughtEvent: false,
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
`}function U(B){return`import type { ProviderPolicyDefaults } from "../../contracts/index.ts";
|
|
28
|
-
|
|
29
|
-
export function ${B}PolicyDefaults(): ProviderPolicyDefaults {
|
|
30
|
-
return {
|
|
31
|
-
blockedPatterns: [],
|
|
32
|
-
minEffort: null,
|
|
33
|
-
untrustedTools: [],
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
`}function w(B){return`import type { HarnessEvent, HarnessEventKind } from "../../contracts/index.ts";
|
|
37
|
-
|
|
38
|
-
// why: empty until this host's own hook names are mapped, so every payload falls through to null.
|
|
39
|
-
export const EVENT_KIND_BY_HOOK: Record<string, HarnessEventKind> = {};
|
|
40
|
-
|
|
41
|
-
/** Never throws on a malformed payload — returns null instead. */
|
|
42
|
-
export function ${B}ToEvent(_raw: Record<string, unknown>): HarnessEvent | null {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
`}function z(B){return`import type { ProviderWiring, RuntimePaths } from "../../contracts/index.ts";
|
|
46
|
-
|
|
47
|
-
// why: a placeholder path, until the real file this provider's host reads hooks from is named.
|
|
48
|
-
const WIRING_TARGET = "TODO: replace with the real wiring target path";
|
|
49
|
-
|
|
50
|
-
export function ${B}WiringTargets(): string[] {
|
|
51
|
-
return [WIRING_TARGET];
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// why: an empty entries list, until this adapter's real hook entries replace it (see claude.wiring.ts's ENTRY_SPECS shape).
|
|
55
|
-
export function ${B}Wiring(_runtime: RuntimePaths): ProviderWiring {
|
|
56
|
-
return { target: WIRING_TARGET, strategy: "replace", entries: [] };
|
|
57
|
-
}
|
|
58
|
-
`}function A(B){return`import type { Decision, HarnessEvent, Rendered } from "../../contracts/index.ts";
|
|
59
|
-
|
|
60
|
-
// why: an unconditional abstain, until this host's real hook response schema replaces it.
|
|
61
|
-
export function ${B}Render(_decision: Decision, _event: HarnessEvent): Rendered {
|
|
62
|
-
return { stdout: null, exitCode: 0 };
|
|
63
|
-
}
|
|
64
|
-
`}function D(B){let G=K(B);return`import type { ProviderPort } from "../provider.port.ts";
|
|
65
|
-
import { ${B}Capabilities } from "./${B}.capabilities.ts";
|
|
66
|
-
import { detect${G} } from "./${B}.detect.ts";
|
|
67
|
-
import { ${B}ToEvent } from "./${B}.inbound.ts";
|
|
68
|
-
import { ${B}Render } from "./${B}.outbound.ts";
|
|
69
|
-
import { ${B}PolicyDefaults } from "./${B}.policy-defaults.ts";
|
|
70
|
-
import { ${B}Wiring, ${B}WiringTargets } from "./${B}.wiring.ts";
|
|
71
|
-
|
|
72
|
-
// why: assembled here so \`tlc harness new-provider\` has one ProviderPort to import into provider.registry.ts.
|
|
73
|
-
export const ${B}Provider: ProviderPort = {
|
|
74
|
-
name: "${B}",
|
|
75
|
-
detect: detect${G},
|
|
76
|
-
capabilities: ${B}Capabilities,
|
|
77
|
-
policyDefaults: ${B}PolicyDefaults,
|
|
78
|
-
toEvent: ${B}ToEvent,
|
|
79
|
-
render: ${B}Render,
|
|
80
|
-
wiring: ${B}Wiring,
|
|
81
|
-
wiringTargets: ${B}WiringTargets,
|
|
82
|
-
};
|
|
83
|
-
`}function F(B){let G=K(B),I=new Date().toISOString().slice(0,10);return`---
|
|
84
|
-
type: Provider
|
|
85
|
-
title: "${G} provider"
|
|
86
|
-
description: "The ${G} adapter — capability descriptor, event mapping, and wiring target."
|
|
87
|
-
tags: [provider, ${B}]
|
|
88
|
-
timestamp: "${I}"
|
|
89
|
-
---
|
|
90
|
-
|
|
91
|
-
# ${G} provider
|
|
92
|
-
|
|
93
|
-
Source: \`src/providers/${B}/\`.
|
|
94
|
-
|
|
95
|
-
## Detection
|
|
96
|
-
|
|
97
|
-
TODO: describe \`${B}.detect.ts\`'s real detection rule.
|
|
98
|
-
|
|
99
|
-
## Capability descriptor
|
|
100
|
-
|
|
101
|
-
\`${B}.capabilities.ts\`:
|
|
102
|
-
|
|
103
|
-
<!-- generated:capabilities -->
|
|
104
|
-
|
|
105
|
-
<!-- /generated -->
|
|
106
|
-
|
|
107
|
-
## Policy defaults
|
|
108
|
-
|
|
109
|
-
TODO: describe \`${B}.policy-defaults.ts\`'s defaults.
|
|
110
|
-
|
|
111
|
-
## Event mapping
|
|
112
|
-
|
|
113
|
-
\`${B}.inbound.ts\` maps ${G}'s own hook names to \`HarnessEventKind\`:
|
|
114
|
-
|
|
115
|
-
<!-- generated:event-mapping -->
|
|
116
|
-
|
|
117
|
-
<!-- /generated -->
|
|
118
|
-
|
|
119
|
-
## Wiring target
|
|
120
|
-
|
|
121
|
-
TODO: describe \`${B}.wiring.ts\`'s wiring target and strategy.
|
|
122
|
-
|
|
123
|
-
## See also
|
|
124
|
-
|
|
125
|
-
- [/providers/index.md](/providers/index.md)
|
|
126
|
-
`}function N(B){let G=H("src","providers",B);return[{path:H(G,`${B}.detect.ts`),content:M(B)},{path:H(G,`${B}.capabilities.ts`),content:T(B)},{path:H(G,`${B}.policy-defaults.ts`),content:U(B)},{path:H(G,`${B}.inbound.ts`),content:w(B)},{path:H(G,`${B}.wiring.ts`),content:z(B)},{path:H(G,`${B}.outbound.ts`),content:A(B)},{path:H(G,"index.ts"),content:D(B)},{path:H("docs","providers",`${B}.md`),content:F(B)}]}function P(B,G=Y){let I=x(B);if(!I.ok)return I;let J=H(G,"src","providers",B);if($(J))return{ok:!1,reason:`${H("src","providers",B)} already exists`};for(let O of N(B)){let Q=H(G,O.path);q(X(Q),{recursive:!0}),C(Q,O.content,"utf8")}return{ok:!0}}function R(B,G=Y){let I=B[0];if(!I){console.error("usage: node tools/dev/new-provider.ts <name>"),process.exitCode=1;return}let J=P(I,G);if(!J.ok){console.error(`new-provider: refusing — ${J.reason}`),process.exitCode=1;return}console.log(`new-provider: scaffolded src/providers/${I}/ and docs/providers/${I}.md`)}if(W.main==W.module)R(process.argv.slice(2));export{x as validateProviderName,N as scaffoldFiles,P as scaffold,R as main};
|
package/tools/new-provider.ts
DELETED
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { dirname, join } from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
|
|
5
|
-
// hazard: this file lived at tools/dev/new-provider.ts (two levels below root) until it moved to tools/ —
|
|
6
|
-
// tools/dev/ is excluded from the published package, and bin/tlc-cli.ts's `new-provider` subcommand imports
|
|
7
|
-
// this module at runtime, so a real install had no file to import at all.
|
|
8
|
-
const repoRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
9
|
-
|
|
10
|
-
// invariant: mirrors provider.contract.test.ts's own safety check ("provider name is safe as a state-file
|
|
11
|
-
// key segment") — no whitespace, no slashes. Reserved names collide with this scaffold's own file layout
|
|
12
|
-
// (`index.ts`) or the generic module concept ("provider").
|
|
13
|
-
const UNSAFE_NAME = /[\s/\\]/;
|
|
14
|
-
const RESERVED_NAMES = new Set(["index", "provider"]);
|
|
15
|
-
|
|
16
|
-
export type NameValidity = { ok: true } | { ok: false; reason: string };
|
|
17
|
-
|
|
18
|
-
export function validateProviderName(name: string): NameValidity {
|
|
19
|
-
if (name.length === 0) {
|
|
20
|
-
return { ok: false, reason: "provider name must not be empty" };
|
|
21
|
-
}
|
|
22
|
-
if (UNSAFE_NAME.test(name)) {
|
|
23
|
-
return { ok: false, reason: "provider name must not contain whitespace or a slash" };
|
|
24
|
-
}
|
|
25
|
-
if (RESERVED_NAMES.has(name.toLowerCase())) {
|
|
26
|
-
return { ok: false, reason: `"${name}" is a reserved name` };
|
|
27
|
-
}
|
|
28
|
-
return { ok: true };
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function capitalize(name: string): string {
|
|
32
|
-
return name.length === 0 ? name : `${name[0]?.toUpperCase()}${name.slice(1)}`;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function detectStub(name: string): string {
|
|
36
|
-
const cap = capitalize(name);
|
|
37
|
-
return `// why: always false until real detection logic replaces it, so the provider stays inert.
|
|
38
|
-
export function detect${cap}(_raw: unknown): boolean {
|
|
39
|
-
return false;
|
|
40
|
-
}
|
|
41
|
-
`;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function capabilitiesStub(name: string): string {
|
|
45
|
-
return `import type { HarnessEventKind, ProviderCapabilities } from "../../contracts/index.ts";
|
|
46
|
-
|
|
47
|
-
export function ${name}Capabilities(): ProviderCapabilities {
|
|
48
|
-
return {
|
|
49
|
-
enforcesHooks: true,
|
|
50
|
-
askSupportedOn: [],
|
|
51
|
-
sessionEnv: false,
|
|
52
|
-
nativeLoopCounter: false,
|
|
53
|
-
dedicatedShellEvent: false,
|
|
54
|
-
toolInputRewrite: false,
|
|
55
|
-
// why: a placeholder, deliberately not a real HarnessEventKind — fails assertSatisfiesContract until replaced.
|
|
56
|
-
toolOutputRewriteOn: ["TODO-fill-in-real-capabilities" as unknown as HarnessEventKind],
|
|
57
|
-
contextAtToolBefore: false,
|
|
58
|
-
contextAtToolAfter: false,
|
|
59
|
-
contextAtStop: false,
|
|
60
|
-
sessionStartContextReliable: false,
|
|
61
|
-
toolOutputAtAfter: false,
|
|
62
|
-
usageInPayload: false,
|
|
63
|
-
effortSignal: false,
|
|
64
|
-
thoughtEvent: false,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
`;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function policyDefaultsStub(name: string): string {
|
|
71
|
-
return `import type { ProviderPolicyDefaults } from "../../contracts/index.ts";
|
|
72
|
-
|
|
73
|
-
export function ${name}PolicyDefaults(): ProviderPolicyDefaults {
|
|
74
|
-
return {
|
|
75
|
-
blockedPatterns: [],
|
|
76
|
-
minEffort: null,
|
|
77
|
-
untrustedTools: [],
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
`;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function inboundStub(name: string): string {
|
|
84
|
-
return `import type { HarnessEvent, HarnessEventKind } from "../../contracts/index.ts";
|
|
85
|
-
|
|
86
|
-
// why: empty until this host's own hook names are mapped, so every payload falls through to null.
|
|
87
|
-
export const EVENT_KIND_BY_HOOK: Record<string, HarnessEventKind> = {};
|
|
88
|
-
|
|
89
|
-
/** Never throws on a malformed payload — returns null instead. */
|
|
90
|
-
export function ${name}ToEvent(_raw: Record<string, unknown>): HarnessEvent | null {
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
`;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function wiringStub(name: string): string {
|
|
97
|
-
return `import type { ProviderWiring, RuntimePaths } from "../../contracts/index.ts";
|
|
98
|
-
|
|
99
|
-
// why: a placeholder path, until the real file this provider's host reads hooks from is named.
|
|
100
|
-
const WIRING_TARGET = "TODO: replace with the real wiring target path";
|
|
101
|
-
|
|
102
|
-
export function ${name}WiringTargets(): string[] {
|
|
103
|
-
return [WIRING_TARGET];
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// why: an empty entries list, until this adapter's real hook entries replace it (see claude.wiring.ts's ENTRY_SPECS shape).
|
|
107
|
-
export function ${name}Wiring(_runtime: RuntimePaths): ProviderWiring {
|
|
108
|
-
return { target: WIRING_TARGET, strategy: "replace", entries: [] };
|
|
109
|
-
}
|
|
110
|
-
`;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function outboundStub(name: string): string {
|
|
114
|
-
return `import type { Decision, HarnessEvent, Rendered } from "../../contracts/index.ts";
|
|
115
|
-
|
|
116
|
-
// why: an unconditional abstain, until this host's real hook response schema replaces it.
|
|
117
|
-
export function ${name}Render(_decision: Decision, _event: HarnessEvent): Rendered {
|
|
118
|
-
return { stdout: null, exitCode: 0 };
|
|
119
|
-
}
|
|
120
|
-
`;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function indexStub(name: string): string {
|
|
124
|
-
const cap = capitalize(name);
|
|
125
|
-
return `import type { ProviderPort } from "../provider.port.ts";
|
|
126
|
-
import { ${name}Capabilities } from "./${name}.capabilities.ts";
|
|
127
|
-
import { detect${cap} } from "./${name}.detect.ts";
|
|
128
|
-
import { ${name}ToEvent } from "./${name}.inbound.ts";
|
|
129
|
-
import { ${name}Render } from "./${name}.outbound.ts";
|
|
130
|
-
import { ${name}PolicyDefaults } from "./${name}.policy-defaults.ts";
|
|
131
|
-
import { ${name}Wiring, ${name}WiringTargets } from "./${name}.wiring.ts";
|
|
132
|
-
|
|
133
|
-
// why: assembled here so \`tlc harness new-provider\` has one ProviderPort to import into provider.registry.ts.
|
|
134
|
-
export const ${name}Provider: ProviderPort = {
|
|
135
|
-
name: "${name}",
|
|
136
|
-
detect: detect${cap},
|
|
137
|
-
capabilities: ${name}Capabilities,
|
|
138
|
-
policyDefaults: ${name}PolicyDefaults,
|
|
139
|
-
toEvent: ${name}ToEvent,
|
|
140
|
-
render: ${name}Render,
|
|
141
|
-
wiring: ${name}Wiring,
|
|
142
|
-
wiringTargets: ${name}WiringTargets,
|
|
143
|
-
};
|
|
144
|
-
`;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function docSkeleton(name: string): string {
|
|
148
|
-
const cap = capitalize(name);
|
|
149
|
-
const timestamp = new Date().toISOString().slice(0, 10);
|
|
150
|
-
return `---
|
|
151
|
-
type: Provider
|
|
152
|
-
title: "${cap} provider"
|
|
153
|
-
description: "The ${cap} adapter — capability descriptor, event mapping, and wiring target."
|
|
154
|
-
tags: [provider, ${name}]
|
|
155
|
-
timestamp: "${timestamp}"
|
|
156
|
-
---
|
|
157
|
-
|
|
158
|
-
# ${cap} provider
|
|
159
|
-
|
|
160
|
-
Source: \`src/providers/${name}/\`.
|
|
161
|
-
|
|
162
|
-
## Detection
|
|
163
|
-
|
|
164
|
-
TODO: describe \`${name}.detect.ts\`'s real detection rule.
|
|
165
|
-
|
|
166
|
-
## Capability descriptor
|
|
167
|
-
|
|
168
|
-
\`${name}.capabilities.ts\`:
|
|
169
|
-
|
|
170
|
-
<!-- generated:capabilities -->
|
|
171
|
-
|
|
172
|
-
<!-- /generated -->
|
|
173
|
-
|
|
174
|
-
## Policy defaults
|
|
175
|
-
|
|
176
|
-
TODO: describe \`${name}.policy-defaults.ts\`'s defaults.
|
|
177
|
-
|
|
178
|
-
## Event mapping
|
|
179
|
-
|
|
180
|
-
\`${name}.inbound.ts\` maps ${cap}'s own hook names to \`HarnessEventKind\`:
|
|
181
|
-
|
|
182
|
-
<!-- generated:event-mapping -->
|
|
183
|
-
|
|
184
|
-
<!-- /generated -->
|
|
185
|
-
|
|
186
|
-
## Wiring target
|
|
187
|
-
|
|
188
|
-
TODO: describe \`${name}.wiring.ts\`'s wiring target and strategy.
|
|
189
|
-
|
|
190
|
-
## See also
|
|
191
|
-
|
|
192
|
-
- [/providers/index.md](/providers/index.md)
|
|
193
|
-
`;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
export type ScaffoldFile = { path: string; content: string };
|
|
197
|
-
|
|
198
|
-
export function scaffoldFiles(name: string): ScaffoldFile[] {
|
|
199
|
-
const dir = join("src", "providers", name);
|
|
200
|
-
return [
|
|
201
|
-
{ path: join(dir, `${name}.detect.ts`), content: detectStub(name) },
|
|
202
|
-
{ path: join(dir, `${name}.capabilities.ts`), content: capabilitiesStub(name) },
|
|
203
|
-
{ path: join(dir, `${name}.policy-defaults.ts`), content: policyDefaultsStub(name) },
|
|
204
|
-
{ path: join(dir, `${name}.inbound.ts`), content: inboundStub(name) },
|
|
205
|
-
{ path: join(dir, `${name}.wiring.ts`), content: wiringStub(name) },
|
|
206
|
-
// why: SPEC_DEVIATION — emits 8 files, not the 6 named in design.md/tasks.md, adding `<name>.outbound.ts` and
|
|
207
|
-
// `index.ts`. Reason: ProviderPort requires a `render` field and an assembled object, the same two-extra-file
|
|
208
|
-
// shape claude/index.ts and cursor/index.ts already use — without them there is no ProviderPort value for T6
|
|
209
|
-
// to register or T7 to run through assertSatisfiesContract (see commit 20da58e).
|
|
210
|
-
{ path: join(dir, `${name}.outbound.ts`), content: outboundStub(name) },
|
|
211
|
-
{ path: join(dir, "index.ts"), content: indexStub(name) },
|
|
212
|
-
{ path: join("docs", "providers", `${name}.md`), content: docSkeleton(name) },
|
|
213
|
-
];
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
export function scaffold(name: string, root = repoRoot): NameValidity {
|
|
217
|
-
const validity = validateProviderName(name);
|
|
218
|
-
if (!validity.ok) {
|
|
219
|
-
return validity;
|
|
220
|
-
}
|
|
221
|
-
const providerDir = join(root, "src", "providers", name);
|
|
222
|
-
if (existsSync(providerDir)) {
|
|
223
|
-
return { ok: false, reason: `${join("src", "providers", name)} already exists` };
|
|
224
|
-
}
|
|
225
|
-
for (const file of scaffoldFiles(name)) {
|
|
226
|
-
const fullPath = join(root, file.path);
|
|
227
|
-
mkdirSync(dirname(fullPath), { recursive: true });
|
|
228
|
-
writeFileSync(fullPath, file.content, "utf8");
|
|
229
|
-
}
|
|
230
|
-
return { ok: true };
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
export function main(argv: string[], root = repoRoot): void {
|
|
234
|
-
const name = argv[0];
|
|
235
|
-
if (!name) {
|
|
236
|
-
console.error("usage: node tools/dev/new-provider.ts <name>");
|
|
237
|
-
process.exitCode = 1;
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
const result = scaffold(name, root);
|
|
241
|
-
if (!result.ok) {
|
|
242
|
-
console.error(`new-provider: refusing — ${result.reason}`);
|
|
243
|
-
process.exitCode = 1;
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
console.log(`new-provider: scaffolded src/providers/${name}/ and docs/providers/${name}.md`);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (import.meta.main) {
|
|
250
|
-
main(process.argv.slice(2));
|
|
251
|
-
}
|