chief-clancy 0.8.0 → 0.8.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.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Autonomous, board-driven development for Claude Code.**
4
4
 
5
- [![npm](https://img.shields.io/npm/v/chief-clancy?style=for-the-badge&color=cb3837)](https://www.npmjs.com/package/chief-clancy) [![License: MIT](https://img.shields.io/badge/License-MIT-blue?style=for-the-badge)](./LICENSE) [![Tests](https://img.shields.io/badge/tests-1206%20passing-brightgreen?style=for-the-badge)](docs/TESTING.md) [![GitHub Stars](https://img.shields.io/github/stars/Pushedskydiver/clancy?style=for-the-badge)](https://github.com/Pushedskydiver/clancy/stargazers)
5
+ [![npm](https://img.shields.io/npm/v/chief-clancy?style=for-the-badge&color=cb3837)](https://www.npmjs.com/package/chief-clancy) [![License: MIT](https://img.shields.io/badge/License-MIT-blue?style=for-the-badge)](./LICENSE) [![Tests](https://img.shields.io/badge/tests-1217%20passing-brightgreen?style=for-the-badge)](docs/TESTING.md) [![GitHub Stars](https://img.shields.io/github/stars/Pushedskydiver/clancy?style=for-the-badge)](https://github.com/Pushedskydiver/clancy/stargazers)
6
6
 
7
7
  > [!WARNING]
8
8
  > Clancy is in early development. Expect bugs, breaking changes, and rough edges. If you hit an issue, please [open a bug report](https://github.com/Pushedskydiver/clancy/issues/new).
@@ -13,7 +13,7 @@ npx chief-clancy
13
13
 
14
14
  Works on Mac, Linux, and Windows.
15
15
 
16
- [What it does](#what-it-does) · [Install](#install) · [Commands](#commands) · [Supported boards](#supported-boards) · [Comparison](./COMPARISON.md) · [Roadmap](./ROADMAP.md) · [Contributing](./CONTRIBUTING.md)
16
+ [What it does](#what-it-does) · [Install](#install) · [Commands](#commands) · [Supported boards](#supported-boards) · [Comparison](./docs/COMPARISON.md) · [Roadmap](./ROADMAP.md) · [Contributing](./CONTRIBUTING.md)
17
17
 
18
18
  ---
19
19
 
@@ -53,7 +53,7 @@ Clancy is for developers who:
53
53
  - Your tickets are large, vague, or span multiple sessions — Clancy works best with small, well-scoped tickets
54
54
  - You don't use a Kanban board — you can still use `/clancy:map-codebase` for codebase scanning, but the run loop won't apply
55
55
 
56
- Evaluating other tools? See [COMPARISON.md](./COMPARISON.md) for a side-by-side with GSD and PAUL.
56
+ Evaluating other tools? See [COMPARISON.md](./docs/COMPARISON.md) for a side-by-side with GSD and PAUL.
57
57
 
58
58
  ---
59
59
 
@@ -1,6 +1,6 @@
1
- import{spawnSync as nt}from"node:child_process";import{existsSync as st}from"node:fs";import{dirname as rt,join as it,resolve as ct}from"node:path";import{setTimeout as F}from"node:timers/promises";import{fileURLToPath as _}from"node:url";function $(t){let n=Math.floor(t/1e3);if(n<60)return`${n}s`;let o=Math.floor(n/60),r=n%60;if(o<60)return r>0?`${o}m ${r}s`:`${o}m`;let l=Math.floor(o/60),c=o%60;return c>0?`${l}h ${c}m`:`${l}h`}function j(t){return t.includes("hooks.slack.com")}function B(t){return JSON.stringify({text:t})}function H(t){return JSON.stringify({type:"message",attachments:[{contentType:"application/vnd.microsoft.card.adaptive",content:{$schema:"http://adaptivecards.io/schemas/adaptive-card.json",type:"AdaptiveCard",version:"1.4",body:[{type:"TextBlock",text:t,wrap:!0}]}}]})}async function N(t,n){let o=j(t)?B(n):H(n);try{let r=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:o});r.ok||console.warn(`\u26A0 Notification failed: HTTP ${r.status}`)}catch{console.warn("\u26A0 Notification failed: could not reach webhook")}}var p=t=>`\x1B[2m${t}\x1B[0m`,w=t=>`\x1B[1m${t}\x1B[0m`;var P=t=>`\x1B[32m${t}\x1B[0m`,x=t=>`\x1B[31m${t}\x1B[0m`,M=t=>`\x1B[33m${t}\x1B[0m`;import{mkdirSync as Z,readFileSync as z,writeFileSync as G}from"node:fs";import{join as v}from"node:path";import{mkdirSync as yt,readFileSync as K,renameSync as ht,writeFileSync as gt}from"node:fs";import{join as V}from"node:path";function Y(t){return V(t,".clancy","quality.json")}function q(t){try{let n=K(Y(t),"utf8"),o=JSON.parse(n);if(o&&typeof o.tickets=="object"&&o.tickets!==null&&!Array.isArray(o.tickets))return W(o),o}catch{}return{tickets:{},summary:{totalTickets:0,avgReworkCycles:0,avgVerificationRetries:0,avgDuration:0}}}function W(t){let n=Object.values(t.tickets),o=n.length;if(o===0){t.summary={totalTickets:0,avgReworkCycles:0,avgVerificationRetries:0,avgDuration:0};return}let r=0,l=0,c=0,s=0;for(let a of n)r+=a.reworkCycles,l+=a.verificationRetries,a.duration!=null&&(c+=a.duration,s++);t.summary={totalTickets:o,avgReworkCycles:Math.round(r/o*100)/100,avgVerificationRetries:Math.round(l/o*100)/100,avgDuration:s>0?Math.round(c/s*100)/100:0}}function R(t){let n=q(t);if(Object.keys(n.tickets).length!==0)return n}import{appendFileSync as Tt,mkdirSync as wt,readFileSync as J}from"node:fs";import{dirname as vt,join as X}from"node:path";function b(t){let n=X(t,".clancy","progress.txt"),o;try{o=J(n,"utf8")}catch{return[]}let r=[];for(let l of o.split(`
2
- `)){let c=l.trim();if(!c)continue;let s=c.split(" | ");if(s.length<4)continue;let a=s[0];if(s[1]==="BRIEF"||s[1]==="APPROVE_BRIEF"){r.push({timestamp:a,key:s[2],summary:s.slice(3).join(" | "),status:s[1]});continue}let m=s[1],u,y,g,k=[];for(let f=2;f<s.length;f++){let d=s[f],S=d.match(/^pr:(\d+)$/),i=d.match(/^parent:(.+)$/);S?y=parseInt(S[1],10):i?g=i[1]:f>=3&&!u&&d===d.toUpperCase()&&d.length>1?u=d:k.push(d)}u&&(u==="APPROVE"&&(u="APPROVE_PLAN"),r.push({timestamp:a,key:m,summary:k.join(" | "),status:u,...y!=null&&{prNumber:y},...g!=null&&{parent:g}}))}return r}function tt(t,n){let o=v(t,".clancy","costs.log"),r;try{r=z(o,"utf8")}catch{return[]}let l=[];for(let c of r.split(`
3
- `)){let s=c.trim();if(!s)continue;let a=s.split(" | ");if(a.length<4)continue;let m=a[0],u=new Date(m).getTime();Number.isNaN(u)||u<n||l.push({timestamp:m,key:a[1],duration:a[2],tokens:a[3]})}return l}function et(t){return/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/.test(t)?new Date(t.replace(" ","T")+":00Z").getTime():NaN}var A=new Set(["DONE","PR_CREATED","PUSHED","EPIC_PR_CREATED","RESUMED"]),ot=new Set(["SKIPPED","PUSH_FAILED","TIME_LIMIT"]);function I(t,n,o){let r=Math.floor(n/6e4)*6e4,c=b(t).filter(e=>{let h=et(e.timestamp);return!Number.isNaN(h)&&h>=r}),s=tt(t,n),a=new Map;for(let e of s)a.set(e.key,e);let m=new Map;for(let e of c)m.set(e.key,e);let u=[];for(let e of m.values()){let h=a.get(e.key);u.push({key:e.key,summary:e.summary,status:e.status,...e.prNumber!=null&&{prNumber:e.prNumber},...h&&{duration:h.duration,tokens:h.tokens}})}let y=u.filter(e=>A.has(e.status)),g=u.filter(e=>ot.has(e.status)),k=$(o-n),f=0;for(let e of s){let h=e.tokens.match(/~(\d[\d,]*)/);h&&(f+=parseInt(h[1].replace(/,/g,""),10))}let d=new Date(n),S=`${d.getUTCFullYear()}-${String(d.getUTCMonth()+1).padStart(2,"0")}-${String(d.getUTCDate()).padStart(2,"0")}`,i=[];i.push(`# AFK Session Report \u2014 ${S}`),i.push(""),i.push("## Summary"),i.push(`- Tickets completed: ${y.length}`),i.push(`- Tickets failed: ${g.length}`),i.push(`- Total duration: ${k}`),f>0&&i.push(`- Estimated token usage: ${f.toLocaleString("en-US")}`),i.push(""),i.push("## Tickets"),u.length===0&&(i.push(""),i.push("No tickets were processed in this session."));for(let e of u){let Q=A.has(e.status)?"\u2713":"\u2717";i.push(""),i.push(`### ${Q} ${e.key} \u2014 ${e.summary}`),e.duration&&i.push(`- Duration: ${e.duration}`),e.tokens&&i.push(`- Tokens: ${e.tokens}`),e.prNumber!=null&&i.push(`- PR: #${e.prNumber}`),i.push(`- Status: ${e.status}`)}let T=u.filter(e=>e.prNumber!=null).map(e=>`#${e.prNumber}`),C=g.map(e=>e.key);if(T.length>0||C.length>0){i.push(""),i.push("## Next Steps"),T.length>0&&i.push(`- Review PRs ${T.join(", ")}`);for(let e of C)i.push(`- ${e} needs manual intervention`)}try{let e=R(t);e&&(i.push(""),i.push("## Quality Metrics"),i.push(`- Avg rework cycles: ${e.summary.avgReworkCycles}`),i.push(`- Avg verification retries: ${e.summary.avgVerificationRetries}`),e.summary.avgDuration>0&&i.push(`- Avg delivery time: ${$(e.summary.avgDuration)}`))}catch{}i.push("");let D=i.join(`
4
- `);try{let e=v(t,".clancy","session-report.md");Z(v(t,".clancy"),{recursive:!0}),G(e,D,"utf8")}catch{}return D}function L(t){let n=t.trim().match(/^(\d{1,2}):(\d{2})$/);if(!n)return null;let o=parseInt(n[1],10),r=parseInt(n[2],10);return o<0||o>23||r<0||r>59?null:{hours:o,minutes:r}}var O=!1;function at(t,n,o=new Date){let r=L(t),l=L(n);if(!r||!l)return O||(console.warn("\u26A0 Invalid quiet hours format. Expected HH:MM (24h). Quiet hours disabled."),O=!0),0;let c=o.getHours()*60+o.getMinutes(),s=r.hours*60+r.minutes,a=l.hours*60+l.minutes,m=!1;if(s<a?m=c>=s&&c<a:s>a&&(m=c>=s||c<a),!m)return 0;let u=a-c;u<=0&&(u+=1440);let y=o.getSeconds()*1e3+o.getMilliseconds();return Math.max(0,u*6e4-y)}var E={noTickets:/No tickets found|No issues found|All done/,skipped:/Ticket skipped/,preflightFail:/^✗ /m};function ut(t){return E.noTickets.test(t)?{stop:!0,reason:"No more tickets \u2014 all done"}:E.skipped.test(t)?{stop:!0,reason:"Ticket was skipped \u2014 update the ticket and re-run"}:E.preflightFail.test(t)?{stop:!0,reason:"Preflight check failed"}:{stop:!1}}async function lt(t,n=5){let o=it(t,"clancy-once.js");if(!st(o)){console.error(x("\u2717 clancy-once.js not found in"),t);return}console.log(p("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510")),console.log(p("\u2502")+w(" \u{1F916} Clancy \u2014 AFK mode ")+p("\u2502")),console.log(p("\u2502")+p(` "I'm on it. Proceed to the abandoned warehouse." `)+p("\u2502")),console.log(p("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));let r=Date.now();for(let c=1;c<=n;c++){let s=process.env.CLANCY_QUIET_START,a=process.env.CLANCY_QUIET_END;if(s&&a){let f=at(s,a);if(f>0){let d=Math.ceil(f/6e4);console.log(""),console.log(M(`\u23F8 Quiet hours active (${s}\u2013${a}). Sleeping ${d} minutes until ${a}.`)),await F(f),console.log(p(" Quiet hours ended. Resuming."))}}else(s&&!a||!s&&a)&&console.log(p(" \u26A0 Only one of CLANCY_QUIET_START / CLANCY_QUIET_END is set \u2014 skipping quiet hours check."));let m=Date.now();console.log(""),console.log(w(`\u{1F501} Iteration ${c}/${n}`));let u=nt("node",[o],{encoding:"utf8",stdio:["inherit","pipe","inherit"],cwd:process.cwd(),env:{...process.env,CLANCY_AFK_MODE:"1"}}),y=u.stdout??"";y&&process.stdout.write(y);let g=$(Date.now()-m);if(u.error){console.error(x(`\u2717 Failed to run clancy-once: ${u.error.message}`));return}let k=ut(y);if(k.stop){let f=$(Date.now()-r);console.log(""),console.log(p(` Iteration ${c} took ${g}`)),console.log(`
5
- ${k.reason}`),console.log(p(` Total: ${c} iteration${c>1?"s":""} in ${f}`)),U(r);return}console.log(p(` Iteration ${c} took ${g}`)),c<n&&await F(2e3)}let l=$(Date.now()-r);console.log(""),console.log(P(`\u{1F3C1} Completed ${n} iterations`)+p(` (${l})`)),console.log(p(` "That's some good police work."`)),console.log(p(" Run clancy-afk again to continue.")),U(r)}function U(t){try{let n=I(process.cwd(),t,Date.now());console.log(""),console.log(p("\u2500\u2500\u2500 Session Report \u2500\u2500\u2500")),console.log(n);let o=process.env.CLANCY_NOTIFY_WEBHOOK;if(o){let c=`Clancy AFK: ${n.split(`
6
- `).filter(s=>s.startsWith("- Tickets")||s.startsWith("- Total")).join(". ")}. Report: .clancy/session-report.md`;N(o,c).catch(()=>{})}}catch{}}if(process.argv[1]&&_(import.meta.url)===ct(process.argv[1])){let t=rt(_(import.meta.url)),n=parseInt(process.env.MAX_ITERATIONS??"5",10)||5;lt(t,n)}export{ut as checkStopCondition,at as getQuietSleepMs,L as parseTime,lt as runAfkLoop};
1
+ import{spawnSync as nt}from"node:child_process";import{existsSync as st}from"node:fs";import{dirname as rt,join as it,resolve as ct}from"node:path";import{setTimeout as F}from"node:timers/promises";import{fileURLToPath as _}from"node:url";function $(t){let o=Math.floor(t/1e3);if(o<60)return`${o}s`;let n=Math.floor(o/60),i=o%60;if(n<60)return i>0?`${n}m ${i}s`:`${n}m`;let l=Math.floor(n/60),a=n%60;return a>0?`${l}h ${a}m`:`${l}h`}function j(t){return t.includes("hooks.slack.com")}function B(t){return JSON.stringify({text:t})}function H(t){return JSON.stringify({type:"message",attachments:[{contentType:"application/vnd.microsoft.card.adaptive",content:{$schema:"http://adaptivecards.io/schemas/adaptive-card.json",type:"AdaptiveCard",version:"1.4",body:[{type:"TextBlock",text:t,wrap:!0}]}}]})}async function N(t,o){let n=j(t)?B(o):H(o);try{let i=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:n});i.ok||console.warn(`\u26A0 Notification failed: HTTP ${i.status}`)}catch{console.warn("\u26A0 Notification failed: could not reach webhook")}}var m=t=>`\x1B[2m${t}\x1B[0m`,w=t=>`\x1B[1m${t}\x1B[0m`;var P=t=>`\x1B[32m${t}\x1B[0m`,x=t=>`\x1B[31m${t}\x1B[0m`,R=t=>`\x1B[33m${t}\x1B[0m`;import{mkdirSync as Z,readFileSync as z,writeFileSync as G}from"node:fs";import{join as v}from"node:path";import{mkdirSync as ht,readFileSync as K,renameSync as gt,writeFileSync as kt}from"node:fs";import{join as V}from"node:path";function Y(t){return V(t,".clancy","quality.json")}function q(t){try{let o=K(Y(t),"utf8"),n=JSON.parse(o);if(n&&typeof n.tickets=="object"&&n.tickets!==null&&!Array.isArray(n.tickets))return W(n),n}catch{}return{tickets:{},summary:{totalTickets:0,avgReworkCycles:0,avgVerificationRetries:0,avgDuration:0}}}function W(t){let o=Object.values(t.tickets),n=o.length;if(n===0){t.summary={totalTickets:0,avgReworkCycles:0,avgVerificationRetries:0,avgDuration:0};return}let i=0,l=0,a=0,s=0;for(let c of o)i+=c.reworkCycles,l+=c.verificationRetries,c.duration!=null&&(a+=c.duration,s++);t.summary={totalTickets:n,avgReworkCycles:Math.round(i/n*100)/100,avgVerificationRetries:Math.round(l/n*100)/100,avgDuration:s>0?Math.round(a/s*100)/100:0}}function M(t){let o=q(t);if(Object.keys(o.tickets).length!==0)return o}import{appendFileSync as wt,mkdirSync as xt,readFileSync as J}from"node:fs";import{dirname as Et,join as X}from"node:path";function b(t){let o=X(t,".clancy","progress.txt"),n;try{n=J(o,"utf8")}catch{return[]}let i=[];for(let l of n.split(`
2
+ `)){let a=l.trim();if(!a)continue;let s=a.split(" | ");if(s.length<4)continue;let c=s[0];if(s[1]==="BRIEF"||s[1]==="APPROVE_BRIEF"){i.push({timestamp:c,key:s[2],summary:s.slice(3).join(" | "),status:s[1]});continue}let p=s[1],u,d,h,k=[];for(let y=2;y<s.length;y++){let f=s[y],S=f.match(/^pr:(\d+)$/),r=f.match(/^parent:(.+)$/);S?d=parseInt(S[1],10):r?h=r[1]:y>=3&&!u&&f===f.toUpperCase()&&f.length>1?u=f:k.push(f)}u&&(u==="APPROVE"&&(u="APPROVE_PLAN"),i.push({timestamp:c,key:p,summary:k.join(" | "),status:u,...d!=null&&{prNumber:d},...h!=null&&{parent:h}}))}return i}function tt(t,o){let n=v(t,".clancy","costs.log"),i;try{i=z(n,"utf8")}catch{return[]}let l=[];for(let a of i.split(`
3
+ `)){let s=a.trim();if(!s)continue;let c=s.split(" | ");if(c.length<4)continue;let p=c[0],u=new Date(p).getTime();Number.isNaN(u)||u<o||l.push({timestamp:p,key:c[1],duration:c[2],tokens:c[3]})}return l}function et(t){return/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/.test(t)?new Date(t.replace(" ","T")+":00Z").getTime():NaN}var A=new Set(["DONE","PR_CREATED","PUSHED","EPIC_PR_CREATED","RESUMED"]),ot=new Set(["SKIPPED","PUSH_FAILED","TIME_LIMIT"]);function I(t,o,n){let i=Math.floor(o/6e4)*6e4,a=b(t).filter(e=>{let g=et(e.timestamp);return!Number.isNaN(g)&&g>=i}),s=tt(t,o),c=new Map;for(let e of s)c.set(e.key,e);let p=new Map;for(let e of a)p.set(e.key,e);let u=[];for(let e of p.values()){let g=c.get(e.key);u.push({key:e.key,summary:e.summary,status:e.status,...e.prNumber!=null&&{prNumber:e.prNumber},...g&&{duration:g.duration,tokens:g.tokens}})}let d=u.filter(e=>A.has(e.status)),h=u.filter(e=>ot.has(e.status)),k=$(n-o),y=0;for(let e of s){let g=e.tokens.match(/~(\d[\d,]*)/);g&&(y+=parseInt(g[1].replace(/,/g,""),10))}let f=new Date(o),S=`${f.getUTCFullYear()}-${String(f.getUTCMonth()+1).padStart(2,"0")}-${String(f.getUTCDate()).padStart(2,"0")}`,r=[];r.push(`# AFK Session Report \u2014 ${S}`),r.push(""),r.push("## Summary"),r.push(`- Tickets completed: ${d.length}`),r.push(`- Tickets failed: ${h.length}`),r.push(`- Total duration: ${k}`),y>0&&r.push(`- Estimated token usage: ${y.toLocaleString("en-US")}`),r.push(""),r.push("## Tickets"),u.length===0&&(r.push(""),r.push("No tickets were processed in this session."));for(let e of u){let Q=A.has(e.status)?"\u2713":"\u2717";r.push(""),r.push(`### ${Q} ${e.key} \u2014 ${e.summary}`),e.duration&&r.push(`- Duration: ${e.duration}`),e.tokens&&r.push(`- Tokens: ${e.tokens}`),e.prNumber!=null&&r.push(`- PR: #${e.prNumber}`),r.push(`- Status: ${e.status}`)}let T=u.filter(e=>e.prNumber!=null).map(e=>`#${e.prNumber}`),C=h.map(e=>e.key);if(T.length>0||C.length>0){r.push(""),r.push("## Next Steps"),T.length>0&&r.push(`- Review PRs ${T.join(", ")}`);for(let e of C)r.push(`- ${e} needs manual intervention`)}try{let e=M(t);e&&(r.push(""),r.push("## Quality Metrics"),r.push(`- Avg rework cycles: ${e.summary.avgReworkCycles}`),r.push(`- Avg verification retries: ${e.summary.avgVerificationRetries}`),e.summary.avgDuration>0&&r.push(`- Avg delivery time: ${$(e.summary.avgDuration)}`))}catch{}r.push("");let D=r.join(`
4
+ `);try{let e=v(t,".clancy","session-report.md");Z(v(t,".clancy"),{recursive:!0}),G(e,D,"utf8")}catch{}return D}function L(t){let o=t.trim().match(/^(\d{1,2}):(\d{2})$/);if(!o)return null;let n=parseInt(o[1],10),i=parseInt(o[2],10);return n<0||n>23||i<0||i>59?null:{hours:n,minutes:i}}var O=!1;function at(t,o,n=new Date){let i=L(t),l=L(o);if(!i||!l)return O||(console.warn("\u26A0 Invalid quiet hours format. Expected HH:MM (24h). Quiet hours disabled."),O=!0),0;let a=n.getHours()*60+n.getMinutes(),s=i.hours*60+i.minutes,c=l.hours*60+l.minutes,p=!1;if(s<c?p=a>=s&&a<c:s>c&&(p=a>=s||a<c),!p)return 0;let u=c-a;u<=0&&(u+=1440);let d=n.getSeconds()*1e3+n.getMilliseconds();return Math.max(0,u*6e4-d)}var E={noTickets:/No tickets found|No issues found|All done/,skipped:/Ticket skipped/,preflightFail:/^✗ /m};function ut(t){return E.noTickets.test(t)?{stop:!0,reason:"No more tickets \u2014 all done"}:E.skipped.test(t)?{stop:!0,reason:"Ticket was skipped \u2014 update the ticket and re-run"}:E.preflightFail.test(t)?{stop:!0,reason:"Preflight check failed"}:{stop:!1}}function lt(t){return nt("node",[t],{encoding:"utf8",stdio:["inherit","pipe","inherit"],cwd:process.cwd(),env:{...process.env,CLANCY_AFK_MODE:"1"}})}async function pt(t,o=5,n=lt){let i=it(t,"clancy-once.js");if(!st(i)){console.error(x("\u2717 clancy-once.js not found in"),t);return}console.log(m("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510")),console.log(m("\u2502")+w(" \u{1F916} Clancy \u2014 AFK mode ")+m("\u2502")),console.log(m("\u2502")+m(` "I'm on it. Proceed to the abandoned warehouse." `)+m("\u2502")),console.log(m("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));let l=Date.now();for(let s=1;s<=o;s++){let c=process.env.CLANCY_QUIET_START,p=process.env.CLANCY_QUIET_END;if(c&&p){let f=at(c,p);if(f>0){let S=Math.ceil(f/6e4);console.log(""),console.log(R(`\u23F8 Quiet hours active (${c}\u2013${p}). Sleeping ${S} minutes until ${p}.`)),await F(f),console.log(m(" Quiet hours ended. Resuming."))}}else(c&&!p||!c&&p)&&console.log(m(" \u26A0 Only one of CLANCY_QUIET_START / CLANCY_QUIET_END is set \u2014 skipping quiet hours check."));let u=Date.now();console.log(""),console.log(w(`\u{1F501} Iteration ${s}/${o}`));let d=await n(i),h=d.stdout??"";h&&process.stdout.write(h);let k=$(Date.now()-u);if(d.error){console.error(x(`\u2717 Failed to run clancy-once: ${d.error.message}`));return}let y=ut(h);if(y.stop){let f=$(Date.now()-l);console.log(""),console.log(m(` Iteration ${s} took ${k}`)),console.log(`
5
+ ${y.reason}`),console.log(m(` Total: ${s} iteration${s>1?"s":""} in ${f}`)),U(l);return}console.log(m(` Iteration ${s} took ${k}`)),s<o&&await F(2e3)}let a=$(Date.now()-l);console.log(""),console.log(P(`\u{1F3C1} Completed ${o} iterations`)+m(` (${a})`)),console.log(m(` "That's some good police work."`)),console.log(m(" Run clancy-afk again to continue.")),U(l)}function U(t){try{let o=I(process.cwd(),t,Date.now());console.log(""),console.log(m("\u2500\u2500\u2500 Session Report \u2500\u2500\u2500")),console.log(o);let n=process.env.CLANCY_NOTIFY_WEBHOOK;if(n){let a=`Clancy AFK: ${o.split(`
6
+ `).filter(s=>s.startsWith("- Tickets")||s.startsWith("- Total")).join(". ")}. Report: .clancy/session-report.md`;N(n,a).catch(()=>{})}}catch{}}if(process.argv[1]&&_(import.meta.url)===ct(process.argv[1])){let t=rt(_(import.meta.url)),o=parseInt(process.env.MAX_ITERATIONS??"5",10)||5;pt(t,o)}export{ut as checkStopCondition,at as getQuietSleepMs,L as parseTime,pt as runAfkLoop};