@lovek4d/kickflip 0.7.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/CONTEXT.md +53 -49
  2. package/SKILL.md +17 -13
  3. package/bin/event-lines.mjs +1 -1
  4. package/bin/kickflip.mjs +3 -3
  5. package/board/agents.mjs +2 -2
  6. package/board/arch-review.mjs +4 -4
  7. package/board/bin/board.mjs +113 -105
  8. package/board/domain.mjs +7 -7
  9. package/board/finish.mjs +4 -4
  10. package/board/headless.mjs +8 -8
  11. package/board/hub-lines.mjs +1 -0
  12. package/board/onboarding.mjs +3 -3
  13. package/board/public/app-arch.js +1 -1
  14. package/board/public/app-cards.js +6 -7
  15. package/board/public/app-header.js +1 -1
  16. package/board/public/app-lanes.js +39 -62
  17. package/board/public/app-needs.js +16 -11
  18. package/board/public/app-reader.js +9 -5
  19. package/board/public/app.js +3 -3
  20. package/board/public/attach.js +18 -2
  21. package/board/public/dialogs.js +11 -13
  22. package/board/public/hub-kit.js +1 -1
  23. package/board/public/hub.css +0 -1
  24. package/board/public/linear-key.js +2 -2
  25. package/board/public/new-task.js +1 -1
  26. package/board/public/prototype-kit.js +2 -5
  27. package/board/public/router.js +1 -1
  28. package/board/public/sidebar.css +3 -3
  29. package/board/public/sidebar.js +2 -2
  30. package/board/public/style.css +43 -43
  31. package/board/public/ui-cards.js +25 -20
  32. package/board/public/ui-hub.js +1 -5
  33. package/board/public/ui-links.js +5 -5
  34. package/board/public/ui-order.js +6 -7
  35. package/board/public/ui-questions.js +17 -13
  36. package/board/public/ui-sidebar.js +1 -1
  37. package/board/public/ui-threads.js +29 -21
  38. package/board/public/ui.js +23 -6
  39. package/board/public/view-hub-settings.js +11 -19
  40. package/board/public/view-hub.js +25 -11
  41. package/board/public/view-notes.js +1 -1
  42. package/board/public/view-questions.js +18 -5
  43. package/board/public/view-settings.js +7 -7
  44. package/board/render-check.mjs +10 -10
  45. package/board/review.mjs +1 -1
  46. package/board/server.mjs +18 -19
  47. package/board/stop.mjs +1 -1
  48. package/board/store.mjs +26 -26
  49. package/board/tickets.mjs +3 -3
  50. package/hooks-plugin/hooks/board-data-gate.mjs +6 -6
  51. package/hub/awake.mjs +1 -1
  52. package/hub/board-repo.mjs +1 -1
  53. package/hub/boards.mjs +4 -4
  54. package/hub/github.mjs +3 -3
  55. package/hub/hub.mjs +11 -11
  56. package/hub/incoming.mjs +2 -2
  57. package/hub/linear.mjs +1 -1
  58. package/hub/locks.mjs +1 -1
  59. package/hub/new-board.mjs +2 -2
  60. package/hub/owner-checks.mjs +1 -1
  61. package/hub/readiness.mjs +1 -1
  62. package/hub/review-send.mjs +1 -1
  63. package/hub/slack.mjs +1 -1
  64. package/hub/try.mjs +6 -7
  65. package/hub/views.mjs +1 -1
  66. package/package.json +1 -1
  67. package/shared/usage-gate.mjs +1 -1
  68. package/starter/server.mjs +3 -3
@@ -1 +1 @@
1
- var n=Object.defineProperty;var t=(e,a)=>n(e,"name",{value:a,configurable:!0});import{waitsAgainLine as d}from"../board/domain.mjs";const r=["main-pulled","settings","ask-done","reviewers-removed","reviewers-added","question-reopen","repo-changed","ping","hello","card-hold","card-done","card-unbump","card-unblocked","grill-more","prototype-request","visual-request","usage-resumed"],i=t(e=>{try{return JSON.parse(e).kind}catch{return null}},"kindOf"),h=t(e=>r.includes(i(e)),"recordOnly"),l={ask:t(e=>`new task ${e.id} received: sort it: file it with item add --from-ask, fold it with item relate-ask, or answer it with reply`,"ask"),"owner-answer":t(e=>`answer received to question ${e.data?.id}: act on it on its card`,"owner-answer"),ping:t(e=>`ping ${e.id} from the hub answered`,"ping"),hello:t(e=>`ping ${e.id} from the hub answered`,"hello"),"overnight-triage":t(e=>`Overnight triage ${e.data?.date} asks Claude to reply: ${e.data?.cards?.length} cards wait on open questions; run the triage, then board.mjs reply ${e.id}`,"overnight-triage"),"pr-review":t(e=>`review asked on PR #${e.data?.pr}: the board was idle; read its diff and review post it`,"pr-review"),"orchestrator-waits":t(e=>[e.data?.added?.length&&`waiting on you now: ${e.data.added.join(", ")}`,e.data?.again?.length&&d(e.data.again,Date.parse(e.ts))].filter(Boolean).join(" \xB7 "),"orchestrator-waits"),"check-github":t(e=>e.data?.reason==="morning"?"the daily wake: run one GitHub tick, then walk every lane top-down once: clear each blocked-until condition that holds, and act on any card stuck with no wait; a thread line on each card you act on, nothing else":"GitHub check asked: run one GitHub tick","check-github"),"github-change":t(e=>`${e.data?.prs?.length??0} PRs changed on GitHub: run one GitHub tick`,"github-change"),"review-answer":t(e=>e.data?.answer==="skip"?`skip answered on the review of PR #${e.data?.pr}: record it posted, post nothing`:`post answered on the review of PR #${e.data?.pr}: review post it`,"review-answer"),"plan-waves":t(e=>`Plan waves asked on ${e.data?.keys?.length??0} Todo cards: triage them into waves and ask the owner about them`,"plan-waves"),"arch-review":t(e=>`architecture review asked on ${e.data?.key}: review ${e.data?.repo} on ${e.data?.branch} and file the findings`,"arch-review"),rules:t(e=>`${e.data?.scope==="hub"?"hub":"board"} rules changed: the rule lines below are the new ones; follow them from now`,"rules"),"prototype-overdue":t(e=>`prototype overdue on ${e.data?.key}: dispatch its prototype agent now`,"prototype-overdue")},u={"item-message":t((e,a)=>String(a.data?.key).startsWith("ask:")&&a.data?.intent==="build"?`Implement asked on ${e}: file it with item add --from-ask --phase ready, with its --size and notes`:a.data?.intent==="build"?`Implement asked on ${e}: it is staging, the message in its notes; judge it against the whole board, then item set --phase ready --text "<what you checked>", or --phase grilling or prototyping`:`message received from ${e}'s thread: reply in its thread`,"item-message"),"card-bump":t(e=>`${e} bumped: handle its waits lines first; with none, leave it`,"card-bump"),"ask-bump":t(e=>`${e} bumped: take that ask first`,"ask-bump"),"ask-edited":t(e=>`${e} edited: use the new text, and say what changed if you had started`,"ask-edited"),"ask-done":t(e=>`${e} marked done`,"ask-done"),"ask-withdrawn":t(e=>`${e} deleted: drop it`,"ask-withdrawn"),"card-done":t(e=>`${e} marked done`,"card-done"),"card-hold":t((e,a)=>`${e} ${a.data?.hold?"held":"resumed"}`,"card-hold"),"card-unblocked":t(e=>`${e} unblocked`,"card-unblocked"),"prototype-request":t(e=>`prototype asked on ${e}`,"prototype-request"),"visual-request":t(e=>`visual asked on ${e}: the board launches its visual agent`,"visual-request"),"reask-request":t(e=>`question asked again on ${e}: ask it again with question add reask_of`,"reask-request"),"builder-ask":t((e,a)=>`${a.data?.ask} from ${e}'s builder: ${a.data?.text} (builder answer it, or builder defer it)`,"builder-ask"),"owner-check-broken":t((e,a)=>`${e}'s owner-step check can't run: ${a.data?.error}; fix it with item set --check, or drop it`,"owner-check-broken"),"card-added":t(e=>`${e} added by an agent: link it and file it: item notes, then item set --relate and --phase`,"card-added")};export{u as ON_CARD,l as PLAIN,r as RECORD_ONLY,h as recordOnly};
1
+ var r=Object.defineProperty;var t=(e,a)=>r(e,"name",{value:a,configurable:!0});import{waitsAgainLine as d}from"../board/domain.mjs";const i=["main-pulled","settings","ask-done","reviewers-removed","reviewers-added","question-reopen","repo-changed","ping","hello","card-hold","card-done","card-unbump","card-unblocked","grill-more","prototype-request","visual-request","usage-resumed"],n=t(e=>{try{return JSON.parse(e).kind}catch{return null}},"kindOf"),h=t(e=>i.includes(n(e)),"recordOnly"),l={ask:t(e=>`new task ${e.id} received: sort it: file it with item add --from-ask, fold it with item relate-ask, or answer it with reply`,"ask"),"owner-answer":t(e=>`answer received to question ${e.data?.id}: act on it on its card`,"owner-answer"),ping:t(e=>`ping ${e.id} from the hub answered`,"ping"),hello:t(e=>`ping ${e.id} from the hub answered`,"hello"),"overnight-triage":t(e=>`Overnight triage ${e.data?.date} asks Claude to reply: ${e.data?.cards?.length} cards wait on open questions; run the triage, then board.mjs reply ${e.id}`,"overnight-triage"),"pr-review":t(e=>`review asked on PR #${e.data?.pr}: the board was idle; read its diff and review post it`,"pr-review"),"orchestrator-waits":t(e=>[e.data?.added?.length&&`waiting on you now: ${e.data.added.join(", ")}`,e.data?.again?.length&&d(e.data.again,Date.parse(e.ts))].filter(Boolean).join(" \xB7 "),"orchestrator-waits"),"check-github":t(e=>e.data?.reason==="morning"?"the daily wake: run one GitHub tick, then walk every lane top-down once: clear each blocked-until condition that holds, and act on any card stuck with no wait; a thread line on each card you act on, nothing else":"GitHub check asked: run one GitHub tick","check-github"),"github-change":t(e=>`${e.data?.prs?.length??0} PRs changed on GitHub: run one GitHub tick`,"github-change"),"review-answer":t(e=>e.data?.answer==="skip"?`skip answered on the review of PR #${e.data?.pr}: record it posted, post nothing`:`post answered on the review of PR #${e.data?.pr}: review post it`,"review-answer"),"plan-waves":t(e=>`Plan waves asked on ${e.data?.keys?.length??0} Todo cards: triage them into waves and ask the owner about them`,"plan-waves"),"arch-review":t(e=>`architecture review asked on ${e.data?.key}: review ${e.data?.repo} on ${e.data?.branch} and file the findings`,"arch-review"),rules:t(e=>`${e.data?.scope==="hub"?"hub":"board"} rules changed: the rule lines below are the new ones; follow them from now`,"rules"),"prototype-overdue":t(e=>`prototype overdue on ${e.data?.key}: dispatch its prototype agent now`,"prototype-overdue")},u={"item-message":t((e,a)=>String(a.data?.key).startsWith("ask:")&&a.data?.intent==="build"?`Implement asked on ${e}: file it with item add --from-ask --phase ready, with its --size and notes`:a.data?.intent==="build"?`Implement asked on ${e}: it is staging, the message in its notes; judge it against the whole board, then item set --phase ready --text "<what you checked>", or --phase grilling or prototyping`:`message received from ${e}'s thread: reply in its thread`,"item-message"),"card-bump":t(e=>`${e} bumped: handle its waits lines first; with none, leave it`,"card-bump"),"ask-bump":t(e=>`${e} bumped: take that ask first`,"ask-bump"),"ask-edited":t(e=>`${e} edited: use the new text, and say what changed if you had started`,"ask-edited"),"ask-done":t(e=>`${e} marked done`,"ask-done"),"ask-withdrawn":t(e=>`${e} deleted: drop it`,"ask-withdrawn"),"card-done":t(e=>`${e} marked done`,"card-done"),"card-hold":t((e,a)=>`${e} ${a.data?.hold?"held":"resumed"}`,"card-hold"),"card-unblocked":t(e=>`${e} unblocked`,"card-unblocked"),"prototype-request":t(e=>`prototype asked on ${e}`,"prototype-request"),"visual-request":t(e=>`visual asked on ${e}: the board launches its visual agent`,"visual-request"),"reask-request":t(e=>`question asked again on ${e}: ask it again with question add reask_of`,"reask-request"),"builder-resume":t((e,a)=>`${e}'s pause cleared (${a.data?.cleared}): tell its builder in your session to carry on where it stopped`,"builder-resume"),"builder-ask":t((e,a)=>`${a.data?.ask} from ${e}'s builder: ${a.data?.text} (builder answer it, or builder defer it)`,"builder-ask"),"owner-check-broken":t((e,a)=>`${e}'s owner-step check can't run: ${a.data?.error}; fix it with item set --check, or drop it`,"owner-check-broken"),"card-added":t(e=>`${e} added by an agent: link it and file it: item notes, then item set --relate and --phase`,"card-added")};export{u as ON_CARD,l as PLAIN,i as RECORD_ONLY,h as recordOnly};
package/bin/kickflip.mjs CHANGED
@@ -35,7 +35,7 @@
35
35
  // --hub makes this board the hub board, as `set --hub` does; a join without either keeps the flag.
36
36
  // The hub board's session runs in the foreground in tmux (`hub`); every other board's in the background
37
37
  // --template board runs the shared board (../board/server.mjs) from the site dir,
38
- // writing its board.json; --items is "waves" (default) or a wave.json-format file, made with no
38
+ // writing its board.json; --items is "waves" (default) or an item file, made with no
39
39
  // items when missing.
40
40
  // It then runs handshake, unless this session already answered the board's ping, and
41
41
  // asks the hub for one poll (as poll-now --keep-queue), so the board's GitHub link passes
@@ -162,8 +162,8 @@
162
162
  // Exit codes: 0 ok, 1 error, 2 bad usage, 3 route owned by another session (join/leave), or its recorded session is
163
163
  // live (start/resume/hub). hub passes through claude's exit code.
164
164
  var Ne=Object.defineProperty;var p=(e,t)=>Ne(e,"name",{value:t,configurable:!0});import w from"node:fs";import u from"node:path";import D from"node:os";import re from"node:net";import Te from"node:readline/promises";import{execFileSync as ae,spawnSync as K}from"node:child_process";import{fileURLToPath as Ie}from"node:url";import{ICON_KEYS as ce}from"../hub/icons.mjs";import{liveSessions as G}from"../hub/live.mjs";import{isBoardSite as L,writeManifest as v}from"../hub/boards.mjs";import{joinName as Fe,routeFromName as F,routeRecord as g,validRoute as C}from"../hub/routes.mjs";import{defaultBranch as Ce,githubFromOrigin as Ee}from"../hub/repos.mjs";import{hasBuildScript as Re,onboardingItems as Be}from"../board/onboarding.mjs";import{uninstallStatusLine as Ae,storedPath as De,nodeBin as U,watcherCommand as Le,tailnetUrl as Pe}from"./install.mjs";import{hubService as de}from"../shared/service.mjs";import{tailscaleBin as ue}from"../shared/tailscale.mjs";import{runDoctor as He}from"./doctor.mjs";import{cloneRoot as Je}from"../shared/repos.mjs";const y=Je(),N=u.join(D.homedir(),".claude","kickflip"),ze=u.join(N,"manifest.json"),le=u.join(N,"logs"),T=Number(process.env.KICKFLIP_HUB_PORT||4500),W=4600,qe=/^#([0-9a-f]{3}|[0-9a-f]{6})$/i,[Y,...j]=process.argv.slice(2),s=Object.assign(Object.create(null),{_:[]});for(let e=0;e<j.length;e++){const t=j[e];if(t.startsWith("--")){const n=t.slice(2);n==="fix"||n==="force"||n==="json"||n==="urgent"||n==="now"||n==="keep-queue"||n==="hub"||n==="no-hub"||n==="dry-run"||n==="no-restart"||n==="watch"&&(e+1>=j.length||j[e+1].startsWith("--"))?s[n]=!0:s[n]=j[++e]}else s._.push(t)}const f=p(e=>console.log(typeof e=="string"?e:JSON.stringify(e,null,2)),"out");class I extends Error{static{p(this,"Exit")}constructor(t,n){super(t),this.code=n}}const i=p((e,t=1)=>{throw new I(e,t)},"fail");function Q(){s.icon&&!ce.includes(s.icon)&&i(`--icon takes one of: ${ce.join(", ")}`,2),s.color&&!qe.test(s.color)&&i("--color takes a CSS hex color, such as #3b82f6",2)}p(Q,"checkLook");function X(e){for(const t of["icon","color"])s[t]===""?delete e[t]:s[t]!==void 0&&(e[t]=s[t])}p(X,"applyLook");const fe=p(()=>s.hub&&s["no-hub"]&&i("pass --hub or --no-hub, not both",2),"checkHub"),he=p(e=>i(`/${e} is not a board; only a board can be the hub board`,2),"notBoard");function Z(e,t,n){if(s["no-hub"]&&delete n.hub,!!s.hub){L(n)||he(t);for(const[o,h]of Object.entries(e.sites))o!==t&&h?.hub&&delete h.hub;n.hub=!0}}p(Z,"applyHub");function k(){try{const e=JSON.parse(w.readFileSync(ze,"utf8"));return e.sites||={},e}catch(e){if(e.code==="ENOENT")return{version:1,sites:{}};i("manifest unreadable: "+e.message)}}p(k,"readManifest");const pe=p(e=>{try{return JSON.parse(w.readFileSync(e,"utf8"))}catch{return null}},"readJSON"),E=p(()=>import(u.join(y,"hub","sessions.mjs")),"sessionsModule");async function P(e){const{SessionError:t}=await E();try{return await e()}catch(n){throw n instanceof t&&i(n.message,n.reason==="live"?3:n.reason==="hub"?2:1),n}}p(P,"sessionCall");async function me(e){const{handshake:t}=await import(u.join(y,"hub","boards.mjs"));try{return await t(e,k())}catch(n){return i(n.message,2)}}p(me,"runHandshake");async function Me(e){for(let t=0;t<2;t++){let n;try{n=await fetch(`http://127.0.0.1:${T}/_hub/poll`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({flush:!1}),signal:AbortSignal.timeout(5*6e4)})}catch(h){return`not run (${h.message})`}if(!n.ok)return`not run (the hub answered ${n.status})`;const o=await n.json();if(o.checked?.includes(e))return"reached this board";if(o.failed?.length)return`missed this board: ${o.failed[0]}`}return"missed this board"}p(Me,"pollFor");async function O(e){const{withManifestLock:t}=await import(u.join(y,"hub","boards.mjs"));try{return await t(e)}catch(n){throw n.code==="ELOCKED"&&i(n.message),n}}p(O,"withLock");function we(e){return new Promise(t=>{const n=re.connect({port:e,host:"127.0.0.1"}),o=p(h=>{n.destroy(),t(h)},"done");n.setTimeout(500,()=>o(!1)),n.on("connect",()=>o(!0)),n.on("error",()=>o(!1))})}p(we,"connects");function Ke(e){return new Promise(t=>{const n=re.createServer();n.once("error",()=>t(!1)),n.listen(e,"0.0.0.0",()=>n.close(()=>t(!0)))})}p(Ke,"bindable");async function H(e){const t=new Set([...Object.values(e.sites),...Object.values(e.closed||{}).map(n=>n.site)].map(n=>n?.port));for(let n=W;n<W+400;n++)if(!t.has(n)&&!await we(n)&&await Ke(n))return n;i("no free port found from "+W)}p(H,"freePort");function Ge(){const e=ue();if(!e)return"";try{return(JSON.parse(ae(e,["status","--json"],{encoding:"utf8",stdio:["ignore","pipe","ignore"]})).Self?.DNSName||"").replace(/\.$/,"")}catch{return""}}p(Ge,"tailnetHost");const V=p(e=>{const t=Ge();return t?`https://${t}/${e}/`:`http://127.0.0.1:${T}/${e}/`},"siteUrl");async function J(){try{return await(await fetch(`http://127.0.0.1:${T}/_hub/status.json`,{signal:AbortSignal.timeout(1500)})).json()}catch{return null}}p(J,"hubStatus");const ge=p(e=>{try{return w.statSync(e).isDirectory()}catch{return!1}},"isFolder"),Ue=p(e=>{try{return w.realpathSync(e)}catch{return null}},"realPath");function be(e=t=>{}){(typeof s.name!="string"||!s.name.trim())&&i('join needs --name "<name>": the board or page name, which gives its route',2);const t=s.name.trim().replace(/\s+/g," "),n=F(t);return C(n)||i(`--name "${t}" needs a letter or digit for its route`,2),{route:n,title:t===n&&e(n)?.title||t}}p(be,"named");function ye(e,{title:t,items:n,tickets:o,repo:h,ref:d}){w.mkdirSync(u.join(e,"data"),{recursive:!0});const r=u.join(e,"board.json"),l=w.existsSync(r)?JSON.parse(w.readFileSync(r,"utf8")):{};l.title=t,l.items=De(n||l.items||"waves"),o&&(l.tickets=o),w.writeFileSync(r,JSON.stringify(l,null,2)+`
165
- `);const c=l.items==="waves"?null:u.resolve(e,l.items);if(c&&!w.existsSync(c)){w.mkdirSync(u.dirname(c),{recursive:!0});const a=l.tickets?.source==="internal"?l.tickets.prefix:void 0,b=Be(c,{prefix:a,hasBuild:Re(h,d)});w.writeFileSync(c,JSON.stringify({title:l.title,items:b},null,2)+`
166
- `)}return[U(),u.join(y,"board","server.mjs"),e]}p(ye,"boardFiles");async function ee(e,t){const{startSession:n,withSessionLock:o}=await E();return o(e,async()=>{const h=await P(()=>n(e,{manifest:k(),...t}));return await O(async()=>{const d=k(),r={session:h.session,label:h.name,since:new Date().toISOString(),repo:h.repo,...h.model?{model:h.model}:{}};g(d.sites,e)?(d.sites[e].owner=r,d.sites[e].updated=r.since):d.starting={...d.starting,[e]:{...g(d.starting,e),...r}},await v(d)}),h})}p(ee,"startRecorded");async function z(e){const{hubSession:t}=await E(),n=await P(()=>t({manifest:k(),repo:e}));process.exitCode=n.status}p(z,"openHub");const ke=p((e,...t)=>{const n=K("git",["-C",e,...t],{encoding:"utf8"});return n.status===0?n.stdout.trim():null},"git"),te=p(()=>!!(process.stdin.isTTY&&process.stdout.isTTY),"canAsk");async function ne(e,t="stopped at the prompt; nothing changed"){const n=Te.createInterface({input:process.stdin,output:process.stdout});try{return(await n.question(e)).trim()}catch(o){throw o.code==="ABORT_ERR"&&i(t),o}finally{n.close()}}p(ne,"ask");const Se=p((e,t)=>!!(g(e.sites,t)||g(e.starting,t)||g(e.closed,t)),"taken");function We(e){return!/^[A-Za-z0-9 ]+$/.test(e)||!C(F(e))?"A board name takes letters, digits and spaces, up to 41 characters of route.":Se(k(),F(e))?`/${F(e)} is taken; pick another name.`:null}p(We,"nameProblem");const je="--template board --items data/wave.json";async function Ye(){const e=ke(process.cwd(),"rev-parse","--show-toplevel");e||i(`${process.cwd()} is not in a git repo; run kickflip join in the folder of the repo to add as a board`,2);const t=w.realpathSync(e);await J()||i("the hub is not running; run kickflip init to set it up, then kickflip join here again");const n=Object.entries(k().sites).find(([,m])=>L(m)&&m.owner?.repo&&Ue(m.owner.repo)===t),o=await Ce(t);if(n)return Qe(...n,o);const h=u.basename(t).replace(/[^A-Za-z0-9]+/g," ").trim();let d=s.name?.trim();for(d===void 0&&!te()&&i('join needs --name "<Board name>" when it has no terminal to ask in',2);;){d===void 0&&(d=await ne(`Board name${h?` (${h})`:""}: `)||h);const m=We(d);if(!m)break;s.name!==void 0&&i(m,2),f(m),d=void 0}const r=d.replace(/\s+/g," "),l=F(r),c=ke(t,"remote","get-url","origin"),a=c?Ee(c):null,{trustFolder:b}=await import("../hub/trust.mjs");try{b(t)}catch(m){i(`Couldn't trust this folder for Claude Code: ${m.message}`)}if(await O(async()=>{const m=k();Se(m,l)&&i(`/${l} is taken; pick another name`,2);const S=u.join(N,"sites",l),$=new Date().toISOString(),x={dir:S,port:await H(m),cmd:ye(S,{title:r,items:"data/wave.json",repo:t,ref:o||"HEAD"}),watch:["data/events.jsonl"],title:r,local:!0,...a?{github:a}:{},...o?{branch:o}:{},owner:{repo:t},created:$,updated:$};m.sites={...m.sites,[l]:x},Z(m,l,x),await v(m)}),f(`Made ${r} at ${V(l)}, working in ${t}${a?` (${a})`:""}.`),s.hub)return f("It is the hub board, so its session runs in tmux, attached here."),z(t);try{await ee(l,{repo:t,prompt:je})}catch(m){throw m instanceof I&&i(`${m.message}
165
+ `);const c=l.items==="waves"?null:u.resolve(e,l.items);if(c&&!w.existsSync(c)){w.mkdirSync(u.dirname(c),{recursive:!0});const a=Be(c,{hasBuild:Re(h,d)});w.writeFileSync(c,JSON.stringify({title:l.title,items:a},null,2)+`
166
+ `)}return[U(),u.join(y,"board","server.mjs"),e]}p(ye,"boardFiles");async function ee(e,t){const{startSession:n,withSessionLock:o}=await E();return o(e,async()=>{const h=await P(()=>n(e,{manifest:k(),...t}));return await O(async()=>{const d=k(),r={session:h.session,label:h.name,since:new Date().toISOString(),repo:h.repo,...h.model?{model:h.model}:{}};g(d.sites,e)?(d.sites[e].owner=r,d.sites[e].updated=r.since):d.starting={...d.starting,[e]:{...g(d.starting,e),...r}},await v(d)}),h})}p(ee,"startRecorded");async function z(e){const{hubSession:t}=await E(),n=await P(()=>t({manifest:k(),repo:e}));process.exitCode=n.status}p(z,"openHub");const ke=p((e,...t)=>{const n=K("git",["-C",e,...t],{encoding:"utf8"});return n.status===0?n.stdout.trim():null},"git"),te=p(()=>!!(process.stdin.isTTY&&process.stdout.isTTY),"canAsk");async function ne(e,t="stopped at the prompt; nothing changed"){const n=Te.createInterface({input:process.stdin,output:process.stdout});try{return(await n.question(e)).trim()}catch(o){throw o.code==="ABORT_ERR"&&i(t),o}finally{n.close()}}p(ne,"ask");const Se=p((e,t)=>!!(g(e.sites,t)||g(e.starting,t)||g(e.closed,t)),"taken");function We(e){return!/^[A-Za-z0-9 ]+$/.test(e)||!C(F(e))?"A board name takes letters, digits and spaces, up to 41 characters of route.":Se(k(),F(e))?`/${F(e)} is taken; pick another name.`:null}p(We,"nameProblem");const je="--template board --items data/items.json";async function Ye(){const e=ke(process.cwd(),"rev-parse","--show-toplevel");e||i(`${process.cwd()} is not in a git repo; run kickflip join in the folder of the repo to add as a board`,2);const t=w.realpathSync(e);await J()||i("the hub is not running; run kickflip init to set it up, then kickflip join here again");const n=Object.entries(k().sites).find(([,m])=>L(m)&&m.owner?.repo&&Ue(m.owner.repo)===t),o=await Ce(t);if(n)return Qe(...n,o);const h=u.basename(t).replace(/[^A-Za-z0-9]+/g," ").trim();let d=s.name?.trim();for(d===void 0&&!te()&&i('join needs --name "<Board name>" when it has no terminal to ask in',2);;){d===void 0&&(d=await ne(`Board name${h?` (${h})`:""}: `)||h);const m=We(d);if(!m)break;s.name!==void 0&&i(m,2),f(m),d=void 0}const r=d.replace(/\s+/g," "),l=F(r),c=ke(t,"remote","get-url","origin"),a=c?Ee(c):null,{trustFolder:b}=await import("../hub/trust.mjs");try{b(t)}catch(m){i(`Couldn't trust this folder for Claude Code: ${m.message}`)}if(await O(async()=>{const m=k();Se(m,l)&&i(`/${l} is taken; pick another name`,2);const S=u.join(N,"sites",l),$=new Date().toISOString(),x={dir:S,port:await H(m),cmd:ye(S,{title:r,items:"data/items.json",repo:t,ref:o||"HEAD"}),watch:["data/events.jsonl"],title:r,local:!0,...a?{github:a}:{},...o?{branch:o}:{},owner:{repo:t},created:$,updated:$};m.sites={...m.sites,[l]:x},Z(m,l,x),await v(m)}),f(`Made ${r} at ${V(l)}, working in ${t}${a?` (${a})`:""}.`),s.hub)return f("It is the hub board, so its session runs in tmux, attached here."),z(t);try{await ee(l,{repo:t,prompt:je})}catch(m){throw m instanceof I&&i(`${m.message}
167
167
  The board is made; run kickflip join here again to start its session.`,m.code),m}f("Its session is starting in the background, and joins the board shortly.")}p(Ye,"joinFolder");function $e(e){const t=de(),n=t.restart();n&&i(`${e}: ${n}; run ${t.restartCommand}`),f("restarted the hub")}p($e,"restartHub");async function Qe(e,t,n){const o=t.title||e;f(`This folder already has a board: ${o} (/${e}).`),n&&n!==t.branch&&(await O(async()=>{const l=k();Object.assign(g(l.sites,e),{branch:n,updated:new Date().toISOString()}),await v(l)}),f(`Its branch is now ${n}${t.branch?` (it was ${t.branch})`:""}.`)),te()||i(t.hub===!0?"Open its session with kickflip hub.":"Run kickflip join in a terminal to restart its session, or tap Restart on its row of the Hub page.");const h=(await ne("Restart its session? (Y/n) ")).toLowerCase();if(h&&!"yes".startsWith(h))return;if(!t.owner.session)return t.hub===!0?z(t.owner.repo):(await ee(e,{repo:t.owner.repo,prompt:je}),f("Its session is starting in the background."));const d=await fetch(`http://127.0.0.1:${T}/_hub/restart-board`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({route:e}),signal:AbortSignal.timeout(5*6e4)}),r=await d.json();if(d.ok||i(r.error),f(`Restarted ${o}'s session.`),t.hub===!0)return z(t.owner.repo)}p(Qe,"restartFolderBoard");const se=["title","description","icon","color","created","watch","filter","hub","github","remote","branch","local"],ve=["notifications","quiet","codexShare"],R="kickflip-board",Xe=/^(\.watch-cursor-.*\.json|watcher\.json)$/,B=p((e,t)=>Object.fromEntries(t.filter(n=>e[n]!==void 0).map(n=>[n,e[n]])),"pick"),q=p(e=>/^[\w@%+=:,./-]+$/.test(e)?e:`'${String(e).replace(/'/g,"'\\''")}'`,"shellQuote");function Oe(e){const t=K("tar",e[0]==="-czf"?["--no-xattrs",...e]:e,{encoding:"utf8",env:{...process.env,COPYFILE_DISABLE:"1"}});t.status!==0&&i(`tar ${e[0]} failed: ${(t.stderr||t.error?.message||"").trim()}`)}p(Oe,"tar");function Ze(e,t){const n=e.entry.title||e.route,o=Object.keys(e.settings).map(h=>({notifications:"Notifications",quiet:"Quiet hours",codexShare:"Codex share"})[h]);return`# Moving ${n} (/${e.route}) to another machine
168
168
 
169
169
  Exported from ${e.from} at ${e.exported_at}. This bundle holds the board's folder and manifest entry${o.length?`, and the hub's ${o.length>1?`${o.slice(0,-1).join(", ")} and ${o.at(-1)}`:o[0]} settings, which import writes over the new machine's`:""}.
package/board/agents.mjs CHANGED
@@ -1,2 +1,2 @@
1
- var g=Object.defineProperty;var t=(e,n)=>g(e,"name",{value:n,configurable:!0});import a from"node:fs";import y from"node:path";import{spawn as v,execFileSync as h}from"node:child_process";import{claudeBin as w,inherited as _}from"../hub/sessions.mjs";function I(e){try{return y.resolve(e,h("git",["-C",e,"rev-parse","--git-common-dir"],{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim())}catch{return null}}t(I,"gitCommonDir");const A={claude:{bin:w,args:t(({prompt:e,model:n="opus"})=>["-p",e,"--permission-mode","auto","--model",n,"--output-format","stream-json","--verbose"],"args"),session:t(e=>{try{const n=JSON.parse(e);return n.type==="system"&&n.subtype==="init"?n.session_id??null:null}catch{return null}},"session")},codex:{bin:t(()=>process.env.KICKFLIP_CODEX||"codex","bin"),args:t(({prompt:e,cwd:n,site:c})=>["exec","--color","never","--cd",n,"--sandbox","workspace-write",...[c,I(n)].filter(Boolean).flatMap(u=>["--add-dir",u]),"-c","sandbox_workspace_write.network_access=true",e],"args"),session:t(e=>e.match(/^session id: (\S+)/)?.[1]??null,"session")}},O=15e3;function T(e,n,c,u){return new Promise(x=>{let o=!1;const l=t(i=>{o||(o=!0,clearInterval(f),clearTimeout(r),e.off("exit",p),x(i))},"finish"),s=t(()=>{const i=a.readFileSync(n,"utf8").slice(c);for(const m of i.split(`
2
- `)){const S=u(m);if(S)return l(S)}},"scan"),f=setInterval(s,100),r=setTimeout(()=>l(null),O),p=t(()=>{s(),l(null)},"exited");e.once("exit",p)})}t(T,"sessionOf");function F({kind:e,model:n,cwd:c,site:u,prompt:x,log:o,bin:l}){const s=A[e];if(!s)throw new Error(`no agent of kind ${e}`);const f=l??s.bin();a.mkdirSync(y.dirname(o),{recursive:!0});const r=a.openSync(o,"a"),p=a.fstatSync(r).size,i=Object.fromEntries(Object.entries(process.env).filter(([m])=>!_(m)));return y.isAbsolute(f)&&(i.PATH=`${y.dirname(f)}:${i.PATH||""}`),new Promise((m,S)=>{const d=v(f,s.args({prompt:x,model:n,cwd:c,site:u}),{cwd:c,env:i,detached:!0,stdio:["ignore",r,r]});d.once("spawn",async()=>{a.closeSync(r);const b=await T(d,o,p,s.session);d.unref(),m({pid:d.pid,sessionId:b,log:o})}),d.once("error",b=>{a.closeSync(r),S(b)})})}t(F,"launchAgent");export{F as launchAgent};
1
+ var v=Object.defineProperty;var t=(e,n)=>v(e,"name",{value:n,configurable:!0});import a from"node:fs";import y from"node:path";import{spawn as h,execFileSync as w}from"node:child_process";import{claudeBin as _,inherited as I}from"../hub/sessions.mjs";function A(e){try{return y.resolve(e,w("git",["-C",e,"rev-parse","--git-common-dir"],{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim())}catch{return null}}t(A,"gitCommonDir");const O={claude:{bin:_,args:t(({prompt:e,model:n="opus",resume:o})=>[...o?["--resume",o]:[],"-p",e,"--permission-mode","auto","--model",n,"--output-format","stream-json","--verbose"],"args"),session:t(e=>{try{const n=JSON.parse(e);return n.type==="system"&&n.subtype==="init"?n.session_id??null:null}catch{return null}},"session")},codex:{bin:t(()=>process.env.KICKFLIP_CODEX||"codex","bin"),args:t(({prompt:e,cwd:n,site:o})=>["exec","--color","never","--cd",n,"--sandbox","workspace-write",...[o,A(n)].filter(Boolean).flatMap(u=>["--add-dir",u]),"-c","sandbox_workspace_write.network_access=true",e],"args"),session:t(e=>e.match(/^session id: (\S+)/)?.[1]??null,"session")}},T=15e3;function E(e,n,o,u){return new Promise(x=>{let r=!1;const l=t(m=>{r||(r=!0,clearInterval(i),clearTimeout(f),e.off("exit",s),x(m))},"finish"),S=t(()=>{const m=a.readFileSync(n,"utf8").slice(o);for(const d of m.split(`
2
+ `)){const c=u(d);if(c)return l(c)}},"scan"),i=setInterval(S,100),f=setTimeout(()=>l(null),T),s=t(()=>{S(),l(null)},"exited");e.once("exit",s)})}t(E,"sessionOf");function N({kind:e,model:n,cwd:o,site:u,prompt:x,log:r,bin:l,resume:S}){const i=O[e];if(!i)throw new Error(`no agent of kind ${e}`);const f=l??i.bin();a.mkdirSync(y.dirname(r),{recursive:!0});const s=a.openSync(r,"a"),m=a.fstatSync(s).size,d=Object.fromEntries(Object.entries(process.env).filter(([c])=>!I(c)));return y.isAbsolute(f)&&(d.PATH=`${y.dirname(f)}:${d.PATH||""}`),new Promise((c,g)=>{const p=h(f,i.args({prompt:x,model:n,cwd:o,site:u,resume:S}),{cwd:o,env:d,detached:!0,stdio:["ignore",s,s]});p.once("spawn",async()=>{a.closeSync(s);const b=await E(p,r,m,i.session);p.unref(),c({pid:p.pid,sessionId:b,log:r})}),p.once("error",b=>{a.closeSync(s),g(b)})})}t(N,"launchAgent");export{N as launchAgent};
@@ -1,5 +1,5 @@
1
- var I=Object.defineProperty;var r=(e,n)=>I(e,"name",{value:n,configurable:!0});import v from"node:fs/promises";import L from"node:os";import g from"node:path";import{boardRepo as W}from"../hub/board-repo.mjs";import{samePath as q}from"../hub/boards.mjs";import{cloneRoot as J}from"../shared/repos.mjs";import{readUsageGate as P,pauseReason as T,holdsWork as U}from"../shared/usage-gate.mjs";import{reviewWaits as _}from"../shared/skills.mjs";import{unlanded as K}from"./domain.mjs";import{BoardError as O,addItem as x,appendThread as Q,boardConfig as G,cardName as N,changeLineup as H,createLineup as M,itemSources as F,parseKey as X,readJSON as A,resolveQuestion as E,setItem as z,startBuilder as V,topicQuestions as B,wavesDir as R}from"./store.mjs";const $=r(e=>g.join(e,"data","arch-reviews.json"),"recordsFile"),C=r(async e=>(await A($(e),{reviews:[]})).reviews||[],"readReviews"),Y={paths:["hub/","board/","shared/","CONTEXT.md","docs/adr/"],data:["~/.claude/kickflip/manifest.json","~/.claude/kickflip/sites/*/data/wave.json","~/.claude/kickflip/sites/*/data/owner.json","~/.claude/kickflip/sites/*/data/events.jsonl"]},Z=r(({github:e,repo:n})=>(e?e.split("/").pop():g.basename(n||""))||"this repo","repoName");function me(e,n,s,{repo:d,waits:h}){const p=new Map(n.map(a=>[a.key,a])),o=r(a=>{const u=p.get(a.key);if(!u)return null;const t=r(i=>i.key!==a.key&&(a.lineup?i.lineup?.slug===a.wave:i.wave===a.wave),"inWave"),c=u.lane!=="done";return{key:a.key,ref:u.ref,wave:a.title,at:a.at,open:c,running:c&&!u.questions,findings:s.filter(i=>i.topic===`item:${a.key}`&&!i.round_of&&!i.confirm_of).length,built:n.filter(i=>t(i)&&i.lane==="done"&&!K(i)).length}},"view");return{repo:d,waits:h,last:[...e].reverse().map(o).find(Boolean)||null}}r(me,"archOf");const ee=r(e=>e.toLocaleDateString("en-CA").replaceAll("-",""),"stampOf"),te=r(e=>e.toLocaleDateString("en-US",{month:"short",day:"numeric"}),"dayOf");async function D(e,n){let s=e;for(let d=2;await n(s);d++)s=`${e}-${d}`;return s}r(D,"freeSlug");const ae=r(e=>{throw new O(e)},"fail"),ie=r(e=>v.stat(e).then(()=>!0,()=>!1),"exists");async function he(e,n,{now:s=new Date,home:d=L.homedir()}={}){const h=_(d);if(h)throw Object.assign(new O(h),{status:409});const p=P({home:d,dir:e});if(U(p))throw Object.assign(new O(T(p)),{status:409});const o=W(n.route),a=Z(o),u=`Architecture ${te(s)}`,t=`arch-${ee(s)}`,c=await F(e),i=(await G(e)).items==="waves";let f,w=null,l;if(i){f=await D(t,S=>ie(g.join(R(),S)));const y=g.join(R(),f,"wave.json");await v.mkdir(g.dirname(y),{recursive:!0}),await v.writeFile(y,JSON.stringify({title:u,...o.github?{repo:o.github}:{},items:[]},null,2)+`
2
- `),l=(await x(e,`${f}/audit`,{title:`Architecture review of ${a}`})).key}else{const[y]=c,S=(await Promise.all(c.map(async b=>(await A(b.file,{}))?.lineups||[]))).flat();w=await D(t,b=>c.some(j=>j.slug===b)||S.some(j=>j.slug===b)),f=y.slug,l=(await x(e,f,{title:`Architecture review of ${a}`})).key}w&&await M(e,w,{title:u,goal:`Act on the architecture review of ${a}`,steps:[l],note:`${await N(e,l)} is the audit of ${a}: its Notes are the report, and it asks one question per finding. Each finding you don't skip becomes a card after it, built from the option you pick.`}),await V(e,l,{label:"Architecture review"}),await Q(e,l,{text:`Started an architecture review of ${a}. Its report becomes this card's Notes, with one question per finding.`});const k=await C(e);k.push({key:l,wave:w||f,lineup:!!w,title:u,repo:o.repo,at:s.toISOString()}),await v.mkdir(g.dirname($(e)),{recursive:!0}),await v.writeFile($(e)+".tmp",JSON.stringify({reviews:k},null,2)+`
3
- `),await v.rename($(e)+".tmp",$(e));const m={key:l,repo:o.repo,github:o.github||null,branch:o.branch||null,wave:w||f,lineup:!!w,...o.repo&&q(o.repo,J())?{scope:Y}:{}};return{key:l,data:m}}r(he,"startReview");async function ve(e,n){const s=(await C(e)).find(t=>t.key===n)||ae(`${n} is not an architecture review's audit card`),{wave:d}=X(n),{asked:h}=await B(e,`item:${n}`),p=[];for(const t of h.filter(c=>c.state==="answered")){const c=t.answers.at(-1),i=t.options.find(m=>m.id===c.choice);if(!i)continue;if(i.id==="skip"||i.label==="Skip"){await E(e,t.id,"Skipped."),p.push(`skipped: ${t.id}`);continue}const f=[i.detail,...(i.practice||[]).map(m=>`- ${m}`),c.note.trim()&&`Owner's note: ${c.note.trim()}`,`From the finding: ${t.question}`].filter(Boolean).join(`
1
+ var D=Object.defineProperty;var r=(e,a)=>D(e,"name",{value:a,configurable:!0});import g from"node:fs/promises";import E from"node:os";import k from"node:path";import{boardRepo as I}from"../hub/board-repo.mjs";import{samePath as J}from"../hub/boards.mjs";import{cloneRoot as P}from"../shared/repos.mjs";import{readUsageGate as U,pauseReason as _,holdsWork as q}from"../shared/usage-gate.mjs";import{reviewWaits as K}from"../shared/skills.mjs";import{unlanded as L}from"./domain.mjs";import{BoardError as j,addItem as O,appendStatus as Q,boardConfig as T,cardName as x,changeWave as G,createWave as H,itemSources as N,parseKey as M,readJSON as A,resolveQuestion as W,setItem as X,startBuilder as z,topicQuestions as F,wavesDir as B}from"./store.mjs";const $=r(e=>k.join(e,"data","arch-reviews.json"),"recordsFile"),R=r(async e=>(await A($(e),{reviews:[]})).reviews||[],"readReviews"),V={paths:["hub/","board/","shared/","CONTEXT.md","docs/adr/"],data:["~/.claude/kickflip/manifest.json","~/.claude/kickflip/sites/*/data/items.json","~/.claude/kickflip/sites/*/data/owner.json","~/.claude/kickflip/sites/*/data/events.jsonl"]},Y=r(({github:e,repo:a})=>(e?e.split("/").pop():k.basename(a||""))||"this repo","repoName");function pe(e,a,s,{repo:f,waits:m}){const u=new Map(a.map(i=>[i.key,i])),n=r(i=>{const w=u.get(i.key);if(!w)return null;const t=r(o=>o.key!==i.key&&(i.wave?o.wave?.slug===i.wave:o.file===i.file),"inWave"),c=w.lane!=="done";return{key:i.key,ref:w.ref,wave:i.title,at:i.at,open:c,running:c&&!w.questions,findings:s.filter(o=>o.topic===`item:${i.key}`&&!o.round_of&&!o.confirm_of).length,built:a.filter(o=>t(o)&&o.lane==="done"&&!L(o)).length}},"view");return{repo:f,waits:m,last:[...e].reverse().map(n).find(Boolean)||null}}r(pe,"archOf");const Z=r(e=>e.toLocaleDateString("en-CA").replaceAll("-",""),"stampOf"),ee=r(e=>e.toLocaleDateString("en-US",{month:"short",day:"numeric"}),"dayOf");async function C(e,a){let s=e;for(let f=2;await a(s);f++)s=`${e}-${f}`;return s}r(C,"freeSlug");const te=r(e=>{throw new j(e)},"fail"),ae=r(e=>g.stat(e).then(()=>!0,()=>!1),"exists");async function me(e,a,{now:s=new Date,home:f=E.homedir()}={}){const m=K(f);if(m)throw Object.assign(new j(m),{status:409});const u=U({home:f,dir:e});if(q(u))throw Object.assign(new j(_(u)),{status:409});const n=I(a.route),i=Y(n),w=`Architecture ${ee(s)}`,t=`arch-${Z(s)}`,c=await N(e),o=(await T(e)).items==="waves";let p,l;if(o){const d=await C(t,v=>ae(k.join(B(),v))),y=k.join(B(),d,"wave.json");await g.mkdir(k.dirname(y),{recursive:!0}),await g.writeFile(y,JSON.stringify({title:w,...n.github?{repo:n.github}:{},items:[]},null,2)+`
2
+ `),l=(await O(e,`${d}/audit`,{title:`Architecture review of ${i}`})).key,p={file:d}}else{const[d]=c,y=(await Promise.all(c.map(async v=>(await A(v.file,{}))?.waves||[]))).flat();p={wave:await C(t,v=>c.some(S=>S.slug===v)||y.some(S=>S.slug===v))},l=(await O(e,d.slug,{title:`Architecture review of ${i}`})).key}p.wave&&await H(e,p.wave,{title:w,goal:`Act on the architecture review of ${i}`,cards:[l],note:`${await x(e,l)} is the audit of ${i}: its Notes are the report, and it asks one question per finding. Each finding you don't skip becomes a card after it, built from the option you pick.`}),await z(e,l,{label:"Architecture review"}),await Q(e,l,"arch-review",{card:i});const h=await R(e);h.push({key:l,...p,title:w,repo:n.repo,at:s.toISOString()}),await g.mkdir(k.dirname($(e)),{recursive:!0}),await g.writeFile($(e)+".tmp",JSON.stringify({reviews:h},null,2)+`
3
+ `),await g.rename($(e)+".tmp",$(e));const b={key:l,repo:n.repo,github:n.github||null,branch:n.branch||null,...p,...n.repo&&J(n.repo,P())?{scope:V}:{}};return{key:l,data:b}}r(me,"startReview");async function he(e,a){const s=(await R(e)).find(t=>t.key===a)||te(`${a} is not an architecture review's audit card`),{file:f}=M(a),{asked:m}=await F(e,`item:${a}`),u=[];for(const t of m.filter(c=>c.state==="answered")){const c=t.answers.at(-1),o=t.options.find(d=>d.id===c.choice);if(!o)continue;if(o.id==="skip"||o.label==="Skip"){await W(e,t.id,"Skipped."),u.push(`skipped: ${t.id}`);continue}const p=[o.detail,...(o.practice||[]).map(d=>`- ${d}`),c.note.trim()&&`Owner's note: ${c.note.trim()}`,`From the finding: ${t.question}`].filter(Boolean).join(`
4
4
 
5
- `),w=(await A((await F(e)).find(m=>m.slug===d).file,{items:[]})).items.at(-1)?.id,l=await x(e,d,{title:i.label,notes:f,...s.lineup?{}:{deps:w}});s.lineup&&await H(e,s.wave,{add:l.key});const k=await N(e,l.key);await E(e,t.id,`Filed as ${k}.`),p.push(`filed: ${k} from ${t.id}`,...l.lines||[])}const o=(await B(e,`item:${n}`)).asked.filter(t=>t.state!=="resolved");if(o.length)return{lines:[...p,`waiting: ${o.map(t=>t.id).join(", ")}`]};const a=(await B(e,`item:${n}`)).asked.filter(t=>/^Filed as /.test(t.resolved||"")).map(t=>t.resolved.slice(9,-1)),u=await z(e,n,{done:!0,text:a.length?`Every finding is settled: filed ${a.join(", ")}.`:"Every finding is settled: none filed."});return{lines:[...p,...u.lines,`closed: ${await N(e,n)}`]}}r(ve,"fileFindings");export{Y as HUB_SCOPE,me as archOf,ve as fileFindings,C as readReviews,Z as repoName,he as startReview};
5
+ `),l=(await A((await N(e)).find(d=>d.slug===f).file,{items:[]})).items.at(-1)?.id,h=await O(e,f,{title:o.label,notes:p,...s.wave?{}:{deps:l}});s.wave&&await G(e,s.wave,{add:h.key,after:a});const b=await x(e,h.key);await W(e,t.id,`Filed as ${b}.`),u.push(`filed: ${b} from ${t.id}`,...h.lines||[])}const n=(await F(e,`item:${a}`)).asked.filter(t=>t.state!=="resolved");if(n.length)return{lines:[...u,`waiting: ${n.map(t=>t.id).join(", ")}`]};const i=(await F(e,`item:${a}`)).asked.filter(t=>/^Filed as /.test(t.resolved||"")).map(t=>t.resolved.slice(9,-1)),w=await X(e,a,{done:!0,closing:["findings-settled",{filed:i.join(", ")}]});return{lines:[...u,...w.lines,`closed: ${await x(e,a)}`]}}r(he,"fileFindings");export{V as HUB_SCOPE,pe as archOf,he as fileFindings,R as readReviews,Y as repoName,me as startReview};