buildkeel 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -135,7 +135,7 @@ ${t.comment}`:t.comment}this.doc.range[2]=t.offset;break}default:this.errors.pus
135
135
  `)+1;for(;t!==0;)this.onNewLine(this.offset+t),t=this.source.indexOf(`
136
136
  `,t)+1}yield*this.pop();break;default:yield*this.pop(),yield*this.step()}}*blockMap(e){let t=e.items[e.items.length-1];switch(this.type){case"newline":if(this.onKeyLine=!1,t.value){let n="end"in t.value?t.value.end:void 0;(Array.isArray(n)?n[n.length-1]:void 0)?.type==="comment"?n?.push(this.sourceToken):e.items.push({start:[this.sourceToken]})}else t.sep?t.sep.push(this.sourceToken):t.start.push(this.sourceToken);return;case"space":case"comment":if(t.value)e.items.push({start:[this.sourceToken]});else if(t.sep)t.sep.push(this.sourceToken);else{if(this.atIndentedComment(t.start,e.indent)){let i=e.items[e.items.length-2]?.value?.end;if(Array.isArray(i)){zt(i,t.start),i.push(this.sourceToken),e.items.pop();return}}t.start.push(this.sourceToken)}return}if(this.indent>=e.indent){let n=!this.onKeyLine&&this.indent===e.indent,i=n&&(t.sep||t.explicitKey)&&this.type!=="seq-item-ind",r=[];if(i&&t.sep&&!t.value){let o=[];for(let a=0;a<t.sep.length;++a){let l=t.sep[a];switch(l.type){case"newline":o.push(a);break;case"space":break;case"comment":l.indent>e.indent&&(o.length=0);break;default:o.length=0}}o.length>=2&&(r=t.sep.splice(o[1]))}switch(this.type){case"anchor":case"tag":i||t.value?(r.push(this.sourceToken),e.items.push({start:r}),this.onKeyLine=!0):t.sep?t.sep.push(this.sourceToken):t.start.push(this.sourceToken);return;case"explicit-key-ind":!t.sep&&!t.explicitKey?(t.start.push(this.sourceToken),t.explicitKey=!0):i||t.value?(r.push(this.sourceToken),e.items.push({start:r,explicitKey:!0})):this.stack.push({type:"block-map",offset:this.offset,indent:this.indent,items:[{start:[this.sourceToken],explicitKey:!0}]}),this.onKeyLine=!0;return;case"map-value-ind":if(t.explicitKey)if(t.sep)if(t.value)e.items.push({start:[],key:null,sep:[this.sourceToken]});else if(ee(t.sep,"map-value-ind"))this.stack.push({type:"block-map",offset:this.offset,indent:this.indent,items:[{start:r,key:null,sep:[this.sourceToken]}]});else if(Go(t.key)&&!ee(t.sep,"newline")){let o=ke(t.start),a=t.key,l=t.sep;l.push(this.sourceToken),delete t.key,delete t.sep,this.stack.push({type:"block-map",offset:this.offset,indent:this.indent,items:[{start:o,key:a,sep:l}]})}else r.length>0?t.sep=t.sep.concat(r,this.sourceToken):t.sep.push(this.sourceToken);else if(ee(t.start,"newline"))Object.assign(t,{key:null,sep:[this.sourceToken]});else{let o=ke(t.start);this.stack.push({type:"block-map",offset:this.offset,indent:this.indent,items:[{start:o,key:null,sep:[this.sourceToken]}]})}else t.sep?t.value||i?e.items.push({start:r,key:null,sep:[this.sourceToken]}):ee(t.sep,"map-value-ind")?this.stack.push({type:"block-map",offset:this.offset,indent:this.indent,items:[{start:[],key:null,sep:[this.sourceToken]}]}):t.sep.push(this.sourceToken):Object.assign(t,{key:null,sep:[this.sourceToken]});this.onKeyLine=!0;return;case"alias":case"scalar":case"single-quoted-scalar":case"double-quoted-scalar":{let o=this.flowScalar(this.type);i||t.value?(e.items.push({start:r,key:o,sep:[]}),this.onKeyLine=!0):t.sep?this.stack.push(o):(Object.assign(t,{key:o,sep:[]}),this.onKeyLine=!0);return}default:{let o=this.startBlockValue(e);if(o){if(o.type==="block-seq"){if(!t.explicitKey&&t.sep&&!ee(t.sep,"newline")){yield*this.pop({type:"error",offset:this.offset,message:"Unexpected block-seq-ind on same line with key",source:this.source});return}}else n&&e.items.push({start:r});this.stack.push(o);return}}}}yield*this.pop(),yield*this.step()}*blockSequence(e){let t=e.items[e.items.length-1];switch(this.type){case"newline":if(t.value){let n="end"in t.value?t.value.end:void 0;(Array.isArray(n)?n[n.length-1]:void 0)?.type==="comment"?n?.push(this.sourceToken):e.items.push({start:[this.sourceToken]})}else t.start.push(this.sourceToken);return;case"space":case"comment":if(t.value)e.items.push({start:[this.sourceToken]});else{if(this.atIndentedComment(t.start,e.indent)){let i=e.items[e.items.length-2]?.value?.end;if(Array.isArray(i)){zt(i,t.start),i.push(this.sourceToken),e.items.pop();return}}t.start.push(this.sourceToken)}return;case"anchor":case"tag":if(t.value||this.indent<=e.indent)break;t.start.push(this.sourceToken);return;case"seq-item-ind":if(this.indent!==e.indent)break;t.value||ee(t.start,"seq-item-ind")?e.items.push({start:[this.sourceToken]}):t.start.push(this.sourceToken);return}if(this.indent>e.indent){let n=this.startBlockValue(e);if(n){this.stack.push(n);return}}yield*this.pop(),yield*this.step()}*flowCollection(e){let t=e.items[e.items.length-1];if(this.type==="flow-error-end"){let n;do yield*this.pop(),n=this.peek(1);while(n?.type==="flow-collection")}else if(e.end.length===0){switch(this.type){case"comma":case"explicit-key-ind":!t||t.sep?e.items.push({start:[this.sourceToken]}):t.start.push(this.sourceToken);return;case"map-value-ind":!t||t.value?e.items.push({start:[],key:null,sep:[this.sourceToken]}):t.sep?t.sep.push(this.sourceToken):Object.assign(t,{key:null,sep:[this.sourceToken]});return;case"space":case"comment":case"newline":case"anchor":case"tag":!t||t.value?e.items.push({start:[this.sourceToken]}):t.sep?t.sep.push(this.sourceToken):t.start.push(this.sourceToken);return;case"alias":case"scalar":case"single-quoted-scalar":case"double-quoted-scalar":{let i=this.flowScalar(this.type);!t||t.value?e.items.push({start:[],key:i,sep:[]}):t.sep?this.stack.push(i):Object.assign(t,{key:i,sep:[]});return}case"flow-map-end":case"flow-seq-end":e.end.push(this.sourceToken);return}let n=this.startBlockValue(e);n?this.stack.push(n):(yield*this.pop(),yield*this.step())}else{let n=this.peek(2);if(n.type==="block-map"&&(this.type==="map-value-ind"&&n.indent===e.indent||this.type==="newline"&&!n.items[n.items.length-1].sep))yield*this.pop(),yield*this.step();else if(this.type==="map-value-ind"&&n.type!=="flow-collection"){let i=Qt(n),r=ke(i);Wo(e);let o=e.end.splice(1,e.end.length);o.push(this.sourceToken);let a={type:"block-map",offset:e.offset,indent:e.indent,items:[{start:r,key:e,sep:o}]};this.onKeyLine=!0,this.stack[this.stack.length-1]=a}else yield*this.lineEnd(e)}}flowScalar(e){if(this.onNewLine){let t=this.source.indexOf(`
137
137
  `)+1;for(;t!==0;)this.onNewLine(this.offset+t),t=this.source.indexOf(`
138
- `,t)+1}return{type:e,offset:this.offset,indent:this.indent,source:this.source}}startBlockValue(e){switch(this.type){case"alias":case"scalar":case"single-quoted-scalar":case"double-quoted-scalar":return this.flowScalar(this.type);case"block-scalar-header":return{type:"block-scalar",offset:this.offset,indent:this.indent,props:[this.sourceToken],source:""};case"flow-map-start":case"flow-seq-start":return{type:"flow-collection",offset:this.offset,indent:this.indent,start:this.sourceToken,items:[],end:[]};case"seq-item-ind":return{type:"block-seq",offset:this.offset,indent:this.indent,items:[{start:[this.sourceToken]}]};case"explicit-key-ind":{this.onKeyLine=!0;let t=Qt(e),n=ke(t);return n.push(this.sourceToken),{type:"block-map",offset:this.offset,indent:this.indent,items:[{start:n,explicitKey:!0}]}}case"map-value-ind":{this.onKeyLine=!0;let t=Qt(e),n=ke(t);return{type:"block-map",offset:this.offset,indent:this.indent,items:[{start:n,key:null,sep:[this.sourceToken]}]}}}return null}atIndentedComment(e,t){return this.type!=="comment"||this.indent<=t?!1:e.every(n=>n.type==="newline"||n.type==="space")}*documentEnd(e){this.type!=="doc-mode"&&(e.end?e.end.push(this.sourceToken):e.end=[this.sourceToken],this.type==="newline"&&(yield*this.pop()))}*lineEnd(e){switch(this.type){case"comma":case"doc-start":case"doc-end":case"flow-seq-end":case"flow-map-end":case"map-value-ind":yield*this.pop(),yield*this.step();break;case"newline":this.onKeyLine=!1;default:e.end?e.end.push(this.sourceToken):e.end=[this.sourceToken],this.type==="newline"&&(yield*this.pop())}}};Qo.Parser=xn});var ta=g(ze=>{"use strict";var zo=On(),ld=Fe(),Qe=Ye(),cd=vs(),ud=O(),fd=Bn(),Xo=Kn();function Zo(s){let e=s.prettyErrors!==!1;return{lineCounter:s.lineCounter||e&&new fd.LineCounter||null,prettyErrors:e}}function dd(s,e={}){let{lineCounter:t,prettyErrors:n}=Zo(e),i=new Xo.Parser(t?.addNewLine),r=new zo.Composer(e),o=Array.from(r.compose(i.parse(s)));if(n&&t)for(let a of o)a.errors.forEach(Qe.prettifyError(s,t)),a.warnings.forEach(Qe.prettifyError(s,t));return o.length>0?o:Object.assign([],{empty:!0},r.streamInfo())}function ea(s,e={}){let{lineCounter:t,prettyErrors:n}=Zo(e),i=new Xo.Parser(t?.addNewLine),r=new zo.Composer(e),o=null;for(let a of r.compose(i.parse(s),!0,s.length))if(!o)o=a;else if(o.options.logLevel!=="silent"){o.errors.push(new Qe.YAMLParseError(a.range.slice(0,2),"MULTIPLE_DOCS","Source contains multiple documents; please use YAML.parseAllDocuments()"));break}return n&&t&&(o.errors.forEach(Qe.prettifyError(s,t)),o.warnings.forEach(Qe.prettifyError(s,t))),o}function hd(s,e,t){let n;typeof e=="function"?n=e:t===void 0&&e&&typeof e=="object"&&(t=e);let i=ea(s,t);if(!i)return null;if(i.warnings.forEach(r=>cd.warn(i.options.logLevel,r)),i.errors.length>0){if(i.options.logLevel!=="silent")throw i.errors[0];i.errors=[]}return i.toJS(Object.assign({reviver:n},t))}function pd(s,e,t){let n=null;if(typeof e=="function"||Array.isArray(e)?n=e:t===void 0&&e&&(t=e),typeof t=="string"&&(t=t.length),typeof t=="number"){let i=Math.round(t);t=i<1?void 0:i>8?{indent:8}:{indent:i}}if(s===void 0){let{keepUndefined:i}=t??e??{};if(!i)return}return ud.isDocument(s)&&!n?s.toString(t):new ld.Document(s,n,t).toString(t)}ze.parse=hd;ze.parseAllDocuments=dd;ze.parseDocument=ea;ze.stringify=pd});var jn=g(E=>{"use strict";var md=On(),gd=Fe(),yd=rn(),Rn=Ye(),bd=Ee(),te=O(),wd=Q(),Sd=q(),kd=X(),vd=Z(),Nd=Wt(),Ad=Dn(),Od=Bn(),Ed=Kn(),Xt=ta(),sa=ve();E.Composer=md.Composer;E.Document=gd.Document;E.Schema=yd.Schema;E.YAMLError=Rn.YAMLError;E.YAMLParseError=Rn.YAMLParseError;E.YAMLWarning=Rn.YAMLWarning;E.Alias=bd.Alias;E.isAlias=te.isAlias;E.isCollection=te.isCollection;E.isDocument=te.isDocument;E.isMap=te.isMap;E.isNode=te.isNode;E.isPair=te.isPair;E.isScalar=te.isScalar;E.isSeq=te.isSeq;E.Pair=wd.Pair;E.Scalar=Sd.Scalar;E.YAMLMap=kd.YAMLMap;E.YAMLSeq=vd.YAMLSeq;E.CST=Nd;E.Lexer=Ad.Lexer;E.LineCounter=Od.LineCounter;E.Parser=Ed.Parser;E.parse=Xt.parse;E.parseAllDocuments=Xt.parseAllDocuments;E.parseDocument=Xt.parseDocument;E.stringify=Xt.stringify;E.visit=sa.visit;E.visitAsync=sa.visitAsync});import{appendFileSync as dh,readFileSync as hh}from"node:fs";var Ba=["claude-code","antigravity"],ei=s=>Ba.includes(s);function R(s,e){return s==="antigravity"?`/bk-${e}`:`/bk:${e}`}function ti(s,e){return s.replace(/\{\{bk:([a-z][a-z-]*)\}\}/g,(t,n)=>R(e,n))}import{isAbsolute as ns,relative as ja,resolve as ii}from"node:path";import{existsSync as si}from"node:fs";import{dirname as xa,join as ni,resolve as Ka}from"node:path";var Ra=".buildkeel.yaml";function et(s){let e=Ka(s);for(;;){if(si(ni(e,Ra)))return e;if(si(ni(e,".git")))return null;let t=xa(e);if(t===e)return null;e=t}}var oi=["write_to_file","replace_file_content","multi_replace_file_content"],Nh=oi.join("|");var Fa={decision:"allow"},ri={decision:"ask"},is=(s,e)=>{let t=ja(s,e);return t===""||!t.startsWith("..")&&!ns(t)};function ss(s,e){let t=(s.workspacePaths??[]).filter(i=>typeof i=="string"&&i!==""),n=e?[...t.filter(i=>is(i,e)),...t.filter(i=>!is(i,e))]:t;return n.find(i=>et(i)!==null)??n[0]??process.cwd()}var ai={parse(s,e){let t=s??{};switch(e[0]){case"PreInvocation":return t.conversationId?{kind:"prompt",cwd:ss(t),conversation:t.conversationId,newMessage:t.invocationNum===0,transcript:t.transcriptPath??null}:{kind:"ignored"};case"PreToolUse":case"PostToolUse":{let n=t.toolCall?.name??"",i=t.toolCall?.args?.TargetFile,r=oi.includes(n)&&typeof i=="string"&&i!==""?i:null;if(e[0]==="PostToolUse"){if(!r||t.error)return{kind:"ignored"};let l=ss(t,ns(r)?r:void 0);return{kind:"after-edit",cwd:l,tool:n,files:[ii(l,r)],...t.conversationId?{deferTo:t.conversationId}:{}}}let o=ss(t,r&&ns(r)?r:void 0),a=[...t.workspacePaths??[],...t.artifactDirectoryPath?[t.artifactDirectoryPath]:[]].filter(l=>typeof l=="string"&&l!=="");return{kind:"before-edit",cwd:o,tool:n,files:r?[ii(o,r)]:[],folders:a}}default:return{kind:"ignored"}}},render(s,e){if(e.kind==="prompt"){let t=s.context?{injectSteps:[{ephemeralMessage:s.context}]}:{};return{stdout:JSON.stringify(t),exitCode:0}}if(e.kind==="before-edit"){let t=e.files.length>0&&e.files.every(i=>(e.folders??[]).some(r=>is(r,i))),n=s.block?{decision:"deny",reason:s.block}:t?Fa:ri;return{stdout:JSON.stringify(n),exitCode:0}}return{stdout:"{}",exitCode:0}},failed:s=>({stdout:JSON.stringify(s[0]==="PreToolUse"?ri:{}),exitCode:0})};var li={Edit:"file_path",Write:"file_path",MultiEdit:"file_path",NotebookEdit:"notebook_path"},Oh=Object.keys(li).join("|"),Ha=["Bash","PowerShell"],Ua={startup:"startup",resume:"resume",clear:"clear",compact:"compact",fork:"resume"},ci={parse(s){let e=s??{},t=e.cwd??process.cwd();switch(e.hook_event_name){case"SessionStart":{let n=Ua[e.source??"startup"]??"startup";return{kind:"session-start",cwd:t,reason:n}}case"PreToolUse":case"PostToolUse":{let n=e.tool_name??"";if(e.hook_event_name==="PreToolUse"&&Ha.includes(n)){let o=e.tool_input?.command;return typeof o=="string"?{kind:"before-shell",cwd:t,tool:n,command:o}:{kind:"ignored"}}let i=li[n],r=i?e.tool_input?.[i]:void 0;return typeof r!="string"?{kind:"ignored"}:{kind:e.hook_event_name==="PreToolUse"?"before-edit":"after-edit",cwd:t,tool:n,files:[r]}}default:return{kind:"ignored"}}},render(s,e){if(e.kind==="session-start")return{stdout:s.context??"",exitCode:0};if(e.kind==="before-shell"&&s.context)return{stdout:JSON.stringify({hookSpecificOutput:{hookEventName:"PreToolUse",additionalContext:s.context}}),exitCode:0};if(e.kind==="before-edit"&&(s.block||s.context)){let t={hookSpecificOutput:{hookEventName:"PreToolUse",...s.block?{permissionDecision:"deny",permissionDecisionReason:s.block}:{},...s.context?{additionalContext:s.context}:{}}};return{stdout:JSON.stringify(t),exitCode:0}}return e.kind==="after-edit"&&s.context?{stdout:JSON.stringify({hookSpecificOutput:{hookEventName:"PostToolUse",additionalContext:s.context}}),exitCode:0}:{stdout:"",exitCode:0}},failed:()=>({stdout:"",exitCode:1})};import{existsSync as Th,readFileSync as Ya,writeFileSync as qh}from"node:fs";import{join as rs}from"node:path";var P=".buildkeel",Ja="hook-cache.json",Va="context.md",ui=s=>rs(s,P,Va),Wa="context-short.md",fi=s=>rs(s,P,Wa);function Ga(s){return rs(s,P,Ja)}function di(s){try{let e=JSON.parse(Ya(Ga(s),"utf8"));return e?.version===1?e:null}catch{return null}}var Ih=`${P}/`;import{appendFileSync as Id,closeSync as $d,fstatSync as _d,mkdirSync as la,openSync as Pd,readFileSync as ca,readSync as Dd,renameSync as ua,rmSync as Md,statSync as Bd,writeFileSync as xd}from"node:fs";import{homedir as Kd}from"node:os";import{join as ts}from"node:path";import{randomBytes as Qa}from"node:crypto";import{dirname as za,join as Xa}from"node:path";var hi=s=>Xa(za(s),`.${Qa(6).toString("hex")}.bk-tmp`);import{readFileSync as Td}from"node:fs";var Fn=Zn(jn(),1);var Cd=Zn(jn(),1);var Hn={type:"test-plan",dir:"test-plan",file:"plan.md",idSuffix:"-TP"},Un={type:"design",dir:"design",file:"design.md",idSuffix:"-DD"},om=[{type:Hn.type,dir:Hn.dir,file:Hn.file,suffix:"TP"},{type:Un.type,dir:Un.dir,file:Un.file,suffix:"DD"}];function na(s){return/^([A-Z][A-Z0-9]*-[A-Za-z0-9]+)(?:-|$)/.exec(s)?.[1]??null}var Zt=(s,e)=>e?`${s} "${e}"`:s;function Yn(s,e){let t=[{key:"setup",text:`BuildKeel is set up in this repository. Until the user runs a BuildKeel command, or you are about to change a file, converse as you would without it, and leave BuildKeel's artifacts, IDs and commands out of it. Be a thinking partner: check facts in the code and docs before asking; for a design choice, lay out the options, trade-offs, risks and pitfalls, then ask one question in prose and say why it matters. Recommend only when the evidence supports it, and say what it rests on; never assume the user's intent, priorities or constraints, and ask when only they know. When you look something up, say what you found and give the sources. Keep what was decided apart from what is still open. Take the space the reasoning needs; do not cut it short or push toward a conclusion or a plan. Use your question tool only for a closed choice between comparable options, and let several answers be picked when more than one can apply. Until the user signals the discussion has reached a point, end your replies without mentioning BuildKeel, the spec tree or its artifacts, and without offering to draft, record, plan or build anything; settling the last open question is not that signal, so wait for the user to say it. When they signal it, such as with "sounds good" or "I think that's it", ask once, in prose, whether there is more to explore or they are ready to capture it with ${R(e,"capture")}; if they keep talking or say no, do not offer again until something new is settled. Never start it yourself.`,fingerprint:""}];if(s.activeUnit){let n=s.activeBranch?` on branch ${s.activeBranch}`:"";t.push({key:"unit",text:`Active unit: ${Zt(s.activeUnit,s.activeTitle)}${n}.`,fingerprint:`${s.activeUnit}${n}`})}else s.untrackedSince?t.push({key:"unit",text:`No active unit in this working tree: the user chose to work without one here since ${s.untrackedSince}, so changes made now belong to no unit. Starting a unit with ${R(e,"build")} <ID> ends this.`,fingerprint:`untracked:${s.untrackedSince}`}):t.push({key:"unit",text:"No unit is active in this working tree.",fingerprint:""});if(t.push({key:"rules",text:"Read and change the spec tree only through the BuildKeel tools, never by editing its files.",fingerprint:""}),t.push({key:"review",text:"Before you save anything to the spec tree, show what changes, in full, in your reply, and ask in plain text. Agreement saves it; anything else is feedback: discuss it, then list each point with how it was addressed, show the final version, and save only once the user agrees. When work would change a document the user agreed, say which, what and why, and take it through the same review. Never add, change or remove a requirement, criterion, scenario or task on your own initiative."+(s.mode==="spec-driven"?" Report each tick in a line; do not ask about ticks. Tick with save_draft tick, change one requirement with save_draft requirement, and read one file of a change with query file, rather than resending or rereading whole files.":""),fingerprint:s.mode==="spec-driven"?"spec-driven":""}),s.mode==="spec-driven")t.push({key:"stages",text:"This spec tree is Spec-driven: its capability specs say how the system behaves now, and each change says how it will change; nothing in it is approved. Before discussing how the system behaves, look up the capability specs with the BuildKeel query tool. Each BuildKeel command follows its part for a Spec-driven tree.",fingerprint:"spec-driven"},{key:"consistency",text:"When you edit one file of an open change (its proposal, a spec change, its design or its tasks), check the others against the edit, in any direction, and name the follow-on edits in the same reply; then take each through the review, one edit at a time, with what changes and why. Never edit code as part of this.",fingerprint:"spec-driven"},{key:"build",text:`While a change is open, commit each task's work as it is done, adding "BuildKeel-Task: <number>" to a commit that does exactly that one task, and tick each task once its verification passes. Name a change with its title, never by its ID alone, such as CH-001 "Download historical market data". When the user asks in words to build a change's tasks, suggest ${R(e,"build")} once; if they say to go ahead without it, build the tasks anyway by the same rules, and if they say not now, build nothing. A specific edit they ask for needs no suggestion. While building, stop and ask when a test fails and you cannot fix it, or a requirement is unclear. Before adding a requirement to a change with more than half its tasks ticked, ask once whether it belongs in that change or a new one, and draft nothing until they answer.`,fingerprint:`spec-driven|${e}`},{key:"tests",text:"Put the ID of the scenario a test checks in square brackets in the test's name, one ID per bracket.",fingerprint:"spec-driven"}),s.guide==="on"&&t.push({key:"guide",text:"This person chose to be guided through their first change: at each step of it, from drafting to finishing, add one or two plain lines on what just happened and what comes next, as BuildKeel's tool results say. If they ask you to stop explaining, turn it off with the BuildKeel manage tool (action guide, guide off).",fingerprint:"on"});else if(s.stages){let n=(s.stageExamples??[]).map(r=>`\`${r}\``),i=n.length>1?`${n.slice(0,-1).join(", ")} or ${n.at(-1)}`:n[0]??"";t.push({key:"stages",text:`This spec tree's stages, as the team names them: ${s.stages}. Use these names with the user, in each command's help, questions and output; BuildKeel's tools accept them as types, and the usual names too.`+(i?` Examples of what to draft, in the team's words: ${i}; use these in place of a command's usual examples.`:""),fingerprint:`${s.stages}|${n.join(",")}`})}return t}var qd=8e3;function ra(s,e){try{return ti(Td(s,"utf8").trimEnd(),e)}catch{return null}}function Ld(s,e,t){return e.activeUnit?ra(ui(s),t):null}function es(s,e,t,n){let i=ia(Ld(s,e,t),e,t,n);if(i.length<=qd||!e.activeUnit)return i;let r=ra(fi(s),t);return r?ia(r,e,t,n):i}function ia(s,e,t,n){let i=["BuildKeel:"];for(let r of Yn(e,t)){let o=r.key==="unit"&&e.activeUnit?s?n?" Its context is loaded again below.":" Its context follows.":" BuildKeel has no context for it; call the BuildKeel get_context tool to see why.":"";i.push(`- ${r.text}${o}`)}return s&&i.push("",s),i.join(`
138
+ `,t)+1}return{type:e,offset:this.offset,indent:this.indent,source:this.source}}startBlockValue(e){switch(this.type){case"alias":case"scalar":case"single-quoted-scalar":case"double-quoted-scalar":return this.flowScalar(this.type);case"block-scalar-header":return{type:"block-scalar",offset:this.offset,indent:this.indent,props:[this.sourceToken],source:""};case"flow-map-start":case"flow-seq-start":return{type:"flow-collection",offset:this.offset,indent:this.indent,start:this.sourceToken,items:[],end:[]};case"seq-item-ind":return{type:"block-seq",offset:this.offset,indent:this.indent,items:[{start:[this.sourceToken]}]};case"explicit-key-ind":{this.onKeyLine=!0;let t=Qt(e),n=ke(t);return n.push(this.sourceToken),{type:"block-map",offset:this.offset,indent:this.indent,items:[{start:n,explicitKey:!0}]}}case"map-value-ind":{this.onKeyLine=!0;let t=Qt(e),n=ke(t);return{type:"block-map",offset:this.offset,indent:this.indent,items:[{start:n,key:null,sep:[this.sourceToken]}]}}}return null}atIndentedComment(e,t){return this.type!=="comment"||this.indent<=t?!1:e.every(n=>n.type==="newline"||n.type==="space")}*documentEnd(e){this.type!=="doc-mode"&&(e.end?e.end.push(this.sourceToken):e.end=[this.sourceToken],this.type==="newline"&&(yield*this.pop()))}*lineEnd(e){switch(this.type){case"comma":case"doc-start":case"doc-end":case"flow-seq-end":case"flow-map-end":case"map-value-ind":yield*this.pop(),yield*this.step();break;case"newline":this.onKeyLine=!1;default:e.end?e.end.push(this.sourceToken):e.end=[this.sourceToken],this.type==="newline"&&(yield*this.pop())}}};Qo.Parser=xn});var ta=g(ze=>{"use strict";var zo=On(),ld=Fe(),Qe=Ye(),cd=vs(),ud=O(),fd=Bn(),Xo=Kn();function Zo(s){let e=s.prettyErrors!==!1;return{lineCounter:s.lineCounter||e&&new fd.LineCounter||null,prettyErrors:e}}function dd(s,e={}){let{lineCounter:t,prettyErrors:n}=Zo(e),i=new Xo.Parser(t?.addNewLine),r=new zo.Composer(e),o=Array.from(r.compose(i.parse(s)));if(n&&t)for(let a of o)a.errors.forEach(Qe.prettifyError(s,t)),a.warnings.forEach(Qe.prettifyError(s,t));return o.length>0?o:Object.assign([],{empty:!0},r.streamInfo())}function ea(s,e={}){let{lineCounter:t,prettyErrors:n}=Zo(e),i=new Xo.Parser(t?.addNewLine),r=new zo.Composer(e),o=null;for(let a of r.compose(i.parse(s),!0,s.length))if(!o)o=a;else if(o.options.logLevel!=="silent"){o.errors.push(new Qe.YAMLParseError(a.range.slice(0,2),"MULTIPLE_DOCS","Source contains multiple documents; please use YAML.parseAllDocuments()"));break}return n&&t&&(o.errors.forEach(Qe.prettifyError(s,t)),o.warnings.forEach(Qe.prettifyError(s,t))),o}function hd(s,e,t){let n;typeof e=="function"?n=e:t===void 0&&e&&typeof e=="object"&&(t=e);let i=ea(s,t);if(!i)return null;if(i.warnings.forEach(r=>cd.warn(i.options.logLevel,r)),i.errors.length>0){if(i.options.logLevel!=="silent")throw i.errors[0];i.errors=[]}return i.toJS(Object.assign({reviver:n},t))}function pd(s,e,t){let n=null;if(typeof e=="function"||Array.isArray(e)?n=e:t===void 0&&e&&(t=e),typeof t=="string"&&(t=t.length),typeof t=="number"){let i=Math.round(t);t=i<1?void 0:i>8?{indent:8}:{indent:i}}if(s===void 0){let{keepUndefined:i}=t??e??{};if(!i)return}return ud.isDocument(s)&&!n?s.toString(t):new ld.Document(s,n,t).toString(t)}ze.parse=hd;ze.parseAllDocuments=dd;ze.parseDocument=ea;ze.stringify=pd});var jn=g(E=>{"use strict";var md=On(),gd=Fe(),yd=rn(),Rn=Ye(),bd=Ee(),te=O(),wd=Q(),Sd=q(),kd=X(),vd=Z(),Nd=Wt(),Ad=Dn(),Od=Bn(),Ed=Kn(),Xt=ta(),sa=ve();E.Composer=md.Composer;E.Document=gd.Document;E.Schema=yd.Schema;E.YAMLError=Rn.YAMLError;E.YAMLParseError=Rn.YAMLParseError;E.YAMLWarning=Rn.YAMLWarning;E.Alias=bd.Alias;E.isAlias=te.isAlias;E.isCollection=te.isCollection;E.isDocument=te.isDocument;E.isMap=te.isMap;E.isNode=te.isNode;E.isPair=te.isPair;E.isScalar=te.isScalar;E.isSeq=te.isSeq;E.Pair=wd.Pair;E.Scalar=Sd.Scalar;E.YAMLMap=kd.YAMLMap;E.YAMLSeq=vd.YAMLSeq;E.CST=Nd;E.Lexer=Ad.Lexer;E.LineCounter=Od.LineCounter;E.Parser=Ed.Parser;E.parse=Xt.parse;E.parseAllDocuments=Xt.parseAllDocuments;E.parseDocument=Xt.parseDocument;E.stringify=Xt.stringify;E.visit=sa.visit;E.visitAsync=sa.visitAsync});import{appendFileSync as dh,readFileSync as hh}from"node:fs";var Ba=["claude-code","antigravity"],ei=s=>Ba.includes(s);function R(s,e){return s==="antigravity"?`/bk-${e}`:`/bk:${e}`}function ti(s,e){return s.replace(/\{\{bk:([a-z][a-z-]*)\}\}/g,(t,n)=>R(e,n))}import{isAbsolute as ns,relative as ja,resolve as ii}from"node:path";import{existsSync as si}from"node:fs";import{dirname as xa,join as ni,resolve as Ka}from"node:path";var Ra=".buildkeel.yaml";function et(s){let e=Ka(s);for(;;){if(si(ni(e,Ra)))return e;if(si(ni(e,".git")))return null;let t=xa(e);if(t===e)return null;e=t}}var oi=["write_to_file","replace_file_content","multi_replace_file_content"],Nh=oi.join("|");var Fa={decision:"allow"},ri={decision:"ask"},is=(s,e)=>{let t=ja(s,e);return t===""||!t.startsWith("..")&&!ns(t)};function ss(s,e){let t=(s.workspacePaths??[]).filter(i=>typeof i=="string"&&i!==""),n=e?[...t.filter(i=>is(i,e)),...t.filter(i=>!is(i,e))]:t;return n.find(i=>et(i)!==null)??n[0]??process.cwd()}var ai={parse(s,e){let t=s??{};switch(e[0]){case"PreInvocation":return t.conversationId?{kind:"prompt",cwd:ss(t),conversation:t.conversationId,newMessage:t.invocationNum===0,transcript:t.transcriptPath??null}:{kind:"ignored"};case"PreToolUse":case"PostToolUse":{let n=t.toolCall?.name??"",i=t.toolCall?.args?.TargetFile,r=oi.includes(n)&&typeof i=="string"&&i!==""?i:null;if(e[0]==="PostToolUse"){if(!r||t.error)return{kind:"ignored"};let l=ss(t,ns(r)?r:void 0);return{kind:"after-edit",cwd:l,tool:n,files:[ii(l,r)],...t.conversationId?{deferTo:t.conversationId}:{}}}let o=ss(t,r&&ns(r)?r:void 0),a=[...t.workspacePaths??[],...t.artifactDirectoryPath?[t.artifactDirectoryPath]:[]].filter(l=>typeof l=="string"&&l!=="");return{kind:"before-edit",cwd:o,tool:n,files:r?[ii(o,r)]:[],folders:a}}default:return{kind:"ignored"}}},render(s,e){if(e.kind==="prompt"){let t=s.context?{injectSteps:[{ephemeralMessage:s.context}]}:{};return{stdout:JSON.stringify(t),exitCode:0}}if(e.kind==="before-edit"){let t=e.files.length>0&&e.files.every(i=>(e.folders??[]).some(r=>is(r,i))),n=s.block?{decision:"deny",reason:s.block}:t?Fa:ri;return{stdout:JSON.stringify(n),exitCode:0}}return{stdout:"{}",exitCode:0}},failed:s=>({stdout:JSON.stringify(s[0]==="PreToolUse"?ri:{}),exitCode:0})};var li={Edit:"file_path",Write:"file_path",MultiEdit:"file_path",NotebookEdit:"notebook_path"},Oh=Object.keys(li).join("|"),Ha=["Bash","PowerShell"],Ua={startup:"startup",resume:"resume",clear:"clear",compact:"compact",fork:"resume"},ci={parse(s){let e=s??{},t=e.cwd??process.cwd();switch(e.hook_event_name){case"SessionStart":{let n=Ua[e.source??"startup"]??"startup";return{kind:"session-start",cwd:t,reason:n}}case"PreToolUse":case"PostToolUse":{let n=e.tool_name??"";if(e.hook_event_name==="PreToolUse"&&Ha.includes(n)){let o=e.tool_input?.command;return typeof o=="string"?{kind:"before-shell",cwd:t,tool:n,command:o}:{kind:"ignored"}}let i=li[n],r=i?e.tool_input?.[i]:void 0;return typeof r!="string"?{kind:"ignored"}:{kind:e.hook_event_name==="PreToolUse"?"before-edit":"after-edit",cwd:t,tool:n,files:[r]}}default:return{kind:"ignored"}}},render(s,e){if(e.kind==="session-start")return{stdout:s.context??"",exitCode:0};if(e.kind==="before-shell"&&s.context)return{stdout:JSON.stringify({hookSpecificOutput:{hookEventName:"PreToolUse",additionalContext:s.context}}),exitCode:0};if(e.kind==="before-edit"&&(s.block||s.context)){let t={hookSpecificOutput:{hookEventName:"PreToolUse",...s.block?{permissionDecision:"deny",permissionDecisionReason:s.block}:{},...s.context?{additionalContext:s.context}:{}}};return{stdout:JSON.stringify(t),exitCode:0}}return e.kind==="after-edit"&&s.context?{stdout:JSON.stringify({hookSpecificOutput:{hookEventName:"PostToolUse",additionalContext:s.context}}),exitCode:0}:{stdout:"",exitCode:0}},failed:()=>({stdout:"",exitCode:1})};import{existsSync as Th,readFileSync as Ya,writeFileSync as qh}from"node:fs";import{join as rs}from"node:path";var P=".buildkeel",Ja="hook-cache.json",Va="context.md",ui=s=>rs(s,P,Va),Wa="context-short.md",fi=s=>rs(s,P,Wa);function Ga(s){return rs(s,P,Ja)}function di(s){try{let e=JSON.parse(Ya(Ga(s),"utf8"));return e?.version===1?e:null}catch{return null}}var Ih=`${P}/`;import{appendFileSync as Id,closeSync as $d,fstatSync as _d,mkdirSync as la,openSync as Pd,readFileSync as ca,readSync as Dd,renameSync as ua,rmSync as Md,statSync as Bd,writeFileSync as xd}from"node:fs";import{homedir as Kd}from"node:os";import{join as ts}from"node:path";import{randomBytes as Qa}from"node:crypto";import{dirname as za,join as Xa}from"node:path";var hi=s=>Xa(za(s),`.${Qa(6).toString("hex")}.bk-tmp`);import{readFileSync as Td}from"node:fs";var Fn=Zn(jn(),1);var Cd=Zn(jn(),1);var Hn={type:"test-plan",dir:"test-plan",file:"plan.md",idSuffix:"-TP"},Un={type:"design",dir:"design",file:"design.md",idSuffix:"-DD"},om=[{type:Hn.type,dir:Hn.dir,file:Hn.file,suffix:"TP"},{type:Un.type,dir:Un.dir,file:Un.file,suffix:"DD"}];function na(s){return/^([A-Z][A-Z0-9]*-[A-Za-z0-9]+)(?:-|$)/.exec(s)?.[1]??null}var Zt=(s,e)=>e?`${s} "${e}"`:s;function Yn(s,e){let t=[{key:"setup",text:`BuildKeel is set up in this repository. Until the user runs a BuildKeel command, or you are about to change a file, converse as you would without it, and leave BuildKeel's artifacts, IDs and commands out of it. Be a thinking partner: check facts in the code and docs before asking; for a design choice, lay out the options, trade-offs, risks and pitfalls, then ask one question in prose and say why it matters. Recommend only when the evidence supports it, and say what it rests on; never assume the user's intent, priorities or constraints, and ask when only they know. When you look something up, say what you found and give the sources. Keep what was decided apart from what is still open. Take the space the reasoning needs; do not cut it short or push toward a conclusion or a plan. Use your question tool only for a closed choice between comparable options, and let several answers be picked when more than one can apply. Until the user signals the discussion has reached a point, end your replies without mentioning BuildKeel, the spec tree or its artifacts, and without offering to draft, record, plan or build anything; settling the last open question is not that signal, so wait for the user to say it. When they signal it, such as with "sounds good" or "I think that's it", ask once, in prose, whether there is more to explore or they are ready to capture it with ${R(e,"capture")}; if they keep talking or say no, do not offer again until something new is settled. Never start it yourself.`,fingerprint:""}];if(s.activeUnit){let n=s.activeBranch?` on branch ${s.activeBranch}`:"";t.push({key:"unit",text:`Active unit: ${Zt(s.activeUnit,s.activeTitle)}${n}.`,fingerprint:`${s.activeUnit}${n}`})}else s.untrackedSince?t.push({key:"unit",text:`No active unit in this working tree: the user chose to work without one here since ${s.untrackedSince}, so changes made now belong to no unit. Starting a unit with ${R(e,"build")} <ID> ends this.`,fingerprint:`untracked:${s.untrackedSince}`}):t.push({key:"unit",text:"No unit is active in this working tree.",fingerprint:""});if(t.push({key:"rules",text:"Read and change the spec tree only through the BuildKeel tools, never by editing its files.",fingerprint:""}),t.push({key:"review",text:"Before you save anything to the spec tree, show what changes, in full, in your reply, and ask in plain text. Agreement saves it; anything else is feedback: discuss it, then list each point with how it was addressed, show the final version, and save only once the user agrees. When work would change a document the user agreed, say which, what and why, and take it through the same review. Never add, change or remove a requirement, criterion, scenario or task on your own initiative. Check a reported problem against the spec and its code first: propose, as your reading, a defect, a change in behavior, a gap or code no spec covers; record nothing until the user confirms. If agreed behavior looks wrong while building, stop and ask, naming the requirement, criterion or scenario and why; never build something else or edit the spec without the answer. Kept, build it as agreed; changed, update the spec first, then the code."+(s.mode==="spec-driven"?" Report each tick in a line; do not ask about ticks. Tick with save_draft tick, change one requirement with save_draft requirement, and read one file of a change with query file, rather than resending or rereading whole files.":""),fingerprint:s.mode==="spec-driven"?"spec-driven":""}),s.mode==="spec-driven")t.push({key:"stages",text:"This spec tree is Spec-driven: its capability specs say how the system behaves now, and each change says how it will change; nothing in it is approved. Before discussing how the system behaves, look up the capability specs with the BuildKeel query tool. Each BuildKeel command follows its part for a Spec-driven tree.",fingerprint:"spec-driven"},{key:"consistency",text:"When you edit one file of an open change (its proposal, a spec change, its design or its tasks), check the others against the edit, in any direction, and name the follow-on edits in the same reply; then take each through the review, one edit at a time, with what changes and why. Never edit code as part of this.",fingerprint:"spec-driven"},{key:"build",text:`While a change is open, commit each task's work as it is done, adding "BuildKeel-Task: <number>" to a commit that does exactly that one task, and tick each task once its verification passes. Name a change with its title, never by its ID alone, such as CH-001 "Download historical market data". When the user asks in words to build a change's tasks, suggest ${R(e,"build")} once; if they say to go ahead without it, build the tasks anyway by the same rules, and if they say not now, build nothing. A specific edit they ask for needs no suggestion. While building, stop and ask when a test fails and you cannot fix it, or a requirement is unclear. Before adding a requirement to a change with more than half its tasks ticked, ask once whether it belongs in that change or a new one, and draft nothing until they answer.`,fingerprint:`spec-driven|${e}`},{key:"tests",text:"Put the ID of the scenario a test checks in square brackets in the test's name, one ID per bracket.",fingerprint:"spec-driven"}),s.guide==="on"&&t.push({key:"guide",text:"This person chose to be guided through their first change: at each step of it, from drafting to finishing, add one or two plain lines on what just happened and what comes next, as BuildKeel's tool results say. If they ask you to stop explaining, turn it off with the BuildKeel manage tool (action guide, guide off).",fingerprint:"on"});else if(s.stages){let n=(s.stageExamples??[]).map(r=>`\`${r}\``),i=n.length>1?`${n.slice(0,-1).join(", ")} or ${n.at(-1)}`:n[0]??"";t.push({key:"stages",text:`This spec tree's stages, as the team names them: ${s.stages}. Use these names with the user, in each command's help, questions and output; BuildKeel's tools accept them as types, and the usual names too.`+(i?` Examples of what to draft, in the team's words: ${i}; use these in place of a command's usual examples.`:""),fingerprint:`${s.stages}|${n.join(",")}`})}return t}var qd=8e3;function ra(s,e){try{return ti(Td(s,"utf8").trimEnd(),e)}catch{return null}}function Ld(s,e,t){return e.activeUnit?ra(ui(s),t):null}function es(s,e,t,n){let i=ia(Ld(s,e,t),e,t,n);if(i.length<=qd||!e.activeUnit)return i;let r=ra(fi(s),t);return r?ia(r,e,t,n):i}function ia(s,e,t,n){let i=["BuildKeel:"];for(let r of Yn(e,t)){let o=r.key==="unit"&&e.activeUnit?s?n?" Its context is loaded again below.":" Its context follows.":" BuildKeel has no context for it; call the BuildKeel get_context tool to see why.":"";i.push(`- ${r.text}${o}`)}return s&&i.push("",s),i.join(`
139
139
  `)}var oa=({event:s,codeRoot:e,cache:t,host:n})=>s.kind!=="session-start"?null:{context:es(e,t,n,s.reason==="compact")};var fa=(s,e,t)=>ts(s,P,"conversations",`${e.replace(/[^A-Za-z0-9_-]/g,"_").slice(0,100)}.${t}`),Rd=(s,e)=>fa(s,e,"json"),da=(s,e)=>fa(s,e,"notices");function ha(s,e,t){let n=da(s,e);la(ts(n,".."),{recursive:!0}),Id(n,`${JSON.stringify(t)}
140
140
  `)}function jd(s,e){let t=da(s,e),n=hi(t),i;try{ua(t,n),i=ca(n,"utf8")}catch{return[]}finally{Md(n,{force:!0})}return i.split(`
141
141
  `).filter(Boolean).flatMap(r=>{try{return[String(JSON.parse(r))]}catch{return[]}})}function Fd(s){try{let e=JSON.parse(ca(s,"utf8"));return e.version===1?e:null}catch{return null}}function aa(s,e){la(ts(s,".."),{recursive:!0});let t=`${s}.${process.pid}.tmp`;xd(t,`${JSON.stringify(e,null,2)}
@@ -143,7 +143,7 @@ ${t.comment}`:t.comment}this.doc.range[2]=t.offset;break}default:this.errors.pus
143
143
  `))if(h.trim())try{let u=JSON.parse(h);typeof u.step_index!="number"||typeof u.type!="string"?l=!1:u.type==="CHECKPOINT"&&(c=Math.max(c??-1,u.step_index))}catch{l=!1}return{ok:l,offset:i+a+1,checkpoint:c}}catch{return{ok:!1,offset:e,checkpoint:null}}finally{$d(t)}}function Ud(s){if(!s)return 0;try{return Bd(pa(s)).size}catch{return 0}}var ma=s=>{if(s.event.kind!=="prompt")return null;let e=jd(s.codeRoot,s.event.conversation),n=[Yd(s)?.context,...e].filter(Boolean).join(`
144
144
 
145
145
  `);return n?{context:n}:null},Yd=({event:s,codeRoot:e,cache:t,host:n})=>{if(s.kind!=="prompt")return null;let i=Rd(e,s.conversation),r=Yn(t,n),o=Object.fromEntries(r.map(f=>[f.key,f.fingerprint])),a=Fd(i);if(!a)return aa(i,{version:1,startedAt:new Date().toISOString(),reported:o,transcript:{offset:Ud(s.transcript),checkpoint:null}}),{context:es(e,t,n,!1)};let l=s.transcript?Hd(s.transcript,a.transcript.offset):{ok:!1,offset:a.transcript.offset,checkpoint:null},c=l.checkpoint!==null&&(a.transcript.checkpoint===null||l.checkpoint>a.transcript.checkpoint),h={...a,transcript:{offset:l.offset,checkpoint:c?l.checkpoint:a.transcript.checkpoint}},u=null;if(c)u=es(e,t,n,!0),h.reported=o;else if(s.newMessage){let f=r.filter(p=>a.reported[p.key]!==p.fingerprint).map(p=>`- ${p.text}`);if(!l.ok&&t.activeUnit){let p=`- Active unit: ${t.activeUnit}. If its requirements and test cases are no longer in view, call the BuildKeel get_context tool.`,y=f.findIndex(d=>d.startsWith("- Active unit:"));y>=0?f[y]=`${f[y]} If its requirements and test cases are no longer in view, call the BuildKeel get_context tool.`:f.unshift(p)}f.length>0&&(u=["BuildKeel:",...f].join(`
146
- `)),h.reported=o}return JSON.stringify(h)!==JSON.stringify(a)&&aa(i,h),u?{context:u}:null};import{appendFileSync as th,mkdirSync as sh}from"node:fs";import{dirname as nh,join as ih,resolve as rh}from"node:path";import{existsSync as Jd,realpathSync as ga}from"node:fs";import{basename as va,dirname as ya,isAbsolute as Vd,join as Wd,relative as Na,resolve as Gd,sep as Qd}from"node:path";var Jn={versions:"the frozen copy",approvals:"an approval record",completions:"a completion record",repins:"a re-pin decision record"},zd={"test-plan":"TP",design:"DD"},Xd=process.platform==="win32",T=s=>Xd?s.toLowerCase():s;function le(s,e){let t=ba(s,e);if(t!==null)return t;try{let n=e;for(;!Jd(n)&&ya(n)!==n;)n=ya(n);let i=Wd(ga(n),Na(n,e));return ba(ga(s),i)}catch{return null}}var ce=(s,e)=>le(s,e);function ba(s,e){let t=Na(s,e);return t===""||t.startsWith("..")||Vd(t)?null:t.split(Qd).join("/")}var wa="Make the change you want through a new change, drafted with the BuildKeel save_draft tool (type change): finishing it writes the next versions.",Zd="It is kept, read-only, as the history of how the system came to be before this spec tree moved to Full SDLC. Change behavior through the team's stages, as a new version of the artifact it became.";function Sa(s,e){if(s.conversion!=="finished")return eh(s,e);if(T(e)===T("onboarding/conversion.yaml"))return`BuildKeel: ${e} records how this spec tree moved to Full SDLC, and where each scenario went. It was finished when the tree switched, and is never edited.`;let[t]=e.split("/").map(T);return t!==T("capabilities")&&t!==T("changes")?null:`BuildKeel: ${e} is part of the capability specs and changes this spec tree had before it moved to Full SDLC. ${Zd}`}function eh(s,e){let t=e.split("/"),[n,,i]=t.map(T);if(n===T("capabilities")&&t.length>=3){let a=/^v(0|[1-9][0-9]*)\.(md|yaml)$/.exec(va(e)),l=a?`${t[1]} v${a[1]}`:t[1]??"",c=a?.[2]==="yaml"?`the record of ${l}`:a?`${l}, a capability spec version`:`part of capability ${l}`;return`BuildKeel: ${e} is ${c}, written when a change was archived. Capability specs are never edited directly. ${wa}`}if(n!==T("changes")||t.length<3)return null;let r=na((t[1]??"").toUpperCase()),o=r??t[1]??"";return t.length===3&&i===T("discarded.yaml")?`BuildKeel: ${e} is the discard record of ${o}. BuildKeel writes it once when a change is discarded, and it is never edited.`:t.length===3&&i===T("archive.yaml")?`BuildKeel: ${e} is the archive record of ${o}, written once when it was archived. It is never edited.`:t.length===4&&i!==void 0&&Object.hasOwn(Jn,i)?`BuildKeel: ${e} is ${Jn[i]} of ${o}. BuildKeel writes these records once and they are never edited, by hand or by the agent.`:r&&(s.archivedChanges??[]).some(a=>T(a)===T(r))?`BuildKeel: ${e} belongs to ${o}, which is archived: its files are its record and are never edited. ${wa}`:null}function Xe(s,e){let t=ce(s.specRoot,e);return t===null?null:/^bugs\/[^/]+\/closed\.yaml$/.test(T(t))?`BuildKeel: ${t} is the closing record of ${t.split("/")[1]}. BuildKeel writes it once when the bug is closed, and it is never edited. A defect that returns is a new bug.`:s.mode==="spec-driven"?Sa(s,t)??(s.conversion?ka(s,t):null):(s.conversion==="finished"?Sa(s,t):null)??ka(s,t)}function ka(s,e){let t=e.split("/"),n=s.documents??zd,i=t.length>=4&&t[2]!==void 0&&Object.hasOwn(n,t[2])?n[t[2]]:void 0,r=i!==void 0,o=t[r?3:2],a=`${t[1]??""}${r?`-${i}`:""}`,l=r?5:4;if(o&&t.length===l&&s.lockedDirs.map(T).includes(T(o))){if(T(o)==="versions"){let u=/^v(0|[1-9][0-9]*)\.md$/.exec(va(e))?.[1],f=u?`${a} v${u}`:a;return`BuildKeel: ${e} is the frozen copy of ${f}, which is approved and cannot be changed. To change ${a}, call the BuildKeel save_draft tool with id ${a} and the whole changed file: that opens the next version as a draft, and the approved version stays in force until the new one is approved.`}let h=Jn[o.toLowerCase()]??"a record";return`BuildKeel: ${e} is ${h} of ${a}. BuildKeel writes these records once and they are never edited, by hand or by the agent.`+(T(o)==="approvals"?" To approve a new version, use the BuildKeel approve tool.":"")}if((s.lockedSources??[]).find(h=>T(e).startsWith(`${T(h)}/`))){let h=`${t[1]??""}${r?`-${i}`:""}`;return`BuildKeel: ${e} is a source kept with ${h} v0, frozen when version 0 was approved: sources stay exactly as they were read. To change what ${h} says, call the BuildKeel save_draft tool with id ${h} and the whole changed file: that opens its next version as a draft.`}return s.lockedFiles.some(h=>T(h)===T(e))?`BuildKeel: ${e} is the working file of ${a}, which is approved and has no new version open, so it is not edited directly. To change ${a}, call the BuildKeel save_draft tool with id ${a} and the whole changed file: that opens the next version as a draft, and the approved version stays in force until the new one is approved.`:null}var Aa=({event:s,cache:e})=>{if(s.kind!=="before-edit"&&s.kind!=="after-edit")return null;let t=e.lockMode??"block";if(!(t==="block"?s.kind==="before-edit":t==="warn"&&s.kind==="after-edit"))return null;let i=s.files.map(r=>Xe(e,Gd(s.cwd,r))).filter(r=>r!==null);return i.length===0?null:s.kind==="before-edit"?{block:[...new Set(i)].join(`
146
+ `)),h.reported=o}return JSON.stringify(h)!==JSON.stringify(a)&&aa(i,h),u?{context:u}:null};import{appendFileSync as th,mkdirSync as sh}from"node:fs";import{dirname as nh,join as ih,resolve as rh}from"node:path";import{existsSync as Jd,realpathSync as ga}from"node:fs";import{basename as va,dirname as ya,isAbsolute as Vd,join as Wd,relative as Na,resolve as Gd,sep as Qd}from"node:path";var Jn={versions:"the frozen copy",approvals:"an approval record",completions:"a completion record",repins:"a re-pin decision record"},zd={"test-plan":"TP",design:"DD"},Xd=process.platform==="win32",T=s=>Xd?s.toLowerCase():s;function le(s,e){let t=ba(s,e);if(t!==null)return t;try{let n=e;for(;!Jd(n)&&ya(n)!==n;)n=ya(n);let i=Wd(ga(n),Na(n,e));return ba(ga(s),i)}catch{return null}}var ce=(s,e)=>le(s,e);function ba(s,e){let t=Na(s,e);return t===""||t.startsWith("..")||Vd(t)?null:t.split(Qd).join("/")}var wa="Make the change you want through a new change, drafted with the BuildKeel save_draft tool (type change): finishing it writes the next versions.",Zd="It is kept, read-only, as the history of how the system came to be before this spec tree moved to Full SDLC. Change behavior through the team's stages, as a new version of the artifact it became.";function Sa(s,e){if(s.conversion!=="finished")return eh(s,e);if(T(e)===T("onboarding/conversion.yaml"))return`BuildKeel: ${e} records how this spec tree moved to Full SDLC, and where each scenario went. It was finished when the tree switched, and is never edited.`;let[t]=e.split("/").map(T);return t!==T("capabilities")&&t!==T("changes")?null:`BuildKeel: ${e} is part of the capability specs and changes this spec tree had before it moved to Full SDLC. ${Zd}`}function eh(s,e){let t=e.split("/"),[n,,i]=t.map(T);if(n===T("capabilities")&&t.length>=3){let a=/^v(0|[1-9][0-9]*)\.(md|yaml)$/.exec(va(e)),l=a?`${t[1]} v${a[1]}`:t[1]??"",c=a?.[2]==="yaml"?`the record of ${l}`:a?`${l}, a capability spec version`:`part of capability ${l}`;return`BuildKeel: ${e} is ${c}, written when a change was archived. Capability specs are never edited directly. ${wa}`}if(n!==T("changes")||t.length<3)return null;let r=na((t[1]??"").toUpperCase()),o=r??t[1]??"";return t.length===3&&i===T("discarded.yaml")?`BuildKeel: ${e} is the discard record of ${o}. BuildKeel writes it once when a change is discarded, and it is never edited.`:t.length===3&&i===T("archive.yaml")?`BuildKeel: ${e} is the archive record of ${o}, written once when it was archived. It is never edited.`:t.length===4&&i!==void 0&&Object.hasOwn(Jn,i)?`BuildKeel: ${e} is ${Jn[i]} of ${o}. BuildKeel writes these records once and they are never edited, by hand or by the agent.`:r&&(s.archivedChanges??[]).some(a=>T(a)===T(r))?`BuildKeel: ${e} belongs to ${o}, which is archived: its files are its record and are never edited. ${wa}`:null}function Xe(s,e){let t=ce(s.specRoot,e);return t===null?null:/^defects\/[^/]+\/closed\.yaml$/.test(T(t))?`BuildKeel: ${t} is the closing record of ${t.split("/")[1]}. BuildKeel writes it once when the defect is closed, and it is never edited. A defect that returns is a new defect.`:s.mode==="spec-driven"?Sa(s,t)??(s.conversion?ka(s,t):null):(s.conversion==="finished"?Sa(s,t):null)??ka(s,t)}function ka(s,e){let t=e.split("/"),n=s.documents??zd,i=t.length>=4&&t[2]!==void 0&&Object.hasOwn(n,t[2])?n[t[2]]:void 0,r=i!==void 0,o=t[r?3:2],a=`${t[1]??""}${r?`-${i}`:""}`,l=r?5:4;if(o&&t.length===l&&s.lockedDirs.map(T).includes(T(o))){if(T(o)==="versions"){let u=/^v(0|[1-9][0-9]*)\.md$/.exec(va(e))?.[1],f=u?`${a} v${u}`:a;return`BuildKeel: ${e} is the frozen copy of ${f}, which is approved and cannot be changed. To change ${a}, call the BuildKeel save_draft tool with id ${a} and the whole changed file: that opens the next version as a draft, and the approved version stays in force until the new one is approved.`}let h=Jn[o.toLowerCase()]??"a record";return`BuildKeel: ${e} is ${h} of ${a}. BuildKeel writes these records once and they are never edited, by hand or by the agent.`+(T(o)==="approvals"?" To approve a new version, use the BuildKeel approve tool.":"")}if((s.lockedSources??[]).find(h=>T(e).startsWith(`${T(h)}/`))){let h=`${t[1]??""}${r?`-${i}`:""}`;return`BuildKeel: ${e} is a source kept with ${h} v0, frozen when version 0 was approved: sources stay exactly as they were read. To change what ${h} says, call the BuildKeel save_draft tool with id ${h} and the whole changed file: that opens its next version as a draft.`}return s.lockedFiles.some(h=>T(h)===T(e))?`BuildKeel: ${e} is the working file of ${a}, which is approved and has no new version open, so it is not edited directly. To change ${a}, call the BuildKeel save_draft tool with id ${a} and the whole changed file: that opens the next version as a draft, and the approved version stays in force until the new one is approved.`:null}var Aa=({event:s,cache:e})=>{if(s.kind!=="before-edit"&&s.kind!=="after-edit")return null;let t=e.lockMode??"block";if(!(t==="block"?s.kind==="before-edit":t==="warn"&&s.kind==="after-edit"))return null;let i=s.files.map(r=>Xe(e,Gd(s.cwd,r))).filter(r=>r!==null);return i.length===0?null:s.kind==="before-edit"?{block:[...new Set(i)].join(`
147
147
 
148
148
  `)}:{context:i.map(r=>`BuildKeel: this tree's lock gate is set to warn, so the edit went ahead. ${r.replace(/^BuildKeel: /,"")} Validation and the status overview report the change. Tell the user, and undo it unless they asked for it.`).join(`
149
149