@lovek4d/kickflip 0.1.0 → 0.4.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.
- package/CONTEXT.md +239 -104
- package/README.md +54 -4
- package/SKILL.md +63 -42
- package/bin/doctor.mjs +4 -3
- package/bin/install.mjs +3 -3
- package/bin/kickflip.mjs +103 -40
- package/bin/statusline.mjs +4 -3
- package/bin/wait-events.mjs +2 -2
- package/board/arch-review.mjs +5 -0
- package/board/auto-review.mjs +2 -0
- package/board/bin/agent.mjs +1 -1
- package/board/bin/board.mjs +371 -82
- package/board/bin/builder-stop-hook.mjs +4 -3
- package/board/domain.mjs +8 -3
- package/board/finish.mjs +3 -0
- package/board/headless.mjs +3 -0
- package/board/on-host.mjs +1 -0
- package/board/permissions.mjs +2 -0
- package/board/public/app-arch.js +75 -0
- package/board/public/app-asks.js +66 -28
- package/board/public/app-banner.js +2 -2
- package/board/public/app-cards.js +354 -77
- package/board/public/app-core.js +40 -8
- package/board/public/app-header.js +33 -8
- package/board/public/app-lanes.js +238 -34
- package/board/public/app-needs.js +2 -0
- package/board/public/app-snapshot.js +45 -11
- package/board/public/app-threads.js +20 -21
- package/board/public/app.html +6 -2
- package/board/public/app.js +114 -33
- package/board/public/attach.js +39 -3
- package/board/public/dialogs.js +161 -118
- package/board/public/hub-kit.js +27 -7
- package/board/public/hub.css +26 -33
- package/board/public/linear-key.js +143 -0
- package/board/public/log.html +48 -0
- package/board/public/prompts.js +9 -8
- package/board/public/prototype-kit.js +6 -6
- package/board/public/prototype-switcher.js +13 -5
- package/board/public/prototype.html +1 -0
- package/board/public/router.js +14 -10
- package/board/public/rules-editor.js +4 -5
- package/board/public/shell.js +21 -7
- package/board/public/sidebar.css +51 -11
- package/board/public/sidebar.js +124 -19
- package/board/public/style.css +244 -56
- package/board/public/toasts.js +5 -3
- package/board/public/ui-cards.js +236 -66
- package/board/public/ui-enums.js +5 -4
- package/board/public/ui-header.js +23 -14
- package/board/public/ui-hub.js +34 -16
- package/board/public/ui-links.js +18 -10
- package/board/public/ui-markdown.js +1 -1
- package/board/public/ui-order.js +21 -0
- package/board/public/ui-page-header.js +13 -8
- package/board/public/ui-patch.js +53 -0
- package/board/public/ui-phase-bars.js +17 -0
- package/board/public/ui-prompts.js +8 -8
- package/board/public/ui-questions.js +35 -11
- package/board/public/ui-sidebar.js +83 -21
- package/board/public/ui-snapshot.js +30 -13
- package/board/public/ui-threads.js +38 -23
- package/board/public/ui.js +96 -16
- package/board/public/view-frame.js +1 -1
- package/board/public/view-hub-settings.js +67 -44
- package/board/public/view-hub.js +105 -13
- package/board/public/view-notes.js +8 -5
- package/board/public/view-questions.js +123 -52
- package/board/public/view-settings.js +235 -62
- package/board/review.mjs +1 -0
- package/board/server.mjs +18 -7
- package/board/store.mjs +15 -6
- package/board/tickets.mjs +3 -3
- package/board/worktree.mjs +20 -13
- package/docs/hub-session.md +4 -4
- package/hooks-plugin/hooks/codex-share-auto.mjs +7 -5
- package/hooks-plugin/hooks/watcher-stop.mjs +41 -0
- package/hub/awake.mjs +1 -0
- package/hub/board-repo.mjs +1 -1
- package/hub/boards.mjs +4 -4
- package/hub/close-board.mjs +1 -1
- package/hub/duration.mjs +1 -0
- package/hub/github.mjs +3 -4
- package/hub/home.mjs +1 -1
- package/hub/hub.mjs +12 -10
- package/hub/incoming.mjs +2 -0
- package/hub/linear.mjs +2 -2
- package/hub/live.mjs +2 -2
- package/hub/new-board.mjs +2 -2
- package/hub/permission-denied-hook.mjs +2 -0
- package/hub/pr-report.mjs +3 -0
- package/hub/presence.mjs +1 -1
- package/hub/prompts.mjs +1 -1
- package/hub/push.mjs +2 -2
- package/hub/readiness.mjs +2 -0
- package/hub/repos.mjs +3 -2
- package/hub/restart-board.mjs +1 -0
- package/hub/routes.mjs +1 -1
- package/hub/schedule.mjs +1 -1
- package/hub/sessions.mjs +2 -2
- package/hub/shell.mjs +2 -1
- package/hub/slack.mjs +2 -0
- package/hub/stream.mjs +12 -12
- package/hub/sw.js +2 -2
- package/hub/trust.mjs +2 -0
- package/hub/try.mjs +8 -3
- package/hub/upgrade.mjs +1 -0
- package/hub/usage.mjs +3 -3
- package/hub/views.mjs +1 -1
- package/package.json +5 -1
- package/shared/card-prs.mjs +1 -0
- package/shared/colors.css +9 -6
- package/shared/fake-gh.mjs +105 -0
- package/shared/repos.mjs +1 -1
- package/shared/service.mjs +16 -16
- package/shared/skills.mjs +2 -0
- package/shared/usage-gate.mjs +1 -1
- package/starter/public/claude.js +3 -2
- package/bin/web-ts.mjs +0 -2
- package/board/public/view-polling.js +0 -87
package/bin/wait-events.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var
|
|
3
|
-
`).filter(Boolean)}catch{}
|
|
2
|
+
var P=Object.defineProperty;var t=(e,i)=>P(e,"name",{value:i,configurable:!0});import c from"node:fs";import f from"node:path";import L from"node:os";import{routeRecord as m}from"../hub/routes.mjs";import{readHubRules as q,rulesOf as D,ruleLines as G}from"../hub/rules.mjs";import{boardAt as H,linksOf as U,hubLink as W,stopPreviousWatcher as _}from"../hub/boards.mjs";import{parentOf as J,sessionGone as K}from"../hub/live.mjs";import{ackHello as Q,cardName as B,markWorking as C,settingsLines as M,settingsOf as z,WORKING_KINDS as V}from"../board/store.mjs";import{readUsageGate as E,heldUsageEvent as X}from"../shared/usage-gate.mjs";const[O,g]=process.argv.slice(2);(!O||!g)&&(console.error("usage: wait-events.mjs <route> <session-id>"),process.exit(2));const Y=f.join(L.homedir(),".claude","kickflip","manifest.json");function A(){try{return m(JSON.parse(c.readFileSync(Y,"utf8")).sites,O)||null}catch{return}}t(A,"site");function v(e){const i=e.filter?new RegExp(e.filter):null,n=Object.create(null);for(const l of e.watch||["data/events.jsonl"]){let o=[];try{o=c.readFileSync(f.resolve(e.dir,l),"utf8").split(`
|
|
3
|
+
`).filter(Boolean)}catch{}n[l]=i?o.filter(r=>i.test(r)):o}return n}t(v,"matching");const Z=`quiet The owner doesn't read this session's terminal. Answer a prompt typed in it in full. A wake, a task notification or a builder's result is not one: write no text of your own, since board.mjs and this watcher print one line for each thing done. Talk and questions go on the board, never AskUserQuestion; only a failure the board can't show, such as the hub being down, gets one line here. Each message to the owner stands alone in its card's thread: name cards by label (#96), and a pointer such as "above" or "here" must make sense in that thread, never point into this session.`,I={ask:t(e=>`new task ${e.id} received`,"ask"),"owner-answer":t(e=>`answer received to question ${e.data?.id}`,"owner-answer"),hello:t(e=>`hello ${e.id} from the hub answered`,"hello"),"overnight-triage":t(e=>`Overnight triage ${e.data?.date} asked: ${e.data?.cards?.length} cards wait on your answers`,"overnight-triage"),"pr-review":t(e=>`review asked on PR #${e.data?.pr}: the board was idle`,"pr-review"),"review-answer":t(e=>`${e.data?.answer==="skip"?"skip":"post"} answered on the review of PR #${e.data?.pr}`,"review-answer")},R={"item-message":t(e=>`message received from ${e}'s thread`,"item-message"),"card-build":t(e=>`build started on ${e}`,"card-build"),"card-merge":t(e=>`merge asked on ${e}`,"card-merge"),"card-done":t(e=>`${e} marked done`,"card-done"),"card-bump":t(e=>`${e} bumped`,"card-bump"),"ask-bump":t(e=>`${e} bumped`,"ask-bump"),"ask-edited":t(e=>`${e} edited`,"ask-edited"),"ask-done":t(e=>`${e} marked done`,"ask-done"),"ask-withdrawn":t(e=>`${e} deleted`,"ask-withdrawn"),"card-hold":t((e,i)=>`${e} ${i.data?.hold?"held":"resumed"}`,"card-hold"),"card-unblocked":t(e=>`${e} unblocked`,"card-unblocked"),"card-pull":t(e=>`${e} pulled in`,"card-pull"),"card-plan":t(e=>`plan asked on ${e}`,"card-plan"),"prototype-request":t(e=>`prototype asked on ${e}`,"prototype-request"),"builder-ask":t((e,i)=>`${i.data?.ask} from ${e}'s builder: ${i.data?.text}`,"builder-ask")};async function ee(e,i){let n;try{n=JSON.parse(i)}catch{return null}if(typeof n?.kind!="string")return null;const l=typeof n.data?.key=="string"?n.data.key:n.kind.startsWith("ask-")&&typeof n.data?.id=="string"?`ask:${n.data.id}`:null,o=l&&await B(e,l).catch(()=>l);return m(I,n.kind)?I[n.kind](n):o&&m(R,n.kind)?R[n.kind](o,n):o?`${n.kind} on ${o}`:`${n.kind} received`}t(ee,"plainLine");const k=A();k||(console.log("site-removed"),process.exit(0));const w=f.resolve(k.dir,"data",`.watch-cursor-${g}.json`),S=f.resolve(k.dir,"data","watcher.json"),j=[process.ppid,J(process.ppid)],T=t(()=>{j[1]??=J(j[0]),K(g,j)&&process.exit(0)},"orphaned");_(k.dir,O,g);try{c.mkdirSync(f.dirname(w),{recursive:!0}),c.writeFileSync(S,JSON.stringify({pid:process.pid,since:new Date().toISOString()}))}catch{}process.on("exit",()=>{try{const e=JSON.parse(c.readFileSync(S,"utf8"));e.pid===process.pid&&c.writeFileSync(S,JSON.stringify({...e,exited_at:new Date().toISOString()}))}catch{}});let p;try{p=JSON.parse(c.readFileSync(w,"utf8"))}catch{}for(p||(p=Object.fromEntries(Object.entries(v(k)).map(([e,i])=>[e,i.length])),c.mkdirSync(f.dirname(w),{recursive:!0}),c.writeFileSync(w,JSON.stringify(p)));;){await new Promise(o=>setTimeout(o,2e3));const e=A();if(e===void 0)continue;e===null&&(console.log("site-removed"),process.exit(0)),e.owner?.session&&e.owner.session!==g&&(console.log("handed-over "+JSON.stringify(e.owner)),process.exit(0)),T();const i=t(o=>Object.fromEntries(Object.entries(o).map(([r,$])=>[r,[...m(p.$usageHeld,r)||[],...$.slice(m(p,r)||0)]])),"pending"),n=t((o,r)=>r.state!=="paused"||!X(o),"eligible"),l=E();if(Object.values(i(v(e))).some(o=>o.some(r=>n(r,l)))){await new Promise(s=>setTimeout(s,2e3)),T();const o=v(e),r=E(),$=i(o);if(!Object.values($).some(s=>s.some(a=>n(a,r))))continue;const b=Object.create(null),N=[],x=[];for(const[s,a]of Object.entries($)){b[s]=a.filter(h=>!n(h,r));for(const h of a.filter(y=>n(y,r))){console.log(`event ${s} ${h}`);const y=await ee(e.dir,h);y&&console.log(y);try{const u=JSON.parse(h);V.includes(u.kind)&&typeof u.id=="string"&&N.push(u.id),u.kind==="hello"&&typeof u.id=="string"&&x.push(u.id)}catch{}}}let d=null;try{d=JSON.parse(c.readFileSync(f.resolve(e.dir,"board.json"),"utf8"))}catch(s){s.code!=="ENOENT"&&(d={})}if(d&&await C(e.dir,N).catch(s=>console.log(`working marks not written (${s.message})`)),d)for(const s of x)await Q(e.dir,s).catch(a=>console.log(`hello ${s} not answered (${a.message})`));if(d)for(const s of G(q(),D(d)))console.log(`rule ${s}`);if(d)for(const s of M(z(d)))console.log(s);d&&console.log(Z),r.state==="paused"&&console.log(`usage paused ${r.resets_at} ${r.window} ${r.used}%`);try{const s=d&&H(e.dir);if(s)for(const a of[...U(s,{listen:"listening"}),W()])a.ok||console.log(`link ${a.link} ${a.detail}`)}catch(s){console.log(`link registry could not check the links (${s.message})`)}const F=Object.fromEntries(Object.entries(o).map(([s,a])=>[s,a.length]));Object.values(b).some(s=>s.length)&&(F.$usageHeld=b),c.writeFileSync(w,JSON.stringify(F)),process.exit(0)}}
|
|
@@ -0,0 +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 q}from"../hub/board-repo.mjs";import{samePath as J}from"../hub/boards.mjs";import{cloneRoot as P}from"../shared/repos.mjs";import{readUsageGate as T,pauseReason as U}from"../shared/usage-gate.mjs";import{reviewWaits as W}from"../shared/skills.mjs";import{BoardError as O,addItem as x,appendThread as _,boardConfig as K,cardName as N,changeLineup as Q,createLineup as G,itemSources as F,parseKey as H,readJSON as A,resolveQuestion as E,setItem as M,startBuilder as X,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"),z={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"]},V=r(({github:e,repo:n})=>(e?e.split("/").pop():g.basename(n||""))||"this repo","repoName");function pe(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"&&i.status!=="dropped").length}},"view");return{repo:d,waits:h,last:[...e].reverse().map(o).find(Boolean)||null}}r(pe,"archOf");const Y=r(e=>e.toLocaleDateString("en-CA").replaceAll("-",""),"stampOf"),Z=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 ee=r(e=>{throw new O(e)},"fail"),te=r(e=>v.stat(e).then(()=>!0,()=>!1),"exists");async function fe(e,n,{now:s=new Date,home:d=L.homedir()}={}){const h=W(d);if(h)throw Object.assign(new O(h),{status:409});const p=T({home:d});if(p.state==="paused")throw Object.assign(new O(U(p)),{status:409});const o=q(n.route),a=V(o),u=`Architecture ${Z(s)}`,t=`arch-${Y(s)}`,c=await F(e),i=(await K(e)).items==="waves";let f,w=null,l;if(i){f=await D(t,S=>te(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 G(e,w,{title:u,goal:`Act on the architecture review of ${a}`,phases:[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 X(e,l,{label:"Architecture review"}),await _(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&&J(o.repo,P())?{scope:z}:{}};return{key:l,data:m}}r(fe,"startReview");async function we(e,n){const s=(await C(e)).find(t=>t.key===n)||ee(`${n} is not an architecture review's audit card`),{wave:d}=H(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(`
|
|
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 Q(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 M(e,n,{status:"done",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(we,"fileFindings");export{z as HUB_SCOPE,pe as archOf,we as fileFindings,C as readReviews,V as repoName,fe as startReview};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var k=Object.defineProperty;var s=(n,r)=>k(n,"name",{value:r,configurable:!0});const B=864e5,g={needs:0,reviewing:1,posted:2},f=s((n,r,u)=>n.filter(a=>a.kind===r&&a.data?.pr===u).sort((a,m)=>String(a.ts).localeCompare(String(m.ts))).at(-1),"newest");function D({on:n,incoming:r,records:u={},events:a=[],refs:m=new Map,now:S=Date.now()}){const h=new Map((r?.prs||[]).map(e=>[e.number,e])),d=s((e,t,i,l)=>({number:e.number,title:e.title,author:e.author,url:e.url,asked:!!e.asked,touches:(e.touches||[]).map(p=>({key:p,ref:m.get(p)||p})),state:i,at:l,text:t?.text||null,reviewUrl:t?.review_url||null}),"card"),o=[],w=[];for(const e of h.values()){const t=u[e.number],i=f(a,"pr-review",e.number),l=t?.state==="needs"&&f(a,"review-answer",e.number);l&&String(l.ts)>String(t.at)?o.push(d(e,t,"reviewing",l.ts)):i&&i.data.head===e.head&&!(t?.head===e.head&&String(t.at)>String(i.ts))?o.push(d(e,t,"reviewing",i.ts)):(t&&o.push(d(e,t,t.state,t.at)),(!t||t.state==="posted"&&t.head!==e.head)&&w.push(e))}const v=s(e=>e.state==="posted"&&S-Date.parse(e.at)>864e5,"old"),$=[...o.filter(v).map(e=>({...e,open:!0})),...Object.values(u).filter(e=>!h.has(e.number)).map(e=>({...d(e,e,e.state,e.at),open:!1}))].sort((e,t)=>String(t.at).localeCompare(String(e.at))),b=o.filter(e=>!v(e)).sort((e,t)=>g[e.state]-g[t.state]||String(t.at).localeCompare(String(e.at)));w.sort((e,t)=>+!!t.asked-+!!e.asked||e.number-t.number);const x=o.some(e=>e.state==="reviewing");return{on:!!n,shown:!!n||b.length>0,at:r?.at||null,repo:r?.repo||null,cards:b,earlier:$,waiting:w.length,due:n&&!x&&w[0]||null}}s(D,"reviewsOf");const c=s(n=>n.cards.filter(r=>r.state==="needs"),"held"),A=s(n=>c(n).map(r=>({kind:"Review",title:`PR #${r.number} ${r.title}`,text:`PR #${r.number} ${r.title} \xB7 review held for you`,url:`#review=${r.number}`,review:r.number})),"reviewNeeds"),C=s(n=>c(n).map(r=>({tag:`review-${r.number}`,kind:"review",title:`PR #${r.number}: review held for you`,body:String(r.text||r.title).split(`
|
|
2
|
+
`)[0],url:`#review=${r.number}`,now:!0,questions:[]})),"reviewActions");export{C as reviewActions,A as reviewNeeds,D as reviewsOf};
|
package/board/bin/agent.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{main as
|
|
2
|
+
import{main as e}from"./board.mjs";process.exitCode=await e(["builder",...process.argv.slice(2)]);
|