buildkeel 0.1.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.
Files changed (33) hide show
  1. package/README.md +1 -3
  2. package/antigravity/bin/bk-launch +4 -8
  3. package/antigravity/bin/bk-launch.cmd +7 -16
  4. package/antigravity/bin/bk-start.cjs +25 -0
  5. package/antigravity/dist/hooks/main.js +17 -15
  6. package/antigravity/dist/server/main.js +384 -262
  7. package/antigravity/package.json +1 -1
  8. package/antigravity/plugin.json +1 -1
  9. package/antigravity/rules/buildkeel.md +4 -4
  10. package/antigravity/skills/bk-adhoc/SKILL.md +1 -1
  11. package/antigravity/skills/bk-approve/SKILL.md +4 -2
  12. package/antigravity/skills/bk-build/SKILL.md +13 -2
  13. package/antigravity/skills/bk-capture/SKILL.md +64 -15
  14. package/antigravity/skills/bk-done/SKILL.md +11 -1
  15. package/antigravity/skills/bk-impact/SKILL.md +8 -1
  16. package/antigravity/skills/bk-init/SKILL.md +20 -8
  17. package/antigravity/skills/bk-migrate/SKILL.md +1 -1
  18. package/antigravity/skills/bk-new/SKILL.md +17 -4
  19. package/antigravity/skills/bk-onboard/SKILL.md +22 -3
  20. package/antigravity/skills/bk-start/SKILL.md +7 -1
  21. package/antigravity/skills/bk-stats/SKILL.md +1 -1
  22. package/antigravity/skills/bk-status/SKILL.md +2 -0
  23. package/antigravity/templates/design.md +1 -0
  24. package/antigravity/templates/epic.md +1 -0
  25. package/antigravity/templates/idea.md +1 -0
  26. package/antigravity/templates/spec-driven/capability.md +28 -0
  27. package/antigravity/templates/spec-driven/proposal.md +40 -0
  28. package/antigravity/templates/spec-driven/spec-change.md +43 -0
  29. package/antigravity/templates/spec-driven/tasks.md +17 -0
  30. package/antigravity/templates/story.md +1 -0
  31. package/antigravity/templates/task.md +1 -0
  32. package/cli.mjs +2 -2
  33. package/package.json +1 -1
package/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # BuildKeel
2
2
 
3
- BuildKeel keeps your requirements, tests and code connected, from the business idea down to the commit, and gives your coding agent that full picture before it writes anything. It works in Claude Code and Antigravity.
4
-
5
- > **Early access.** Commands and behavior may change between 0.x releases. Your spec trees keep working across them: a change of format comes with `/bk:migrate`.
3
+ BuildKeel keeps your specs, tests and code connected, and gives your coding agent that picture before it writes anything. It works in Claude Code and Antigravity, in one of two modes: **Spec-driven**, where capability specs say how your system behaves and each piece of work is a change to them, with no approvals; or **Full SDLC**, your team's chain from business idea down, with approvals and gates.
6
4
 
7
5
  ## Install in Antigravity
8
6
 
@@ -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 re,readFileSync as ie}from"node:fs";var dt=["claude-code","antigravity"],I=t=>dt.includes(t);function y(t,n){return t==="antigravity"?`/bk-${n}`:`/bk:${n}`}function R(t,n){return t.replace(/\{\{bk:([a-z][a-z-]*)\}\}/g,(e,o)=>y(n,o))}import{isAbsolute as $,relative as ht,resolve as D}from"node:path";import{existsSync as N}from"node:fs";import{dirname as pt,join as j,resolve as ft}from"node:path";var gt=".buildkeel.yaml";function w(t){let n=ft(t);for(;;){if(N(j(n,gt)))return n;if(N(j(n,".git")))return null;let e=pt(n);if(e===n)return null;n=e}}var K=["write_to_file","replace_file_content","multi_replace_file_content"],ge=K.join("|");var mt={decision:"allow"},B={decision:"ask"},C=(t,n)=>{let e=ht(t,n);return e===""||!e.startsWith("..")&&!$(e)};function b(t,n){let e=(t.workspacePaths??[]).filter(r=>typeof r=="string"&&r!==""),o=n?[...e.filter(r=>C(r,n)),...e.filter(r=>!C(r,n))]:e;return o.find(r=>w(r)!==null)??o[0]??process.cwd()}var F={parse(t,n){let e=t??{};switch(n[0]){case"PreInvocation":return e.conversationId?{kind:"prompt",cwd:b(e),conversation:e.conversationId,newMessage:e.invocationNum===0,transcript:e.transcriptPath??null}:{kind:"ignored"};case"PreToolUse":case"PostToolUse":{let o=e.toolCall?.name??"",r=e.toolCall?.args?.TargetFile,i=K.includes(o)&&typeof r=="string"&&r!==""?r:null;if(n[0]==="PostToolUse"){if(!i||e.error)return{kind:"ignored"};let s=b(e,$(i)?i:void 0);return{kind:"after-edit",cwd:s,tool:o,files:[D(s,i)],...e.conversationId?{deferTo:e.conversationId}:{}}}let c=b(e,i&&$(i)?i:void 0),a=[...e.workspacePaths??[],...e.artifactDirectoryPath?[e.artifactDirectoryPath]:[]].filter(s=>typeof s=="string"&&s!=="");return{kind:"before-edit",cwd:c,tool:o,files:i?[D(c,i)]:[],folders:a}}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(r=>(n.folders??[]).some(i=>C(i,r))),o=t.block?{decision:"deny",reason:t.block}:e?mt:B;return{stdout:JSON.stringify(o),exitCode:0}}return{stdout:"{}",exitCode:0}},failed:t=>({stdout:JSON.stringify(t[0]==="PreToolUse"?B:{}),exitCode:0})};var P={Edit:"file_path",Write:"file_path",MultiEdit:"file_path",NotebookEdit:"notebook_path"},me=Object.keys(P).join("|"),kt={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=kt[n.source??"startup"]??"startup";return{kind:"session-start",cwd:e,reason:o}}case"PreToolUse":case"PostToolUse":{let o=n.tool_name??"",r=P[o],i=r?n.tool_input?.[r]:void 0;return typeof i!="string"?{kind:"ignored"}:{kind:n.hook_event_name==="PreToolUse"?"before-edit":"after-edit",cwd:e,tool:o,files:[i]}}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 xe,readFileSync as yt,writeFileSync as we}from"node:fs";import{join as L}from"node:path";var d=".buildkeel",xt="hook-cache.json",wt="context.md",J=t=>L(t,d,wt);function vt(t){return L(t,d,xt)}function M(t){try{return JSON.parse(yt(vt(t),"utf8"))}catch{return null}}var Se=`${d}/`;import{appendFileSync as bt,closeSync as $t,fstatSync as Ct,mkdirSync as z,openSync as _t,readFileSync as V,readSync as Et,renameSync as Tt,rmSync as Ot,statSync as At,writeFileSync as It}from"node:fs";import{homedir as Rt}from"node:os";import{join as S}from"node:path";import{readFileSync as St}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.stages){let o=(t.stageExamples??[]).map(i=>`\`${i}\``),r=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.`+(r?` Examples of what to draft, in the team's words: ${r}; use these in place of a command's usual examples.`:""),fingerprint:`${t.stages}|${o.join(",")}`})}return e}function Ht(t,n,e){if(!n.activeUnit)return null;try{return R(St(J(t),"utf8").trimEnd(),e)}catch{return null}}function v(t,n,e,o){let r=Ht(t,n,e),i=["BuildKeel:"];for(let c of _(n,e)){let a=c.key==="unit"&&n.activeUnit?r?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.":"";i.push(`- ${c.text}${a}`)}return r&&i.push("",r),i.join(`
3
- `)}var W=({event:t,codeRoot:n,cache:e,host:o})=>t.kind!=="session-start"?null:{context:v(n,e,o,t.reason==="compact")};var q=(t,n,e)=>S(t,d,"conversations",`${n.replace(/[^A-Za-z0-9_-]/g,"_").slice(0,100)}.${e}`),Nt=(t,n)=>q(t,n,"json"),Y=(t,n)=>q(t,n,"notices");function X(t,n,e){let o=Y(t,n);z(S(o,".."),{recursive:!0}),bt(o,`${JSON.stringify(e)}
4
- `)}function jt(t,n){let e=Y(t,n),o;try{o=V(e,"utf8")}catch{return[]}return Ot(e,{force:!0}),o.split(`
5
- `).filter(Boolean).flatMap(r=>{try{return[String(JSON.parse(r))]}catch{return[]}})}function Dt(t){try{let n=JSON.parse(V(t,"utf8"));return n.version===1?n:null}catch{return null}}function G(t,n){z(S(t,".."),{recursive:!0});let e=`${t}.${process.pid}.tmp`;It(e,`${JSON.stringify(n,null,2)}
6
- `),Tt(e,t)}var Z=t=>t==="~"||t.startsWith("~/")?S(Rt(),t.slice(1)):t;function Bt(t,n){let e;try{e=_t(Z(t),"r")}catch{return{ok:!1,offset:n,checkpoint:null}}try{let o=Ct(e).size,r=o<n?0:n,i=Buffer.alloc(o-r),c=i.length>0?Et(e,i,0,i.length,r):0,a=i.subarray(0,c).lastIndexOf(10);if(a<0)return{ok:!0,offset:r,checkpoint:null};let s=!0,l=null;for(let f of i.subarray(0,a).toString("utf8").split(`
7
- `))if(f.trim())try{let u=JSON.parse(f);typeof u.step_index!="number"||typeof u.type!="string"?s=!1:u.type==="CHECKPOINT"&&(l=Math.max(l??-1,u.step_index))}catch{s=!1}return{ok:s,offset:r+a+1,checkpoint:l}}catch{return{ok:!1,offset:n,checkpoint:null}}finally{$t(e)}}function Kt(t){if(!t)return 0;try{return At(Z(t)).size}catch{return 0}}var Q=t=>{if(t.event.kind!=="prompt")return null;let n=jt(t.codeRoot,t.event.conversation),o=[Ft(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},Ft=({event:t,codeRoot:n,cache:e,host:o})=>{if(t.kind!=="prompt")return null;let r=Nt(n,t.conversation),i=_(e,o),c=Object.fromEntries(i.map(p=>[p.key,p.fingerprint])),a=Dt(r);if(!a)return G(r,{version:1,startedAt:new Date().toISOString(),reported:c,transcript:{offset:Kt(t.transcript),checkpoint:null}}),{context:v(n,e,o,!1)};let s=t.transcript?Bt(t.transcript,a.transcript.offset):{ok:!1,offset:a.transcript.offset,checkpoint:null},l=s.checkpoint!==null&&(a.transcript.checkpoint===null||s.checkpoint>a.transcript.checkpoint),f={...a,transcript:{offset:s.offset,checkpoint:l?s.checkpoint:a.transcript.checkpoint}},u=null;if(l)u=v(n,e,o,!0),f.reported=c;else if(t.newMessage){let p=i.filter(g=>a.reported[g.key]!==g.fingerprint).map(g=>`- ${g.text}`);if(!s.ok&&e.activeUnit){let g=`- Active unit: ${e.activeUnit}. If its requirements and test cases are no longer in view, call the BuildKeel get_context tool.`,H=p.findIndex(ut=>ut.startsWith("- Active unit:"));H>=0?p[H]=`${p[H]} If its requirements and test cases are no longer in view, call the BuildKeel get_context tool.`:p.unshift(g)}p.length>0&&(u=["BuildKeel:",...p].join(`
10
- `)),f.reported=c}return JSON.stringify(f)!==JSON.stringify(a)&&G(r,f),u?{context:u}:null};import{appendFileSync as qt,mkdirSync as Yt}from"node:fs";import{dirname as Xt,join as Zt,resolve as Qt}from"node:path";import{existsSync as Pt,realpathSync as tt}from"node:fs";import{basename as Ut,dirname as et,isAbsolute as Lt,join as Jt,relative as ot,resolve as Mt,sep as Wt}from"node:path";var Gt={versions:"the frozen copy",approvals:"an approval record",completions:"a completion record",repins:"a re-pin decision record"},zt={"test-plan":"TP",design:"DD"},Vt=process.platform==="win32",h=t=>Vt?t.toLowerCase():t;function m(t,n){let e=nt(t,n);if(e!==null)return e;try{let o=n;for(;!Pt(o)&&et(o)!==o;)o=et(o);let r=Jt(tt(o),ot(o,n));return nt(tt(t),r)}catch{return null}}var k=(t,n)=>m(t,n);function nt(t,n){let e=ot(t,n);return e===""||e.startsWith("..")||Lt(e)?null:e.split(Wt).join("/")}function x(t,n){let e=k(t.specRoot,n);if(e===null)return null;let o=e.split("/"),r=t.documents??zt,i=o.length>=4&&o[2]!==void 0&&Object.hasOwn(r,o[2])?r[o[2]]:void 0,c=i!==void 0,a=o[c?3:2],s=`${o[1]??""}${c?`-${i}`:""}`,l=c?5:4;if(a&&o.length===l&&t.lockedDirs.map(h).includes(h(a))){if(h(a)==="versions"){let p=/^v(0|[1-9][0-9]*)\.md$/.exec(Ut(e))?.[1],g=p?`${s} v${p}`:s;return`BuildKeel: ${e} is the frozen copy of ${g}, 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=Gt[a.toLowerCase()]??"a record";return`BuildKeel: ${e} is ${u} of ${s}. BuildKeel writes these records once and they are never edited, by hand or by the agent.`+(h(a)==="approvals"?" To approve a new version, use the BuildKeel approve tool.":"")}if((t.lockedSources??[]).find(u=>h(e).startsWith(`${h(u)}/`))){let u=`${o[1]??""}${c?`-${i}`:""}`;return`BuildKeel: ${e} 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=>h(u)===h(e))?`BuildKeel: ${e} 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 rt=({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 r=t.files.map(i=>x(n,Mt(t.cwd,i))).filter(i=>i!==null);return r.length===0?null:t.kind==="before-edit"?{block:r[0]}:{context:r.map(i=>`BuildKeel: this tree's lock gate is set to warn, so the edit went ahead. ${i.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 E=(t,n)=>Zt(t,d,"journal",`${n}.jsonl`),it=({event:t,codeRoot:n,cache:e})=>{if(t.kind!=="after-edit"||!e.activeUnit)return null;let o=new Date().toISOString(),r=[];for(let c of t.files){let a=Qt(t.cwd,c);if(x(e,a)||k(e.specRoot,a)!==null)continue;let s=m(n,a);if(s===null||s===d||s.startsWith(`${d}/`))continue;let l={at:o,tool:t.tool,file:s};r.push(JSON.stringify(l))}if(r.length===0)return null;let i=E(n,e.activeUnit);return Yt(Xt(i),{recursive:!0}),qt(i,`${r.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 te}from"node:path";var st=({event:t,codeRoot:n,cache:e,host:o})=>{if(t.kind!=="before-edit"||e.activeUnit||e.untrackedSince)return null;let r=t.files.flatMap(i=>{let c=te(t.cwd,i);if(k(e.specRoot,c)!==null)return[];let a=m(n,c);return a===null||a===d||a.startsWith(`${d}/`)?[]:[a]});return r.length===0?null:{block:`BuildKeel: there is no active unit in this working tree, so a change to ${r.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 ee}from"node:fs";import{resolve as ne}from"node:path";function T(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 A=8;function oe(t,n){try{let e=ee(E(t,n),"utf8").split(`
15
- `).filter(Boolean);return new Set(e.map(o=>JSON.parse(o).file))}catch{return new Set}}var at=({event:t,codeRoot:n,cache:e})=>{if(t.kind!=="after-edit"||!e.activeUnit||e.scope.length===0)return null;let o=oe(n,e.activeUnit),r=[];for(let a of t.files){let s=ne(t.cwd,a);if(x(e,s)||k(e.specRoot,s)!==null)continue;let l=m(n,s);l===null||l===d||l.startsWith(`${d}/`)||!T(l,e.scope)&&!O(l)&&!o.has(l)&&!r.includes(l)&&r.push(l)}if(r.length===0)return null;let i=e.activeUnit,c=e.scope.slice(0,A).join(", ")+(e.scope.length>A?`, and ${e.scope.length-A} more`:"");return{context:`BuildKeel: ${r.join(", ")} ${r.length===1?"is":"are"} outside the areas ${i} relates to so far (${c}): its scope and its story's, and what its own work and the other work on its story changed. If the change belongs to ${i}, carry on and tell the user it widens what ${i} touches. If it belongs to other work, stop and ask the user which unit it belongs to.`}};function ct(t,n,e="claude-code"){if(t.kind==="ignored")return{};let o=w(t.cwd);if(!o)return{};let r=M(o);if(!r)return{};let i=n.map(s=>s({event:t,codeRoot:o,cache:r,host:e})).filter(s=>s!==null),c=i.find(s=>s.block)?.block,a=i.map(s=>s.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 a&&t.kind==="after-edit"&&t.deferTo?(X(o,t.deferTo,a),c?{block:c}:{}):{...c?{block:c}:{},...a?{context:a}:{}}}var lt=[rt,st,at,it,W,Q];var se={"claude-code":U,antigravity:F};function ae(t){let n=t.indexOf("--host"),e=n>=0?t[n+1]:void 0,o=e&&I(e)?se[e]:void 0;if(!o)return process.stderr.write(`BuildKeel hook: unsupported host "${e??""}".
18
- `),1;let r=t.filter((i,c)=>c!==n&&c!==n+1);try{let i=ie(0,"utf8");process.env.BUILDKEEL_HOOK_LOG&&re(process.env.BUILDKEEL_HOOK_LOG,`${JSON.stringify({host:e,args:r,input:i.trim()?JSON.parse(i):null})}
19
- `);let c=o.parse(i.trim()?JSON.parse(i):{},r),a=ct(c,lt,e),{stdout:s,exitCode:l}=o.render(a,c);return s&&process.stdout.write(s),l}catch(i){process.stderr.write(`BuildKeel hook failed: ${i instanceof Error?i.message:String(i)}
20
- `);let{stdout:c,exitCode:a}=o.failed(r);return c&&process.stdout.write(c),a}}process.exitCode=ae(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));