buildkeel 0.2.0 → 0.3.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.
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
- # Starts a BuildKeel component after checking for Node.js 20 or later.
2
+ # Starts a BuildKeel component after checking for Node.js 20 or later
3
+ # (the version itself is checked by bk-start.cjs).
3
4
  # Usage: bk-launch <server|hook> [args...]
4
5
  # BK_LAUNCH_DIST overrides the directory holding the built components.
5
6
  set -u
@@ -28,11 +29,6 @@ MSG
28
29
  }
29
30
 
30
31
  command -v node >/dev/null 2>&1 || fail "Node.js was not found on your PATH."
31
- version=$(node -p "process.versions.node" 2>/dev/null) || fail "Node.js could not be run."
32
- major=${version%%.*}
33
- case "$major" in
34
- ''|*[!0-9]*) fail "Could not read the Node.js version." ;;
35
- esac
36
- [ "$major" -ge "$REQUIRED_MAJOR" ] || fail "Found Node.js $version."
37
32
 
38
- exec node "$entry" "$@"
33
+ # bk-start.cjs checks the version and runs the component in one Node process.
34
+ exec node "$here/bk-start.cjs" "$entry" "$@"
@@ -1,5 +1,6 @@
1
1
  @echo off
2
- rem Starts a BuildKeel component after checking for Node.js 20 or later.
2
+ rem Starts a BuildKeel component after checking for Node.js 20 or later
3
+ rem (the version itself is checked by bk-start.cjs).
3
4
  rem Usage: bk-launch <server|hook> [args...]
4
5
  rem BK_LAUNCH_DIST overrides the directory holding the built components.
5
6
  setlocal EnableDelayedExpansion
@@ -13,7 +14,9 @@ echo usage: bk-launch ^<server^|hook^> [args...] 1>&2
13
14
  exit /b 64
14
15
 
15
16
  :target_ok
16
- if defined BK_LAUNCH_DIST (set "DIST=%BK_LAUNCH_DIST%") else (set "DIST=%~dp0..\dist")
17
+ rem Taken before the arguments are shifted: shift moves the script's own path too.
18
+ set "HERE=%~dp0"
19
+ if defined BK_LAUNCH_DIST (set "DIST=%BK_LAUNCH_DIST%") else (set "DIST=%HERE%..\dist")
17
20
 
18
21
  set "ARGS="
19
22
  :collect
@@ -28,20 +31,8 @@ if errorlevel 1 (
28
31
  set "REASON=Node.js was not found on your PATH."
29
32
  goto :fail
30
33
  )
31
- set "VERSION="
32
- for /f "delims=" %%v in ('node -p "process.versions.node" 2^>nul') do set "VERSION=%%v"
33
- if not defined VERSION (
34
- set "REASON=Node.js could not be run."
35
- goto :fail
36
- )
37
- for /f "tokens=1 delims=." %%m in ("%VERSION%") do set "MAJOR=%%m"
38
- set /a MAJOR_NUM=MAJOR 2>nul
39
- if %MAJOR_NUM% LSS %REQUIRED_MAJOR% (
40
- set "REASON=Found Node.js %VERSION%."
41
- goto :fail
42
- )
43
-
44
- node "%DIST%\%ENTRY%" %ARGS%
34
+ rem bk-start.cjs checks the version and runs the component in one Node process.
35
+ node "%HERE%bk-start.cjs" "%DIST%\%ENTRY%" %ARGS%
45
36
  exit /b %ERRORLEVEL%
46
37
 
47
38
  :fail
@@ -0,0 +1,25 @@
1
+ // Checks for Node.js 20 or later, then runs a BuildKeel component in this same
2
+ // process. Started by bk-launch, so each start costs one Node process, not two.
3
+ // Usage: node bk-start.cjs <component entry> [args...]
4
+ // Kept to old syntax so an old Node can still read it and say what is needed.
5
+ "use strict";
6
+
7
+ var REQUIRED_MAJOR = 20;
8
+
9
+ var version = process.versions.node;
10
+ var major = Number(version.split(".")[0]);
11
+ if (!(major >= REQUIRED_MAJOR)) {
12
+ process.stderr.write(
13
+ "BuildKeel needs Node.js " + REQUIRED_MAJOR + " or later. Found Node.js " + version + ".\n" +
14
+ 'Install it from https://nodejs.org or with your version manager (for example "nvm install ' + REQUIRED_MAJOR + '"), then start a new session.\n',
15
+ );
16
+ process.exit(1);
17
+ }
18
+
19
+ var entry = process.argv[2];
20
+ // The component reads its own arguments from process.argv.slice(2), as if Node had started it directly.
21
+ process.argv.splice(1, 2, entry);
22
+ import(require("node:url").pathToFileURL(entry).href).catch(function (error) {
23
+ process.stderr.write("BuildKeel could not start " + entry + ": " + (error && error.message ? error.message : String(error)) + "\n");
24
+ process.exit(1);
25
+ });
@@ -1,20 +1,22 @@
1
1
  import { createRequire as __bkCreateRequire } from 'node:module'; const require = __bkCreateRequire(import.meta.url);
2
- import{appendFileSync as le,readFileSync as de}from"node:fs";var mt=["claude-code","antigravity"],A=t=>mt.includes(t);function y(t,n){return t==="antigravity"?`/bk-${n}`:`/bk:${n}`}function B(t,n){return t.replace(/\{\{bk:([a-z][a-z-]*)\}\}/g,(e,o)=>y(n,o))}import{isAbsolute as H,relative as vt,resolve as D}from"node:path";import{existsSync as K}from"node:fs";import{dirname as kt,join as N,resolve as yt}from"node:path";var wt=".buildkeel.yaml";function v(t){let n=yt(t);for(;;){if(K(N(n,wt)))return n;if(K(N(n,".git")))return null;let e=kt(n);if(e===n)return null;n=e}}var F=["write_to_file","replace_file_content","multi_replace_file_content"],we=F.join("|");var xt={decision:"allow"},j={decision:"ask"},C=(t,n)=>{let e=vt(t,n);return e===""||!e.startsWith("..")&&!H(e)};function $(t,n){let e=(t.workspacePaths??[]).filter(i=>typeof i=="string"&&i!==""),o=n?[...e.filter(i=>C(i,n)),...e.filter(i=>!C(i,n))]:e;return o.find(i=>v(i)!==null)??o[0]??process.cwd()}var L={parse(t,n){let e=t??{};switch(n[0]){case"PreInvocation":return e.conversationId?{kind:"prompt",cwd:$(e),conversation:e.conversationId,newMessage:e.invocationNum===0,transcript:e.transcriptPath??null}:{kind:"ignored"};case"PreToolUse":case"PostToolUse":{let o=e.toolCall?.name??"",i=e.toolCall?.args?.TargetFile,r=F.includes(o)&&typeof i=="string"&&i!==""?i:null;if(n[0]==="PostToolUse"){if(!r||e.error)return{kind:"ignored"};let c=$(e,H(r)?r:void 0);return{kind:"after-edit",cwd:c,tool:o,files:[D(c,r)],...e.conversationId?{deferTo:e.conversationId}:{}}}let a=$(e,r&&H(r)?r:void 0),s=[...e.workspacePaths??[],...e.artifactDirectoryPath?[e.artifactDirectoryPath]:[]].filter(c=>typeof c=="string"&&c!=="");return{kind:"before-edit",cwd:a,tool:o,files:r?[D(a,r)]:[],folders:s}}default:return{kind:"ignored"}}},render(t,n){if(n.kind==="prompt"){let e=t.context?{injectSteps:[{ephemeralMessage:t.context}]}:{};return{stdout:JSON.stringify(e),exitCode:0}}if(n.kind==="before-edit"){let e=n.files.length>0&&n.files.every(i=>(n.folders??[]).some(r=>C(r,i))),o=t.block?{decision:"deny",reason:t.block}:e?xt:j;return{stdout:JSON.stringify(o),exitCode:0}}return{stdout:"{}",exitCode:0}},failed:t=>({stdout:JSON.stringify(t[0]==="PreToolUse"?j:{}),exitCode:0})};var P={Edit:"file_path",Write:"file_path",MultiEdit:"file_path",NotebookEdit:"notebook_path"},xe=Object.keys(P).join("|"),St={startup:"startup",resume:"resume",clear:"clear",compact:"compact",fork:"resume"},U={parse(t){let n=t??{},e=n.cwd??process.cwd();switch(n.hook_event_name){case"SessionStart":{let o=St[n.source??"startup"]??"startup";return{kind:"session-start",cwd:e,reason:o}}case"PreToolUse":case"PostToolUse":{let o=n.tool_name??"",i=P[o],r=i?n.tool_input?.[i]:void 0;return typeof r!="string"?{kind:"ignored"}:{kind:n.hook_event_name==="PreToolUse"?"before-edit":"after-edit",cwd:e,tool:o,files:[r]}}default:return{kind:"ignored"}}},render(t,n){if(n.kind==="session-start")return{stdout:t.context??"",exitCode:0};if(n.kind==="before-edit"&&(t.block||t.context)){let e={hookSpecificOutput:{hookEventName:"PreToolUse",...t.block?{permissionDecision:"deny",permissionDecisionReason:t.block}:{},...t.context?{additionalContext:t.context}:{}}};return{stdout:JSON.stringify(e),exitCode:0}}return n.kind==="after-edit"&&t.context?{stdout:JSON.stringify({hookSpecificOutput:{hookEventName:"PostToolUse",additionalContext:t.context}}),exitCode:0}:{stdout:"",exitCode:0}},failed:()=>({stdout:"",exitCode:1})};import{existsSync as $e,readFileSync as bt,writeFileSync as He}from"node:fs";import{join as J}from"node:path";var p=".buildkeel",$t="hook-cache.json",Ht="context.md",M=t=>J(t,p,Ht);function Ct(t){return J(t,p,$t)}function W(t){try{return JSON.parse(bt(Ct(t),"utf8"))}catch{return null}}var _e=`${p}/`;import{appendFileSync as Tt,closeSync as It,fstatSync as Ot,mkdirSync as q,openSync as Rt,readFileSync as V,readSync as At,renameSync as Bt,rmSync as Kt,statSync as Nt,writeFileSync as Dt}from"node:fs";import{homedir as jt}from"node:os";import{join as S}from"node:path";import{readFileSync as _t}from"node:fs";function _(t,n){let e=[{key:"setup",text:"BuildKeel is set up in this repository. Converse as you would without it: follow the user's lead, explore ideas, options and code freely, and leave BuildKeel's artifacts, IDs and commands out of it until the user runs a BuildKeel command, asks you to change code, or asks to move work forward. BuildKeel's instructions then say how to go on.",fingerprint:""}];if(t.activeUnit){let o=t.activeBranch?` on branch ${t.activeBranch}`:"";e.push({key:"unit",text:`Active unit: ${t.activeUnit}${o}.`,fingerprint:`${t.activeUnit}${o}`})}else t.untrackedSince?e.push({key:"unit",text:`No active unit in this working tree: the user chose to work without one here since ${t.untrackedSince}, so changes made now belong to no unit. Starting a unit with ${y(n,"start")} <ID> ends this.`,fingerprint:`untracked:${t.untrackedSince}`}):e.push({key:"unit",text:"No unit is active in this working tree.",fingerprint:""});if(e.push({key:"rules",text:"Read and change the spec tree only through the BuildKeel tools, never by editing its files.",fingerprint:""}),t.mode==="spec-driven")e.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 propose the follow-on edits in the same reply, for the user to accept or decline. Never edit code as part of this.",fingerprint:"spec-driven"},{key:"tests",text:"Put the ID of the scenario a test checks in square brackets in the test's name.",fingerprint:"spec-driven"}),t.guide==="on"&&e.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(t.stages){let o=(t.stageExamples??[]).map(r=>`\`${r}\``),i=o.length>1?`${o.slice(0,-1).join(", ")} or ${o.at(-1)}`:o[0]??"";e.push({key:"stages",text:`This spec tree's stages, as the team names them: ${t.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:`${t.stages}|${o.join(",")}`})}return e}function Et(t,n,e){if(!n.activeUnit)return null;try{return B(_t(M(t),"utf8").trimEnd(),e)}catch{return null}}function x(t,n,e,o){let i=Et(t,n,e),r=["BuildKeel:"];for(let a of _(n,e)){let s=a.key==="unit"&&n.activeUnit?i?o?" Its context is loaded again below.":" Its context follows.":" BuildKeel has no context for it; call the BuildKeel get_context tool to see why.":"";r.push(`- ${a.text}${s}`)}return i&&r.push("",i),r.join(`
3
- `)}var G=({event:t,codeRoot:n,cache:e,host:o})=>t.kind!=="session-start"?null:{context:x(n,e,o,t.reason==="compact")};var Y=(t,n,e)=>S(t,p,"conversations",`${n.replace(/[^A-Za-z0-9_-]/g,"_").slice(0,100)}.${e}`),Ft=(t,n)=>Y(t,n,"json"),X=(t,n)=>Y(t,n,"notices");function Z(t,n,e){let o=X(t,n);q(S(o,".."),{recursive:!0}),Tt(o,`${JSON.stringify(e)}
4
- `)}function Lt(t,n){let e=X(t,n),o;try{o=V(e,"utf8")}catch{return[]}return Kt(e,{force:!0}),o.split(`
5
- `).filter(Boolean).flatMap(i=>{try{return[String(JSON.parse(i))]}catch{return[]}})}function Pt(t){try{let n=JSON.parse(V(t,"utf8"));return n.version===1?n:null}catch{return null}}function z(t,n){q(S(t,".."),{recursive:!0});let e=`${t}.${process.pid}.tmp`;Dt(e,`${JSON.stringify(n,null,2)}
6
- `),Bt(e,t)}var Q=t=>t==="~"||t.startsWith("~/")?S(jt(),t.slice(1)):t;function Ut(t,n){let e;try{e=Rt(Q(t),"r")}catch{return{ok:!1,offset:n,checkpoint:null}}try{let o=Ot(e).size,i=o<n?0:n,r=Buffer.alloc(o-i),a=r.length>0?At(e,r,0,r.length,i):0,s=r.subarray(0,a).lastIndexOf(10);if(s<0)return{ok:!0,offset:i,checkpoint:null};let c=!0,l=null;for(let u of r.subarray(0,s).toString("utf8").split(`
7
- `))if(u.trim())try{let f=JSON.parse(u);typeof f.step_index!="number"||typeof f.type!="string"?c=!1:f.type==="CHECKPOINT"&&(l=Math.max(l??-1,f.step_index))}catch{c=!1}return{ok:c,offset:i+s+1,checkpoint:l}}catch{return{ok:!1,offset:n,checkpoint:null}}finally{It(e)}}function Jt(t){if(!t)return 0;try{return Nt(Q(t)).size}catch{return 0}}var tt=t=>{if(t.event.kind!=="prompt")return null;let n=Lt(t.codeRoot,t.event.conversation),o=[Mt(t)?.context,...n].filter(Boolean).join(`
2
+ import{appendFileSync as fe,readFileSync as he}from"node:fs";var yt=["claude-code","antigravity"],A=t=>yt.includes(t);function g(t,n){return t==="antigravity"?`/bk-${n}`:`/bk:${n}`}function B(t,n){return t.replace(/\{\{bk:([a-z][a-z-]*)\}\}/g,(e,o)=>g(n,o))}import{isAbsolute as H,relative as St,resolve as j}from"node:path";import{existsSync as K}from"node:fs";import{dirname as wt,join as N,resolve as vt}from"node:path";var xt=".buildkeel.yaml";function v(t){let n=vt(t);for(;;){if(K(N(n,xt)))return n;if(K(N(n,".git")))return null;let e=wt(n);if(e===n)return null;n=e}}var F=["write_to_file","replace_file_content","multi_replace_file_content"],be=F.join("|");var bt={decision:"allow"},D={decision:"ask"},C=(t,n)=>{let e=St(t,n);return e===""||!e.startsWith("..")&&!H(e)};function $(t,n){let e=(t.workspacePaths??[]).filter(i=>typeof i=="string"&&i!==""),o=n?[...e.filter(i=>C(i,n)),...e.filter(i=>!C(i,n))]:e;return o.find(i=>v(i)!==null)??o[0]??process.cwd()}var L={parse(t,n){let e=t??{};switch(n[0]){case"PreInvocation":return e.conversationId?{kind:"prompt",cwd:$(e),conversation:e.conversationId,newMessage:e.invocationNum===0,transcript:e.transcriptPath??null}:{kind:"ignored"};case"PreToolUse":case"PostToolUse":{let o=e.toolCall?.name??"",i=e.toolCall?.args?.TargetFile,r=F.includes(o)&&typeof i=="string"&&i!==""?i:null;if(n[0]==="PostToolUse"){if(!r||e.error)return{kind:"ignored"};let c=$(e,H(r)?r:void 0);return{kind:"after-edit",cwd:c,tool:o,files:[j(c,r)],...e.conversationId?{deferTo:e.conversationId}:{}}}let a=$(e,r&&H(r)?r:void 0),s=[...e.workspacePaths??[],...e.artifactDirectoryPath?[e.artifactDirectoryPath]:[]].filter(c=>typeof c=="string"&&c!=="");return{kind:"before-edit",cwd:a,tool:o,files:r?[j(a,r)]:[],folders:s}}default:return{kind:"ignored"}}},render(t,n){if(n.kind==="prompt"){let e=t.context?{injectSteps:[{ephemeralMessage:t.context}]}:{};return{stdout:JSON.stringify(e),exitCode:0}}if(n.kind==="before-edit"){let e=n.files.length>0&&n.files.every(i=>(n.folders??[]).some(r=>C(r,i))),o=t.block?{decision:"deny",reason:t.block}:e?bt:D;return{stdout:JSON.stringify(o),exitCode:0}}return{stdout:"{}",exitCode:0}},failed:t=>({stdout:JSON.stringify(t[0]==="PreToolUse"?D:{}),exitCode:0})};var P={Edit:"file_path",Write:"file_path",MultiEdit:"file_path",NotebookEdit:"notebook_path"},He=Object.keys(P).join("|"),$t={startup:"startup",resume:"resume",clear:"clear",compact:"compact",fork:"resume"},U={parse(t){let n=t??{},e=n.cwd??process.cwd();switch(n.hook_event_name){case"SessionStart":{let o=$t[n.source??"startup"]??"startup";return{kind:"session-start",cwd:e,reason:o}}case"PreToolUse":case"PostToolUse":{let o=n.tool_name??"",i=P[o],r=i?n.tool_input?.[i]:void 0;return typeof r!="string"?{kind:"ignored"}:{kind:n.hook_event_name==="PreToolUse"?"before-edit":"after-edit",cwd:e,tool:o,files:[r]}}default:return{kind:"ignored"}}},render(t,n){if(n.kind==="session-start")return{stdout:t.context??"",exitCode:0};if(n.kind==="before-edit"&&(t.block||t.context)){let e={hookSpecificOutput:{hookEventName:"PreToolUse",...t.block?{permissionDecision:"deny",permissionDecisionReason:t.block}:{},...t.context?{additionalContext:t.context}:{}}};return{stdout:JSON.stringify(e),exitCode:0}}return n.kind==="after-edit"&&t.context?{stdout:JSON.stringify({hookSpecificOutput:{hookEventName:"PostToolUse",additionalContext:t.context}}),exitCode:0}:{stdout:"",exitCode:0}},failed:()=>({stdout:"",exitCode:1})};import{existsSync as Ee,readFileSync as Ht,writeFileSync as Te}from"node:fs";import{join as J}from"node:path";var p=".buildkeel",Ct="hook-cache.json",_t="context.md",W=t=>J(t,p,_t);function Et(t){return J(t,p,Ct)}function M(t){try{let n=JSON.parse(Ht(Et(t),"utf8"));return n?.version===1?n:null}catch{return null}}var Re=`${p}/`;import{appendFileSync as Bt,closeSync as Kt,fstatSync as Nt,mkdirSync as V,openSync as jt,readFileSync as Y,readSync as Dt,renameSync as X,rmSync as Ft,statSync as Lt,writeFileSync as Pt}from"node:fs";import{homedir as Ut}from"node:os";import{join as S}from"node:path";import{randomBytes as Tt}from"node:crypto";import{dirname as It,join as Rt}from"node:path";var G=t=>Rt(It(t),`.${Tt(6).toString("hex")}.bk-tmp`);import{readFileSync as Ot}from"node:fs";function _(t,n){let e=[{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 ${g(n,"capture")}; if they keep talking or say no, do not offer again until something new is settled. Never start it yourself.`,fingerprint:""}];if(t.activeUnit){let o=t.activeBranch?` on branch ${t.activeBranch}`:"";e.push({key:"unit",text:`Active unit: ${t.activeUnit}${o}.`,fingerprint:`${t.activeUnit}${o}`})}else t.untrackedSince?e.push({key:"unit",text:`No active unit in this working tree: the user chose to work without one here since ${t.untrackedSince}, so changes made now belong to no unit. Starting a unit with ${g(n,"start")} <ID> ends this.`,fingerprint:`untracked:${t.untrackedSince}`}):e.push({key:"unit",text:"No unit is active in this working tree.",fingerprint:""});if(e.push({key:"rules",text:"Read and change the spec tree only through the BuildKeel tools, never by editing its files.",fingerprint:""}),t.mode==="spec-driven")e.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 propose the follow-on edits in the same reply, for the user to accept or decline. Never edit code as part of this.",fingerprint:"spec-driven"},{key:"tests",text:"Put the ID of the scenario a test checks in square brackets in the test's name.",fingerprint:"spec-driven"}),t.guide==="on"&&e.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(t.stages){let o=(t.stageExamples??[]).map(r=>`\`${r}\``),i=o.length>1?`${o.slice(0,-1).join(", ")} or ${o.at(-1)}`:o[0]??"";e.push({key:"stages",text:`This spec tree's stages, as the team names them: ${t.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:`${t.stages}|${o.join(",")}`})}return e}function At(t,n,e){if(!n.activeUnit)return null;try{return B(Ot(W(t),"utf8").trimEnd(),e)}catch{return null}}function x(t,n,e,o){let i=At(t,n,e),r=["BuildKeel:"];for(let a of _(n,e)){let s=a.key==="unit"&&n.activeUnit?i?o?" Its context is loaded again below.":" Its context follows.":" BuildKeel has no context for it; call the BuildKeel get_context tool to see why.":"";r.push(`- ${a.text}${s}`)}return i&&r.push("",i),r.join(`
3
+ `)}var q=({event:t,codeRoot:n,cache:e,host:o})=>t.kind!=="session-start"?null:{context:x(n,e,o,t.reason==="compact")};var Z=(t,n,e)=>S(t,p,"conversations",`${n.replace(/[^A-Za-z0-9_-]/g,"_").slice(0,100)}.${e}`),Jt=(t,n)=>Z(t,n,"json"),Q=(t,n)=>Z(t,n,"notices");function tt(t,n,e){let o=Q(t,n);V(S(o,".."),{recursive:!0}),Bt(o,`${JSON.stringify(e)}
4
+ `)}function Wt(t,n){let e=Q(t,n),o=G(e),i;try{X(e,o),i=Y(o,"utf8")}catch{return[]}finally{Ft(o,{force:!0})}return i.split(`
5
+ `).filter(Boolean).flatMap(r=>{try{return[String(JSON.parse(r))]}catch{return[]}})}function Mt(t){try{let n=JSON.parse(Y(t,"utf8"));return n.version===1?n:null}catch{return null}}function z(t,n){V(S(t,".."),{recursive:!0});let e=`${t}.${process.pid}.tmp`;Pt(e,`${JSON.stringify(n,null,2)}
6
+ `),X(e,t)}var et=t=>t==="~"||t.startsWith("~/")?S(Ut(),t.slice(1)):t;function Gt(t,n){let e;try{e=jt(et(t),"r")}catch{return{ok:!1,offset:n,checkpoint:null}}try{let o=Nt(e).size,i=o<n?0:n,r=Buffer.alloc(o-i),a=r.length>0?Dt(e,r,0,r.length,i):0,s=r.subarray(0,a).lastIndexOf(10);if(s<0)return{ok:!0,offset:i,checkpoint:null};let c=!0,l=null;for(let u of r.subarray(0,s).toString("utf8").split(`
7
+ `))if(u.trim())try{let f=JSON.parse(u);typeof f.step_index!="number"||typeof f.type!="string"?c=!1:f.type==="CHECKPOINT"&&(l=Math.max(l??-1,f.step_index))}catch{c=!1}return{ok:c,offset:i+s+1,checkpoint:l}}catch{return{ok:!1,offset:n,checkpoint:null}}finally{Kt(e)}}function qt(t){if(!t)return 0;try{return Lt(et(t)).size}catch{return 0}}var nt=t=>{if(t.event.kind!=="prompt")return null;let n=Wt(t.codeRoot,t.event.conversation),o=[zt(t)?.context,...n].filter(Boolean).join(`
8
8
 
9
- `);return o?{context:o}:null},Mt=({event:t,codeRoot:n,cache:e,host:o})=>{if(t.kind!=="prompt")return null;let i=Ft(n,t.conversation),r=_(e,o),a=Object.fromEntries(r.map(h=>[h.key,h.fingerprint])),s=Pt(i);if(!s)return z(i,{version:1,startedAt:new Date().toISOString(),reported:a,transcript:{offset:Jt(t.transcript),checkpoint:null}}),{context:x(n,e,o,!1)};let c=t.transcript?Ut(t.transcript,s.transcript.offset):{ok:!1,offset:s.transcript.offset,checkpoint:null},l=c.checkpoint!==null&&(s.transcript.checkpoint===null||c.checkpoint>s.transcript.checkpoint),u={...s,transcript:{offset:c.offset,checkpoint:l?c.checkpoint:s.transcript.checkpoint}},f=null;if(l)f=x(n,e,o,!0),u.reported=a;else if(t.newMessage){let h=r.filter(k=>s.reported[k.key]!==k.fingerprint).map(k=>`- ${k.text}`);if(!c.ok&&e.activeUnit){let k=`- Active unit: ${e.activeUnit}. If its requirements and test cases are no longer in view, call the BuildKeel get_context tool.`,b=h.findIndex(gt=>gt.startsWith("- Active unit:"));b>=0?h[b]=`${h[b]} If its requirements and test cases are no longer in view, call the BuildKeel get_context tool.`:h.unshift(k)}h.length>0&&(f=["BuildKeel:",...h].join(`
10
- `)),u.reported=a}return JSON.stringify(u)!==JSON.stringify(s)&&z(i,u),f?{context:f}:null};import{appendFileSync as te,mkdirSync as ee}from"node:fs";import{dirname as ne,join as oe,resolve as re}from"node:path";import{existsSync as Wt,realpathSync as et}from"node:fs";import{basename as at,dirname as nt,isAbsolute as Gt,join as zt,relative as ct,resolve as qt,sep as Vt}from"node:path";var E={versions:"the frozen copy",approvals:"an approval record",completions:"a completion record",repins:"a re-pin decision record"},Yt={"test-plan":"TP",design:"DD"},Xt=process.platform==="win32",d=t=>Xt?t.toLowerCase():t;function g(t,n){let e=ot(t,n);if(e!==null)return e;try{let o=n;for(;!Wt(o)&&nt(o)!==o;)o=nt(o);let i=zt(et(o),ct(o,n));return ot(et(t),i)}catch{return null}}var m=(t,n)=>g(t,n);function ot(t,n){let e=ct(t,n);return e===""||e.startsWith("..")||Gt(e)?null:e.split(Vt).join("/")}var rt="Make the change you want through a new change, drafted with the BuildKeel save_draft tool (type change): finishing it writes the next versions.",Zt="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 it(t,n){if(t.conversion!=="finished")return Qt(t,n);if(d(n)===d("onboarding/conversion.yaml"))return`BuildKeel: ${n} 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[e]=n.split("/").map(d);return e!==d("capabilities")&&e!==d("changes")?null:`BuildKeel: ${n} is part of the capability specs and changes this spec tree had before it moved to Full SDLC. ${Zt}`}function Qt(t,n){let e=n.split("/"),[o,i="",r]=e.map(d);if(o===d("capabilities")&&e.length>=3){let s=/^v(0|[1-9][0-9]*)\.(md|yaml)$/.exec(at(n)),c=s?`${e[1]} v${s[1]}`:e[1]??"",l=s?.[2]==="yaml"?`the record of ${c}`:s?`${c}, a capability spec version`:`part of capability ${c}`;return`BuildKeel: ${n} is ${l}, written when a change was archived. Capability specs are never edited directly. ${rt}`}if(o!==d("changes")||e.length<3)return null;let a=e[1]??"";return e.length===3&&r===d("discarded.yaml")?`BuildKeel: ${n} is the discard record of ${a}. BuildKeel writes it once when a change is discarded, and it is never edited.`:e.length===3&&r===d("archive.yaml")?`BuildKeel: ${n} is the archive record of ${a}, written once when it was archived. It is never edited.`:e.length===4&&r!==void 0&&Object.hasOwn(E,r)?`BuildKeel: ${n} is ${E[r]} of ${a}. BuildKeel writes these records once and they are never edited, by hand or by the agent.`:(t.archivedChanges??[]).some(s=>d(s)===i)?`BuildKeel: ${n} belongs to ${a}, which is archived: its files are its record and are never edited. ${rt}`:null}function w(t,n){let e=m(t.specRoot,n);return e===null?null:/^bugs\/[^/]+\/closed\.yaml$/.test(d(e))?`BuildKeel: ${e} is the closing record of ${e.split("/")[1]}. BuildKeel writes it once when the bug is closed, and it is never edited. A defect that returns is a new bug.`:t.mode==="spec-driven"?it(t,e)??(t.conversion?st(t,e):null):(t.conversion==="finished"?it(t,e):null)??st(t,e)}function st(t,n){let e=n.split("/"),o=t.documents??Yt,i=e.length>=4&&e[2]!==void 0&&Object.hasOwn(o,e[2])?o[e[2]]:void 0,r=i!==void 0,a=e[r?3:2],s=`${e[1]??""}${r?`-${i}`:""}`,c=r?5:4;if(a&&e.length===c&&t.lockedDirs.map(d).includes(d(a))){if(d(a)==="versions"){let f=/^v(0|[1-9][0-9]*)\.md$/.exec(at(n))?.[1],h=f?`${s} v${f}`:s;return`BuildKeel: ${n} is the frozen copy of ${h}, which is approved and cannot be changed. To change ${s}, call the BuildKeel save_draft tool with id ${s} 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 u=E[a.toLowerCase()]??"a record";return`BuildKeel: ${n} is ${u} of ${s}. BuildKeel writes these records once and they are never edited, by hand or by the agent.`+(d(a)==="approvals"?" To approve a new version, use the BuildKeel approve tool.":"")}if((t.lockedSources??[]).find(u=>d(n).startsWith(`${d(u)}/`))){let u=`${e[1]??""}${r?`-${i}`:""}`;return`BuildKeel: ${n} is a source kept with ${u} v0, frozen when version 0 was approved: sources stay exactly as they were read. To change what ${u} says, call the BuildKeel save_draft tool with id ${u} and the whole changed file: that opens its next version as a draft.`}return t.lockedFiles.some(u=>d(u)===d(n))?`BuildKeel: ${n} is the working file of ${s}, which is approved and has no new version open, so it is not edited directly. To change ${s}, call the BuildKeel save_draft tool with id ${s} 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 lt=({event:t,cache:n})=>{if(t.kind!=="before-edit"&&t.kind!=="after-edit")return null;let e=n.lockMode??"block";if(!(e==="block"?t.kind==="before-edit":e==="warn"&&t.kind==="after-edit"))return null;let i=t.files.map(r=>w(n,qt(t.cwd,r))).filter(r=>r!==null);return i.length===0?null:t.kind==="before-edit"?{block:i[0]}:{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(`
9
+ `);return o?{context:o}:null},zt=({event:t,codeRoot:n,cache:e,host:o})=>{if(t.kind!=="prompt")return null;let i=Jt(n,t.conversation),r=_(e,o),a=Object.fromEntries(r.map(h=>[h.key,h.fingerprint])),s=Mt(i);if(!s)return z(i,{version:1,startedAt:new Date().toISOString(),reported:a,transcript:{offset:qt(t.transcript),checkpoint:null}}),{context:x(n,e,o,!1)};let c=t.transcript?Gt(t.transcript,s.transcript.offset):{ok:!1,offset:s.transcript.offset,checkpoint:null},l=c.checkpoint!==null&&(s.transcript.checkpoint===null||c.checkpoint>s.transcript.checkpoint),u={...s,transcript:{offset:c.offset,checkpoint:l?c.checkpoint:s.transcript.checkpoint}},f=null;if(l)f=x(n,e,o,!0),u.reported=a;else if(t.newMessage){let h=r.filter(y=>s.reported[y.key]!==y.fingerprint).map(y=>`- ${y.text}`);if(!c.ok&&e.activeUnit){let y=`- Active unit: ${e.activeUnit}. If its requirements and test cases are no longer in view, call the BuildKeel get_context tool.`,b=h.findIndex(kt=>kt.startsWith("- Active unit:"));b>=0?h[b]=`${h[b]} If its requirements and test cases are no longer in view, call the BuildKeel get_context tool.`:h.unshift(y)}h.length>0&&(f=["BuildKeel:",...h].join(`
10
+ `)),u.reported=a}return JSON.stringify(u)!==JSON.stringify(s)&&z(i,u),f?{context:f}:null};import{appendFileSync as re,mkdirSync as ie}from"node:fs";import{dirname as se,join as ae,resolve as ce}from"node:path";import{existsSync as Vt,realpathSync as ot}from"node:fs";import{basename as lt,dirname as rt,isAbsolute as Yt,join as Xt,relative as dt,resolve as Zt,sep as Qt}from"node:path";var E={versions:"the frozen copy",approvals:"an approval record",completions:"a completion record",repins:"a re-pin decision record"},te={"test-plan":"TP",design:"DD"},ee=process.platform==="win32",d=t=>ee?t.toLowerCase():t;function m(t,n){let e=it(t,n);if(e!==null)return e;try{let o=n;for(;!Vt(o)&&rt(o)!==o;)o=rt(o);let i=Xt(ot(o),dt(o,n));return it(ot(t),i)}catch{return null}}var k=(t,n)=>m(t,n);function it(t,n){let e=dt(t,n);return e===""||e.startsWith("..")||Yt(e)?null:e.split(Qt).join("/")}var st="Make the change you want through a new change, drafted with the BuildKeel save_draft tool (type change): finishing it writes the next versions.",ne="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 at(t,n){if(t.conversion!=="finished")return oe(t,n);if(d(n)===d("onboarding/conversion.yaml"))return`BuildKeel: ${n} 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[e]=n.split("/").map(d);return e!==d("capabilities")&&e!==d("changes")?null:`BuildKeel: ${n} is part of the capability specs and changes this spec tree had before it moved to Full SDLC. ${ne}`}function oe(t,n){let e=n.split("/"),[o,i="",r]=e.map(d);if(o===d("capabilities")&&e.length>=3){let s=/^v(0|[1-9][0-9]*)\.(md|yaml)$/.exec(lt(n)),c=s?`${e[1]} v${s[1]}`:e[1]??"",l=s?.[2]==="yaml"?`the record of ${c}`:s?`${c}, a capability spec version`:`part of capability ${c}`;return`BuildKeel: ${n} is ${l}, written when a change was archived. Capability specs are never edited directly. ${st}`}if(o!==d("changes")||e.length<3)return null;let a=e[1]??"";return e.length===3&&r===d("discarded.yaml")?`BuildKeel: ${n} is the discard record of ${a}. BuildKeel writes it once when a change is discarded, and it is never edited.`:e.length===3&&r===d("archive.yaml")?`BuildKeel: ${n} is the archive record of ${a}, written once when it was archived. It is never edited.`:e.length===4&&r!==void 0&&Object.hasOwn(E,r)?`BuildKeel: ${n} is ${E[r]} of ${a}. BuildKeel writes these records once and they are never edited, by hand or by the agent.`:(t.archivedChanges??[]).some(s=>d(s)===i)?`BuildKeel: ${n} belongs to ${a}, which is archived: its files are its record and are never edited. ${st}`:null}function w(t,n){let e=k(t.specRoot,n);return e===null?null:/^bugs\/[^/]+\/closed\.yaml$/.test(d(e))?`BuildKeel: ${e} is the closing record of ${e.split("/")[1]}. BuildKeel writes it once when the bug is closed, and it is never edited. A defect that returns is a new bug.`:t.mode==="spec-driven"?at(t,e)??(t.conversion?ct(t,e):null):(t.conversion==="finished"?at(t,e):null)??ct(t,e)}function ct(t,n){let e=n.split("/"),o=t.documents??te,i=e.length>=4&&e[2]!==void 0&&Object.hasOwn(o,e[2])?o[e[2]]:void 0,r=i!==void 0,a=e[r?3:2],s=`${e[1]??""}${r?`-${i}`:""}`,c=r?5:4;if(a&&e.length===c&&t.lockedDirs.map(d).includes(d(a))){if(d(a)==="versions"){let f=/^v(0|[1-9][0-9]*)\.md$/.exec(lt(n))?.[1],h=f?`${s} v${f}`:s;return`BuildKeel: ${n} is the frozen copy of ${h}, which is approved and cannot be changed. To change ${s}, call the BuildKeel save_draft tool with id ${s} 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 u=E[a.toLowerCase()]??"a record";return`BuildKeel: ${n} is ${u} of ${s}. BuildKeel writes these records once and they are never edited, by hand or by the agent.`+(d(a)==="approvals"?" To approve a new version, use the BuildKeel approve tool.":"")}if((t.lockedSources??[]).find(u=>d(n).startsWith(`${d(u)}/`))){let u=`${e[1]??""}${r?`-${i}`:""}`;return`BuildKeel: ${n} is a source kept with ${u} v0, frozen when version 0 was approved: sources stay exactly as they were read. To change what ${u} says, call the BuildKeel save_draft tool with id ${u} and the whole changed file: that opens its next version as a draft.`}return t.lockedFiles.some(u=>d(u)===d(n))?`BuildKeel: ${n} is the working file of ${s}, which is approved and has no new version open, so it is not edited directly. To change ${s}, call the BuildKeel save_draft tool with id ${s} 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 ut=({event:t,cache:n})=>{if(t.kind!=="before-edit"&&t.kind!=="after-edit")return null;let e=n.lockMode??"block";if(!(e==="block"?t.kind==="before-edit":e==="warn"&&t.kind==="after-edit"))return null;let i=t.files.map(r=>w(n,Zt(t.cwd,r))).filter(r=>r!==null);return i.length===0?null:t.kind==="before-edit"?{block:[...new Set(i)].join(`
11
11
 
12
- `)}};var T=(t,n)=>oe(t,p,"journal",`${n}.jsonl`),dt=({event:t,codeRoot:n,cache:e})=>{if(t.kind!=="after-edit"||!e.activeUnit)return null;let o=new Date().toISOString(),i=[];for(let a of t.files){let s=re(t.cwd,a);if(w(e,s)||m(e.specRoot,s)!==null)continue;let c=g(n,s);if(c===null||c===p||c.startsWith(`${p}/`))continue;let l={at:o,tool:t.tool,file:c};i.push(JSON.stringify(l))}if(i.length===0)return null;let r=T(n,e.activeUnit);return ee(ne(r),{recursive:!0}),te(r,`${i.join(`
12
+ `)}:{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(`
13
+
14
+ `)}};var T=(t,n)=>ae(t,p,"journal",`${n}.jsonl`),pt=({event:t,codeRoot:n,cache:e})=>{if(t.kind!=="after-edit"||!e.activeUnit)return null;let o=new Date().toISOString(),i=[];for(let a of t.files){let s=ce(t.cwd,a);if(w(e,s)||k(e.specRoot,s)!==null)continue;let c=m(n,s);if(c===null||c===p||c.startsWith(`${p}/`))continue;let l={at:o,tool:t.tool,file:c};i.push(JSON.stringify(l))}if(i.length===0)return null;let r=T(n,e.activeUnit);return ie(se(r),{recursive:!0}),re(r,`${i.join(`
13
15
  `)}
14
- `),null};import{resolve as ie}from"node:path";var ut=({event:t,codeRoot:n,cache:e,host:o})=>{if(t.kind!=="before-edit"||e.activeUnit||e.untrackedSince)return null;let i=t.files.flatMap(r=>{let a=ie(t.cwd,r);if(m(e.specRoot,a)!==null)return[];let s=g(n,a);return s===null||s===p||s.startsWith(`${p}/`)?[]:[s]});return i.length===0?null:{block:`BuildKeel: there is no active unit in this working tree, so a change to ${i.join(", ")} would belong to no unit of work. Before changing it, ask the user how to go on, recommending the choice that fits: capture what you discussed with ${y(o,"capture")}, which turns it into the artifacts the team's process needs, to approve and then build; start the unit it belongs to with ${y(o,"start")} <ID>; record it as an ad-hoc unit, with a short title you propose (create it with the BuildKeel save_draft tool, type adhoc, then start it with start_work); or go ahead without a unit, which only the user can choose (then call the BuildKeel manage tool with action untracked). Make the change once one of these is done.`}};import{readFileSync as se}from"node:fs";import{resolve as ae}from"node:path";function I(t,n){return n.some(e=>e.endsWith("/")?t.startsWith(e):t===e||t.startsWith(`${e}/`))}function O(t){let n=t.split("/"),e=n.at(-1)??"";return n.slice(0,-1).some(o=>["test","tests","__tests__","spec"].includes(o))||/[._-](test|spec)\.[^.]+$/.test(e)||/^test_.*\.py$/.test(e)}var R=8;function ce(t,n){try{let e=se(T(t,n),"utf8").split(`
15
- `).filter(Boolean);return new Set(e.map(o=>JSON.parse(o).file))}catch{return new Set}}var pt=({event:t,codeRoot:n,cache:e})=>{if(t.kind!=="after-edit"||!e.activeUnit||e.scope.length===0)return null;let o=ce(n,e.activeUnit),i=[];for(let s of t.files){let c=ae(t.cwd,s);if(w(e,c)||m(e.specRoot,c)!==null)continue;let l=g(n,c);l===null||l===p||l.startsWith(`${p}/`)||!I(l,e.scope)&&!O(l)&&!o.has(l)&&!i.includes(l)&&i.push(l)}if(i.length===0)return null;let r=e.activeUnit,a=e.scope.slice(0,R).join(", ")+(e.scope.length>R?`, and ${e.scope.length-R} more`:"");return{context:`BuildKeel: ${i.join(", ")} ${i.length===1?"is":"are"} outside the areas ${r} relates to so far (${a}): its scope and its story's, and what its own work and the other work on its story changed. If the change belongs to ${r}, carry on and tell the user it widens what ${r} touches. If it belongs to other work, stop and ask the user which unit it belongs to.`}};function ft(t,n,e="claude-code"){if(t.kind==="ignored")return{};let o=v(t.cwd);if(!o)return{};let i=W(o);if(!i)return{};let r=n.map(c=>c({event:t,codeRoot:o,cache:i,host:e})).filter(c=>c!==null),a=r.find(c=>c.block)?.block,s=r.map(c=>c.context).filter(Boolean).join(`
16
+ `),null};import{resolve as le}from"node:path";var ft=({event:t,codeRoot:n,cache:e,host:o})=>{if(t.kind!=="before-edit"||e.activeUnit||e.untrackedSince)return null;let i=t.files.flatMap(r=>{let a=le(t.cwd,r);if(k(e.specRoot,a)!==null)return[];let s=m(n,a);return s===null||s===p||s.startsWith(`${p}/`)?[]:[s]});return i.length===0?null:{block:`BuildKeel: there is no active unit in this working tree, so a change to ${i.join(", ")} would belong to no unit of work. Before changing it, ask the user how to go on, recommending the choice that fits: capture what you discussed, which the user starts by running ${g(o,"capture")} (you cannot start it), turning it into the artifacts the team's process needs, to approve and then build; start the unit it belongs to with ${g(o,"start")} <ID>; record it as an ad-hoc unit, with a short title you propose (create it with the BuildKeel save_draft tool, type adhoc, then start it with start_work); or go ahead without a unit, which only the user can choose (then call the BuildKeel manage tool with action untracked). Make the change once one of these is done.`}};import{readFileSync as de}from"node:fs";import{resolve as ue}from"node:path";function I(t,n){return n.some(e=>e.endsWith("/")?t.startsWith(e):t===e||t.startsWith(`${e}/`))}function R(t){let n=t.split("/"),e=n.at(-1)??"";return n.slice(0,-1).some(o=>["test","tests","__tests__","spec"].includes(o))||/[._-](test|spec)\.[^.]+$/.test(e)||/^test_.*\.py$/.test(e)}var O=8;function pe(t,n){try{let e=de(T(t,n),"utf8").split(`
17
+ `).filter(Boolean);return new Set(e.map(o=>JSON.parse(o).file))}catch{return new Set}}var ht=({event:t,codeRoot:n,cache:e})=>{if(t.kind!=="after-edit"||!e.activeUnit||e.scope.length===0)return null;let o=pe(n,e.activeUnit),i=[];for(let s of t.files){let c=ue(t.cwd,s);if(w(e,c)||k(e.specRoot,c)!==null)continue;let l=m(n,c);l===null||l===p||l.startsWith(`${p}/`)||!I(l,e.scope)&&!R(l)&&!o.has(l)&&!i.includes(l)&&i.push(l)}if(i.length===0)return null;let r=e.activeUnit,a=e.scope.slice(0,O).join(", ")+(e.scope.length>O?`, and ${e.scope.length-O} more`:"");return{context:`BuildKeel: ${i.join(", ")} ${i.length===1?"is":"are"} outside the areas ${r} relates to so far (${a}): its scope and its story's, and what its own work and the other work on its story changed. If the change belongs to ${r}, carry on and tell the user it widens what ${r} touches. If it belongs to other work, stop and ask the user which unit it belongs to.`}};function gt(t,n,e="claude-code"){if(t.kind==="ignored")return{};let o=v(t.cwd);if(!o)return{};let i=M(o);if(!i)return{};let r=n.map(c=>c({event:t,codeRoot:o,cache:i,host:e})).filter(c=>c!==null),a=r.find(c=>c.block)?.block,s=r.map(c=>c.context).filter(Boolean).join(`
16
18
 
17
- `);return s&&t.kind==="after-edit"&&t.deferTo?(Z(o,t.deferTo,s),a?{block:a}:{}):{...a?{block:a}:{},...s?{context:s}:{}}}var ht=[lt,ut,pt,dt,G,tt];var ue={"claude-code":U,antigravity:L};function pe(t){let n=t.indexOf("--host"),e=n>=0?t[n+1]:void 0,o=e&&A(e)?ue[e]:void 0;if(!o)return process.stderr.write(`BuildKeel hook: unsupported host "${e??""}".
18
- `),1;let i=t.filter((r,a)=>a!==n&&a!==n+1);try{let r=de(0,"utf8");process.env.BUILDKEEL_HOOK_LOG&&le(process.env.BUILDKEEL_HOOK_LOG,`${JSON.stringify({host:e,args:i,input:r.trim()?JSON.parse(r):null})}
19
- `);let a=o.parse(r.trim()?JSON.parse(r):{},i),s=ft(a,ht,e),{stdout:c,exitCode:l}=o.render(s,a);return c&&process.stdout.write(c),l}catch(r){process.stderr.write(`BuildKeel hook failed: ${r instanceof Error?r.message:String(r)}
20
- `);let{stdout:a,exitCode:s}=o.failed(i);return a&&process.stdout.write(a),s}}process.exitCode=pe(process.argv.slice(2));
19
+ `);return s&&t.kind==="after-edit"&&t.deferTo?(tt(o,t.deferTo,s),a?{block:a}:{}):{...a?{block:a}:{},...s?{context:s}:{}}}var mt=[ut,ft,ht,pt,q,nt];var ge={"claude-code":U,antigravity:L};function me(t){let n=t.indexOf("--host"),e=n>=0?t[n+1]:void 0,o=e&&A(e)?ge[e]:void 0;if(!o)return process.stderr.write(`BuildKeel hook: unsupported host "${e??""}".
20
+ `),1;let i=t.filter((r,a)=>a!==n&&a!==n+1);try{let r=he(0,"utf8");process.env.BUILDKEEL_HOOK_LOG&&fe(process.env.BUILDKEEL_HOOK_LOG,`${JSON.stringify({host:e,args:i,input:r.trim()?JSON.parse(r):null})}
21
+ `);let a=o.parse(r.trim()?JSON.parse(r):{},i),s=gt(a,mt,e),{stdout:c,exitCode:l}=o.render(s,a);return c&&process.stdout.write(c),l}catch(r){process.stderr.write(`BuildKeel hook failed: ${r instanceof Error?r.message:String(r)}
22
+ `);let{stdout:a,exitCode:s}=o.failed(i);return a&&process.stdout.write(a),s}}process.exitCode=me(process.argv.slice(2));