devcoach 2.7.0-next.615.ge571c8f → 2.7.0-next.623.g2d36462

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
@@ -16,7 +16,7 @@
16
16
 
17
17
  **Stay sharp while your AI does the work.**
18
18
 
19
- devcoach connects to Claude Code, Claude Desktop, Gemini CLI *(beta)*, Codex CLI *(beta)*, Cursor, Windsurf, and other MCP-compatible tools. After every task you complete, it delivers a short targeted lesson calibrated to what you already know — no generic tutorials, no repeated topics, nothing to open.
19
+ devcoach connects to Claude Code, Claude Desktop, Gemini CLI *(beta)*, Codex CLI *(beta)*, Cursor, Windsurf, and other MCP-compatible tools. When your agent finishes a task, devcoach teaches you one thing from it **that you don't know yet**: it keeps a map of what you already know precisely so it can skip it, and pitches each lesson at your level — no generic tutorials, no repeated topics, nothing to open.
20
20
 
21
21
  Everything runs **locally**. No data leaves your machine. One SQLite file at `~/.devcoach/coaching.db`.
22
22
 
@@ -26,7 +26,7 @@ Everything runs **locally**. No data leaves your machine. One SQLite file at `~/
26
26
 
27
27
  ## Why devcoach?
28
28
 
29
- AI agents now write much of our code — which makes it easy to ship more while understanding less. devcoach turns each task your agent finishes into one short, in-context lesson, so you keep learning as you go. The deeper bet: as AI grows more capable, the scarce skill becomes *validating* what it produces — and that ability only survives if you keep practicing it.
29
+ AI agents now write much of our code — which makes it easy to ship more while understanding less. devcoach turns the tasks your agent finishes into short, in-context lessons on what you don't know yet, so you keep learning as you go. The deeper bet: as AI grows more capable, the scarce skill becomes *validating* what it produces — and that ability only survives if you keep practicing it.
30
30
 
31
31
  → [Why devcoach exists](docs/why.md)
32
32
 
@@ -50,7 +50,7 @@ flowchart TD
50
50
 
51
51
  S --> Y(["Silent — pacing re-armed"])
52
52
  F --> H(["Done — counter reset"])
53
- F -.->|prompts| U(["You: ✅ / ❌"])
53
+ F -.->|prompts| U(["You: ✅ / 💡 / ❌"])
54
54
  ```
55
55
 
56
56
  → [Full decision flow: session startup · lesson selection · depth calibration](docs/how-it-works.md)
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import {a as a$2,c as c$1}from'./chunk-QU6UQJWC.js';import {p,q,h as h$1,m,a as a$1,e,g,j as j$1,l,n}from'./chunk-Y6CPVRWE.js';import {A,E as E$1,B as B$1,z,a,G,X as X$1,Ba,Fa,Ca,R,V as V$1,S,W as W$1,Da,T,U,L,Ga,ra,va,N,M,P as P$1,H,K as K$1,Ea,J as J$1,O,Y as Y$1,x,Aa,za,c,d,b as b$1,oa,qa,s,ta,sa,ua,xa,wa}from'./chunk-MXY574EH.js';import {existsSync,readFileSync,mkdirSync,writeFileSync}from'fs';import {dirname,join,extname}from'path';import {fileURLToPath}from'url';import {serve}from'@hono/node-server';import {Hono}from'hono';import {html,raw}from'hono/html';function j(r){return JSON.stringify(r??"").replace(/[<>&\u2028\u2029]/g,e=>String.raw`\u`+(e.codePointAt(0)??0).toString(16).padStart(4,"0"))}function We(r,e){return r+e.replace(/[^A-Za-z0-9_-]/g,g=>`_${(g.codePointAt(0)??0).toString(16)}`)}var lt=html`<svg class="w-4 h-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><circle cx="4" cy="10" r="1.75"/><circle cx="10" cy="10" r="1.75"/><circle cx="16" cy="10" r="1.75"/></svg>`;function Y(r,e="",g=""){return html`<div class="relative ${e}" ${raw(g)} x-data="{ open: false }" @click.outside="open = false" @keydown.escape="open = false">
2
+ import {a as a$2,c as c$1}from'./chunk-QU6UQJWC.js';import {p,q,h as h$1,m,a as a$1,e,g,j as j$1,l,n}from'./chunk-CYMYTHSN.js';import {A,E as E$1,B as B$1,z,a,G,X as X$1,Ba,Fa,Ca,R,V as V$1,S,W as W$1,Da,T,U,L,Ga,ra,va,N,M,P as P$1,H,K as K$1,Ea,J as J$1,O,Y as Y$1,x,Aa,za,c,d,b as b$1,oa,qa,s,ta,sa,ua,xa,wa}from'./chunk-LMBPZNJL.js';import {existsSync,readFileSync,mkdirSync,writeFileSync}from'fs';import {dirname,join,extname}from'path';import {fileURLToPath}from'url';import {serve}from'@hono/node-server';import {Hono}from'hono';import {html,raw}from'hono/html';function j(r){return JSON.stringify(r??"").replace(/[<>&\u2028\u2029]/g,e=>String.raw`\u`+(e.codePointAt(0)??0).toString(16).padStart(4,"0"))}function We(r,e){return r+e.replace(/[^A-Za-z0-9_-]/g,g=>`_${(g.codePointAt(0)??0).toString(16)}`)}var lt=html`<svg class="w-4 h-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><circle cx="4" cy="10" r="1.75"/><circle cx="10" cy="10" r="1.75"/><circle cx="16" cy="10" r="1.75"/></svg>`;function Y(r,e="",g=""){return html`<div class="relative ${e}" ${raw(g)} x-data="{ open: false }" @click.outside="open = false" @keydown.escape="open = false">
3
3
  <button type="button" @click="open = !open" :aria-expanded="open" aria-label="More actions" title="More actions" class="w-8 h-8 inline-flex items-center justify-center rounded-lg border transition bg-white dark:bg-gray-900 border-gray-200 dark:border-gray-700 text-gray-500 dark:text-gray-400 hover:border-accent-400 hover:text-gray-800 dark:hover:text-gray-100">${lt}</button>
4
4
  <div x-show="open" x-transition:enter="transition ease-out duration-100" x-transition:enter-start="opacity-0 scale-95" x-transition:enter-end="opacity-100 scale-100" class="absolute right-0 top-full mt-1 z-50 bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-xl shadow-lg w-56 p-1" style="display:none">
5
5
  ${r}
package/dist/bin.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- var r=process.argv[2]??"",e=new Set(["stop-hook","prompt-hook","gemini-stop-hook","gemini-prompt-hook","codex-stop-hook","codex-prompt-hook","onboard-hook","lesson-ready"]);try{if(e.has(r)){let{runHook:o}=await import('./hooks-DW62LD3W.js');o(r);}else {let{runCli:o}=await import('./commands-FZH4WKXT.js');await o();}}catch(o){o?.name==="SchemaTooNewError"&&(console.error(o.message),process.exit(3)),console.error(o),process.exit(1);}
2
+ var r=process.argv[2]??"",e=new Set(["stop-hook","prompt-hook","gemini-stop-hook","gemini-prompt-hook","codex-stop-hook","codex-prompt-hook","onboard-hook","lesson-ready"]);try{if(e.has(r)){let{runHook:o}=await import('./hooks-PDYR37TY.js');o(r);}else {let{runCli:o}=await import('./commands-WOIS2SJU.js');await o();}}catch(o){o?.name==="SchemaTooNewError"&&(console.error(o.message),process.exit(3)),console.error(o),process.exit(1);}
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import {t,u,pa,da,ea,ga,fa,D,ha,ia,la,ja,ka,ma,ra,ya,e,a}from'./chunk-MXY574EH.js';import {lstatSync,mkdirSync,readFileSync,rmSync,existsSync,writeFileSync}from'fs';import {join,sep,basename}from'path';import {execFileSync}from'child_process';import st from'dns';import it from'http';import at from'https';import {isIP}from'net';var w=/^[a-z0-9-]+$/,I=u,h=class extends Error{};function tt(e){if(!w.test(e))throw new h(`Invalid course id '${e}'`)}function y(e){return tt(e),join(t,e)}function W(e){return join(y(e),I)}function B(e){let t=W(e);if(!t.startsWith(y(e)+sep))return null;try{let r=lstatSync(t);return !r.isFile()||r.size>pa?null:t}catch{return null}}function H(e$1){try{let t=B(e$1);if(t===null)return !1;let r=e(readFileSync(t,"utf8"));return r===null||Buffer.byteLength(r)>pa?!1:(writeFileSync(t,r),!0)}catch{return false}}function et(e,t$1){let r=ya(t$1,"course"),n=r;for(let o=2;ea(e,n)!==null||existsSync(join(t,n));o++)n=`${r}-${o}`;return n}function Ct(e,t){let r=new Date().toISOString(),n={id:et(e,t.title),lesson_id:t.lesson_id??null,topic_id:t.topic_id,title:t.title,goal:t.goal??null,prerequisites:t.prerequisites??[],status:"active",created_at:r,updated_at:r,completed_at:null};return da(e,n),mkdirSync(y(n.id),{recursive:true}),{...n,steps:[]}}function E(e,t){if(!w.test(t))return null;let r=ea(e,t);return r?{...r,steps:ga(e,t)}:null}function bt(e,t={}){return fa(e,t).map(r=>({...r,steps:ga(e,r.id)}))}function xt(e,t,r){return D(e,()=>{let n=E(e,t);if(!n)throw new h(`Course '${t}' not found`);let o=n.steps.length,i=r.after??o;if(!Number.isInteger(i)||i<0||i>o)throw new h(`'after' must be between 0 and ${o} (the course has ${o} steps)`);let a=B(t);if(!a)throw new h(`Course '${t}' has no document yet \u2014 write ${W(t)} first`);let f=readFileSync(a,"utf8");if(!new RegExp(`\\sid=["']${r.anchor}["']`).test(f))throw new h(`No element with id="${r.anchor}" in ${a}`);if(n.steps.some(s=>s.anchor===r.anchor))throw new h(`Step anchor '${r.anchor}' is already registered`);let l={course_id:t,position:i+1,title:r.title,kind:r.kind,anchor:r.anchor,status:"todo",done_at:null};return i<o&&ha(e,t,i+1),ia(e,l),la(e,t,new Date().toISOString()),H(t),l})}function Rt(e,t,r,n){let o=new Date().toISOString();return H(t),D(e,()=>{if(!ja(e,t,r,n,n==="done"?o:null))return null;let a=ga(e,t),f=a.length>0&&a.every(s=>s.status!=="todo"),l=ea(e,t);return l&&f&&l.status==="active"?ka(e,t,"completed",o):l&&!f&&l.status==="completed"?ka(e,t,"active",o):la(e,t,o),E(e,t)})}function _t(e,t,r){return w.test(t)&&ka(e,t,r,new Date().toISOString())?E(e,t):null}function Lt(e,t){if(!w.test(t))return false;let r=ma(e,t);return rmSync(y(t),{recursive:true,force:true}),r}function Dt(e){return {done:e.steps.filter(t=>t.status!=="todo").length,total:e.steps.length}}function C(...e){try{return execFileSync("git",e,{encoding:"utf8",timeout:3e3,stdio:["ignore","pipe","ignore"]}).trim()||null}catch{return null}}var G={"github.com":"github","gitlab.com":"gitlab","bitbucket.org":"bitbucket"};function ot(e){if(!e)return [null,null];let t=/^git@([^:]+):(.+?)(?:\.git)?$/.exec(e);if(t){let n=(t[1]??"").toLowerCase();return [t[2]??e,G[n]??"local"]}let r=/^https?:\/\/([^/]+)\/(.+?)(?:\.git)?$/.exec(e);if(r){let n=(r[1]??"").toLowerCase();return [r[2]??e,G[n]??"local"]}return [e,"local"]}function Tt(){return C("config","user.name")}function vt(){let e=process.cwd(),t=C("rev-parse","--abbrev-ref","HEAD"),r=C("rev-parse","HEAD"),n=C("remote","get-url","origin"),[o,i]=ot(n),a;return o?a=o.replace(/\/+$/,"").split("/").pop()??null:a=basename(e)||null,t==="HEAD"&&(t=null),{project:a,repository:o,branch:t,commit_hash:r,folder:e,repository_platform:i}}var j="That URL is too large to be a lesson.",b="Only public http(s) URLs can be imported.",ut=new Set([301,302,303,307,308]);function lt(e){return /^https?:\/\/\S+$/i.test(e.trim())}function z(e){let[t=0,r=0]=e.split(".").map(Number);return t===0||t===10||t===127||t===169&&r===254||t===172&&r>=16&&r<=31||t===192&&r===168||t===100&&r>=64&&r<=127||t>=224}function K(e){let t=e.replace(/^\[|\]$/g,"").replace(/%.*$/,"").toLowerCase();if(isIP(t)===4)return z(t);if(isIP(t)!==6)return true;let r=/^::ffff:(\d+\.\d+\.\d+\.\d+)$/.exec(t);if(r?.[1])return z(r[1]);if(t==="::"||t==="::1")return true;let n=t.split(":")[0]??"";return !!(/^f[cd]/.test(n)||/^fe[89ab]/.test(n))}async function ct(e){return (await st.promises.lookup(e,{all:true})).map(r=>r.address)}async function pt(e,t){let r=e.hostname.replace(/^\[|\]$/g,"").toLowerCase();if(r==="localhost"||r.endsWith(".localhost"))throw new ra(b);if(isIP(r)){if(K(r))throw new ra(b);return r}let n;try{n=await t(r);}catch{throw new ra("The URL could not be reached \u2014 copy the lesson text instead.")}let o=n[0];if(!o||n.some(i=>K(i)))throw new ra(b);return o}var ft=(e,t,r)=>new Promise((n,o)=>{let i=isIP(t)===6?6:4,a$1=(s,c,m)=>{c.all?m(null,[{address:t,family:i}]):m(null,t,i);},l=(e.protocol==="https:"?at:it).request(e,{method:"GET",lookup:a$1,signal:r,headers:{accept:"text/plain, text/markdown, application/json;q=0.9, */*;q=0.5","user-agent":`devcoach/${a}`}},s=>{let c=s.headers["content-length"];n({status:s.statusCode??0,location:s.headers.location??null,contentLength:c===void 0?null:Number(c),body:s,abort:()=>s.destroy()});});l.on("error",o),l.end();});async function ht(e,t){if(e.contentLength!==null&&e.contentLength>t)throw e.abort(),new ra(j);let r=[],n=0;for await(let o of e.body){if(n+=o.byteLength,n>t)throw e.abort(),new ra(j);r.push(o);}return Buffer.concat(r).toString("utf8")}async function Ft(e,t={}){let r=t.timeoutMs??5e3,n=t.maxBytes??256*1024,o=t.maxRedirects??3,i=t.lookup??ct,a=t.transport??ft;if(!lt(e))throw new ra("Only http(s) URLs can be imported.");let f=new AbortController,l=setTimeout(()=>f.abort(),r);try{let s=new URL(e.trim());for(let c=0;;c++){let m=await pt(s,i),p=await a(s,m,f.signal);if(ut.has(p.status)){if(p.abort(),!p.location||c>=o)throw new ra("The URL redirects too many times \u2014 copy the lesson text instead.");if(s=new URL(p.location,s),!/^https?:$/.test(s.protocol))throw new ra(b);continue}if(p.status<200||p.status>=300)throw p.abort(),new ra(`The URL answered ${p.status} \u2014 nothing to import there.`);return await ht(p,n)}}catch(s){if(s instanceof ra)throw s;let c=s?.name==="AbortError"?"timed out":"could not be reached";throw new ra(`The URL ${c} \u2014 copy the lesson text instead.`)}finally{clearTimeout(l);}}export{w as a,h as b,y as c,W as d,B as e,Ct as f,E as g,bt as h,xt as i,Rt as j,_t as k,Lt as l,Dt as m,Tt as n,vt as o,lt as p,Ft as q};
2
+ import {t,u,pa,da,ea,ga,fa,D,ha,ia,la,ja,ka,ma,ra,ya,e,a}from'./chunk-LMBPZNJL.js';import {lstatSync,mkdirSync,readFileSync,rmSync,existsSync,writeFileSync}from'fs';import {join,sep,basename}from'path';import {execFileSync}from'child_process';import st from'dns';import it from'http';import at from'https';import {isIP}from'net';var w=/^[a-z0-9-]+$/,I=u,h=class extends Error{};function tt(e){if(!w.test(e))throw new h(`Invalid course id '${e}'`)}function y(e){return tt(e),join(t,e)}function W(e){return join(y(e),I)}function B(e){let t=W(e);if(!t.startsWith(y(e)+sep))return null;try{let r=lstatSync(t);return !r.isFile()||r.size>pa?null:t}catch{return null}}function H(e$1){try{let t=B(e$1);if(t===null)return !1;let r=e(readFileSync(t,"utf8"));return r===null||Buffer.byteLength(r)>pa?!1:(writeFileSync(t,r),!0)}catch{return false}}function et(e,t$1){let r=ya(t$1,"course"),n=r;for(let o=2;ea(e,n)!==null||existsSync(join(t,n));o++)n=`${r}-${o}`;return n}function Ct(e,t){let r=new Date().toISOString(),n={id:et(e,t.title),lesson_id:t.lesson_id??null,topic_id:t.topic_id,title:t.title,goal:t.goal??null,prerequisites:t.prerequisites??[],status:"active",created_at:r,updated_at:r,completed_at:null};return da(e,n),mkdirSync(y(n.id),{recursive:true}),{...n,steps:[]}}function E(e,t){if(!w.test(t))return null;let r=ea(e,t);return r?{...r,steps:ga(e,t)}:null}function bt(e,t={}){return fa(e,t).map(r=>({...r,steps:ga(e,r.id)}))}function xt(e,t,r){return D(e,()=>{let n=E(e,t);if(!n)throw new h(`Course '${t}' not found`);let o=n.steps.length,i=r.after??o;if(!Number.isInteger(i)||i<0||i>o)throw new h(`'after' must be between 0 and ${o} (the course has ${o} steps)`);let a=B(t);if(!a)throw new h(`Course '${t}' has no document yet \u2014 write ${W(t)} first`);let f=readFileSync(a,"utf8");if(!new RegExp(`\\sid=["']${r.anchor}["']`).test(f))throw new h(`No element with id="${r.anchor}" in ${a}`);if(n.steps.some(s=>s.anchor===r.anchor))throw new h(`Step anchor '${r.anchor}' is already registered`);let l={course_id:t,position:i+1,title:r.title,kind:r.kind,anchor:r.anchor,status:"todo",done_at:null};return i<o&&ha(e,t,i+1),ia(e,l),la(e,t,new Date().toISOString()),H(t),l})}function Rt(e,t,r,n){let o=new Date().toISOString();return H(t),D(e,()=>{if(!ja(e,t,r,n,n==="done"?o:null))return null;let a=ga(e,t),f=a.length>0&&a.every(s=>s.status!=="todo"),l=ea(e,t);return l&&f&&l.status==="active"?ka(e,t,"completed",o):l&&!f&&l.status==="completed"?ka(e,t,"active",o):la(e,t,o),E(e,t)})}function _t(e,t,r){return w.test(t)&&ka(e,t,r,new Date().toISOString())?E(e,t):null}function Lt(e,t){if(!w.test(t))return false;let r=ma(e,t);return rmSync(y(t),{recursive:true,force:true}),r}function Dt(e){return {done:e.steps.filter(t=>t.status!=="todo").length,total:e.steps.length}}function C(...e){try{return execFileSync("git",e,{encoding:"utf8",timeout:3e3,stdio:["ignore","pipe","ignore"]}).trim()||null}catch{return null}}var G={"github.com":"github","gitlab.com":"gitlab","bitbucket.org":"bitbucket"};function ot(e){if(!e)return [null,null];let t=/^git@([^:]+):(.+?)(?:\.git)?$/.exec(e);if(t){let n=(t[1]??"").toLowerCase();return [t[2]??e,G[n]??"local"]}let r=/^https?:\/\/([^/]+)\/(.+?)(?:\.git)?$/.exec(e);if(r){let n=(r[1]??"").toLowerCase();return [r[2]??e,G[n]??"local"]}return [e,"local"]}function Tt(){return C("config","user.name")}function vt(){let e=process.cwd(),t=C("rev-parse","--abbrev-ref","HEAD"),r=C("rev-parse","HEAD"),n=C("remote","get-url","origin"),[o,i]=ot(n),a;return o?a=o.replace(/\/+$/,"").split("/").pop()??null:a=basename(e)||null,t==="HEAD"&&(t=null),{project:a,repository:o,branch:t,commit_hash:r,folder:e,repository_platform:i}}var j="That URL is too large to be a lesson.",b="Only public http(s) URLs can be imported.",ut=new Set([301,302,303,307,308]);function lt(e){return /^https?:\/\/\S+$/i.test(e.trim())}function z(e){let[t=0,r=0]=e.split(".").map(Number);return t===0||t===10||t===127||t===169&&r===254||t===172&&r>=16&&r<=31||t===192&&r===168||t===100&&r>=64&&r<=127||t>=224}function K(e){let t=e.replace(/^\[|\]$/g,"").replace(/%.*$/,"").toLowerCase();if(isIP(t)===4)return z(t);if(isIP(t)!==6)return true;let r=/^::ffff:(\d+\.\d+\.\d+\.\d+)$/.exec(t);if(r?.[1])return z(r[1]);if(t==="::"||t==="::1")return true;let n=t.split(":")[0]??"";return !!(/^f[cd]/.test(n)||/^fe[89ab]/.test(n))}async function ct(e){return (await st.promises.lookup(e,{all:true})).map(r=>r.address)}async function pt(e,t){let r=e.hostname.replace(/^\[|\]$/g,"").toLowerCase();if(r==="localhost"||r.endsWith(".localhost"))throw new ra(b);if(isIP(r)){if(K(r))throw new ra(b);return r}let n;try{n=await t(r);}catch{throw new ra("The URL could not be reached \u2014 copy the lesson text instead.")}let o=n[0];if(!o||n.some(i=>K(i)))throw new ra(b);return o}var ft=(e,t,r)=>new Promise((n,o)=>{let i=isIP(t)===6?6:4,a$1=(s,c,m)=>{c.all?m(null,[{address:t,family:i}]):m(null,t,i);},l=(e.protocol==="https:"?at:it).request(e,{method:"GET",lookup:a$1,signal:r,headers:{accept:"text/plain, text/markdown, application/json;q=0.9, */*;q=0.5","user-agent":`devcoach/${a}`}},s=>{let c=s.headers["content-length"];n({status:s.statusCode??0,location:s.headers.location??null,contentLength:c===void 0?null:Number(c),body:s,abort:()=>s.destroy()});});l.on("error",o),l.end();});async function ht(e,t){if(e.contentLength!==null&&e.contentLength>t)throw e.abort(),new ra(j);let r=[],n=0;for await(let o of e.body){if(n+=o.byteLength,n>t)throw e.abort(),new ra(j);r.push(o);}return Buffer.concat(r).toString("utf8")}async function Ft(e,t={}){let r=t.timeoutMs??5e3,n=t.maxBytes??256*1024,o=t.maxRedirects??3,i=t.lookup??ct,a=t.transport??ft;if(!lt(e))throw new ra("Only http(s) URLs can be imported.");let f=new AbortController,l=setTimeout(()=>f.abort(),r);try{let s=new URL(e.trim());for(let c=0;;c++){let m=await pt(s,i),p=await a(s,m,f.signal);if(ut.has(p.status)){if(p.abort(),!p.location||c>=o)throw new ra("The URL redirects too many times \u2014 copy the lesson text instead.");if(s=new URL(p.location,s),!/^https?:$/.test(s.protocol))throw new ra(b);continue}if(p.status<200||p.status>=300)throw p.abort(),new ra(`The URL answered ${p.status} \u2014 nothing to import there.`);return await ht(p,n)}}catch(s){if(s instanceof ra)throw s;let c=s?.name==="AbortError"?"timed out":"could not be reached";throw new ra(`The URL ${c} \u2014 copy the lesson text instead.`)}finally{clearTimeout(l);}}export{w as a,h as b,y as c,W as d,B as e,Ct as f,E as g,bt as h,xt as i,Rt as j,_t as k,Lt as l,Dt as m,Tt as n,vt as o,lt as p,Ft as q};
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import {r,E as E$1,B as B$1,ba,na,Ja,q,D,Ia}from'./chunk-MXY574EH.js';import {existsSync,mkdirSync,statSync,writeFileSync,appendFileSync,fstatSync,readFileSync}from'fs';import {join}from'path';function N(e){try{let o=JSON.parse(e);return {stop_hook_active:o?.stop_hook_active===!0,permission_mode:typeof o?.permission_mode=="string"?o.permission_mode:null,session_id:typeof o?.session_id=="string"?o.session_id:null}}catch{return {stop_hook_active:false,permission_mode:null,session_id:null}}}function l(){let e={stop_hook_active:false,permission_mode:null,session_id:null};try{let o=fstatSync(0);return !o.isFIFO()&&!o.isFile()&&!o.isSocket()?e:N(readFileSync(0,"utf8"))}catch{return e}}function $(e){return e.stop_hook_active||e.permission_mode==="plan"}function s(e,o,t){if(process.env.DEVCOACH_HOOK_DEBUG==="1")try{mkdirSync(q,{recursive:!0});let n=join(q,"hook.log");existsSync(n)&&statSync(n).size>256*1024&&writeFileSync(n,""),appendFileSync(n,`${new Date().toISOString()} ${e} ${(o??"-").slice(0,8)} ${t}
2
+ import {r,E as E$1,B as B$1,ba,na,Ja,q,D,Ia}from'./chunk-LMBPZNJL.js';import {existsSync,mkdirSync,statSync,writeFileSync,appendFileSync,fstatSync,readFileSync}from'fs';import {join}from'path';function N(e){try{let o=JSON.parse(e);return {stop_hook_active:o?.stop_hook_active===!0,permission_mode:typeof o?.permission_mode=="string"?o.permission_mode:null,session_id:typeof o?.session_id=="string"?o.session_id:null}}catch{return {stop_hook_active:false,permission_mode:null,session_id:null}}}function l(){let e={stop_hook_active:false,permission_mode:null,session_id:null};try{let o=fstatSync(0);return !o.isFIFO()&&!o.isFile()&&!o.isSocket()?e:N(readFileSync(0,"utf8"))}catch{return e}}function $(e){return e.stop_hook_active||e.permission_mode==="plan"}function s(e,o,t){if(process.env.DEVCOACH_HOOK_DEBUG==="1")try{mkdirSync(q,{recursive:!0});let n=join(q,"hook.log");existsSync(n)&&statSync(n).size>256*1024&&writeFileSync(n,""),appendFileSync(n,`${new Date().toISOString()} ${e} ${(o??"-").slice(0,8)} ${t}
3
3
  `);}catch{}}var E={claude:"block",gemini:"deny",codex:"block"};function u(e,o,t){let n={decision:E[e],reason:o};t&&(n.systemMessage=t),process.stdout.write(`${JSON.stringify(n)}
4
4
  `),process.exit(0);}var g="devcoach: the user has no coaching profile yet. Ask them how they want to set it up and STRONGLY recommend Automatic \u2014 devcoach scans their full Claude Code history across all their projects (call get_onboarding for the detected stack and per-project evidence) so the profile reflects what they actually build. Also offer Guided (a short conversation to map topics and confidence levels) and Import (restore from a backup), but lead with Automatic; do not pick for them. After they choose, build the profile and save it by calling the devcoach `complete_onboarding` MCP tool (not a shell command) before ending your turn.";function I(e=l()){if($(e)&&process.exit(0),existsSync(r)){let o;try{o=E$1(t=>B$1(t)!==null||ba(t).knowledge_ready);}catch(t){s("onboard-hook",e.session_id,`error: ${t}`),process.exit(0);}o&&process.exit(0);}s("onboard-hook",e.session_id,"onboarding cue"),u("claude",g);}var a=10,w={claude:{cue:"Invoke the `devcoach` skill (Skill tool)",prime:"invoke the `devcoach` skill (Skill tool)"},gemini:{cue:"Activate the `devcoach` skill (activate_skill tool)",prime:"activate the `devcoach` skill (activate_skill tool)"},codex:{cue:"Activate the `devcoach` skill",prime:"activate the `devcoach` skill"}};function T(e,o="claude"){let t=e%a===0,n=Math.ceil(e/a)*a,i=t?`This is lesson #${e} \u2014 a notebook checkpoint (every ${a} lessons): after log_lesson returns the feedback, also read references/calibration.md in the devcoach skill and run its notebook-update step (a direct file write), recording "(updated after ${e} lessons)".`:`Skip the notebook update this time \u2014 it is refreshed only every ${a} lessons (next checkpoint at lesson #${n}).`;return `devcoach: a lesson is due for the technical work just completed. Do it now \u2014 do not acknowledge this message and do not explain what you are about to do.
5
5
 
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
- import {readFileSync,existsSync,lstatSync,mkdirSync,writeFileSync}from'fs';import {dirname,join}from'path';import {fileURLToPath}from'url';import {z as z$1}from'zod';import {createRequire}from'module';import {homedir}from'os';import {strToU8,zipSync,unzipSync,deflateSync,strFromU8,Inflate}from'fflate';var he={version:"2.7.0-next.615.ge571c8f"};var N=he.version;var ct=dirname(fileURLToPath(import.meta.url)),lt='<script data-devcoach="highlighter"></script>',Ee=/<script data-devcoach="highlighter">\s*<\/script>/,ut=/<script data-devcoach="highlighter">\s*(?!<\/script>)\S/;function pt(){for(let e of ["../assets","../../assets"]){let t=join(ct,e,"static","vendor");if(existsSync(join(t,"highlight.min.js")))return t}return null}var _e={light:{keyword:"#0033b3",string:"#067d17",comment:"#5f6672",number:"#1750eb",title:"#00627a",type:"#871094",attr:"#374151",meta:"#7a6a00"},dark:{keyword:"#cf8e6d",string:"#6aab73",comment:"#8b8f96",number:"#2aacb8",title:"#56a8f5",type:"#c77dbb",attr:"#bcbec4",meta:"#b3ae60"}};function Se(e){return Object.entries(e).map(([t,n])=>`--syn-${t}:${n}`).join(";")}function dt(){let e=Se(_e.light),t=Se(_e.dark);return [`:where(:root){${e}}`,`@media (prefers-color-scheme: dark){:where(:root:not([data-theme="light"])){${t}}}`,`:where(:root[data-theme="dark"]){${t}}`,"pre code.hljs{display:block;background:transparent;color:inherit;padding:0}",".hljs-comment,.hljs-quote{color:var(--syn-comment);font-style:italic}",".hljs-keyword,.hljs-selector-tag,.hljs-literal,.hljs-doctag{color:var(--syn-keyword)}",".hljs-string,.hljs-regexp,.hljs-addition,.hljs-selector-attr,.hljs-selector-pseudo{color:var(--syn-string)}",".hljs-number,.hljs-symbol,.hljs-bullet,.hljs-link{color:var(--syn-number)}",".hljs-title,.hljs-section,.hljs-name,.hljs-selector-id,.hljs-selector-class{color:var(--syn-title)}",".hljs-type,.hljs-built_in,.hljs-title.class_,.hljs-tag{color:var(--syn-type)}",".hljs-attr,.hljs-attribute,.hljs-variable,.hljs-template-variable,.hljs-property,.hljs-params{color:var(--syn-attr)}",".hljs-meta,.hljs-deletion,.hljs-subst{color:var(--syn-meta)}",".hljs-emphasis{font-style:italic}.hljs-strong{font-weight:bold}"].join("")}var mt=';(function(){function run(){hljs.configure({ignoreUnescapedHTML:true});document.querySelectorAll("pre code").forEach(function(el){if(el.dataset.highlighted||el.classList.contains("nohighlight"))return;hljs.highlightElement(el);});}if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",run);else run();})();',T;function ye(){if(T!==void 0)return T;T=null;try{let e=pt();if(e===null)return T;let t=readFileSync(join(e,"highlight.min.js"),"utf8"),n=readFileSync(join(e,"highlight.LICENSE.txt"),"utf8").trim();if(/<\/script/i.test(t)||n.includes("*/"))return T;T=`<style data-devcoach="highlighter">${dt()}</style>
2
+ import {readFileSync,existsSync,lstatSync,mkdirSync,writeFileSync}from'fs';import {dirname,join}from'path';import {fileURLToPath}from'url';import {z as z$1}from'zod';import {createRequire}from'module';import {homedir}from'os';import {strToU8,zipSync,unzipSync,deflateSync,strFromU8,Inflate}from'fflate';var he={version:"2.7.0-next.623.g2d36462"};var N=he.version;var ct=dirname(fileURLToPath(import.meta.url)),lt='<script data-devcoach="highlighter"></script>',Ee=/<script data-devcoach="highlighter">\s*<\/script>/,ut=/<script data-devcoach="highlighter">\s*(?!<\/script>)\S/;function pt(){for(let e of ["../assets","../../assets"]){let t=join(ct,e,"static","vendor");if(existsSync(join(t,"highlight.min.js")))return t}return null}var _e={light:{keyword:"#0033b3",string:"#067d17",comment:"#5f6672",number:"#1750eb",title:"#00627a",type:"#871094",attr:"#374151",meta:"#7a6a00"},dark:{keyword:"#cf8e6d",string:"#6aab73",comment:"#8b8f96",number:"#2aacb8",title:"#56a8f5",type:"#c77dbb",attr:"#bcbec4",meta:"#b3ae60"}};function Se(e){return Object.entries(e).map(([t,n])=>`--syn-${t}:${n}`).join(";")}function dt(){let e=Se(_e.light),t=Se(_e.dark);return [`:where(:root){${e}}`,`@media (prefers-color-scheme: dark){:where(:root:not([data-theme="light"])){${t}}}`,`:where(:root[data-theme="dark"]){${t}}`,"pre code.hljs{display:block;background:transparent;color:inherit;padding:0}",".hljs-comment,.hljs-quote{color:var(--syn-comment);font-style:italic}",".hljs-keyword,.hljs-selector-tag,.hljs-literal,.hljs-doctag{color:var(--syn-keyword)}",".hljs-string,.hljs-regexp,.hljs-addition,.hljs-selector-attr,.hljs-selector-pseudo{color:var(--syn-string)}",".hljs-number,.hljs-symbol,.hljs-bullet,.hljs-link{color:var(--syn-number)}",".hljs-title,.hljs-section,.hljs-name,.hljs-selector-id,.hljs-selector-class{color:var(--syn-title)}",".hljs-type,.hljs-built_in,.hljs-title.class_,.hljs-tag{color:var(--syn-type)}",".hljs-attr,.hljs-attribute,.hljs-variable,.hljs-template-variable,.hljs-property,.hljs-params{color:var(--syn-attr)}",".hljs-meta,.hljs-deletion,.hljs-subst{color:var(--syn-meta)}",".hljs-emphasis{font-style:italic}.hljs-strong{font-weight:bold}"].join("")}var mt=';(function(){function run(){hljs.configure({ignoreUnescapedHTML:true});document.querySelectorAll("pre code").forEach(function(el){if(el.dataset.highlighted||el.classList.contains("nohighlight"))return;hljs.highlightElement(el);});}if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",run);else run();})();',T;function ye(){if(T!==void 0)return T;T=null;try{let e=pt();if(e===null)return T;let t=readFileSync(join(e,"highlight.min.js"),"utf8"),n=readFileSync(join(e,"highlight.LICENSE.txt"),"utf8").trim();if(/<\/script/i.test(t)||n.includes("*/"))return T;T=`<style data-devcoach="highlighter">${dt()}</style>
3
3
  <script data-devcoach="highlighter">/*
4
4
  Syntax highlighting by highlight.js, inlined by devcoach.
5
5
 
6
6
  ${n}
7
7
  */
8
8
  ${t}
9
- ${mt}</script>`;}catch{T=null;}return T}function gt(e){return ut.test(e)}function ht(e){return /<pre[\s>][\s\S]*?<code[\s>]/.test(e)}function Te(e){if(gt(e)||!Ee.test(e)||!ht(e))return null;let t=ye();return t===null?null:e.replace(Ee,()=>t)}function be(e){let t=ye();return t===null||!e.includes(t)?e:e.replace(t,()=>lt)}var X=z$1.enum(["junior","mid","senior"]),B=z$1.enum(["github","gitlab","bitbucket","local"]),ft=z$1.enum(["know","understood","dont_know"]),qn=z$1.number().int().min(0).max(10);function W(e){return e.toISOString().replace(/\.\d{3}Z$/,"Z")}function Et(e){if(e instanceof Date){if(Number.isNaN(e.getTime()))throw new TypeError("Invalid Date");return e}let t=e.trim();/[zZ]$|[+-]\d{2}:?\d{2}$/.test(t)||(t=t.includes("T")||t.includes(" ")?`${t.replace(" ","T")}Z`:`${t}T00:00:00Z`);let s=new Date(t);if(Number.isNaN(s.getTime()))throw new TypeError(`Cannot parse timestamp ${JSON.stringify(e)} \u2014 expected ISO 8601`);return s}function _t(e){let t=Et(e),n=new Date;return t.getTime()>n.getTime()&&(t=n),W(t)}var St=z$1.union([z$1.string(),z$1.date()]).transform((e,t)=>{try{return _t(e)}catch(n){return t.addIssue({code:"custom",message:n.message}),z$1.NEVER}}),S=z$1.string().nullish().transform(e=>e??null),yt=z$1.object({id:z$1.string(),timestamp:St,topic_id:z$1.string(),categories:z$1.array(z$1.string()),title:z$1.string(),level:X,summary:z$1.string(),body:S,task_context:S,project:S,repository:S,branch:S,commit_hash:S,folder:S,repository_platform:B.nullish().transform(e=>e??null),starred:z$1.boolean().default(false),feedback:ft.nullish().transform(e=>e??null),imported:z$1.boolean().default(false),shared_by:S}),Tt=z$1.object({topic:z$1.string(),confidence:z$1.number().int()}),bt=z$1.object({name:z$1.string(),topics:z$1.array(z$1.string())});z$1.object({knowledge:z$1.array(Tt),groups:z$1.array(bt)});var Rt=z$1.enum(["system","dark","light"]),Lt=z$1.enum(["auto","lessons","knowledge"]),Nt=z$1.enum(["session","global"]);z$1.object({max_per_day:z$1.number().int().default(2),min_gap_minutes:z$1.number().int().default(240),ui_theme:Rt.default("system"),ui_home:Lt.default("auto"),nudge_every:z$1.number().int().min(0).default(10),nudge_scope:Nt.default("session"),share_name:z$1.string().nullable().default(null)});var Ot=z$1.enum(["active","completed","abandoned"]),xt=z$1.enum(["concept","example","practice","check"]),wt=z$1.enum(["todo","done","skipped"]),Dt=z$1.object({concept:z$1.string().min(1),known:z$1.boolean()}),K=z$1.object({course_id:z$1.string(),position:z$1.number().int().min(1),title:z$1.string().min(1),kind:xt,anchor:z$1.string().regex(/^[a-z0-9][a-z0-9-]*$/),status:wt.default("todo"),done_at:S}),G=z$1.object({id:z$1.string().regex(/^[a-z0-9-]+$/),lesson_id:S,topic_id:z$1.string().min(1),title:z$1.string().min(1),goal:S,prerequisites:z$1.array(Dt).default([]),status:Ot.default("active"),created_at:z$1.string(),updated_at:z$1.string(),completed_at:S});function O(e){return yt.parse(e)}var {DatabaseSync:Ut}=createRequire(import.meta.url)("node:sqlite"),ne=process.env.DEVCOACH_DIR??join(homedir(),".devcoach"),se=join(ne,"coaching.db"),U=join(ne,"learning-state.md"),Ce=join(ne,"courses"),Y="settings.json",z="lessons.json",q="knowledge.json",V="learning-state.md",J="courses.json",M="index.html",Mt=new RegExp(`^courses/([a-z0-9-]+)/${M.replace(".","\\.")}$`),as={engineering:8,architecture:8,patterns:7,debugging:8,node:7,javascript:7,typescript:6,python:4,django:3,fastapi:4,docker:8,traefik:7,coolify:7,postgresql:6,redis:6,git:7,ci_cd:6,security:5,performance:6,testing:5,linux:7,networking:6,react:5,frontend:5},Ft=80;function $t(e){return e.trim().slice(0,Ft)}var jt={max_per_day:"2",min_gap_minutes:"240",ui_theme:"system",ui_home:"auto",nudge_every:"10",nudge_scope:"session",share_name:""},Pt=50;function f(e,t,...n){return e.prepare(t).all(...n)}function E(e,t,...n){return e.prepare(t).get(...n)}function u(e,t,...n){return Number(e.prepare(t).run(...n).changes)}function Le(e=se,t=false){e!==":memory:"&&mkdirSync(dirname(e),{recursive:true});let n=new Ut(e,{readOnly:t});return n.exec("PRAGMA busy_timeout = 3000"),n}var v=6,Ht="2.6.0",Z="schema_app_version",Q="schema_min_app_version";function Xt(e){let t=e.upgradedBy?`devcoach ${e.upgradedBy}`:"a newer devcoach",n=e.minApp?`devcoach \u2265 ${e.minApp}`:"a newer devcoach";return `The coaching database was upgraded by ${t} (schema v${e.dbSchema}); this is devcoach ${N} (schema v${e.appSchema}). Reads work; writes need ${n} \u2014 update this install, or use the newer channel.`}var x=class extends Error{constructor(n){super(Xt(n));this.compat=n;this.name="SchemaTooNewError";}compat},Ie=new WeakMap;function Ae(e){return Ie.get(e)??null}function Bt(e){try{let t=f(e,"SELECT key, value FROM settings WHERE key IN (?, ?)",Z,Q),n=s=>t.find(o=>o.key===s)?.value??null;return {upgradedBy:n(Z),minApp:n(Q)}}catch{return {upgradedBy:null,minApp:null}}}function Wt(e){let t=e.prepare("PRAGMA user_version").get();return Number(t?.user_version??0)}function Kt(e=se){let t=Le(e),n=Wt(t);return n>v?(t.close(),t=Le(e,true),t.exec("PRAGMA query_only = 1"),Ie.set(t,{readOnly:true,dbSchema:n,appSchema:v,...Bt(t)}),t):(n<v&&(Yt(t,n),t.exec(`PRAGMA user_version = ${v}`)),t)}function ke(e,t){let n=Ae(e);if(n)throw new x(n);e.exec("BEGIN IMMEDIATE");try{let s=t();return e.exec("COMMIT"),s}catch(s){try{e.exec("ROLLBACK");}catch{}throw s}}function cs(e,t=se){let n=Kt(t);try{return e(n)}catch(s){let o=Ae(n);throw o&&!(s instanceof x)&&/readonly database/i.test(String(s))?new x(o):s}finally{n.close();}}function Gt(e,t){try{return e.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?").get(t)!==void 0}catch{return false}}function Yt(e,t=0){let n=Gt(e,"courses");e.exec(`
9
+ ${mt}</script>`;}catch{T=null;}return T}function gt(e){return ut.test(e)}function ht(e){return /<pre[\s>][\s\S]*?<code[\s>]/.test(e)}function Te(e){if(gt(e)||!Ee.test(e)||!ht(e))return null;let t=ye();return t===null?null:e.replace(Ee,()=>t)}function be(e){let t=ye();return t===null||!e.includes(t)?e:e.replace(t,()=>lt)}var X=z$1.enum(["junior","mid","senior"]),B=z$1.enum(["github","gitlab","bitbucket","local"]),ft=z$1.enum(["know","understood","dont_know"]),qn=z$1.number().int().min(0).max(10);function W(e){return e.toISOString().replace(/\.\d{3}Z$/,"Z")}function Et(e){if(e instanceof Date){if(Number.isNaN(e.getTime()))throw new TypeError("Invalid Date");return e}let t=e.trim();/[zZ]$|[+-]\d{2}:?\d{2}$/.test(t)||(t=t.includes("T")||t.includes(" ")?`${t.replace(" ","T")}Z`:`${t}T00:00:00Z`);let s=new Date(t);if(Number.isNaN(s.getTime()))throw new TypeError(`Cannot parse timestamp ${JSON.stringify(e)} \u2014 expected ISO 8601`);return s}function _t(e){let t=Et(e),n=new Date;return t.getTime()>n.getTime()&&(t=n),W(t)}var St=z$1.union([z$1.string(),z$1.date()]).transform((e,t)=>{try{return _t(e)}catch(n){return t.addIssue({code:"custom",message:n.message}),z$1.NEVER}}),S=z$1.string().nullish().transform(e=>e??null),yt=z$1.object({id:z$1.string(),timestamp:St,topic_id:z$1.string(),categories:z$1.array(z$1.string()),title:z$1.string(),level:X,summary:z$1.string(),body:S,task_context:S,project:S,repository:S,branch:S,commit_hash:S,folder:S,repository_platform:B.nullish().transform(e=>e??null),starred:z$1.boolean().default(false),feedback:ft.nullish().transform(e=>e??null),imported:z$1.boolean().default(false),shared_by:S}),Tt=z$1.object({topic:z$1.string(),confidence:z$1.number().int()}),bt=z$1.object({name:z$1.string(),topics:z$1.array(z$1.string())});z$1.object({knowledge:z$1.array(Tt),groups:z$1.array(bt)});var Rt=z$1.enum(["system","dark","light"]),Lt=z$1.enum(["auto","lessons","knowledge"]),Nt=z$1.enum(["session","global"]);z$1.object({max_per_day:z$1.number().int().default(2),min_gap_minutes:z$1.number().int().default(240),ui_theme:Rt.default("system"),ui_home:Lt.default("auto"),nudge_every:z$1.number().int().min(0).default(10),nudge_scope:Nt.default("session"),share_name:z$1.string().nullable().default(null)});var Ot=z$1.enum(["active","completed","abandoned"]),xt=z$1.enum(["concept","example","practice","check"]),wt=z$1.enum(["todo","done","skipped"]),Dt=z$1.object({concept:z$1.string().min(1),known:z$1.boolean()}),K=z$1.object({course_id:z$1.string(),position:z$1.number().int().min(1),title:z$1.string().min(1),kind:xt,anchor:z$1.string().regex(/^[a-z0-9][a-z0-9-]*$/),status:wt.default("todo"),done_at:S}),G=z$1.object({id:z$1.string().regex(/^[a-z0-9-]+$/),lesson_id:S,topic_id:z$1.string().min(1),title:z$1.string().min(1),goal:S,prerequisites:z$1.array(Dt).default([]),status:Ot.default("active"),created_at:z$1.string(),updated_at:z$1.string(),completed_at:S});function O(e){return yt.parse(e)}var {DatabaseSync:Ut}=createRequire(import.meta.url)("node:sqlite"),ne=process.env.DEVCOACH_DIR??join(homedir(),".devcoach"),se=join(ne,"coaching.db"),U=join(ne,"learning-state.md"),Ce=join(ne,"courses"),Y="settings.json",z="lessons.json",q="knowledge.json",V="learning-state.md",J="courses.json",M="index.html",Mt=new RegExp(`^courses/([a-z0-9-]+)/${M.replace(".","\\.")}$`),as={engineering:8,architecture:8,patterns:7,debugging:8,node:7,javascript:7,typescript:6,python:4,django:3,fastapi:4,docker:8,traefik:7,coolify:7,postgresql:6,redis:6,git:7,ci_cd:6,security:5,performance:6,testing:5,linux:7,networking:6,react:5,frontend:5},Ft=80;function $t(e){return e.trim().slice(0,Ft)}var jt={max_per_day:"2",min_gap_minutes:"240",ui_theme:"system",ui_home:"auto",nudge_every:"10",nudge_scope:"session",share_name:""},Pt=50;function f(e,t,...n){return e.prepare(t).all(...n)}function E(e,t,...n){return e.prepare(t).get(...n)}function u(e,t,...n){return Number(e.prepare(t).run(...n).changes)}function Le(e=se,t=false){e!==":memory:"&&mkdirSync(dirname(e),{recursive:true});let n=new Ut(e,{readOnly:t});return n.exec("PRAGMA busy_timeout = 3000"),n}var v=6,Ht="2.6.0",Z="schema_app_version",Q="schema_min_app_version";function Xt(e){let t=e.upgradedBy?`devcoach ${e.upgradedBy}`:"a newer devcoach",n=e.minApp?`devcoach \u2265 ${e.minApp}`:"a newer devcoach";return `The coaching database was upgraded by ${t} (schema v${e.dbSchema}); this is devcoach ${N} (schema v${e.appSchema}). Reads work; writes need ${n} \u2014 update this install, or use the newer channel.`}var x=class extends Error{constructor(n){super(Xt(n));this.compat=n;this.name="SchemaTooNewError";}compat},Ie=new WeakMap;function ke(e){return Ie.get(e)??null}function Bt(e){try{let t=f(e,"SELECT key, value FROM settings WHERE key IN (?, ?)",Z,Q),n=s=>t.find(o=>o.key===s)?.value??null;return {upgradedBy:n(Z),minApp:n(Q)}}catch{return {upgradedBy:null,minApp:null}}}function Wt(e){let t=e.prepare("PRAGMA user_version").get();return Number(t?.user_version??0)}function Kt(e=se){let t=Le(e),n=Wt(t);return n>v?(t.close(),t=Le(e,true),t.exec("PRAGMA query_only = 1"),Ie.set(t,{readOnly:true,dbSchema:n,appSchema:v,...Bt(t)}),t):(n<v&&(Yt(t,n),t.exec(`PRAGMA user_version = ${v}`)),t)}function Ae(e,t){let n=ke(e);if(n)throw new x(n);e.exec("BEGIN IMMEDIATE");try{let s=t();return e.exec("COMMIT"),s}catch(s){try{e.exec("ROLLBACK");}catch{}throw s}}function cs(e,t=se){let n=Kt(t);try{return e(n)}catch(s){let o=ke(n);throw o&&!(s instanceof x)&&/readonly database/i.test(String(s))?new x(o):s}finally{n.close();}}function Gt(e,t){try{return e.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?").get(t)!==void 0}catch{return false}}function Yt(e,t=0){let n=Gt(e,"courses");e.exec(`
10
10
  CREATE TABLE IF NOT EXISTS lessons (
11
11
  id TEXT PRIMARY KEY,
12
12
  timestamp TEXT NOT NULL,
@@ -108,11 +108,11 @@ ${mt}</script>`;}catch{T=null;}return T}function gt(e){return ut.test(e)}functio
108
108
  CREATE INDEX IF NOT EXISTS idx_lessons_feedback ON lessons (feedback);
109
109
  CREATE INDEX IF NOT EXISTS idx_lessons_topic_id ON lessons (topic_id);
110
110
  `),Vt(e,t,n),Jt(e),y(e,Z,N),y(e,Q,Ht);}var Ne="v4-feedback-understood";function zt(e,t){return e.prepare("SELECT 1 FROM schema_migrations WHERE name = ?").get(t)!==void 0}function qt(e,t){u(e,"INSERT OR IGNORE INTO schema_migrations (name, applied_at) VALUES (?, ?)",t,new Date().toISOString());}function Vt(e,t,n){for(let s of ["ALTER TABLE lessons ADD COLUMN body TEXT","ALTER TABLE lessons ADD COLUMN imported INTEGER NOT NULL DEFAULT 0","ALTER TABLE lessons ADD COLUMN shared_by TEXT"])try{e.exec(s);}catch{}zt(e,Ne)||(t<4&&!n&&e.exec("UPDATE lessons SET feedback = 'understood' WHERE feedback = 'dont_know'"),qt(e,Ne));}function Jt(e){for(let[t,n]of Object.entries(jt))u(e,"INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)",t,n);}var ve="(id, timestamp, topic_id, categories, title, level, summary, body, task_context, project, repository, branch, commit_hash, folder, repository_platform, starred, feedback, imported, shared_by)",Ue="(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";function Me(e){return [e.id,e.timestamp,e.topic_id,JSON.stringify(e.categories),e.title,e.level,e.summary,e.body,e.task_context,e.project,e.repository,e.branch,e.commit_hash,e.folder,e.repository_platform,e.starred?1:0,e.feedback,e.imported?1:0,e.shared_by]}function ls(e,t){u(e,`INSERT OR REPLACE INTO lessons ${ve} VALUES ${Ue}`,...Me(t));}function oe(e,t){return u(e,`INSERT OR IGNORE INTO lessons ${ve} VALUES ${Ue}`,...Me(t))>0}var Zt=[["project","project"],["repository","repository"],["branch","branch"],["commit","commit_hash"]];function Qt(e,t){if(e.date_from==null&&e.date_to==null){let n=fn(e.period??null);n!=null&&t("timestamp >= ?",n);return}if(e.date_from!=null&&t("timestamp >= ?",e.date_from),e.date_to!=null){let n=e.date_to.includes("T")||e.date_to.includes(" ");t("timestamp <= ?",n?e.date_to:`${e.date_to}T23:59:59`);}}function Fe(e){let t=[],n=[],s=(r,...i)=>{t.push(r),n.push(...i);};Qt(e,s),e.category!=null&&s("categories LIKE ?",`%"${e.category}"%`),e.level!=null&&s("level = ?",e.level);for(let[r,i]of Zt){let a=e[r];a!=null&&s(`${i} LIKE ?`,`%${a}%`);}if(e.starred!=null&&s("starred = ?",e.starred?1:0),e.imported!=null&&s("imported = ?",e.imported?1:0),e.shared_by!=null&&s("imported = 1 AND shared_by = ?",e.shared_by),e.search!=null)for(let r of e.search.split(/\s+/).filter(Boolean)){let i=`%${r}%`;s("(title LIKE ? OR topic_id LIKE ? OR summary LIKE ? OR body LIKE ? OR shared_by LIKE ?)",i,i,i,i,i);}return e.feedback==="none"?s("feedback IS NULL"):e.feedback!=null&&s("feedback = ?",e.feedback),{where:t.length?`WHERE ${t.join(" AND ")}`:"",params:n}}function re(e,t={}){let{where:n,params:s}=Fe(t),o=E(e,`SELECT COUNT(*) AS n FROM lessons ${n}`,...s);return Number(o?.n??0)}var en=new Set(["timestamp","level","topic_id","title","feedback"]),tn=new Set(["project","repository","branch","commit_hash","repository_platform"]);function us(e,t={}){let{where:n,params:s}=Fe(t),o=t.sort&&en.has(t.sort)?t.sort:"timestamp",r=(t.order??"desc").toLowerCase()==="asc"?"ASC":"DESC",i=`SELECT * FROM lessons ${n} ORDER BY ${o} ${r}`,a=[...s];if(t.page!=null){let l=t.per_page??25;i+=" LIMIT ? OFFSET ?",a.push(l,(t.page-1)*l);}return f(e,i,...a).map(me)}function ps(e,t){return u(e,"DELETE FROM lessons WHERE id = ?",t)>0}function ds(e,t){if(t.length===0)return 0;let n=t.map(()=>"?").join(", ");return u(e,`DELETE FROM lessons WHERE id IN (${n})`,...t)}function ms(e,t,n){return u(e,"UPDATE lessons SET starred = ? WHERE id = ?",n?1:0,t)>0}function $e(e,t,n){u(e,"UPDATE lessons SET feedback = ? WHERE id = ?",n||null,t);let s=E(e,"SELECT topic_id FROM lessons WHERE id = ?",t);return s?s.topic_id:null}function nn(e){return f(e,"SELECT * FROM lessons ORDER BY timestamp DESC").map(me)}function ie(e,t){let n=0,s=0,o=0;for(let r of t){let i;try{i=O(r);}catch{o+=1;continue}oe(e,i)?n+=1:s+=1;}return {inserted:n,duplicated:s,invalid:o}}function gs(e,t){if(!tn.has(t))throw new Error(`Column not allowed: ${JSON.stringify(t)}`);return f(e,`SELECT DISTINCT ${t} FROM lessons WHERE ${t} IS NOT NULL ORDER BY ${t}`).map(s=>s[t])}function hs(e){return f(e,"SELECT DISTINCT shared_by FROM lessons WHERE imported = 1 AND shared_by IS NOT NULL ORDER BY shared_by").map(t=>t.shared_by)}function je(e,t){let n=E(e,"SELECT * FROM lessons WHERE id = ?",t);return n?me(n):null}function fs(e){return f(e,"SELECT DISTINCT value FROM lessons, json_each(lessons.categories) ORDER BY value").map(n=>n.value)}function Pe(e){return f(e,"SELECT DISTINCT topic_id FROM lessons").map(t=>t.topic_id)}function $(e,t){let n=E(e,"SELECT COUNT(*) AS n FROM lessons WHERE timestamp >= ? AND imported = 0",t);return Number(n?.n??0)}function He(e){let t=E(e,"SELECT timestamp FROM lessons WHERE imported = 0 ORDER BY timestamp DESC LIMIT 1");return t?t.timestamp:null}function w(e){let t={};for(let n of f(e,"SELECT topic, confidence FROM knowledge"))t[n.topic]=Number(n.confidence);return t}function ae(e){return f(e,"SELECT topic, confidence FROM knowledge ORDER BY topic").map(t=>({topic:t.topic,confidence:Number(t.confidence)}))}function ce(e){let t=f(e,"SELECT group_name FROM knowledge_group_names ORDER BY group_name").map(s=>s.group_name),n=new Map;for(let s of f(e,"SELECT group_name, topic FROM knowledge_groups ORDER BY group_name, topic")){let o=s.group_name,r=n.get(o)??[];r.push(s.topic),n.set(o,r);}return t.map(s=>({name:s,topics:n.get(s)??[]}))}function F(e,t,n){let s=Math.max(0,Math.min(10,n)),o=new Date().toISOString();u(e,`INSERT INTO knowledge (topic, confidence, updated_at) VALUES (?, ?, ?)
111
- ON CONFLICT(topic) DO UPDATE SET confidence = excluded.confidence, updated_at = excluded.updated_at`,t,s,o);}function Es(e,t){let n=u(e,"DELETE FROM knowledge WHERE topic = ?",t);return u(e,"DELETE FROM knowledge_groups WHERE topic = ?",t),n>0}function Oe(e,t){let n=t.trim();if(!n)throw new Error("Group name must not be empty");return u(e,"INSERT OR IGNORE INTO knowledge_group_names (group_name) VALUES (?)",n)>0}function _s(e,t){return u(e,"DELETE FROM knowledge_groups WHERE group_name = ?",t),u(e,"DELETE FROM knowledge_group_names WHERE group_name = ?",t)>0}function sn(e,t,n){u(e,"INSERT OR IGNORE INTO knowledge_group_names (group_name) VALUES (?)",n),u(e,"DELETE FROM knowledge_groups WHERE topic = ?",t),u(e,"INSERT OR IGNORE INTO knowledge_groups (group_name, topic) VALUES (?, ?)",n,t);}function Ss(e,t){u(e,"DELETE FROM knowledge_groups WHERE topic = ?",t);}function D(e){let t={};for(let A of f(e,"SELECT key, value FROM settings"))t[A.key]=A.value;let n=t.min_gap_minutes,s=t.min_hours_between,o;n!==void 0?o=Number.parseInt(n,10):s!==void 0?o=Number.parseInt(s,10)*60:o=240;let r=t.max_per_day,i=t.ui_theme??"system",a=i==="dark"||i==="light"||i==="system"?i:"system",l=t.ui_home??"auto",p=l==="lessons"||l==="knowledge"||l==="auto"?l:"auto",d=t.nudge_every,m=d!==void 0?Number.parseInt(d,10):10,_=t.nudge_scope==="global"?"global":"session";return {max_per_day:r!==void 0?Number.parseInt(r,10):2,min_gap_minutes:o,ui_theme:a,ui_home:p,nudge_every:Number.isFinite(m)&&m>=0?m:10,nudge_scope:_,share_name:t.share_name?.trim()||null}}function y(e,t,n){u(e,"INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value",t,n);}function Xe(e,t,n){let s=E(e,"INSERT INTO nudge_state (session_id, interactions, updated_at) VALUES (?, 1, ?) ON CONFLICT(session_id) DO UPDATE SET interactions = interactions + 1, updated_at = excluded.updated_at RETURNING interactions",t,new Date().toISOString()),o=Number(s?.interactions??0);return o===1&&u(e,"DELETE FROM nudge_state WHERE session_id NOT IN (SELECT session_id FROM nudge_state ORDER BY updated_at DESC LIMIT ?)",Pt),n==="global"?le(e,t,n):o}function ys(e){return f(e,"SELECT session_id, interactions, updated_at FROM nudge_state ORDER BY updated_at DESC").map(t=>({session_id:String(t.session_id),interactions:Number(t.interactions),updated_at:String(t.updated_at)}))}function le(e,t,n){let s=n==="global"?E(e,"SELECT COALESCE(SUM(interactions), 0) AS n FROM nudge_state"):E(e,"SELECT interactions AS n FROM nudge_state WHERE session_id = ?",t);return Number(s?.n??0)}function Ts(e){u(e,"DELETE FROM nudge_state"),u(e,"UPDATE cue_state SET pending = 0 WHERE id = 1");}var ue=3;function pe(e){let t=E(e,"SELECT pending, last_cue_at, last_skip_reason FROM cue_state WHERE id = 1");return {pending:Number(t?.pending??0)===1,last_cue_at:t?.last_cue_at??null,last_skip_reason:t?.last_skip_reason??null}}function Be(e){u(e,"INSERT INTO cue_state (id, pending, last_cue_at) VALUES (1, 1, ?) ON CONFLICT(id) DO UPDATE SET pending = 1, last_cue_at = excluded.last_cue_at",new Date().toISOString()),u(e,"DELETE FROM nudge_state");}function bs(e,t){u(e,"INSERT INTO cue_state (id, pending, last_skip_reason) VALUES (1, 0, ?) ON CONFLICT(id) DO UPDATE SET pending = 0, last_skip_reason = excluded.last_skip_reason",t??null),u(e,"DELETE FROM nudge_state");}function de(e){let t=E(e,"SELECT COUNT(*) AS n FROM knowledge");return {knowledge_ready:Number(t?.n??0)>0}}function Rs(e){let t={};for(let n of ["project","repository","branch","repository_platform"]){let s=E(e,`SELECT ${n} AS v, COUNT(*) AS c FROM lessons WHERE ${n} IS NOT NULL AND imported = 0 GROUP BY ${n} ORDER BY c DESC LIMIT 1`);t[n]=s?s.v:null;}return t}function We(e){return G.parse({...e,prerequisites:JSON.parse(String(e.prerequisites??"[]"))})}function on(e){return K.parse(e)}function rn(e,t){u(e,"INSERT INTO courses (id, lesson_id, topic_id, title, goal, prerequisites, status, created_at, updated_at, completed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",t.id,t.lesson_id,t.topic_id,t.title,t.goal,JSON.stringify(t.prerequisites),t.status,t.created_at,t.updated_at,t.completed_at);}function an(e,t){let n=E(e,"SELECT * FROM courses WHERE id = ?",t);return n?We(n):null}function cn(e,t={}){let n=[],s=[];t.status!=null&&(n.push("status = ?"),s.push(t.status)),t.lesson_id!=null&&(n.push("lesson_id = ?"),s.push(t.lesson_id));let o=`SELECT * FROM courses ${n.length?`WHERE ${n.join(" AND ")}`:""} ORDER BY updated_at DESC`;return f(e,o,...s).map(We)}function ln(e,t){return f(e,"SELECT * FROM course_steps WHERE course_id = ? ORDER BY position",t).map(on)}function Ls(e,t,n){u(e,"UPDATE course_steps SET position = position + ? WHERE course_id = ? AND position >= ?",1e5,t,n),u(e,"UPDATE course_steps SET position = position - ? WHERE course_id = ? AND position >= ?",99999,t,1e5);}function un(e,t){u(e,"INSERT INTO course_steps (course_id, position, title, kind, anchor, status, done_at) VALUES (?, ?, ?, ?, ?, ?, ?)",t.course_id,t.position,t.title,t.kind,t.anchor,t.status,t.done_at);}function Ns(e,t,n,s,o){return u(e,"UPDATE course_steps SET status = ?, done_at = ? WHERE course_id = ? AND position = ?",s,o,t,n)>0}function Os(e,t,n,s){return u(e,"UPDATE courses SET status = ?, updated_at = ?, completed_at = CASE WHEN ? = 'completed' THEN ? ELSE NULL END WHERE id = ?",n,s,n,s,t)>0}function xs(e,t,n){u(e,"UPDATE courses SET updated_at = ? WHERE id = ?",n,t);}function ws(e,t){return u(e,"DELETE FROM course_steps WHERE course_id = ?",t),u(e,"DELETE FROM courses WHERE id = ?",t)>0}function Ds(e){return E(e,"SELECT 1 AS one FROM courses WHERE status = 'active' LIMIT 1")!==void 0}function Cs(e){let t=D(e),n=nn(e),s=ae(e),o=ce(e),r=new Map;for(let p of o)for(let d of p.topics)r.set(d,p.name);let i={groups:o.map(p=>p.name),topics:s.map(p=>({topic:p.topic,confidence:p.confidence,group:r.get(p.topic)??null}))},a={[Y]:strToU8(JSON.stringify(t,null,2)),[z]:strToU8(JSON.stringify(n,null,2)),[q]:strToU8(JSON.stringify(i,null,2))};existsSync(U)&&(a[V]=strToU8(readFileSync(U,"utf8")));let l=cn(e);if(l.length){let p=l.flatMap(d=>ln(e,d.id));a[J]=strToU8(JSON.stringify({courses:l,steps:p},null,2));for(let d of l){let m=join(Ce,d.id,M);try{let _=lstatSync(m);_.isFile()&&_.size<=ee&&(a[`courses/${d.id}/${M}`]=strToU8(be(readFileSync(m,"utf8"))));}catch{}}}return zipSync(a)}var ee=2*1024*1024;function pn(e,t,n){if(!(Y in t))return;let s=JSON.parse(strFromU8(t[Y]));"max_per_day"in s&&y(e,"max_per_day",String(s.max_per_day)),"min_gap_minutes"in s&&y(e,"min_gap_minutes",String(s.min_gap_minutes)),(s.ui_theme==="system"||s.ui_theme==="dark"||s.ui_theme==="light")&&y(e,"ui_theme",s.ui_theme),(s.ui_home==="auto"||s.ui_home==="lessons"||s.ui_home==="knowledge")&&y(e,"ui_home",s.ui_home),"nudge_every"in s&&y(e,"nudge_every",String(s.nudge_every)),(s.nudge_scope==="session"||s.nudge_scope==="global")&&y(e,"nudge_scope",s.nudge_scope),typeof s.share_name=="string"&&y(e,"share_name",$t(s.share_name)),n.settings=1;}function dn(e,t,n){if(!(q in t))return;let s=JSON.parse(strFromU8(t[q]));if(!(s&&typeof s=="object"&&Array.isArray(s.topics))){for(let[r,i]of Object.entries(s))F(e,r,i);n.topics=Object.keys(s).length;return}let o=0;for(let r of s.groups??[])Oe(e,r)&&(o+=1);for(let r of s.topics){F(e,r.topic,r.confidence);let i=r.group;i&&i!=="Other"&&(Oe(e,i)&&(o+=1),sn(e,r.topic,i));}n.topics=s.topics.length,n.groups=o;}function mn(e,t,n){if(!(z in t))return;let s=JSON.parse(strFromU8(t[z])),{inserted:o,duplicated:r,invalid:i}=ie(e,s);n.lessons=o,n.skipped=r,n.invalid=i;}function gn(e,t){V in e&&(mkdirSync(dirname(U),{recursive:true}),writeFileSync(U,strFromU8(e[V]),"utf8"),t.learning_state=1);}function hn(e,t,n){if(!(J in t))return;let s=JSON.parse(strFromU8(t[J])),o=new Set;for(let r of s.courses??[])try{let i=G.parse(r);an(e,i.id)||(rn(e,i),n.courses+=1),o.add(i.id);}catch{n.invalid+=1;}for(let r of s.steps??[])try{let i=K.parse(r);if(!o.has(i.course_id))continue;E(e,"SELECT 1 AS one FROM course_steps WHERE course_id = ? AND position = ?",i.course_id,i.position)||un(e,i);}catch{n.invalid+=1;}for(let[r,i]of Object.entries(t)){let a=Mt.exec(r)?.[1];if(!a||!o.has(a)||i.length>ee)continue;let l=join(Ce,a),p=join(l,M);if(existsSync(p))continue;let d=Te(strFromU8(i)),m=d!==null&&Buffer.byteLength(d)<=ee;mkdirSync(l,{recursive:true}),writeFileSync(p,m?d:i);}}function Is(e,t){let n={settings:0,topics:0,groups:0,lessons:0,skipped:0,invalid:0,learning_state:0,courses:0},s=unzipSync(t);return pn(e,s,n),dn(e,s,n),mn(e,s,n),gn(s,n),hn(e,s,n),n}function fn(e){let t=new Date,n;if(e==="today")n=new Date(Date.UTC(t.getUTCFullYear(),t.getUTCMonth(),t.getUTCDate(),0,0,0,0));else if(e==="week")n=new Date(t.getTime()-7*864e5);else if(e==="month")n=new Date(t.getTime()-30*864e5);else if(e==="year")n=new Date(t.getTime()-365*864e5);else return null;return n.toISOString()}function me(e){let t=[];try{let n=JSON.parse(e.categories);Array.isArray(n)&&(t=n);}catch{t=[];}return O({...e,categories:t,starred:!!e.starred,imported:!!e.imported})}function Ke(e){let t=[...e].length,n=Math.max(34-t-2,2),s=Math.floor(n/2);return `### ${"\u2500".repeat(s)} ${e} ${"\u2500".repeat(n-s)}`}function Ge(e){let t=e.level.charAt(0).toUpperCase()+e.level.slice(1),n=e.categories.join(" \xB7 ");return [Ke("\u{1F393} devcoach"),`**${e.title}** \xB7 ${n} \xB7 ${t}`,"",e.body?.trim()||e.summary,"",Ke(`${e.topic_id} \xB7 ${e.level}`)].join(`
111
+ ON CONFLICT(topic) DO UPDATE SET confidence = excluded.confidence, updated_at = excluded.updated_at`,t,s,o);}function Es(e,t){let n=u(e,"DELETE FROM knowledge WHERE topic = ?",t);return u(e,"DELETE FROM knowledge_groups WHERE topic = ?",t),n>0}function Oe(e,t){let n=t.trim();if(!n)throw new Error("Group name must not be empty");return u(e,"INSERT OR IGNORE INTO knowledge_group_names (group_name) VALUES (?)",n)>0}function _s(e,t){return u(e,"DELETE FROM knowledge_groups WHERE group_name = ?",t),u(e,"DELETE FROM knowledge_group_names WHERE group_name = ?",t)>0}function sn(e,t,n){u(e,"INSERT OR IGNORE INTO knowledge_group_names (group_name) VALUES (?)",n),u(e,"DELETE FROM knowledge_groups WHERE topic = ?",t),u(e,"INSERT OR IGNORE INTO knowledge_groups (group_name, topic) VALUES (?, ?)",n,t);}function Ss(e,t){u(e,"DELETE FROM knowledge_groups WHERE topic = ?",t);}function D(e){let t={};for(let k of f(e,"SELECT key, value FROM settings"))t[k.key]=k.value;let n=t.min_gap_minutes,s=t.min_hours_between,o;n!==void 0?o=Number.parseInt(n,10):s!==void 0?o=Number.parseInt(s,10)*60:o=240;let r=t.max_per_day,i=t.ui_theme??"system",a=i==="dark"||i==="light"||i==="system"?i:"system",l=t.ui_home??"auto",p=l==="lessons"||l==="knowledge"||l==="auto"?l:"auto",d=t.nudge_every,m=d!==void 0?Number.parseInt(d,10):10,_=t.nudge_scope==="global"?"global":"session";return {max_per_day:r!==void 0?Number.parseInt(r,10):2,min_gap_minutes:o,ui_theme:a,ui_home:p,nudge_every:Number.isFinite(m)&&m>=0?m:10,nudge_scope:_,share_name:t.share_name?.trim()||null}}function y(e,t,n){u(e,"INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value",t,n);}function Xe(e,t,n){let s=E(e,"INSERT INTO nudge_state (session_id, interactions, updated_at) VALUES (?, 1, ?) ON CONFLICT(session_id) DO UPDATE SET interactions = interactions + 1, updated_at = excluded.updated_at RETURNING interactions",t,new Date().toISOString()),o=Number(s?.interactions??0);return o===1&&u(e,"DELETE FROM nudge_state WHERE session_id NOT IN (SELECT session_id FROM nudge_state ORDER BY updated_at DESC LIMIT ?)",Pt),n==="global"?le(e,t,n):o}function ys(e){return f(e,"SELECT session_id, interactions, updated_at FROM nudge_state ORDER BY updated_at DESC").map(t=>({session_id:String(t.session_id),interactions:Number(t.interactions),updated_at:String(t.updated_at)}))}function le(e,t,n){let s=n==="global"?E(e,"SELECT COALESCE(SUM(interactions), 0) AS n FROM nudge_state"):E(e,"SELECT interactions AS n FROM nudge_state WHERE session_id = ?",t);return Number(s?.n??0)}function Ts(e){u(e,"DELETE FROM nudge_state"),u(e,"UPDATE cue_state SET pending = 0 WHERE id = 1");}var ue=3;function pe(e){let t=E(e,"SELECT pending, last_cue_at, last_skip_reason FROM cue_state WHERE id = 1");return {pending:Number(t?.pending??0)===1,last_cue_at:t?.last_cue_at??null,last_skip_reason:t?.last_skip_reason??null}}function Be(e){u(e,"INSERT INTO cue_state (id, pending, last_cue_at) VALUES (1, 1, ?) ON CONFLICT(id) DO UPDATE SET pending = 1, last_cue_at = excluded.last_cue_at",new Date().toISOString()),u(e,"DELETE FROM nudge_state");}function bs(e,t){u(e,"INSERT INTO cue_state (id, pending, last_skip_reason) VALUES (1, 0, ?) ON CONFLICT(id) DO UPDATE SET pending = 0, last_skip_reason = excluded.last_skip_reason",t??null),u(e,"DELETE FROM nudge_state");}function de(e){let t=E(e,"SELECT COUNT(*) AS n FROM knowledge");return {knowledge_ready:Number(t?.n??0)>0}}function Rs(e){let t={};for(let n of ["project","repository","branch","repository_platform"]){let s=E(e,`SELECT ${n} AS v, COUNT(*) AS c FROM lessons WHERE ${n} IS NOT NULL AND imported = 0 GROUP BY ${n} ORDER BY c DESC LIMIT 1`);t[n]=s?s.v:null;}return t}function We(e){return G.parse({...e,prerequisites:JSON.parse(String(e.prerequisites??"[]"))})}function on(e){return K.parse(e)}function rn(e,t){u(e,"INSERT INTO courses (id, lesson_id, topic_id, title, goal, prerequisites, status, created_at, updated_at, completed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",t.id,t.lesson_id,t.topic_id,t.title,t.goal,JSON.stringify(t.prerequisites),t.status,t.created_at,t.updated_at,t.completed_at);}function an(e,t){let n=E(e,"SELECT * FROM courses WHERE id = ?",t);return n?We(n):null}function cn(e,t={}){let n=[],s=[];t.status!=null&&(n.push("status = ?"),s.push(t.status)),t.lesson_id!=null&&(n.push("lesson_id = ?"),s.push(t.lesson_id));let o=`SELECT * FROM courses ${n.length?`WHERE ${n.join(" AND ")}`:""} ORDER BY updated_at DESC`;return f(e,o,...s).map(We)}function ln(e,t){return f(e,"SELECT * FROM course_steps WHERE course_id = ? ORDER BY position",t).map(on)}function Ls(e,t,n){u(e,"UPDATE course_steps SET position = position + ? WHERE course_id = ? AND position >= ?",1e5,t,n),u(e,"UPDATE course_steps SET position = position - ? WHERE course_id = ? AND position >= ?",99999,t,1e5);}function un(e,t){u(e,"INSERT INTO course_steps (course_id, position, title, kind, anchor, status, done_at) VALUES (?, ?, ?, ?, ?, ?, ?)",t.course_id,t.position,t.title,t.kind,t.anchor,t.status,t.done_at);}function Ns(e,t,n,s,o){return u(e,"UPDATE course_steps SET status = ?, done_at = ? WHERE course_id = ? AND position = ?",s,o,t,n)>0}function Os(e,t,n,s){return u(e,"UPDATE courses SET status = ?, updated_at = ?, completed_at = CASE WHEN ? = 'completed' THEN ? ELSE NULL END WHERE id = ?",n,s,n,s,t)>0}function xs(e,t,n){u(e,"UPDATE courses SET updated_at = ? WHERE id = ?",n,t);}function ws(e,t){return u(e,"DELETE FROM course_steps WHERE course_id = ?",t),u(e,"DELETE FROM courses WHERE id = ?",t)>0}function Ds(e){return E(e,"SELECT 1 AS one FROM courses WHERE status = 'active' LIMIT 1")!==void 0}function Cs(e){let t=D(e),n=nn(e),s=ae(e),o=ce(e),r=new Map;for(let p of o)for(let d of p.topics)r.set(d,p.name);let i={groups:o.map(p=>p.name),topics:s.map(p=>({topic:p.topic,confidence:p.confidence,group:r.get(p.topic)??null}))},a={[Y]:strToU8(JSON.stringify(t,null,2)),[z]:strToU8(JSON.stringify(n,null,2)),[q]:strToU8(JSON.stringify(i,null,2))};existsSync(U)&&(a[V]=strToU8(readFileSync(U,"utf8")));let l=cn(e);if(l.length){let p=l.flatMap(d=>ln(e,d.id));a[J]=strToU8(JSON.stringify({courses:l,steps:p},null,2));for(let d of l){let m=join(Ce,d.id,M);try{let _=lstatSync(m);_.isFile()&&_.size<=ee&&(a[`courses/${d.id}/${M}`]=strToU8(be(readFileSync(m,"utf8"))));}catch{}}}return zipSync(a)}var ee=2*1024*1024;function pn(e,t,n){if(!(Y in t))return;let s=JSON.parse(strFromU8(t[Y]));"max_per_day"in s&&y(e,"max_per_day",String(s.max_per_day)),"min_gap_minutes"in s&&y(e,"min_gap_minutes",String(s.min_gap_minutes)),(s.ui_theme==="system"||s.ui_theme==="dark"||s.ui_theme==="light")&&y(e,"ui_theme",s.ui_theme),(s.ui_home==="auto"||s.ui_home==="lessons"||s.ui_home==="knowledge")&&y(e,"ui_home",s.ui_home),"nudge_every"in s&&y(e,"nudge_every",String(s.nudge_every)),(s.nudge_scope==="session"||s.nudge_scope==="global")&&y(e,"nudge_scope",s.nudge_scope),typeof s.share_name=="string"&&y(e,"share_name",$t(s.share_name)),n.settings=1;}function dn(e,t,n){if(!(q in t))return;let s=JSON.parse(strFromU8(t[q]));if(!(s&&typeof s=="object"&&Array.isArray(s.topics))){for(let[r,i]of Object.entries(s))F(e,r,i);n.topics=Object.keys(s).length;return}let o=0;for(let r of s.groups??[])Oe(e,r)&&(o+=1);for(let r of s.topics){F(e,r.topic,r.confidence);let i=r.group;i&&i!=="Other"&&(Oe(e,i)&&(o+=1),sn(e,r.topic,i));}n.topics=s.topics.length,n.groups=o;}function mn(e,t,n){if(!(z in t))return;let s=JSON.parse(strFromU8(t[z])),{inserted:o,duplicated:r,invalid:i}=ie(e,s);n.lessons=o,n.skipped=r,n.invalid=i;}function gn(e,t){V in e&&(mkdirSync(dirname(U),{recursive:true}),writeFileSync(U,strFromU8(e[V]),"utf8"),t.learning_state=1);}function hn(e,t,n){if(!(J in t))return;let s=JSON.parse(strFromU8(t[J])),o=new Set;for(let r of s.courses??[])try{let i=G.parse(r);an(e,i.id)||(rn(e,i),n.courses+=1),o.add(i.id);}catch{n.invalid+=1;}for(let r of s.steps??[])try{let i=K.parse(r);if(!o.has(i.course_id))continue;E(e,"SELECT 1 AS one FROM course_steps WHERE course_id = ? AND position = ?",i.course_id,i.position)||un(e,i);}catch{n.invalid+=1;}for(let[r,i]of Object.entries(t)){let a=Mt.exec(r)?.[1];if(!a||!o.has(a)||i.length>ee)continue;let l=join(Ce,a),p=join(l,M);if(existsSync(p))continue;let d=Te(strFromU8(i)),m=d!==null&&Buffer.byteLength(d)<=ee;mkdirSync(l,{recursive:true}),writeFileSync(p,m?d:i);}}function Is(e,t){let n={settings:0,topics:0,groups:0,lessons:0,skipped:0,invalid:0,learning_state:0,courses:0},s=unzipSync(t);return pn(e,s,n),dn(e,s,n),mn(e,s,n),gn(s,n),hn(e,s,n),n}function fn(e){let t=new Date,n;if(e==="today")n=new Date(Date.UTC(t.getUTCFullYear(),t.getUTCMonth(),t.getUTCDate(),0,0,0,0));else if(e==="week")n=new Date(t.getTime()-7*864e5);else if(e==="month")n=new Date(t.getTime()-30*864e5);else if(e==="year")n=new Date(t.getTime()-365*864e5);else return null;return n.toISOString()}function me(e){let t=[];try{let n=JSON.parse(e.categories);Array.isArray(n)&&(t=n);}catch{t=[];}return O({...e,categories:t,starred:!!e.starred,imported:!!e.imported})}function Ke(e){let t=[...e].length,n=Math.max(34-t-2,2),s=Math.floor(n/2);return `### ${"\u2500".repeat(s)} ${e} ${"\u2500".repeat(n-s)}`}function Ge(e){let t=e.level.charAt(0).toUpperCase()+e.level.slice(1),n=e.categories.join(" \xB7 ");return [Ke("\u{1F393} devcoach"),`**${e.title}** \xB7 ${n} \xB7 ${t}`,"",e.body?.trim()||e.summary,"",Ke(`${e.topic_id} \xB7 ${e.level}`)].join(`
112
112
  `)}var H="devcoach.lesson",I=1,Tn=`devcoach:lesson:${I}:`,bn="https://ultimaphoenix.github.io/dev-coach/lesson",Rn=".devcoach.md",Ln=256*1024,h=class extends Error{},C=z$1.string().nullish(),Nn=["task_context","project","branch","commit_hash"],On=new Set(["github","gitlab","bitbucket"]),ze=z$1.object({format:z$1.literal(H),version:z$1.literal(I),lesson:z$1.object({title:z$1.string().min(1),summary:z$1.string(),body:z$1.string().nullable(),topic_id:z$1.string().min(1),categories:z$1.array(z$1.string()),level:X,task_context:C,project:C,repository:C,branch:C,commit_hash:C,repository_platform:B.nullish()}),origin:z$1.object({id:z$1.string().min(1),timestamp:z$1.string()}),shared_by:z$1.string().nullable(),shared_at:z$1.string(),app_version:z$1.string()});function js(e,t){let n={title:e.title,summary:e.summary,body:e.body,topic_id:e.topic_id,categories:e.categories,level:e.level};if(t.includeContext){for(let s of Nn)e[s]!=null&&(n[s]=e[s]);e.repository_platform&&On.has(e.repository_platform)&&(e.repository!=null&&(n.repository=e.repository),n.repository_platform=e.repository_platform);}return {format:H,version:I,lesson:n,origin:{id:e.id,timestamp:e.timestamp},shared_by:t.sharedBy,shared_at:W(t.now??new Date),app_version:N}}function Ps(e){return e.anonymous?null:e.explicit!=null?e.explicit.trim()||null:e.setting?.trim()||e.gitUserName?.trim()||null}function qe(e){let t=deflateSync(strToU8(JSON.stringify(e)),{level:9});return Tn+Buffer.from(t).toString("base64url")}var Ve=/devcoach:lesson:(\d+):([A-Za-z0-9_-]+)/,xn=64e3,ge=512,P=class extends Error{};function wn(e,t){let n=[],s=0,o=new Inflate(a=>{if(s+=a.length,s>t)throw new P("too large");n.push(a);});for(let a=0;a<e.length;a+=ge)o.push(e.subarray(a,a+ge),a+ge>=e.length);let r=new Uint8Array(s),i=0;for(let a of n)r.set(a,i),i+=a.length;return r}function Dn(e){let t=new RegExp(`^${Ve.source}$`).exec(e.trim());if(!t)throw new h("That is not a devcoach lesson code.");if(Number(t[1])!==I)throw new h(`This lesson was shared by a newer devcoach (format ${t[1]}) \u2014 upgrade to import it.`);let n=t[2]??"";if(n.length>xn)throw new h("This lesson code is too large to be a lesson.");let s;try{let o=wn(new Uint8Array(Buffer.from(n,"base64url")),Ln);s=strFromU8(o);}catch(o){throw o instanceof P?new h("This lesson code is too large to be a lesson."):new h("This lesson code is damaged or incomplete \u2014 copy it again.")}return Je(s)}function Je(e){let t;try{t=JSON.parse(e);}catch{throw new h("This lesson code is damaged or incomplete \u2014 copy it again.")}let n=ze.safeParse(t);if(!n.success)throw new h("This is not a valid devcoach lesson.");return n.data}function Hs(e,t=qe(e)){let n=e.shared_by?`Shared by ${e.shared_by} with devcoach`:"Shared with devcoach";return [Ge(e.lesson),"",`${n} \u2014 paste it to your agent or run: devcoach import`,t].join(`
113
- `)}function Xs(e,t=qe(e)){return `${bn}#${t}`}function Cn(e,t="lesson"){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,60)||t}function Bs(e){return `${Cn(e.origin.id)}${Rn}`}var In=/^[A-Za-z0-9_.+:@/-]+$/,j=e=>In.test(e)?e:JSON.stringify(e),An=e=>`[${e.map(j).join(", ")}]`;function Ws(e){let t=e.lesson,n=["---",`format: ${H}`,`version: ${I}`,`title: ${JSON.stringify(t.title)}`,`topic_id: ${j(t.topic_id)}`,`level: ${t.level}`,`categories: ${An(t.categories)}`,`summary: ${JSON.stringify(t.summary)}`,`origin_id: ${j(e.origin.id)}`,`origin_timestamp: ${e.origin.timestamp}`,`shared_by: ${e.shared_by==null?"null":JSON.stringify(e.shared_by)}`,`shared_at: ${e.shared_at}`,`app_version: ${j(e.app_version)}`];for(let s of ["task_context","project","repository","branch","commit_hash","repository_platform"]){let o=t[s];o!=null&&n.push(`${s}: ${JSON.stringify(o)}`);}return n.push("---","",t.body??t.summary,""),n.join(`
114
- `)}function kn(e){let t=[],n="",s=false;for(let o=0;o<e.length;o++){let r=e[o]??"";s?(n+=r,r==="\\"?n+=e[++o]??"":r==='"'&&(s=false)):r==='"'?(s=true,n+=r):r===","?(t.push(n),n=""):n+=r;}return t.push(n),t}function Ze(e){let t=e.trim();if(t==="null"||t==="")return null;if(t.startsWith('"'))return JSON.parse(t);if(t.startsWith("[")&&t.endsWith("]")){let n=t.slice(1,-1).trim();return n?kn(n).map(s=>Ze(s)):[]}return t}function vn(e){let t=e.replace(/\r\n?/g,`
113
+ `)}function Xs(e,t=qe(e)){return `${bn}#${t}`}function Cn(e,t="lesson"){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,60)||t}function Bs(e){return `${Cn(e.origin.id)}${Rn}`}var In=/^[A-Za-z0-9_.+:@/-]+$/,j=e=>In.test(e)?e:JSON.stringify(e),kn=e=>`[${e.map(j).join(", ")}]`;function Ws(e){let t=e.lesson,n=["---",`format: ${H}`,`version: ${I}`,`title: ${JSON.stringify(t.title)}`,`topic_id: ${j(t.topic_id)}`,`level: ${t.level}`,`categories: ${kn(t.categories)}`,`summary: ${JSON.stringify(t.summary)}`,`origin_id: ${j(e.origin.id)}`,`origin_timestamp: ${e.origin.timestamp}`,`shared_by: ${e.shared_by==null?"null":JSON.stringify(e.shared_by)}`,`shared_at: ${e.shared_at}`,`app_version: ${j(e.app_version)}`];for(let s of ["task_context","project","repository","branch","commit_hash","repository_platform"]){let o=t[s];o!=null&&n.push(`${s}: ${JSON.stringify(o)}`);}return n.push("---","",t.body??t.summary,""),n.join(`
114
+ `)}function An(e){let t=[],n="",s=false;for(let o=0;o<e.length;o++){let r=e[o]??"";s?(n+=r,r==="\\"?n+=e[++o]??"":r==='"'&&(s=false)):r==='"'?(s=true,n+=r):r===","?(t.push(n),n=""):n+=r;}return t.push(n),t}function Ze(e){let t=e.trim();if(t==="null"||t==="")return null;if(t.startsWith('"'))return JSON.parse(t);if(t.startsWith("[")&&t.endsWith("]")){let n=t.slice(1,-1).trim();return n?An(n).map(s=>Ze(s)):[]}return t}function vn(e){let t=e.replace(/\r\n?/g,`
115
115
  `).split(`
116
116
  `);if(t[0]?.trim()!=="---")throw new h("Missing frontmatter (--- \u2026 ---).");let n=t.findIndex((a,l)=>l>0&&a.trim()==="---");if(n===-1)throw new h("Frontmatter never closes (missing ---).");let s={};for(let a of t.slice(1,n)){if(!a.trim()||a.trimStart().startsWith("#"))continue;let l=a.indexOf(":");if(l===-1)throw new h(`Cannot read frontmatter line: ${a.trim()}`);try{s[a.slice(0,l).trim()]=Ze(a.slice(l+1));}catch{throw new h(`Cannot read frontmatter line: ${a.trim()}`)}}let o=t.slice(n+1).join(`
117
117
  `).trim(),r={format:s.format,version:typeof s.version=="string"?Number(s.version):s.version,lesson:{title:s.title,summary:s.summary,body:o||null,topic_id:s.topic_id,categories:s.categories,level:s.level,task_context:s.task_context??void 0,project:s.project??void 0,repository:s.repository??void 0,branch:s.branch??void 0,commit_hash:s.commit_hash??void 0,repository_platform:s.repository_platform??void 0},origin:{id:s.origin_id,timestamp:s.origin_timestamp},shared_by:s.shared_by??null,shared_at:s.shared_at,app_version:s.app_version},i=ze.safeParse(r);if(!i.success)throw new h("This file is not a valid devcoach lesson.");return i.data}function Qe(e){let t=e.trim();if(!t)throw new h("Nothing to import \u2014 paste a lesson code, link or file.");if(t.startsWith("[")){let o=Ye(t);if(Array.isArray(o))return {kind:"lessons",records:o}}else if(t.startsWith("{")){let o=Ye(t);if(o&&typeof o=="object")return o.format===H?{kind:"shared",source:"json",payload:Je(t)}:{kind:"lessons",records:[o]}}else if(t.startsWith("---"))return {kind:"shared",source:"markdown",payload:vn(t)};let n=[...t.matchAll(new RegExp(Ve.source,"g"))];if(n.length===0)throw new h("Not a devcoach lesson: expected a devcoach:lesson code, a share link, a .devcoach.md file or a lessons JSON export.");let s;for(let o of n.reverse()){let r=[o[0]],i=Un(t.slice(o.index??0));i!==o[0]&&r.push(i);for(let a of r)try{return {kind:"shared",source:"code",payload:Dn(a)}}catch(l){s=l;}}throw s}function Un(e){let t=e.split(`
118
- `).map(s=>s.trim()),n=t[0]??"";for(let s of t.slice(1)){if(!/^[A-Za-z0-9_-]+$/.test(s))break;n+=s;}return n}function Ye(e){try{return JSON.parse(e)}catch{throw new h("This looks like JSON but does not parse \u2014 copy it again.")}}function et(e,t,n=new Date){let s=e.lesson;return O({id:t,timestamp:n,topic_id:s.topic_id,categories:s.categories,title:s.title,level:s.level,summary:s.summary,body:s.body,task_context:s.task_context??null,project:s.project??null,repository:s.repository??null,branch:s.branch??null,commit_hash:s.commit_hash??null,folder:null,repository_platform:s.repository_platform??null,starred:false,feedback:null,imported:true,shared_by:e.shared_by})}var Mn=50;function tt(e,t=Mn){let n=e.origin.id,s=[n];for(let o=1;s.length<t;o++)s.push(o===1?`${n}-shared`:`${n}-shared-${o}`);return s}function nt(e,t){return e.imported&&e.title===t.lesson.title&&e.topic_id===t.lesson.topic_id&&(e.shared_by??null)===(t.shared_by??null)}function st(e){try{let t=D(e),n=new Date,s=new Date(n.getTime()-24*36e5).toISOString(),o=$(e,s);if(o>=t.max_per_day)return {allowed:!1,reason:`Daily limit reached (${o}/${t.max_per_day} lessons in the last 24h)`};let r=He(e);if(r!=null){let i=new Date(r),a=(n.getTime()-i.getTime())/6e4;if(a<t.min_gap_minutes){let l=t.min_gap_minutes-a,p=Math.floor(t.min_gap_minutes/60),d=t.min_gap_minutes%60,m=Math.trunc(l),_=Math.floor(m/60),A=m%60;return {allowed:!1,reason:`Too soon: last lesson ${a<0?`in ${Math.round(-a)}m (future timestamp)`:`${Math.round(a)}m ago`}, minimum interval is ${p}h ${d}m (${_}h ${A}m remaining)`}}}return {allowed:!0}}catch(t){return {allowed:true,reason:`Rate limit check failed: ${t}`}}}function zs(e){try{return {knowledge:ae(e),groups:ce(e)}}catch{return {knowledge:[],groups:[]}}}function Fn(e,t,n){let s=w(e)[t]??5,o=Math.max(0,Math.min(10,s+n));return F(e,t,o),o}function qs(e,t,n){return ke(e,()=>{let s=e.prepare("SELECT feedback, topic_id FROM lessons WHERE id = ?").get(t);if(!s)return null;let o=s.feedback??null,r=w(e);if(o===n)return {topic_id:s.topic_id,previous:o,delta:0,confidence:r[s.topic_id]??null};$e(e,t,n);let i=(n==="know"?1:0)-(o==="know"?1:0),a=i!==0?Fn(e,s.topic_id,i):r[s.topic_id]??null;return {topic_id:s.topic_id,previous:o,delta:i,confidence:a}})}function $n(e){try{let t=new Date,n=re(e),s=new Date(t.getTime()-24*36e5).toISOString(),o=new Date(t.getTime()-7*864e5).toISOString(),r=$(e,s),i=$(e,o),a=w(e),l=Object.entries(a).sort((m,_)=>m[1]-_[1]),p=l.slice(0,5).map(([m,_])=>({topic:m,confidence:_})),d=l.slice(-5).reverse().map(([m,_])=>({topic:m,confidence:_}));return {total_lessons:n,lessons_today:r,lessons_this_week:i,imported_lessons:re(e,{imported:!0}),weakest_topics:p,strongest_topics:d}}catch(t){return {error:String(t)}}}function jn(e,t){let n=Object.hasOwn(w(e),t.lesson.topic_id);for(let s of tt(t)){let o=je(e,s);if(o){if(nt(o,t))return {kind:"shared",inserted:0,duplicated:1,invalid:0,lesson:o,topic_tracked:n};continue}let r=et(t,s);if(oe(e,r))return {kind:"shared",inserted:1,duplicated:0,invalid:0,lesson:r,topic_tracked:n}}return {kind:"shared",inserted:0,duplicated:0,invalid:1,lesson:null,topic_tracked:n}}function Vs(e,t){let n=Qe(t);return n.kind==="lessons"?{kind:"lessons",...ie(e,n.records),lesson:null,topic_tracked:null}:jn(e,n.payload)}function Js(e){try{return Pe(e)}catch{return []}}function Zs(e,t,n){let s=a=>({cue:false,reason:a,nextLessonNumber:0});if(n.planMode)return s("plan mode (not counted)");if(!de(e).knowledge_ready)return s("onboarding not complete");let o=D(e),r="pacing disabled (nudge_every=0)";if(o.nudge_every>0){let a=Xe(e,t??"__nosession__",o.nudge_scope),{pending:l}=pe(e),p=l?Math.min(ue,o.nudge_every):o.nudge_every;if(r=`${a}/${p}${l?" (retry window)":""}`,a<p)return s(`paced (${r})`)}let i=st(e);return i.allowed?(Be(e),{cue:true,reason:`cue (${r})`,nextLessonNumber:Number($n(e).total_lessons??0)+1}):s(`rate limited: ${i.reason??"denied"}`)}function Qs(e,t){try{let n=[],s=!0;if(!de(e).knowledge_ready)return {wouldCue:!1,reasons:["onboarding not complete \u2014 no knowledge topics yet"]};let o=D(e);if(o.nudge_every>0){let i=le(e,t??"__nosession__",o.nudge_scope)+1,{pending:a}=pe(e),l=a?Math.min(ue,o.nudge_every):o.nudge_every;i<l?(s=!1,n.push(`pacing: next stop would be ${i}/${l}${a?" (retry window)":""}`)):n.push(`pacing: next stop reaches the threshold (${i}/${l})`);}else n.push("pacing disabled (nudge_every=0) \u2014 every eligible stop cues");let r=st(e);return r.allowed?n.push("rate limit: allowed"):(s=!1,n.push(`rate limited: ${r.reason??"denied"}`)),{wouldCue:s,reasons:n}}catch(n){return {wouldCue:false,reasons:[`check failed: ${n}`]}}}export{pe as $,x as A,Ws as Aa,Ae as B,st as Ba,Bt as C,zs as Ca,ke as D,Fn as Da,cs as E,qs as Ea,ls as F,$n as Fa,re as G,Vs as Ga,us as H,Js as Ha,ps as I,Zs as Ia,ds as J,Qs as Ja,ms as K,nn as L,gs as M,hs as N,je as O,fs as P,ae as Q,F as R,Es as S,Oe as T,_s as U,sn as V,Ss as W,D as X,y as Y,ys as Z,Ts as _,N as a,bs as aa,ye as b,de as ba,gt as c,Rs as ca,ht as d,rn as da,Te as e,an as ea,X as f,cn as fa,B as g,ln as ga,ft as h,Ls as ha,qn as i,un as ia,Rt as j,Ns as ja,Lt as k,Os as ka,Nt as l,xs as la,Ot as m,ws as ma,xt as n,Ds as na,wt as o,Cs as oa,O as p,ee as pa,ne as q,Is as qa,se as r,h as ra,U as s,js as sa,Ce as t,Ps as ta,M as u,qe as ua,as as v,Dn as va,Ft as w,Hs as wa,$t as x,Xs as xa,v as y,Cn as ya,Xt as z,Bs as za};
118
+ `).map(s=>s.trim()),n=t[0]??"";for(let s of t.slice(1)){if(!/^[A-Za-z0-9_-]+$/.test(s))break;n+=s;}return n}function Ye(e){try{return JSON.parse(e)}catch{throw new h("This looks like JSON but does not parse \u2014 copy it again.")}}function et(e,t,n=new Date){let s=e.lesson;return O({id:t,timestamp:n,topic_id:s.topic_id,categories:s.categories,title:s.title,level:s.level,summary:s.summary,body:s.body,task_context:s.task_context??null,project:s.project??null,repository:s.repository??null,branch:s.branch??null,commit_hash:s.commit_hash??null,folder:null,repository_platform:s.repository_platform??null,starred:false,feedback:null,imported:true,shared_by:e.shared_by})}var Mn=50;function tt(e,t=Mn){let n=e.origin.id,s=[n];for(let o=1;s.length<t;o++)s.push(o===1?`${n}-shared`:`${n}-shared-${o}`);return s}function nt(e,t){return e.imported&&e.title===t.lesson.title&&e.topic_id===t.lesson.topic_id&&(e.shared_by??null)===(t.shared_by??null)}function st(e){try{let t=D(e),n=new Date,s=new Date(n.getTime()-24*36e5).toISOString(),o=$(e,s);if(o>=t.max_per_day)return {allowed:!1,reason:`Daily limit reached (${o}/${t.max_per_day} lessons in the last 24h)`};let r=He(e);if(r!=null){let i=new Date(r),a=(n.getTime()-i.getTime())/6e4;if(a<t.min_gap_minutes){let l=t.min_gap_minutes-a,p=Math.floor(t.min_gap_minutes/60),d=t.min_gap_minutes%60,m=Math.trunc(l),_=Math.floor(m/60),k=m%60;return {allowed:!1,reason:`Too soon: last lesson ${a<0?`in ${Math.round(-a)}m (future timestamp)`:`${Math.round(a)}m ago`}, minimum interval is ${p}h ${d}m (${_}h ${k}m remaining)`}}}return {allowed:!0}}catch(t){return {allowed:true,reason:`Rate limit check failed: ${t}`}}}function zs(e){try{return {knowledge:ae(e),groups:ce(e)}}catch{return {knowledge:[],groups:[]}}}function Fn(e,t,n){let s=w(e)[t]??5,o=Math.max(0,Math.min(10,s+n));return F(e,t,o),o}function qs(e,t,n){return Ae(e,()=>{let s=e.prepare("SELECT feedback, topic_id FROM lessons WHERE id = ?").get(t);if(!s)return null;let o=s.feedback??null,r=w(e);if(o===n)return {topic_id:s.topic_id,previous:o,delta:0,confidence:r[s.topic_id]??null};$e(e,t,n);let i=(n==="know"?1:0)-(o==="know"?1:0),a=i!==0?Fn(e,s.topic_id,i):r[s.topic_id]??null;return {topic_id:s.topic_id,previous:o,delta:i,confidence:a}})}function $n(e){try{let t=new Date,n=re(e),s=new Date(t.getTime()-24*36e5).toISOString(),o=new Date(t.getTime()-7*864e5).toISOString(),r=$(e,s),i=$(e,o),a=w(e),l=Object.entries(a).sort((m,_)=>m[1]-_[1]),p=l.slice(0,5).map(([m,_])=>({topic:m,confidence:_})),d=l.slice(-5).reverse().map(([m,_])=>({topic:m,confidence:_}));return {total_lessons:n,lessons_today:r,lessons_this_week:i,imported_lessons:re(e,{imported:!0}),weakest_topics:p,strongest_topics:d}}catch(t){return {error:String(t)}}}function jn(e,t){let n=Object.hasOwn(w(e),t.lesson.topic_id);for(let s of tt(t)){let o=je(e,s);if(o){if(nt(o,t))return {kind:"shared",inserted:0,duplicated:1,invalid:0,lesson:o,topic_tracked:n};continue}let r=et(t,s);if(oe(e,r))return {kind:"shared",inserted:1,duplicated:0,invalid:0,lesson:r,topic_tracked:n}}return {kind:"shared",inserted:0,duplicated:0,invalid:1,lesson:null,topic_tracked:n}}function Vs(e,t){let n=Qe(t);return n.kind==="lessons"?{kind:"lessons",...ie(e,n.records),lesson:null,topic_tracked:null}:jn(e,n.payload)}function Js(e){try{return Pe(e)}catch{return []}}function Zs(e,t,n){let s=a=>({cue:false,reason:a,nextLessonNumber:0});if(n.planMode)return s("plan mode (not counted)");if(!de(e).knowledge_ready)return s("onboarding not complete");let o=D(e),r="pacing disabled (nudge_every=0)";if(o.nudge_every>0){let a=Xe(e,t??"__nosession__",o.nudge_scope),{pending:l}=pe(e),p=l?Math.min(ue,o.nudge_every):o.nudge_every;if(r=`${a}/${p}${l?" (retry window)":""}`,a<p)return s(`paced (${r})`)}let i=st(e);return i.allowed?(Be(e),{cue:true,reason:`cue (${r})`,nextLessonNumber:Number($n(e).total_lessons??0)+1}):s(`rate limited: ${i.reason??"denied"}`)}function Qs(e,t){try{let n=[],s=!0;if(!de(e).knowledge_ready)return {wouldCue:!1,reasons:["onboarding not complete \u2014 no knowledge topics yet"]};let o=D(e);if(o.nudge_every>0){let i=le(e,t??"__nosession__",o.nudge_scope)+1,{pending:a}=pe(e),l=a?Math.min(ue,o.nudge_every):o.nudge_every;i<l?(s=!1,n.push(`pacing: next stop would be ${i}/${l}${a?" (retry window)":""}`)):n.push(`pacing: next stop reaches the threshold (${i}/${l})`);}else n.push("pacing disabled (nudge_every=0) \u2014 every eligible stop cues");let r=st(e);return r.allowed?n.push("rate limit: allowed"):(s=!1,n.push(`rate limited: ${r.reason??"denied"}`)),{wouldCue:s,reasons:n}}catch(n){return {wouldCue:false,reasons:[`check failed: ${n}`]}}}export{pe as $,x as A,Ws as Aa,ke as B,st as Ba,Bt as C,zs as Ca,Ae as D,Fn as Da,cs as E,qs as Ea,ls as F,$n as Fa,re as G,Vs as Ga,us as H,Js as Ha,ps as I,Zs as Ia,ds as J,Qs as Ja,ms as K,nn as L,gs as M,hs as N,je as O,fs as P,ae as Q,F as R,Es as S,Oe as T,_s as U,sn as V,Ss as W,D as X,y as Y,ys as Z,Ts as _,N as a,bs as aa,ye as b,de as ba,gt as c,Rs as ca,ht as d,rn as da,Te as e,an as ea,X as f,cn as fa,B as g,ln as ga,ft as h,Ls as ha,qn as i,un as ia,Rt as j,Ns as ja,Lt as k,Os as ka,Nt as l,xs as la,Ot as m,ws as ma,xt as n,Ds as na,wt as o,Cs as oa,O as p,ee as pa,ne as q,Is as qa,se as r,h as ra,U as s,js as sa,Ce as t,Ps as ta,M as u,qe as ua,as as v,Dn as va,Ft as w,Hs as wa,$t as x,Xs as xa,v as y,Cn as ya,Xt as z,Bs as za};
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import {e,g,b,d as d$1}from'./chunk-PLVAH2NE.js';export{c as buildLessonCue,d as cmdLessonReady,b as cmdOnboardHook,g as cmdPromptHook,e as cmdStopHook,a as parseHookPayload}from'./chunk-PLVAH2NE.js';import {a as a$1,d as d$2,e as e$1,g as g$2,b as b$2,f}from'./chunk-QU6UQJWC.js';import {c,b as b$1,a as a$2,e as e$3,f as f$1}from'./chunk-MRABMPXO.js';import {h,m,g as g$1,e as e$2,d as d$3,n,o,p,q as q$2}from'./chunk-Y6CPVRWE.js';import {a,E,B as B$1,Ca,H as H$1,O,K,I as I$1,Ea,X as X$1,ta,Y,x as x$1,sa,za,Aa,xa,wa,ua,Ga,ra,Ba,Fa,w,R,V,S as S$1,T as T$1,U,W,oa,Q as Q$1,L,s,qa,q as q$1,r,z,C as C$1,y,ba,Z as Z$1,$,Ja}from'./chunk-MXY574EH.js';import {spawn,spawnSync,execFileSync}from'child_process';import {writeFileSync,existsSync,readFileSync,rmSync,mkdirSync,accessSync,constants}from'fs';import {createInterface}from'readline/promises';import {text}from'stream/consumers';import {Command}from'commander';import {homedir,platform}from'os';import {join,dirname}from'path';var d=(e="")=>{console.log(e);};function _o(){let e=platform();if(e==="darwin")return join(homedir(),"Library","Application Support","Claude","claude_desktop_config.json");if(e==="win32"){let t=process.env.APPDATA??join(homedir(),"AppData","Roaming");return join(t,"Claude","claude_desktop_config.json")}let o=process.env.XDG_CONFIG_HOME??join(homedir(),".config");return join(o,"Claude","claude_desktop_config.json")}var I=join(homedir(),".claude","settings.json"),q=join(homedir(),".claude","skills","devcoach"),ke=join(homedir(),".gemini"),x=join(ke,"settings.json"),Jo=join(ke,"extensions","devcoach","gemini-extension.json"),$e=join(homedir(),".codex"),H=join($e,"hooks.json"),X=join($e,"config.toml"),N=join(homedir(),".agents","skills","devcoach");function xo(e){for(let o of (process.env.PATH??"").split(":"))if(o)try{return accessSync(join(o,e),constants.X_OK),join(o,e)}catch{}return null}var Z=e=>xo(e)!==null;function Io(){return Z("devcoach")?{command:"devcoach",args:["mcp"]}:{command:"npx",args:["-y","devcoach","mcp"]}}function ye(){let e=xo("devcoach");return e?e.includes("fnm_multishells")?"devcoach":e:"npx -y devcoach"}function vo(e,o,t){if(!Z(e))return "";t&&spawnSync(e,t,{encoding:"utf8"});let s=spawnSync(e,o,{encoding:"utf8"});return s.status===0?`${a$1.green("\u2713")} Registered via \`${e} mcp add\``:`${s.stderr??""}${s.stdout??""}`.toLowerCase().includes("already")?`${a$1.yellow("Already registered")} in ${e} (use --force to overwrite)`:`${a$1.red(`${e} mcp add failed:`)} ${(s.stderr||s.stdout||"").trim()}`}function Wo(e,o){if(!Z("claude"))return "";o&&spawnSync("claude",["mcp","remove","--scope",e,"devcoach"],{encoding:"utf8"});let t=Io(),s=spawnSync("claude",["mcp","add","--scope",e,"devcoach",t.command,"--",...t.args],{encoding:"utf8"});return s.status===0?`${a$1.green("\u2713")} Registered via \`claude mcp add\` (scope: ${e})`:`${s.stderr??""}${s.stdout??""}`.toLowerCase().includes("already")?`${a$1.yellow("Already registered")} in Claude Code (use --force to overwrite)`:`${a$1.red("claude mcp add failed:")} ${(s.stderr||s.stdout||"").trim()}`}function C(e){if(!existsSync(e))return {ok:true,data:{}};try{return {ok:!0,data:JSON.parse(readFileSync(e,"utf8"))??{}}}catch{return {ok:false,error:`${a$1.red("\u2717")} ${e} is not valid JSON \u2014 fix it and re-run (left unchanged)`}}}function ue(e,o,t){let s=C(e);if(!s.ok)return s.error;let r=s.data;r.mcpServers??={};let a=r.mcpServers;return a.devcoach&&!t?`${a$1.yellow("Already registered")} in ${e} (use --force to overwrite)`:(a.devcoach=o,mkdirSync(dirname(e),{recursive:true}),writeFileSync(e,`${JSON.stringify(r,null,2)}
2
+ import {e,g,b,d as d$1}from'./chunk-KHKEWKY4.js';export{c as buildLessonCue,d as cmdLessonReady,b as cmdOnboardHook,g as cmdPromptHook,e as cmdStopHook,a as parseHookPayload}from'./chunk-KHKEWKY4.js';import {a as a$1,d as d$2,e as e$1,g as g$2,b as b$2,f}from'./chunk-QU6UQJWC.js';import {c,b as b$1,a as a$2,e as e$3,f as f$1}from'./chunk-MRABMPXO.js';import {h,m,g as g$1,e as e$2,d as d$3,n,o,p,q as q$2}from'./chunk-CYMYTHSN.js';import {a,E,B as B$1,Ca,H as H$1,O,K,I as I$1,Ea,X as X$1,ta,Y,x as x$1,sa,za,Aa,xa,wa,ua,Ga,ra,Ba,Fa,w,R,V,S as S$1,T as T$1,U,W,oa,Q as Q$1,L,s,qa,q as q$1,r,z,C as C$1,y,ba,Z as Z$1,$,Ja}from'./chunk-LMBPZNJL.js';import {spawn,spawnSync,execFileSync}from'child_process';import {writeFileSync,existsSync,readFileSync,rmSync,mkdirSync,accessSync,constants}from'fs';import {createInterface}from'readline/promises';import {text}from'stream/consumers';import {Command}from'commander';import {homedir,platform}from'os';import {join,dirname}from'path';var d=(e="")=>{console.log(e);};function _o(){let e=platform();if(e==="darwin")return join(homedir(),"Library","Application Support","Claude","claude_desktop_config.json");if(e==="win32"){let t=process.env.APPDATA??join(homedir(),"AppData","Roaming");return join(t,"Claude","claude_desktop_config.json")}let o=process.env.XDG_CONFIG_HOME??join(homedir(),".config");return join(o,"Claude","claude_desktop_config.json")}var I=join(homedir(),".claude","settings.json"),q=join(homedir(),".claude","skills","devcoach"),ke=join(homedir(),".gemini"),x=join(ke,"settings.json"),Jo=join(ke,"extensions","devcoach","gemini-extension.json"),$e=join(homedir(),".codex"),H=join($e,"hooks.json"),X=join($e,"config.toml"),N=join(homedir(),".agents","skills","devcoach");function xo(e){for(let o of (process.env.PATH??"").split(":"))if(o)try{return accessSync(join(o,e),constants.X_OK),join(o,e)}catch{}return null}var Z=e=>xo(e)!==null;function Io(){return Z("devcoach")?{command:"devcoach",args:["mcp"]}:{command:"npx",args:["-y","devcoach","mcp"]}}function ye(){let e=xo("devcoach");return e?e.includes("fnm_multishells")?"devcoach":e:"npx -y devcoach"}function vo(e,o,t){if(!Z(e))return "";t&&spawnSync(e,t,{encoding:"utf8"});let s=spawnSync(e,o,{encoding:"utf8"});return s.status===0?`${a$1.green("\u2713")} Registered via \`${e} mcp add\``:`${s.stderr??""}${s.stdout??""}`.toLowerCase().includes("already")?`${a$1.yellow("Already registered")} in ${e} (use --force to overwrite)`:`${a$1.red(`${e} mcp add failed:`)} ${(s.stderr||s.stdout||"").trim()}`}function Wo(e,o){if(!Z("claude"))return "";o&&spawnSync("claude",["mcp","remove","--scope",e,"devcoach"],{encoding:"utf8"});let t=Io(),s=spawnSync("claude",["mcp","add","--scope",e,"devcoach",t.command,"--",...t.args],{encoding:"utf8"});return s.status===0?`${a$1.green("\u2713")} Registered via \`claude mcp add\` (scope: ${e})`:`${s.stderr??""}${s.stdout??""}`.toLowerCase().includes("already")?`${a$1.yellow("Already registered")} in Claude Code (use --force to overwrite)`:`${a$1.red("claude mcp add failed:")} ${(s.stderr||s.stdout||"").trim()}`}function C(e){if(!existsSync(e))return {ok:true,data:{}};try{return {ok:!0,data:JSON.parse(readFileSync(e,"utf8"))??{}}}catch{return {ok:false,error:`${a$1.red("\u2717")} ${e} is not valid JSON \u2014 fix it and re-run (left unchanged)`}}}function ue(e,o,t){let s=C(e);if(!s.ok)return s.error;let r=s.data;r.mcpServers??={};let a=r.mcpServers;return a.devcoach&&!t?`${a$1.yellow("Already registered")} in ${e} (use --force to overwrite)`:(a.devcoach=o,mkdirSync(dirname(e),{recursive:true}),writeFileSync(e,`${JSON.stringify(r,null,2)}
3
3
  `),`${a$1.green("\u2713")} Installed into ${e}`)}function Yo(){let e=ye();return {Stop:{hooks:[{type:"command",command:`${e} stop-hook`,timeout:60}]},UserPromptSubmit:{hooks:[{type:"command",command:`${e} prompt-hook`,timeout:30}]}}}function Xo(){let e=ye();return {AfterAgent:{hooks:[{type:"command",command:`${e} gemini-stop-hook`,timeout:6e4}]},BeforeAgent:{hooks:[{type:"command",command:`${e} gemini-prompt-hook`,timeout:3e4}]}}}function qo(){let e=ye();return {Stop:{hooks:[{type:"command",command:`${e} codex-stop-hook`,timeout:60}]},UserPromptSubmit:{hooks:[{type:"command",command:`${e} codex-prompt-hook`,timeout:30}]}}}function Lo(e){return Object.entries(e.enabledPlugins??{}).filter(([o,t])=>t&&o.startsWith("devcoach@")).map(([o])=>o)}function ve(e){return Lo(e).length>0}function Q(){return existsSync(Jo)}function ee(e){return /dev-?coach/i.test(e)&&/\b(?:stop-hook|prompt-hook|onboard-hook|lesson-ready)\b/.test(e)}function we(e,o){let t=C(e);if(!t.ok)return t.error;let s=t.data;s.hooks??={};let r=s.hooks,a=false;for(let[c,l]of Object.entries(o)){r[c]??=[];let u=r[c],f=u.map((m,p)=>[m,p]).filter(([m])=>(m.hooks??[]).some(p=>ee(p.command??""))).map(([,m])=>m),[g]=f;if(!(f.length===1&&g!==void 0&&JSON.stringify(u[g])===JSON.stringify(l))){for(let m of f.toReversed())u.splice(m,1);u.push(l),a=true;}}return a?(mkdirSync(dirname(e),{recursive:true}),writeFileSync(e,`${JSON.stringify(s,null,2)}
4
4
  `),`${a$1.green("\u2713")} Hooks installed into ${e} (${Object.keys(o).join(" + ")})`):`${a$1.yellow("Already installed")} in ${e} (current layout)`}function Zo(){let e=C(I);return e.ok?ve(e.data)?`${a$1.yellow("Skipped")} \u2014 the devcoach plugin already provides the hooks (installing both would double-count interactions)`:we(I,Yo()):e.error}function Qo(){return Q()?`${a$1.yellow("Skipped")} \u2014 the devcoach Gemini extension already provides the hooks (installing both would double-count interactions)`:we(x,Xo())}function B(e){if(!existsSync(join(e,"SKILL.md")))return "missing";try{if(readFileSync(join(e,".devcoach-version"),"utf8").trim()===a)return "current"}catch{}return "outdated"}function he(e,o){if(!o&&B(e)==="current")return `${a$1.yellow("Already installed")} in ${e} (current version)`;mkdirSync(e,{recursive:true}),writeFileSync(join(e,"SKILL.md"),e$3());let t=f$1();if(t.length){let s=join(e,"references");mkdirSync(s,{recursive:true});for(let r of t)writeFileSync(join(s,r.name),r.content);}return writeFileSync(join(e,".devcoach-version"),`${a}
5
5
  `),`${a$1.green("\u2713")} Installed into ${e}`}function en(e){return Q()?`${a$1.yellow("Skipped")} \u2014 bundled with the devcoach Gemini extension`:he(N,e)}function T(e,o){let t=C(e);return t.ok&&(t.data.hooks?.[o]??[]).some(s=>(s.hooks??[]).some(r=>ee(r.command??"")))}function Ce(){let e=[{name:"Claude Code",dir:q,cmd:"devcoach install",hooked:()=>T(I,"Stop")},{name:"Gemini/Codex",dir:N,cmd:"devcoach install --gemini (or --codex)",hooked:()=>T(x,"AfterAgent")||T(H,"Stop")}];for(let o of e){let t=B(o.dir);if(t==="outdated")return `${a$1.yellow("\u2192")} The devcoach ${o.name} skill is out of date \u2014 run ${a$1.bold(o.cmd)} to refresh it.`;if(t==="missing"&&o.hooked())return `${a$1.yellow("\u2192")} The devcoach ${o.name} skill is not installed \u2014 run ${a$1.bold(o.cmd)} to add it.`}return ""}function Do(e){let o=e.claudeCode||e.claudeDesktop||e.gemini||e.codex,t=e.claudeCode||!o,s=e.claudeDesktop||!o,r=Io(),a=false;if(d(a$1.bold("Setting up devcoach")+a$1.dim(` (${r.command} ${r.args.join(" ")})`)),d(),t){d(a$1.bold("Claude Code"));let c=Wo("user",e.force);if(!c){let l=join(homedir(),".claude.json");c=ue(l,{type:"stdio",env:{},...r},e.force),a=true;}d(` MCP server\u2026 ${c}`),e.skipHook||d(` Hooks\u2026 ${Zo()}`),d(` Skill\u2026 ${he(q,e.force)}`),d();}if(s&&(d(a$1.bold("Claude Desktop")),d(` MCP server\u2026 ${ue(_o(),r,e.force)}`),a=true,d()),e.gemini){d(a$1.bold("Gemini CLI (beta)"));let c=vo("gemini",["mcp","add","-s","user","devcoach",r.command,...r.args],e.force?["mcp","remove","-s","user","devcoach"]:null);c||(c=ue(x,r,e.force)),d(` MCP server\u2026 ${c}`),e.skipHook||d(` Hooks\u2026 ${Qo()}`),d(` Skill\u2026 ${en(e.force)}`),d();}if(e.codex){d(a$1.bold("Codex CLI (beta)"));let c=vo("codex",["mcp","add","devcoach","--",r.command,...r.args],e.force?["mcp","remove","devcoach"]:null);c||(c=`${a$1.yellow("Manual step")} \u2014 codex CLI not found. Add to ${X}:
@@ -33,4 +33,4 @@ ${a$1.green("Setup complete!")} ${Object.keys(a).length} topics saved.`);}finall
33
33
  ${a$1.bold(`devcoach ${a$1.dim(`v${a}`)}`)}`);for(let[t,s]of e)i(` ${a$1.cyan(`devcoach ${t}`.padEnd(32))} ${a$1.dim(s)}`);i(`
34
34
  ${a$1.dim("Run")} ${a$1.bold("devcoach <command> --help")} ${a$1.dim("for per-command options.")}
35
35
  `);let o=Ce();o&&i(`${o}
36
- `);}function S(e){return e??null}function Mn(){let e$1=new Command;return e$1.exitOverride(),e$1.name("devcoach").description("devcoach \u2014 progressive technical coaching").version(`devcoach ${a}`,"-v, --version","output the version number").allowExcessArguments(false).showHelpAfterError("(add --help for usage)").action(()=>En()),e$1.command("mcp").description("Start the MCP server (stdio transport)").action(async()=>{let{runStdio:o}=await import('./server-F7FIMWHD.js');await o();}),e$1.command("profile").description("Show the knowledge map").action(un),e$1.command("lessons").description("List past lessons").option("--period <period>","today | week | month | year | all","all").option("--category <category>","Filter by category tag").option("--project <project>","Filter by project (fuzzy)").option("--repository <repository>","Filter by repository (fuzzy)").option("--branch <branch>","Filter by branch (fuzzy)").option("--commit <commit>","Filter by commit hash prefix (fuzzy)").option("--starred","Show only starred lessons").option("--imported","Show only lessons shared with you").option("--from <name>","Show only lessons shared by this person").option("--feedback <feedback>","know | understood | dont_know | none").option("--level <level>","junior | mid | senior").option("--date-from <date>","Show lessons on or after this date (YYYY-MM-DD[THH:MM])").option("--date-to <date>","Show lessons on or before this date (YYYY-MM-DD[THH:MM])").option("--sort <column>","timestamp | level | topic_id | title | feedback","timestamp").option("--order <order>","asc | desc","desc").action(o=>pn({period:o.period??"all",category:S(o.category),project:S(o.project),repository:S(o.repository),branch:S(o.branch),commit:S(o.commit),starred:!!o.starred,imported:!!o.imported,sharedBy:S(o.from),feedback:S(o.feedback),level:S(o.level),dateFrom:S(o.dateFrom),dateTo:S(o.dateTo),sort:o.sort??"timestamp",order:o.order??"desc"})),e$1.command("lesson").description("Show a single lesson in detail").argument("<id>","Lesson ID").action(o=>gn(o)),e$1.command("courses").description("List your step-by-step courses").action(()=>hn()),e$1.command("course").description("Show a course: steps, progress, document path").argument("<id>","Course ID").action(o=>bn(o)),e$1.command("star").description("Mark a lesson as starred").argument("<id>","Lesson ID").action(o=>Ao(o,true)),e$1.command("unstar").description("Remove the starred mark from a lesson").argument("<id>","Lesson ID").action(o=>Ao(o,false)),e$1.command("delete").description("Permanently delete a lesson").argument("<id>","Lesson ID").action(o=>fn(o)),e$1.command("feedback").description("Record your answer under a lesson: know / understood / dont_know").argument("<id>","Lesson ID").argument("<value>","know | understood | dont_know | clear").action((o,t)=>kn(o,t)),e$1.command("share").description("Share a lesson: copyable text (default), --link, or a .devcoach.md file").argument("[id]","Lesson ID (or use --last)").option("--last","Share the most recent lesson").option("--link","Print the share link only").option("--file [path]","Write a .devcoach.md file (default: <lesson-id>.devcoach.md)").option("--with-context","Include project/branch/commit/task context (off by default)").option("--by <name>","Sender name (remembered as share_name when unset)").option("--anonymous","Share without a sender name").action((o,t)=>Dn(o,t)),e$1.command("import").description("Import a shared lesson: a devcoach:lesson code, a share link, a URL, a .devcoach.md or lessons .json file, - for stdin \u2014 or the clipboard when omitted").argument("[source]","Code, link, URL, file path, or - (stdin); omit to read the clipboard").action(o=>Nn(o)),e$1.command("settings").description("Show current settings").action($n),e$1.command("stats").description("Coaching statistics and rate-limit status").action(yn),e$1.command("set").description("Update a setting (max_per_day | min_gap_minutes | nudge_every | nudge_scope | share_name)").argument("<key>","Setting key").argument("<value>","New value").action((o,t)=>vn(o,t)),e$1.command("knowledge-add").description("Add or update a topic in the knowledge map").argument("<topic>","Topic ID").option("--confidence <n>","Initial confidence 0-10","5").option("--group <group>","Assign to a named group").action((o,t)=>wn(o,Math.max(0,Math.min(10,Number.parseInt(t.confidence??"5",10)||5)),S(t.group))),e$1.command("knowledge-remove").description("Remove a topic from the knowledge map").argument("<topic>","Topic ID").action(o=>Cn(o)),e$1.command("group-add").description("Register a new knowledge group").argument("<name>","Group name").action(o=>Sn(o)),e$1.command("group-remove").description("Delete a knowledge group (topics move to Other)").argument("<name>","Group name").action(o=>_n(o)),e$1.command("group-assign").description("Move a topic to a group").argument("<topic>","Topic ID").argument("<group>","Group name (use 'Other' to ungroup)").action((o,t)=>xn(o,t)),e$1.command("backup").description("Export a full backup (settings + knowledge + lessons) as zip").argument("[file]","Output zip path","devcoach-backup.zip").action(o=>In(o)),e$1.command("restore").description("Restore from a backup zip file").argument("<file>","Path to backup zip").action(o=>Ln(o)),e$1.command("install").description("Register the MCP server + hooks + skill in Claude Code / Claude Desktop (default), Gemini CLI (--gemini, beta), or Codex CLI (--codex, beta)").option("--claude-code","Target Claude Code only").option("--claude-desktop","Target Claude Desktop only").option("--gemini","Target Google Gemini CLI (beta)").option("--codex","Target OpenAI Codex CLI (beta)").option("--force","Overwrite existing devcoach entry").option("--skip-hook","Register MCP server only \u2014 skip the hooks").action(o=>Do({claudeCode:!!o.claudeCode,claudeDesktop:!!o.claudeDesktop,gemini:!!o.gemini,codex:!!o.codex,force:!!o.force,skipHook:!!o.skipHook})),e$1.command("uninstall").description("Remove devcoach's MCP server, hooks and skill from Claude Code / Claude Desktop (default), Gemini CLI (--gemini), Codex CLI (--codex) or every agent (--all). Coaching data is kept unless --data is given. Run it before brew/npm uninstall.").option("--claude-code","Target Claude Code only").option("--claude-desktop","Target Claude Desktop only").option("--gemini","Also remove from Google Gemini CLI (beta)").option("--codex","Also remove from OpenAI Codex CLI (beta)").option("--all","Every agent: Claude Code, Claude Desktop, Gemini CLI, Codex CLI").option("--data","Also delete ~/.devcoach (lessons, profile, notebook) \u2014 asks first").option("--yes","Do not ask before deleting the data (with --data)").action(async o=>Ho({claudeCode:!!o.claudeCode,claudeDesktop:!!o.claudeDesktop,gemini:!!o.gemini,codex:!!o.codex,all:!!o.all,data:!!o.data,yes:!!o.yes})),e$1.command("doctor").description("Diagnose the devcoach \u21C4 agent wiring and the lesson pacing state").action(Oo),e$1.command("ui").description("Launch the web dashboard (Ctrl+C stops it gracefully; --stop stops a running one)").option("--port <port>","Port","7860").option("-o, --open","Open the dashboard in your default browser").option("--stop","Stop the dashboard running on --port (e.g. one started by your agent)").action(async o=>{let t=Number.parseInt(o.port??"7860",10)||7860,{startUi:s,stopUi:r}=await import('./app-GYK6HTJM.js');if(o.stop){i(await r(t)?a$1.green(`\u2713 devcoach UI on port ${t} stopped`):a$1.yellow(`No devcoach UI is running on port ${t}`));return}s(t,{onReady:a=>{o.open&&xe(a);},onListenError:a=>{console.error(a.message),o.open&&a.existingUrl&&xe(a.existingUrl),process.exit(a.exitCode);}});}),e$1.command("setup").description("First-run wizard: import backup or build your knowledge profile").action(An),e$1.command("stop-hook",{hidden:true}).description("Claude Code Stop hook: onboarding check + lesson cue in one spawn").action(()=>e()),e$1.command("prompt-hook",{hidden:true}).description("Claude Code UserPromptSubmit hook: prime the model when a lesson is due").action(()=>g()),e$1.command("gemini-stop-hook",{hidden:true}).description("Gemini CLI AfterAgent hook: onboarding check + lesson cue in one spawn").action(()=>e(void 0,"gemini")),e$1.command("gemini-prompt-hook",{hidden:true}).description("Gemini CLI BeforeAgent hook: prime the model when a lesson is due").action(()=>g(void 0,"gemini")),e$1.command("codex-stop-hook",{hidden:true}).description("Codex CLI Stop hook: onboarding check + lesson cue in one spawn").action(()=>e(void 0,"codex")),e$1.command("codex-prompt-hook",{hidden:true}).description("Codex CLI UserPromptSubmit hook: prime the model when a lesson is due").action(()=>g(void 0,"codex")),e$1.command("onboard-hook",{hidden:true}).description("Claude Code Stop hook: cue onboarding (legacy two-entry layout)").action(()=>b()),e$1.command("lesson-ready",{hidden:true}).description("Claude Code Stop hook: signal a lesson is due (legacy two-entry layout)").action(()=>d$1()),e$1}async function ct(){let e=Mn();try{await e.parseAsync(process.argv);}catch(o){let t=o.code??"";if(!t.startsWith("commander."))throw o;if(t==="commander.helpDisplayed"||t==="commander.help"||t==="commander.version")return;process.exit(2);}}export{ct as runCli};
36
+ `);}function S(e){return e??null}function Mn(){let e$1=new Command;return e$1.exitOverride(),e$1.name("devcoach").description("devcoach \u2014 progressive technical coaching").version(`devcoach ${a}`,"-v, --version","output the version number").allowExcessArguments(false).showHelpAfterError("(add --help for usage)").action(()=>En()),e$1.command("mcp").description("Start the MCP server (stdio transport)").action(async()=>{let{runStdio:o}=await import('./server-SOP33F4T.js');await o();}),e$1.command("profile").description("Show the knowledge map").action(un),e$1.command("lessons").description("List past lessons").option("--period <period>","today | week | month | year | all","all").option("--category <category>","Filter by category tag").option("--project <project>","Filter by project (fuzzy)").option("--repository <repository>","Filter by repository (fuzzy)").option("--branch <branch>","Filter by branch (fuzzy)").option("--commit <commit>","Filter by commit hash prefix (fuzzy)").option("--starred","Show only starred lessons").option("--imported","Show only lessons shared with you").option("--from <name>","Show only lessons shared by this person").option("--feedback <feedback>","know | understood | dont_know | none").option("--level <level>","junior | mid | senior").option("--date-from <date>","Show lessons on or after this date (YYYY-MM-DD[THH:MM])").option("--date-to <date>","Show lessons on or before this date (YYYY-MM-DD[THH:MM])").option("--sort <column>","timestamp | level | topic_id | title | feedback","timestamp").option("--order <order>","asc | desc","desc").action(o=>pn({period:o.period??"all",category:S(o.category),project:S(o.project),repository:S(o.repository),branch:S(o.branch),commit:S(o.commit),starred:!!o.starred,imported:!!o.imported,sharedBy:S(o.from),feedback:S(o.feedback),level:S(o.level),dateFrom:S(o.dateFrom),dateTo:S(o.dateTo),sort:o.sort??"timestamp",order:o.order??"desc"})),e$1.command("lesson").description("Show a single lesson in detail").argument("<id>","Lesson ID").action(o=>gn(o)),e$1.command("courses").description("List your step-by-step courses").action(()=>hn()),e$1.command("course").description("Show a course: steps, progress, document path").argument("<id>","Course ID").action(o=>bn(o)),e$1.command("star").description("Mark a lesson as starred").argument("<id>","Lesson ID").action(o=>Ao(o,true)),e$1.command("unstar").description("Remove the starred mark from a lesson").argument("<id>","Lesson ID").action(o=>Ao(o,false)),e$1.command("delete").description("Permanently delete a lesson").argument("<id>","Lesson ID").action(o=>fn(o)),e$1.command("feedback").description("Record your answer under a lesson: know / understood / dont_know").argument("<id>","Lesson ID").argument("<value>","know | understood | dont_know | clear").action((o,t)=>kn(o,t)),e$1.command("share").description("Share a lesson: copyable text (default), --link, or a .devcoach.md file").argument("[id]","Lesson ID (or use --last)").option("--last","Share the most recent lesson").option("--link","Print the share link only").option("--file [path]","Write a .devcoach.md file (default: <lesson-id>.devcoach.md)").option("--with-context","Include project/branch/commit/task context (off by default)").option("--by <name>","Sender name (remembered as share_name when unset)").option("--anonymous","Share without a sender name").action((o,t)=>Dn(o,t)),e$1.command("import").description("Import a shared lesson: a devcoach:lesson code, a share link, a URL, a .devcoach.md or lessons .json file, - for stdin \u2014 or the clipboard when omitted").argument("[source]","Code, link, URL, file path, or - (stdin); omit to read the clipboard").action(o=>Nn(o)),e$1.command("settings").description("Show current settings").action($n),e$1.command("stats").description("Coaching statistics and rate-limit status").action(yn),e$1.command("set").description("Update a setting (max_per_day | min_gap_minutes | nudge_every | nudge_scope | share_name)").argument("<key>","Setting key").argument("<value>","New value").action((o,t)=>vn(o,t)),e$1.command("knowledge-add").description("Add or update a topic in the knowledge map").argument("<topic>","Topic ID").option("--confidence <n>","Initial confidence 0-10","5").option("--group <group>","Assign to a named group").action((o,t)=>wn(o,Math.max(0,Math.min(10,Number.parseInt(t.confidence??"5",10)||5)),S(t.group))),e$1.command("knowledge-remove").description("Remove a topic from the knowledge map").argument("<topic>","Topic ID").action(o=>Cn(o)),e$1.command("group-add").description("Register a new knowledge group").argument("<name>","Group name").action(o=>Sn(o)),e$1.command("group-remove").description("Delete a knowledge group (topics move to Other)").argument("<name>","Group name").action(o=>_n(o)),e$1.command("group-assign").description("Move a topic to a group").argument("<topic>","Topic ID").argument("<group>","Group name (use 'Other' to ungroup)").action((o,t)=>xn(o,t)),e$1.command("backup").description("Export a full backup (settings + knowledge + lessons) as zip").argument("[file]","Output zip path","devcoach-backup.zip").action(o=>In(o)),e$1.command("restore").description("Restore from a backup zip file").argument("<file>","Path to backup zip").action(o=>Ln(o)),e$1.command("install").description("Register the MCP server + hooks + skill in Claude Code / Claude Desktop (default), Gemini CLI (--gemini, beta), or Codex CLI (--codex, beta)").option("--claude-code","Target Claude Code only").option("--claude-desktop","Target Claude Desktop only").option("--gemini","Target Google Gemini CLI (beta)").option("--codex","Target OpenAI Codex CLI (beta)").option("--force","Overwrite existing devcoach entry").option("--skip-hook","Register MCP server only \u2014 skip the hooks").action(o=>Do({claudeCode:!!o.claudeCode,claudeDesktop:!!o.claudeDesktop,gemini:!!o.gemini,codex:!!o.codex,force:!!o.force,skipHook:!!o.skipHook})),e$1.command("uninstall").description("Remove devcoach's MCP server, hooks and skill from Claude Code / Claude Desktop (default), Gemini CLI (--gemini), Codex CLI (--codex) or every agent (--all). Coaching data is kept unless --data is given. Run it before brew/npm uninstall.").option("--claude-code","Target Claude Code only").option("--claude-desktop","Target Claude Desktop only").option("--gemini","Also remove from Google Gemini CLI (beta)").option("--codex","Also remove from OpenAI Codex CLI (beta)").option("--all","Every agent: Claude Code, Claude Desktop, Gemini CLI, Codex CLI").option("--data","Also delete ~/.devcoach (lessons, profile, notebook) \u2014 asks first").option("--yes","Do not ask before deleting the data (with --data)").action(async o=>Ho({claudeCode:!!o.claudeCode,claudeDesktop:!!o.claudeDesktop,gemini:!!o.gemini,codex:!!o.codex,all:!!o.all,data:!!o.data,yes:!!o.yes})),e$1.command("doctor").description("Diagnose the devcoach \u21C4 agent wiring and the lesson pacing state").action(Oo),e$1.command("ui").description("Launch the web dashboard (Ctrl+C stops it gracefully; --stop stops a running one)").option("--port <port>","Port","7860").option("-o, --open","Open the dashboard in your default browser").option("--stop","Stop the dashboard running on --port (e.g. one started by your agent)").action(async o=>{let t=Number.parseInt(o.port??"7860",10)||7860,{startUi:s,stopUi:r}=await import('./app-N2KKHQYT.js');if(o.stop){i(await r(t)?a$1.green(`\u2713 devcoach UI on port ${t} stopped`):a$1.yellow(`No devcoach UI is running on port ${t}`));return}s(t,{onReady:a=>{o.open&&xe(a);},onListenError:a=>{console.error(a.message),o.open&&a.existingUrl&&xe(a.existingUrl),process.exit(a.exitCode);}});}),e$1.command("setup").description("First-run wizard: import backup or build your knowledge profile").action(An),e$1.command("stop-hook",{hidden:true}).description("Claude Code Stop hook: onboarding check + lesson cue in one spawn").action(()=>e()),e$1.command("prompt-hook",{hidden:true}).description("Claude Code UserPromptSubmit hook: prime the model when a lesson is due").action(()=>g()),e$1.command("gemini-stop-hook",{hidden:true}).description("Gemini CLI AfterAgent hook: onboarding check + lesson cue in one spawn").action(()=>e(void 0,"gemini")),e$1.command("gemini-prompt-hook",{hidden:true}).description("Gemini CLI BeforeAgent hook: prime the model when a lesson is due").action(()=>g(void 0,"gemini")),e$1.command("codex-stop-hook",{hidden:true}).description("Codex CLI Stop hook: onboarding check + lesson cue in one spawn").action(()=>e(void 0,"codex")),e$1.command("codex-prompt-hook",{hidden:true}).description("Codex CLI UserPromptSubmit hook: prime the model when a lesson is due").action(()=>g(void 0,"codex")),e$1.command("onboard-hook",{hidden:true}).description("Claude Code Stop hook: cue onboarding (legacy two-entry layout)").action(()=>b()),e$1.command("lesson-ready",{hidden:true}).description("Claude Code Stop hook: signal a lesson is due (legacy two-entry layout)").action(()=>d$1()),e$1}async function ct(){let e=Mn();try{await e.parseAsync(process.argv);}catch(o){let t=o.code??"";if(!t.startsWith("commander."))throw o;if(t==="commander.helpDisplayed"||t==="commander.help"||t==="commander.version")return;process.exit(2);}}export{ct as runCli};
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- export{c as buildLessonCue,f as buildPromptPrime,d as cmdLessonReady,b as cmdOnboardHook,g as cmdPromptHook,e as cmdStopHook,a as parseHookPayload,h as runHook}from'./chunk-PLVAH2NE.js';import'./chunk-MXY574EH.js';
2
+ export{c as buildLessonCue,f as buildPromptPrime,d as cmdLessonReady,b as cmdOnboardHook,g as cmdPromptHook,e as cmdStopHook,a as parseHookPayload,h as runHook}from'./chunk-KHKEWKY4.js';import'./chunk-LMBPZNJL.js';
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import {d as d$1,f as f$2,e,c as c$1,b as b$2,a as a$2}from'./chunk-MRABMPXO.js';import {o,n,p as p$1,q,b as b$1,f as f$1,d as d$2,c,i as i$1,g as g$1,j as j$1,k as k$1,h as h$1}from'./chunk-Y6CPVRWE.js';import {f,h,g,l,k,j,a as a$1,E,ca,p,F as F$1,_,Da,H,K,I,Ea,aa,i,R,V,S,T,U,w,x,Ca,s,O,ta,X,sa,za,Aa,xa,wa,ua,Ga,ra,o as o$1,n as n$1,m,Fa,Ha,G,Ba,Y,ba,B,v}from'./chunk-MXY574EH.js';import {spawn}from'child_process';import {mkdirSync,existsSync,statSync,writeFileSync,readFileSync}from'fs';import {dirname}from'path';import {McpServer,ResourceTemplate}from'@modelcontextprotocol/server';import {StdioServerTransport}from'@modelcontextprotocol/server/stdio';import {z}from'zod';var d=o=>({type:"text",text:o}),Ne=o=>({content:[d(JSON.stringify(o))]}),a=o=>({isError:true,content:[d(o)]}),b=o=>({content:[d(JSON.stringify(o))],structuredContent:o}),De=z.object({id:z.string(),timestamp:z.string(),topic_id:z.string(),categories:z.array(z.string()),title:z.string(),level:f,summary:z.string(),body:z.string().nullable(),task_context:z.string().nullable(),project:z.string().nullable(),repository:z.string().nullable(),branch:z.string().nullable(),commit_hash:z.string().nullable(),folder:z.string().nullable(),repository_platform:g.nullable(),starred:z.boolean(),feedback:h.nullable(),imported:z.boolean(),shared_by:z.string().nullable()}),Ve=De.extend({reply_check:z.string()}),Pe="Saved \u2014 but saving does NOT display anything. The user sees ONLY plain text you write in your reply; the title/body inside this tool call's ARGUMENTS are invisible to them, and having composed them does not count as having shown the card. Did you already write the full card (both ### band headings + title line + body + tip) as plain reply text, outside any tool call? If yes: output nothing. If no: write the card now, as the final text of your reply. Never write it twice.",Xe=z.object({readOnly:z.literal(true),dbSchema:z.number().int(),appSchema:z.number().int(),upgradedBy:z.string().nullable(),minApp:z.string().nullable()}).nullable().optional(),Ie=z.object({knowledge:z.array(z.object({topic:z.string(),confidence:z.number().int()})),groups:z.array(z.object({name:z.string(),topics:z.array(z.string())})),compat:Xe}),Qe=z.object({max_per_day:z.number().int(),min_gap_minutes:z.number().int(),ui_theme:j,ui_home:k,nudge_every:z.number().int(),nudge_scope:l,share_name:z.string().nullable()}),Ze=z.object({transport:z.enum(["text","link","file"]),code:z.string(),text:z.string().nullable(),link:z.string().nullable(),markdown:z.string().nullable(),filename:z.string().nullable(),shared_by:z.string().nullable(),include_context:z.boolean(),reply_check:z.string()}),et=z.object({kind:z.enum(["shared","lessons"]),inserted:z.number().int(),duplicated:z.number().int(),invalid:z.number().int(),lesson:De.nullable(),topic_tracked:z.boolean().nullable(),reply_check:z.string()}),tt={text:"Reply with `text` verbatim as the final message: the card as markdown, then the last line (devcoach:lesson:1:\u2026) inside a fenced code block, character for character \u2014 never paraphrase, wrap, truncate or split it.",link:"Reply with `link` on its own line, unchanged.",file:"Write `markdown` to `filename` (in the workspace unless the user named a path) with your file tool, then confirm the path in one line; do not print the markdown."},nt="Confirm in one line: the title, who shared it, and that it joined the coaching log (feedback works as usual; it does not count against the daily limit). If topic_tracked is false, offer to track the topic with add_topic \u2014 only on confirmation.",ot=z.object({window_months:z.number().int(),cutoff:z.string(),candidate_count:z.number().int(),over_soft_limit:z.boolean(),candidates:z.array(z.object({name:z.string(),path:z.string(),last_activity:z.string().nullable(),prompt_count:z.number().int()}))});function u(o,p){return {contents:[{uri:o.href,mimeType:"application/json",text:JSON.stringify(p)}]}}function $e(){try{return E(o=>({...Ca(o),compat:B(o)}))}catch(o){return {error:String(o)}}}function Ue(){try{let o="";try{existsSync(s)&&(o=readFileSync(s,"utf8"));}catch{}return {...E(m=>{let g=ba(m).knowledge_ready,f=o.length>0,l=Ba(m),t={allowed:l.allowed,total_lessons:G(m)};return l.reason!=null&&(t.reason=l.reason),{onboarding:{knowledge_ready:g,notebook_ready:f,needs_onboarding:!(g&&f)},rate_limit:t,taught_topics:Ha(m),profile:Ca(m),compat:B(m)}}),notebook:o,notebook_path:s}}catch(o){return {error:String(o)}}}function Ge(){try{let{status:o$1,compat:p}=E(n=>({status:ba(n),compat:B(n)})),m=o$1.knowledge_ready,g=existsSync(s)&&statSync(s).size>0,f=o(),l=c$1(),t=b$2(a$2(f.folder??process.cwd()),l.detected_stack);return {compat:p,knowledge_ready:m,notebook_ready:g,needs_onboarding:!(m&&g),detected_stack:t,detected_projects:l.projects,scanned_projects:l.scanned_projects,default_topics:v,context_ready:f.branch!==null,notebook_path:s}}catch(o){return {error:String(o)}}}var F=(o,p)=>typeof p.error=="string"?a(`${o} failed: ${p.error}`):b(p);function rt(){let o$2=new McpServer({name:"devcoach",version:a$1},{instructions:"Progressive technical coaching server. Use the devcoach_instructions prompt for full coaching behaviour guidelines. Read coaching state with the get_briefing / get_onboarding / get_profile tools; the devcoach:// resources expose the same data for clients that browse resources. share_lesson hands a lesson to another person (text, link or file); import_lesson stores one they were given."});o$2.registerTool("log_lesson",{title:"Log Lesson",description:"Save a delivered lesson to the coaching log. Git metadata (project, repository, branch, commit_hash, folder, repository_platform) is auto-detected from the workspace when omitted. timestamp is always stamped server-side with the current time \u2014 there is no argument for it. Returns the saved Lesson with all resolved fields.",inputSchema:z.object({id:z.string().describe("Unique lesson id (uuid or random string)"),topic_id:z.string().describe("Primary topic id, e.g. 'python'"),categories:z.array(z.string()).describe("Category tags, e.g. ['python','performance']"),title:z.string().describe("Short lesson title"),level:f.describe("Difficulty level: junior | mid | senior"),summary:z.string().describe("1-3 sentence summary shown in the lesson card"),body:z.string().min(1).describe("Clean lesson markdown: the prose + \u{1F4A1} Senior tip only \u2014 NO card bands, NO '>' blockquote, and do not repeat title/category/level (those are their own fields). Required, non-empty."),task_context:z.string().nullish().describe("What the user was doing when taught"),project:z.string().nullish().describe("Project name (auto-detected from git if omitted)"),repository:z.string().nullish().describe("org/repo or path (auto-detected if omitted)"),branch:z.string().nullish().describe("git branch (auto-detected if omitted)"),commit_hash:z.string().nullish().describe("git commit hash (auto-detected if omitted)"),folder:z.string().nullish().describe("workspace folder (auto-detected if omitted)"),repository_platform:g.nullish().describe("github | gitlab | bitbucket | local (auto-detected if omitted)")}),outputSchema:Ve,annotations:{title:"Log Lesson",destructiveHint:false,idempotentHint:true,openWorldHint:false}},async t=>{try{let n=o(),r={};try{r=E(c=>ca(c));}catch{r={};}let i=p({id:t.id,timestamp:new Date,topic_id:t.topic_id,categories:t.categories,title:t.title,level:t.level,summary:t.summary,body:t.body,task_context:t.task_context??null,project:t.project??n.project??r.project??null,repository:t.repository??n.repository??r.repository??null,branch:t.branch??n.branch??r.branch??null,commit_hash:t.commit_hash??n.commit_hash??null,folder:t.folder??n.folder??null,repository_platform:t.repository_platform??n.repository_platform??r.repository_platform??null});return E(c=>{F$1(c,i),_(c);}),{content:[d(`Lesson saved. ${Pe}`),d(JSON.stringify(i))],structuredContent:{...i,reply_check:Pe}}}catch(n){return a(`log_lesson failed: ${n}`)}}),o$2.registerTool("update_knowledge",{title:"Update Knowledge",description:"Adjust the confidence score for a topic by delta (e.g. +1 or -1). Returns the new confidence (0-10). Creates the topic at confidence 5 if it does not exist.",inputSchema:z.object({topic:z.string().describe("Topic id, e.g. 'python'"),delta:z.number().int().describe("Signed change to apply, e.g. +1 or -1")}),annotations:{title:"Update Knowledge",destructiveHint:false,idempotentHint:true,openWorldHint:false}},async t=>{try{let n=E(r=>Da(r,t.topic,t.delta));return {content:[d(String(n))]}}catch(n){return a(`update_knowledge failed for '${t.topic}': ${n}`)}}),o$2.registerTool("get_lessons",{title:"Get Lessons",description:"Query the coaching lesson history. All filters combine. period defaults to all; date_from/date_to override period; limit caps results newest-first (0 = all).",inputSchema:z.object({period:z.enum(["today","week","month","year","all"]).nullish().describe("Time window"),category:z.string().nullish().describe("Filter by a category tag, e.g. 'python'"),level:z.enum(["junior","mid","senior"]).nullish().describe("Filter by difficulty level"),project:z.string().nullish().describe("Fuzzy match on git project"),repository:z.string().nullish().describe("Fuzzy match on git repository"),branch:z.string().nullish().describe("Fuzzy match on git branch"),commit:z.string().nullish().describe("Fuzzy match on commit hash"),starred:z.boolean().nullish().describe("True to return only starred lessons"),imported:z.boolean().nullish().describe("True = only lessons shared with you, false = only your own"),shared_by:z.string().nullish().describe("Only lessons shared by this person (exact sender name; implies imported)"),feedback:z.enum(["know","understood","dont_know","none"]).nullish().describe("Filter by feedback: know (already knew), understood (new, now clear), dont_know (couldn't follow \u2014 a course seed), none (no response)"),search:z.string().nullish().describe("Full-text search over title, topic_id, summary, body and the sender's name"),date_from:z.string().nullish().describe("ISO date/datetime lower bound"),date_to:z.string().nullish().describe("ISO date/datetime upper bound (date-only = end-of-day)"),limit:z.number().int().default(10).describe("Max lessons, newest first. Pass 0 for all.")}),annotations:{title:"Get Lessons",readOnlyHint:true,destructiveHint:false,idempotentHint:true,openWorldHint:false}},t=>{try{let n=E(r=>H(r,{period:t.period,category:t.category,level:t.level,project:t.project,repository:t.repository,branch:t.branch,commit:t.commit,starred:t.starred,imported:t.imported,shared_by:t.shared_by,feedback:t.feedback,search:t.search,date_from:t.date_from,date_to:t.date_to,page:t.limit>0?1:null,per_page:t.limit}));return Ne(n)}catch{return Ne([])}}),o$2.registerTool("star_lesson",{title:"Star Lesson",description:"Set the starred (favourite) flag on a lesson. Returns true if found and updated. Idempotent.",inputSchema:z.object({lesson_id:z.string().describe("Lesson id"),starred:z.boolean().describe("true to favourite, false to unmark")}),annotations:{title:"Star Lesson",destructiveHint:false,idempotentHint:true,openWorldHint:false}},async t=>{try{let n=E(r=>K(r,t.lesson_id,t.starred));return {content:[d(String(n))]}}catch(n){return a(`star_lesson failed for '${t.lesson_id}': ${n}`)}}),o$2.registerTool("delete_lesson",{title:"Delete Lesson",description:"Permanently delete a lesson by id. Returns true if found and deleted.",inputSchema:z.object({lesson_id:z.string().describe("Lesson id to delete")}),annotations:{title:"Delete Lesson",destructiveHint:true,idempotentHint:true,openWorldHint:false}},async t=>{try{let n=E(r=>I(r,t.lesson_id));return {content:[d(String(n))]}}catch(n){return a(`delete_lesson failed for '${t.lesson_id}': ${n}`)}}),o$2.registerTool("submit_feedback",{title:"Submit Feedback",description:"Record the user's answer under a lesson card. know = already knew it (confidence +1); understood = new and now clear (no confidence change); dont_know = couldn't follow this session (no confidence change; the lesson becomes a course seed); clear = remove the answer. Idempotent \u2014 repeating the stored answer changes nothing.",inputSchema:z.object({lesson_id:z.string().describe("Lesson id"),feedback:z.enum(["know","understood","dont_know","clear"]).describe("know | understood | dont_know | clear")}),annotations:{title:"Submit Feedback",destructiveHint:false,idempotentHint:true,openWorldHint:false}},async t=>{try{let n=t.feedback==="clear"?null:t.feedback,r=E(i=>Ea(i,t.lesson_id,n));return {content:[d(String(r!==null))]}}catch(n){return a(`submit_feedback failed for '${t.lesson_id}': ${n}`)}}),o$2.registerTool("skip_lesson",{title:"Skip Lesson",description:"Decline a lesson cue: call this when the coaching hook asked for a lesson but the completed work does not warrant one (pure questions, chat, nothing technical). Re-arms the pacing counter so the cue is not repeated immediately. Never call it after delivering a lesson \u2014 log_lesson already resolves the cue.",inputSchema:z.object({reason:z.string().min(1).describe("One line: why no lesson was warranted (shown in `devcoach doctor`)")}),annotations:{title:"Skip Lesson",destructiveHint:false,idempotentHint:true,openWorldHint:false}},async t=>{try{return E(n=>aa(n,t.reason)),{content:[d("Cue skipped \u2014 pacing re-armed. Output nothing to the user.")]}}catch(n){return a(`skip_lesson failed: ${n}`)}}),o$2.registerTool("add_topic",{title:"Add Topic",description:"Add a topic to the knowledge map, or update its confidence if it exists. Optionally assign to a group (auto-created). Prefer a single-word topic id. Idempotent.",inputSchema:z.object({topic:z.string().describe("Topic id \u2014 prefer a single word, max 3 words"),confidence:i.default(5).describe("Initial confidence 0-10 (default 5)"),group:z.string().nullish().describe("Optional group name; 'Other' if omitted")}),annotations:{title:"Add Topic",destructiveHint:false,idempotentHint:true,openWorldHint:false}},async t=>{try{return E(n=>{R(n,t.topic,t.confidence),t.group&&t.group!=="Other"&&V(n,t.topic,t.group);}),{content:[d("true")]}}catch(n){return a(`add_topic failed for '${t.topic}': ${n}`)}}),o$2.registerTool("remove_topic",{title:"Remove Topic",description:"Remove a topic from the knowledge map entirely. Returns true if it existed.",inputSchema:z.object({topic:z.string().describe("Topic id to remove")}),annotations:{title:"Remove Topic",destructiveHint:true,idempotentHint:true,openWorldHint:false}},async t=>{try{let n=E(r=>S(r,t.topic));return {content:[d(String(n))]}}catch(n){return a(`remove_topic failed for '${t.topic}': ${n}`)}}),o$2.registerTool("add_group",{title:"Add Group",description:"Create a new (initially empty) knowledge group. Idempotent \u2014 returns true either way.",inputSchema:z.object({name:z.string().describe("Group name, e.g. 'Machine Learning'")}),annotations:{title:"Add Group",destructiveHint:false,idempotentHint:true,openWorldHint:false}},async t=>{try{return E(n=>T(n,t.name.trim())),{content:[d("true")]}}catch(n){return a(`add_group failed for '${t.name}': ${n}`)}}),o$2.registerTool("remove_group",{title:"Remove Group",description:"Delete a knowledge group. Its topics move to Other. Returns true if it existed.",inputSchema:z.object({name:z.string().describe("Group name to delete")}),annotations:{title:"Remove Group",destructiveHint:true,idempotentHint:true,openWorldHint:false}},async t=>{try{let n=E(r=>U(r,t.name));return {content:[d(String(n))]}}catch(n){return a(`remove_group failed for '${t.name}': ${n}`)}}),o$2.registerTool("update_settings",{title:"Update Settings",description:"Update a coaching setting. max_per_day: integer 1-20. min_gap_minutes: integer 0-1440 (0 = no cooldown). nudge_every: integer 0-1000 interactions between lesson cues (0 = cue every turn). nudge_scope: 'session' | 'global'. share_name: the sender name proposed when sharing a lesson (max 80 chars, empty clears). Returns the full updated Settings.",inputSchema:z.object({key:z.enum(["max_per_day","min_gap_minutes","nudge_every","nudge_scope","share_name"]).describe("Setting key"),value:z.string().describe("New value (integer string; 'session'|'global' for nudge_scope; free text for share_name)")}),outputSchema:Qe,annotations:{title:"Update Settings",destructiveHint:false,idempotentHint:true,openWorldHint:false}},t=>{let n=i=>b(E(c=>(Y(c,t.key,i),X(c))));if(t.key==="share_name")return t.value.trim().length>w?a(`share_name must be at most ${w} characters`):n(x(t.value));if(t.key==="nudge_scope")return t.value!=="session"&&t.value!=="global"?a("nudge_scope must be 'session' or 'global'"):n(t.value);let r=Number.parseInt(t.value,10);return Number.isNaN(r)?a(`Value must be an integer, got '${t.value}'`):t.key==="max_per_day"&&!(r>=1&&r<=20)?a("max_per_day must be between 1 and 20"):t.key==="min_gap_minutes"&&!(r>=0&&r<=1440)?a("min_gap_minutes must be between 0 and 1440"):t.key==="nudge_every"&&!(r>=0&&r<=1e3)?a("nudge_every must be between 0 and 1000"):n(String(r))}),o$2.registerTool("open_ui",{title:"Open UI",description:"Launch the devcoach web dashboard in the background. port must be 1024-65535.",inputSchema:z.object({port:z.number().int().default(7860).describe("Port (default 7860)")}),annotations:{title:"Open UI",destructiveHint:false,idempotentHint:false,openWorldHint:true}},async t=>{let n=t.port;if(!(n>=1024&&n<=65535))return {content:[d(`error: port ${n} is out of valid range (1024-65535)`)]};let{pingUi:r}=await import('./app-GYK6HTJM.js');return await r(n)?{content:[d(`devcoach UI is already running at http://localhost:${n}`)]}:(spawn(process.execPath,[process.argv[1]??"","ui","--port",String(n)],{detached:true,stdio:"ignore"}).unref(),{content:[d(`devcoach UI starting at http://localhost:${n}`)]})}),o$2.registerTool("stop_ui",{title:"Stop UI",description:"Stop the devcoach web dashboard listening on port (whoever started it \u2014 open_ui or a terminal). Graceful: in-flight requests finish first. Reports stopped=false when nothing is running there. port must be 1024-65535.",inputSchema:z.object({port:z.number().int().default(7860).describe("Port (default 7860)")}),annotations:{title:"Stop UI",destructiveHint:false,idempotentHint:true,openWorldHint:true}},async t=>{let n=t.port;if(!(n>=1024&&n<=65535))return {content:[d(`error: port ${n} is out of valid range (1024-65535)`)]};let{stopUi:r}=await import('./app-GYK6HTJM.js'),i=await r(n);return {content:[d(i?`devcoach UI on port ${n} stopped`:`No devcoach UI is running on port ${n}`)],structuredContent:{stopped:i,port:n}}}),o$2.registerTool("complete_onboarding",{title:"Complete Onboarding",description:"Save the user's initial knowledge profile and mark onboarding complete. Wipes any default-seeded profile. topics: {topic_id: confidence_0_to_10}. groups: {group_name: [topic_id,...]} (topics not in any group go to 'Other'). Ensures learning-state.md exists and is non-empty (a placeholder if needed) \u2014 there is no notebook argument here. Write the real personalized notebook yourself right after, directly to the path in devcoach://onboarding's notebook_path field. Returns the updated Profile.",inputSchema:z.object({topics:z.record(z.string(),i).describe("{topic_id: confidence 0-10}"),groups:z.record(z.string(),z.array(z.string())).optional().describe("{group_name: [topic_id, ...]}")}),outputSchema:Ie,annotations:{title:"Complete Onboarding",destructiveHint:true,idempotentHint:false,openWorldHint:false}},async t=>{try{let n=E(r=>{r.exec("DELETE FROM knowledge; DELETE FROM knowledge_groups; DELETE FROM knowledge_group_names;");for(let[i,c]of Object.entries(t.topics))R(r,i,Math.max(0,Math.min(10,c)));if(t.groups)for(let[i,c]of Object.entries(t.groups))for(let D of c)D in t.topics&&V(r,D,i);return Ca(r)});return mkdirSync(dirname(s),{recursive:!0}),(!existsSync(s)||statSync(s).size===0)&&writeFileSync(s,`# devcoach \u2014 Coaching Notebook
2
+ import {d as d$1,f as f$2,e,c as c$1,b as b$2,a as a$2}from'./chunk-MRABMPXO.js';import {o,n,p as p$1,q,b as b$1,f as f$1,d as d$2,c,i as i$1,g as g$1,j as j$1,k as k$1,h as h$1}from'./chunk-CYMYTHSN.js';import {f,h,g,l,k,j,a as a$1,E,ca,p,F as F$1,_,Da,H,K,I,Ea,aa,i,R,V,S,T,U,w,x,Ca,s,O,ta,X,sa,za,Aa,xa,wa,ua,Ga,ra,o as o$1,n as n$1,m,Fa,Ha,G,Ba,Y,ba,B,v}from'./chunk-LMBPZNJL.js';import {spawn}from'child_process';import {mkdirSync,existsSync,statSync,writeFileSync,readFileSync}from'fs';import {dirname}from'path';import {McpServer,ResourceTemplate}from'@modelcontextprotocol/server';import {StdioServerTransport}from'@modelcontextprotocol/server/stdio';import {z}from'zod';var d=o=>({type:"text",text:o}),Ne=o=>({content:[d(JSON.stringify(o))]}),a=o=>({isError:true,content:[d(o)]}),b=o=>({content:[d(JSON.stringify(o))],structuredContent:o}),De=z.object({id:z.string(),timestamp:z.string(),topic_id:z.string(),categories:z.array(z.string()),title:z.string(),level:f,summary:z.string(),body:z.string().nullable(),task_context:z.string().nullable(),project:z.string().nullable(),repository:z.string().nullable(),branch:z.string().nullable(),commit_hash:z.string().nullable(),folder:z.string().nullable(),repository_platform:g.nullable(),starred:z.boolean(),feedback:h.nullable(),imported:z.boolean(),shared_by:z.string().nullable()}),Ve=De.extend({reply_check:z.string()}),Pe="Saved \u2014 but saving does NOT display anything. The user sees ONLY plain text you write in your reply; the title/body inside this tool call's ARGUMENTS are invisible to them, and having composed them does not count as having shown the card. Did you already write the full card (both ### band headings + title line + body + tip) as plain reply text, outside any tool call? If yes: output nothing. If no: write the card now, as the final text of your reply. Never write it twice.",Xe=z.object({readOnly:z.literal(true),dbSchema:z.number().int(),appSchema:z.number().int(),upgradedBy:z.string().nullable(),minApp:z.string().nullable()}).nullable().optional(),Ie=z.object({knowledge:z.array(z.object({topic:z.string(),confidence:z.number().int()})),groups:z.array(z.object({name:z.string(),topics:z.array(z.string())})),compat:Xe}),Qe=z.object({max_per_day:z.number().int(),min_gap_minutes:z.number().int(),ui_theme:j,ui_home:k,nudge_every:z.number().int(),nudge_scope:l,share_name:z.string().nullable()}),Ze=z.object({transport:z.enum(["text","link","file"]),code:z.string(),text:z.string().nullable(),link:z.string().nullable(),markdown:z.string().nullable(),filename:z.string().nullable(),shared_by:z.string().nullable(),include_context:z.boolean(),reply_check:z.string()}),et=z.object({kind:z.enum(["shared","lessons"]),inserted:z.number().int(),duplicated:z.number().int(),invalid:z.number().int(),lesson:De.nullable(),topic_tracked:z.boolean().nullable(),reply_check:z.string()}),tt={text:"Reply with `text` verbatim as the final message: the card as markdown, then the last line (devcoach:lesson:1:\u2026) inside a fenced code block, character for character \u2014 never paraphrase, wrap, truncate or split it.",link:"Reply with `link` on its own line, unchanged.",file:"Write `markdown` to `filename` (in the workspace unless the user named a path) with your file tool, then confirm the path in one line; do not print the markdown."},nt="Confirm in one line: the title, who shared it, and that it joined the coaching log (feedback works as usual; it does not count against the daily limit). If topic_tracked is false, offer to track the topic with add_topic \u2014 only on confirmation.",ot=z.object({window_months:z.number().int(),cutoff:z.string(),candidate_count:z.number().int(),over_soft_limit:z.boolean(),candidates:z.array(z.object({name:z.string(),path:z.string(),last_activity:z.string().nullable(),prompt_count:z.number().int()}))});function u(o,p){return {contents:[{uri:o.href,mimeType:"application/json",text:JSON.stringify(p)}]}}function $e(){try{return E(o=>({...Ca(o),compat:B(o)}))}catch(o){return {error:String(o)}}}function Ue(){try{let o="";try{existsSync(s)&&(o=readFileSync(s,"utf8"));}catch{}return {...E(m=>{let g=ba(m).knowledge_ready,f=o.length>0,l=Ba(m),t={allowed:l.allowed,total_lessons:G(m)};return l.reason!=null&&(t.reason=l.reason),{onboarding:{knowledge_ready:g,notebook_ready:f,needs_onboarding:!(g&&f)},rate_limit:t,taught_topics:Ha(m),profile:Ca(m),compat:B(m)}}),notebook:o,notebook_path:s}}catch(o){return {error:String(o)}}}function Ge(){try{let{status:o$1,compat:p}=E(n=>({status:ba(n),compat:B(n)})),m=o$1.knowledge_ready,g=existsSync(s)&&statSync(s).size>0,f=o(),l=c$1(),t=b$2(a$2(f.folder??process.cwd()),l.detected_stack);return {compat:p,knowledge_ready:m,notebook_ready:g,needs_onboarding:!(m&&g),detected_stack:t,detected_projects:l.projects,scanned_projects:l.scanned_projects,default_topics:v,context_ready:f.branch!==null,notebook_path:s}}catch(o){return {error:String(o)}}}var F=(o,p)=>typeof p.error=="string"?a(`${o} failed: ${p.error}`):b(p);function rt(){let o$2=new McpServer({name:"devcoach",version:a$1},{instructions:"Progressive technical coaching server. Use the devcoach_instructions prompt for full coaching behaviour guidelines. Read coaching state with the get_briefing / get_onboarding / get_profile tools; the devcoach:// resources expose the same data for clients that browse resources. share_lesson hands a lesson to another person (text, link or file); import_lesson stores one they were given."});o$2.registerTool("log_lesson",{title:"Log Lesson",description:"Save a delivered lesson to the coaching log. Git metadata (project, repository, branch, commit_hash, folder, repository_platform) is auto-detected from the workspace when omitted. timestamp is always stamped server-side with the current time \u2014 there is no argument for it. Returns the saved Lesson with all resolved fields.",inputSchema:z.object({id:z.string().describe("Unique lesson id (uuid or random string)"),topic_id:z.string().describe("Primary topic id, e.g. 'python'"),categories:z.array(z.string()).describe("Category tags, e.g. ['python','performance']"),title:z.string().describe("Short lesson title"),level:f.describe("Difficulty level: junior | mid | senior"),summary:z.string().describe("1-3 sentence summary shown in the lesson card"),body:z.string().min(1).describe("Clean lesson markdown: the prose + \u{1F4A1} Senior tip only \u2014 NO card bands, NO '>' blockquote, and do not repeat title/category/level (those are their own fields). Required, non-empty."),task_context:z.string().nullish().describe("What the user was doing when taught"),project:z.string().nullish().describe("Project name (auto-detected from git if omitted)"),repository:z.string().nullish().describe("org/repo or path (auto-detected if omitted)"),branch:z.string().nullish().describe("git branch (auto-detected if omitted)"),commit_hash:z.string().nullish().describe("git commit hash (auto-detected if omitted)"),folder:z.string().nullish().describe("workspace folder (auto-detected if omitted)"),repository_platform:g.nullish().describe("github | gitlab | bitbucket | local (auto-detected if omitted)")}),outputSchema:Ve,annotations:{title:"Log Lesson",destructiveHint:false,idempotentHint:true,openWorldHint:false}},async t=>{try{let n=o(),r={};try{r=E(c=>ca(c));}catch{r={};}let i=p({id:t.id,timestamp:new Date,topic_id:t.topic_id,categories:t.categories,title:t.title,level:t.level,summary:t.summary,body:t.body,task_context:t.task_context??null,project:t.project??n.project??r.project??null,repository:t.repository??n.repository??r.repository??null,branch:t.branch??n.branch??r.branch??null,commit_hash:t.commit_hash??n.commit_hash??null,folder:t.folder??n.folder??null,repository_platform:t.repository_platform??n.repository_platform??r.repository_platform??null});return E(c=>{F$1(c,i),_(c);}),{content:[d(`Lesson saved. ${Pe}`),d(JSON.stringify(i))],structuredContent:{...i,reply_check:Pe}}}catch(n){return a(`log_lesson failed: ${n}`)}}),o$2.registerTool("update_knowledge",{title:"Update Knowledge",description:"Adjust the confidence score for a topic by delta (e.g. +1 or -1). Returns the new confidence (0-10). Creates the topic at confidence 5 if it does not exist.",inputSchema:z.object({topic:z.string().describe("Topic id, e.g. 'python'"),delta:z.number().int().describe("Signed change to apply, e.g. +1 or -1")}),annotations:{title:"Update Knowledge",destructiveHint:false,idempotentHint:true,openWorldHint:false}},async t=>{try{let n=E(r=>Da(r,t.topic,t.delta));return {content:[d(String(n))]}}catch(n){return a(`update_knowledge failed for '${t.topic}': ${n}`)}}),o$2.registerTool("get_lessons",{title:"Get Lessons",description:"Query the coaching lesson history. All filters combine. period defaults to all; date_from/date_to override period; limit caps results newest-first (0 = all).",inputSchema:z.object({period:z.enum(["today","week","month","year","all"]).nullish().describe("Time window"),category:z.string().nullish().describe("Filter by a category tag, e.g. 'python'"),level:z.enum(["junior","mid","senior"]).nullish().describe("Filter by difficulty level"),project:z.string().nullish().describe("Fuzzy match on git project"),repository:z.string().nullish().describe("Fuzzy match on git repository"),branch:z.string().nullish().describe("Fuzzy match on git branch"),commit:z.string().nullish().describe("Fuzzy match on commit hash"),starred:z.boolean().nullish().describe("True to return only starred lessons"),imported:z.boolean().nullish().describe("True = only lessons shared with you, false = only your own"),shared_by:z.string().nullish().describe("Only lessons shared by this person (exact sender name; implies imported)"),feedback:z.enum(["know","understood","dont_know","none"]).nullish().describe("Filter by feedback: know (already knew), understood (new, now clear), dont_know (couldn't follow \u2014 a course seed), none (no response)"),search:z.string().nullish().describe("Full-text search over title, topic_id, summary, body and the sender's name"),date_from:z.string().nullish().describe("ISO date/datetime lower bound"),date_to:z.string().nullish().describe("ISO date/datetime upper bound (date-only = end-of-day)"),limit:z.number().int().default(10).describe("Max lessons, newest first. Pass 0 for all.")}),annotations:{title:"Get Lessons",readOnlyHint:true,destructiveHint:false,idempotentHint:true,openWorldHint:false}},t=>{try{let n=E(r=>H(r,{period:t.period,category:t.category,level:t.level,project:t.project,repository:t.repository,branch:t.branch,commit:t.commit,starred:t.starred,imported:t.imported,shared_by:t.shared_by,feedback:t.feedback,search:t.search,date_from:t.date_from,date_to:t.date_to,page:t.limit>0?1:null,per_page:t.limit}));return Ne(n)}catch{return Ne([])}}),o$2.registerTool("star_lesson",{title:"Star Lesson",description:"Set the starred (favourite) flag on a lesson. Returns true if found and updated. Idempotent.",inputSchema:z.object({lesson_id:z.string().describe("Lesson id"),starred:z.boolean().describe("true to favourite, false to unmark")}),annotations:{title:"Star Lesson",destructiveHint:false,idempotentHint:true,openWorldHint:false}},async t=>{try{let n=E(r=>K(r,t.lesson_id,t.starred));return {content:[d(String(n))]}}catch(n){return a(`star_lesson failed for '${t.lesson_id}': ${n}`)}}),o$2.registerTool("delete_lesson",{title:"Delete Lesson",description:"Permanently delete a lesson by id. Returns true if found and deleted.",inputSchema:z.object({lesson_id:z.string().describe("Lesson id to delete")}),annotations:{title:"Delete Lesson",destructiveHint:true,idempotentHint:true,openWorldHint:false}},async t=>{try{let n=E(r=>I(r,t.lesson_id));return {content:[d(String(n))]}}catch(n){return a(`delete_lesson failed for '${t.lesson_id}': ${n}`)}}),o$2.registerTool("submit_feedback",{title:"Submit Feedback",description:"Record the user's answer under a lesson card. know = already knew it (confidence +1); understood = new and now clear (no confidence change); dont_know = couldn't follow this session (no confidence change; the lesson becomes a course seed); clear = remove the answer. Idempotent \u2014 repeating the stored answer changes nothing.",inputSchema:z.object({lesson_id:z.string().describe("Lesson id"),feedback:z.enum(["know","understood","dont_know","clear"]).describe("know | understood | dont_know | clear")}),annotations:{title:"Submit Feedback",destructiveHint:false,idempotentHint:true,openWorldHint:false}},async t=>{try{let n=t.feedback==="clear"?null:t.feedback,r=E(i=>Ea(i,t.lesson_id,n));return {content:[d(String(r!==null))]}}catch(n){return a(`submit_feedback failed for '${t.lesson_id}': ${n}`)}}),o$2.registerTool("skip_lesson",{title:"Skip Lesson",description:"Decline a lesson cue: call this when the coaching hook asked for a lesson but the completed work does not warrant one (pure questions, chat, nothing technical). Re-arms the pacing counter so the cue is not repeated immediately. Never call it after delivering a lesson \u2014 log_lesson already resolves the cue.",inputSchema:z.object({reason:z.string().min(1).describe("One line: why no lesson was warranted (shown in `devcoach doctor`)")}),annotations:{title:"Skip Lesson",destructiveHint:false,idempotentHint:true,openWorldHint:false}},async t=>{try{return E(n=>aa(n,t.reason)),{content:[d("Cue skipped \u2014 pacing re-armed. Output nothing to the user.")]}}catch(n){return a(`skip_lesson failed: ${n}`)}}),o$2.registerTool("add_topic",{title:"Add Topic",description:"Add a topic to the knowledge map, or update its confidence if it exists. Optionally assign to a group (auto-created). Prefer a single-word topic id. Idempotent.",inputSchema:z.object({topic:z.string().describe("Topic id \u2014 prefer a single word, max 3 words"),confidence:i.default(5).describe("Initial confidence 0-10 (default 5)"),group:z.string().nullish().describe("Optional group name; 'Other' if omitted")}),annotations:{title:"Add Topic",destructiveHint:false,idempotentHint:true,openWorldHint:false}},async t=>{try{return E(n=>{R(n,t.topic,t.confidence),t.group&&t.group!=="Other"&&V(n,t.topic,t.group);}),{content:[d("true")]}}catch(n){return a(`add_topic failed for '${t.topic}': ${n}`)}}),o$2.registerTool("remove_topic",{title:"Remove Topic",description:"Remove a topic from the knowledge map entirely. Returns true if it existed.",inputSchema:z.object({topic:z.string().describe("Topic id to remove")}),annotations:{title:"Remove Topic",destructiveHint:true,idempotentHint:true,openWorldHint:false}},async t=>{try{let n=E(r=>S(r,t.topic));return {content:[d(String(n))]}}catch(n){return a(`remove_topic failed for '${t.topic}': ${n}`)}}),o$2.registerTool("add_group",{title:"Add Group",description:"Create a new (initially empty) knowledge group. Idempotent \u2014 returns true either way.",inputSchema:z.object({name:z.string().describe("Group name, e.g. 'Machine Learning'")}),annotations:{title:"Add Group",destructiveHint:false,idempotentHint:true,openWorldHint:false}},async t=>{try{return E(n=>T(n,t.name.trim())),{content:[d("true")]}}catch(n){return a(`add_group failed for '${t.name}': ${n}`)}}),o$2.registerTool("remove_group",{title:"Remove Group",description:"Delete a knowledge group. Its topics move to Other. Returns true if it existed.",inputSchema:z.object({name:z.string().describe("Group name to delete")}),annotations:{title:"Remove Group",destructiveHint:true,idempotentHint:true,openWorldHint:false}},async t=>{try{let n=E(r=>U(r,t.name));return {content:[d(String(n))]}}catch(n){return a(`remove_group failed for '${t.name}': ${n}`)}}),o$2.registerTool("update_settings",{title:"Update Settings",description:"Update a coaching setting. max_per_day: integer 1-20. min_gap_minutes: integer 0-1440 (0 = no cooldown). nudge_every: integer 0-1000 interactions between lesson cues (0 = cue every turn). nudge_scope: 'session' | 'global'. share_name: the sender name proposed when sharing a lesson (max 80 chars, empty clears). Returns the full updated Settings.",inputSchema:z.object({key:z.enum(["max_per_day","min_gap_minutes","nudge_every","nudge_scope","share_name"]).describe("Setting key"),value:z.string().describe("New value (integer string; 'session'|'global' for nudge_scope; free text for share_name)")}),outputSchema:Qe,annotations:{title:"Update Settings",destructiveHint:false,idempotentHint:true,openWorldHint:false}},t=>{let n=i=>b(E(c=>(Y(c,t.key,i),X(c))));if(t.key==="share_name")return t.value.trim().length>w?a(`share_name must be at most ${w} characters`):n(x(t.value));if(t.key==="nudge_scope")return t.value!=="session"&&t.value!=="global"?a("nudge_scope must be 'session' or 'global'"):n(t.value);let r=Number.parseInt(t.value,10);return Number.isNaN(r)?a(`Value must be an integer, got '${t.value}'`):t.key==="max_per_day"&&!(r>=1&&r<=20)?a("max_per_day must be between 1 and 20"):t.key==="min_gap_minutes"&&!(r>=0&&r<=1440)?a("min_gap_minutes must be between 0 and 1440"):t.key==="nudge_every"&&!(r>=0&&r<=1e3)?a("nudge_every must be between 0 and 1000"):n(String(r))}),o$2.registerTool("open_ui",{title:"Open UI",description:"Launch the devcoach web dashboard in the background. port must be 1024-65535.",inputSchema:z.object({port:z.number().int().default(7860).describe("Port (default 7860)")}),annotations:{title:"Open UI",destructiveHint:false,idempotentHint:false,openWorldHint:true}},async t=>{let n=t.port;if(!(n>=1024&&n<=65535))return {content:[d(`error: port ${n} is out of valid range (1024-65535)`)]};let{pingUi:r}=await import('./app-N2KKHQYT.js');return await r(n)?{content:[d(`devcoach UI is already running at http://localhost:${n}`)]}:(spawn(process.execPath,[process.argv[1]??"","ui","--port",String(n)],{detached:true,stdio:"ignore"}).unref(),{content:[d(`devcoach UI starting at http://localhost:${n}`)]})}),o$2.registerTool("stop_ui",{title:"Stop UI",description:"Stop the devcoach web dashboard listening on port (whoever started it \u2014 open_ui or a terminal). Graceful: in-flight requests finish first. Reports stopped=false when nothing is running there. port must be 1024-65535.",inputSchema:z.object({port:z.number().int().default(7860).describe("Port (default 7860)")}),annotations:{title:"Stop UI",destructiveHint:false,idempotentHint:true,openWorldHint:true}},async t=>{let n=t.port;if(!(n>=1024&&n<=65535))return {content:[d(`error: port ${n} is out of valid range (1024-65535)`)]};let{stopUi:r}=await import('./app-N2KKHQYT.js'),i=await r(n);return {content:[d(i?`devcoach UI on port ${n} stopped`:`No devcoach UI is running on port ${n}`)],structuredContent:{stopped:i,port:n}}}),o$2.registerTool("complete_onboarding",{title:"Complete Onboarding",description:"Save the user's initial knowledge profile and mark onboarding complete. Wipes any default-seeded profile. topics: {topic_id: confidence_0_to_10}. groups: {group_name: [topic_id,...]} (topics not in any group go to 'Other'). Ensures learning-state.md exists and is non-empty (a placeholder if needed) \u2014 there is no notebook argument here. Write the real personalized notebook yourself right after, directly to the path in devcoach://onboarding's notebook_path field. Returns the updated Profile.",inputSchema:z.object({topics:z.record(z.string(),i).describe("{topic_id: confidence 0-10}"),groups:z.record(z.string(),z.array(z.string())).optional().describe("{group_name: [topic_id, ...]}")}),outputSchema:Ie,annotations:{title:"Complete Onboarding",destructiveHint:true,idempotentHint:false,openWorldHint:false}},async t=>{try{let n=E(r=>{r.exec("DELETE FROM knowledge; DELETE FROM knowledge_groups; DELETE FROM knowledge_group_names;");for(let[i,c]of Object.entries(t.topics))R(r,i,Math.max(0,Math.min(10,c)));if(t.groups)for(let[i,c]of Object.entries(t.groups))for(let D of c)D in t.topics&&V(r,D,i);return Ca(r)});return mkdirSync(dirname(s),{recursive:!0}),(!existsSync(s)||statSync(s).size===0)&&writeFileSync(s,`# devcoach \u2014 Coaching Notebook
3
3
  `),b(n)}catch(n){return a(`complete_onboarding failed: ${n}`)}}),o$2.registerTool("preview_deep_scan",{title:"Preview Deep Scan",description:"Cheap, metadata-only pre-check for 'Automatic (Deep)' onboarding. Counts and lists Claude Code projects whose last recorded activity falls within a rolling window (months back from now) \u2014 a real date window, not a fixed top-N. Reads no prompt/conversation text, only project paths and activity timestamps. Call this BEFORE spawning the deep-read subagent: over_soft_limit true means there are enough candidates that the user should be asked whether to narrow the window, proceed anyway, or pick specific projects.",inputSchema:z.object({months:z.number().int().min(1).max(24).default(3).describe("Rolling window size in months, counting back from now")}),outputSchema:ot,annotations:{title:"Preview Deep Scan",readOnlyHint:true,destructiveHint:false,idempotentHint:true,openWorldHint:false}},t=>{try{return b({...d$1(t.months)})}catch(n){return a(`preview_deep_scan failed: ${n}`)}});let p$2=t=>({title:t,readOnlyHint:true,destructiveHint:false,idempotentHint:true,openWorldHint:false});o$2.registerTool("get_briefing",{title:"Lesson Briefing",description:"Everything needed before delivering a lesson, in ONE call: onboarding status, rate limit, taught topics, knowledge profile, the coaching notebook text and its notebook_path. Same data as the devcoach://briefing resource \u2014 call the tool, it needs no server name.",inputSchema:z.object({}),annotations:p$2("Lesson Briefing")},()=>F("get_briefing",Ue())),o$2.registerTool("get_onboarding",{title:"Onboarding Status & Detected Stack",description:"Onboarding status (knowledge_ready, notebook_ready, needs_onboarding), the stack detected across the full Claude Code history with per-project provenance, the default topics and the notebook_path. Same data as the devcoach://onboarding resource.",inputSchema:z.object({}),annotations:p$2("Onboarding Status & Detected Stack")},()=>F("get_onboarding",Ge())),o$2.registerTool("get_profile",{title:"Knowledge Profile",description:"Current knowledge map: topics with confidence 0\u201310 and their groups. Same data as the devcoach://profile resource.",inputSchema:z.object({}),outputSchema:Ie,annotations:p$2("Knowledge Profile")},()=>F("get_profile",$e())),o$2.registerTool("share_lesson",{title:"Share Lesson",description:"Hand a lesson to another person. transport 'text' (default) returns the card plus a one-line code to paste anywhere; 'link' a server-less URL that previews the lesson and imports it into their dashboard; 'file' the contents of a .devcoach.md file. Only title, summary, body, topic, categories and level travel unless include_context is true; local paths never do. Pick the lesson with get_lessons first.",inputSchema:z.object({lesson_id:z.string().describe("Lesson id (from get_lessons)"),transport:z.enum(["text","link","file"]).default("text").describe("text = card + code to paste; link = share URL; file = .devcoach.md contents"),include_context:z.boolean().default(false).describe("Include project/branch/commit/task context (off by default \u2014 privacy). Local paths are never included."),shared_by:z.string().nullish().describe("Sender name. Omit \u2192 the share_name setting, then git user.name. Empty string \u2192 anonymous.")}),outputSchema:Ze,annotations:p$2("Share Lesson")},t=>{try{let n$1=E(r=>{let i=O(r,t.lesson_id);if(!i)return null;let c=ta({explicit:t.shared_by??null,setting:X(r).share_name,gitUserName:n()});return sa(i,{includeContext:t.include_context,sharedBy:c})});return n$1?b({transport:t.transport,code:ua(n$1),text:t.transport==="text"?wa(n$1):null,link:t.transport==="link"?xa(n$1):null,markdown:t.transport==="file"?Aa(n$1):null,filename:t.transport==="file"?za(n$1):null,shared_by:n$1.shared_by,include_context:t.include_context,reply_check:tt[t.transport]}):a(`share_lesson: lesson '${t.lesson_id}' not found \u2014 call get_lessons to pick one`)}catch(n){return a(`share_lesson failed: ${n}`)}}),o$2.registerTool("import_lesson",{title:"Import Lesson",description:"Store a lesson someone shared. Pass whatever the user handed you, verbatim: the devcoach:lesson:1:\u2026 code, the whole copied card, the share link, a URL to the lesson text, the contents of a .devcoach.md file, or a lessons JSON export. The lesson joins the coaching log as their own (feedback works as usual) but never counts against the daily limit; re-importing the same share is reported as a duplicate, not an error.",inputSchema:z.object({payload:z.string().min(1).describe("The code, link, URL, card text, file text or JSON \u2014 verbatim")}),outputSchema:et,annotations:{title:"Import Lesson",destructiveHint:false,idempotentHint:true,openWorldHint:true}},async t=>{try{let n=p$1(t.payload)&&!t.payload.includes("#devcoach:lesson:")?await q(t.payload):t.payload,r=E(i=>Ga(i,n));return b({...r,reply_check:nt})}catch(n){return n instanceof ra?a(`import_lesson: ${n.message}`):a(`import_lesson failed: ${n}`)}});let m$1=z.object({course_id:z.string(),position:z.number().int(),title:z.string(),kind:n$1,anchor:z.string(),status:o$1,done_at:z.string().nullable()}),g$2=z.object({id:z.string(),lesson_id:z.string().nullable(),topic_id:z.string(),title:z.string(),goal:z.string().nullable(),prerequisites:z.array(z.object({concept:z.string(),known:z.boolean()})),status:m,created_at:z.string(),updated_at:z.string(),completed_at:z.string().nullable(),steps:z.array(m$1)}),f$3='Write ONE self-contained HTML document at document_path (inline CSS + JS, no external URLs, no fetch, no <form>, no alert/confirm/prompt) with one <section id="step-N"> per step, using your file tools \u2014 never paste HTML into the chat. Code goes in <pre><code class="language-\u2026"> and the document carries ONE <script data-devcoach="highlighter"></script> placeholder: devcoach fills it with the highlighter when you register steps (never write one yourself, never re-read the whole file afterwards). Then register every section with add_course_step (position order, anchor = the section id). Teach one step per message and tell the user the course is on the dashboard under Courses (/devcoach:ui).';o$2.registerTool("create_course",{title:"Create Course",description:"Start a step-by-step course, usually from a lesson the user couldn't follow. Pass the prerequisite chain you explored ([{concept, known}], top-down). Returns the course row, course_dir and document_path \u2014 write the document there yourself, then call add_course_step for each section.",inputSchema:z.object({title:z.string().min(1).describe("Course title, e.g. 'From sums to logarithms'"),topic_id:z.string().min(1).describe("The lesson-style topic id the course belongs to"),goal:z.string().nullish().describe("One sentence: what the user will be able to do"),lesson_id:z.string().nullish().describe("The seed lesson's id, when the course grows from one"),prerequisites:z.array(z.object({concept:z.string().min(1),known:z.boolean()})).default([]).describe("The explored chain, top-down: [{concept, known}]")}),outputSchema:g$2.extend({course_dir:z.string(),document_path:z.string(),seed_context:z.object({task_context:z.string().nullable(),project:z.string().nullable(),repository:z.string().nullable(),branch:z.string().nullable()}).nullable(),reply_check:z.string()}),annotations:{title:"Create Course",destructiveHint:false,idempotentHint:false,openWorldHint:false}},async t=>{try{let n=E(r=>{let i=t.lesson_id?O(r,t.lesson_id):null;if(t.lesson_id&&!i)throw new b$1(`lesson '${t.lesson_id}' not found \u2014 call get_lessons to pick one`);return {course:f$1(r,{title:t.title,topic_id:t.topic_id,goal:t.goal??null,lesson_id:t.lesson_id??null,prerequisites:t.prerequisites}),seed:i}});return b({...n.course,course_dir:c(n.course.id),document_path:d$2(n.course.id),seed_context:n.seed?{task_context:n.seed.task_context,project:n.seed.project,repository:n.seed.repository,branch:n.seed.branch}:null,reply_check:f$3})}catch(n){return a(`create_course failed: ${n}`)}}),o$2.registerTool("add_course_step",{title:"Add Course Step",description:"Register a step of a course: the section with id=<anchor> must already exist in the course document. Appended by default; pass `after` to insert it after that step (0 = first) \u2014 the following steps are renumbered.",inputSchema:z.object({course_id:z.string().describe("Course id (from create_course)"),title:z.string().min(1).describe("Step title"),kind:n$1.describe("concept | example | practice | check"),anchor:z.string().regex(/^[a-z0-9][a-z0-9-]*$/).describe("The section's id in the document, e.g. step-1"),after:z.number().int().min(0).nullish().describe("Insert after this step number (0 = first); omit to append")}),outputSchema:g$2.extend({step:m$1}),annotations:{title:"Add Course Step",destructiveHint:false,idempotentHint:false,openWorldHint:false}},async t=>{try{let n=E(r=>{let i=i$1(r,t.course_id,{title:t.title,kind:t.kind,anchor:t.anchor,after:t.after});return {...g$1(r,t.course_id),step:i}});return b(n)}catch(n){return a(`add_course_step failed: ${n}`)}}),o$2.registerTool("update_course_progress",{title:"Update Course Progress",description:"With position: set that step to todo | done | skipped (the course completes itself once every step is done or skipped). Without position: set the course to active | completed | abandoned.",inputSchema:z.object({course_id:z.string().describe("Course id"),position:z.number().int().min(1).nullish().describe("Step number (1-based); omit for the course"),status:z.string().describe("Step: todo | done | skipped \u2014 course: active | completed | abandoned")}),outputSchema:g$2,annotations:{title:"Update Course Progress",destructiveHint:false,idempotentHint:true,openWorldHint:false}},async t=>{try{let n=E(r=>{if(t.position!=null){let i=o$1.parse(t.status);return j$1(r,t.course_id,t.position,i)}return k$1(r,t.course_id,m.parse(t.status))});return n?b(n):a(`update_course_progress: course '${t.course_id}'${t.position!=null?` step ${t.position}`:""} not found \u2014 call get_courses`)}catch(n){return a(`update_course_progress failed: ${n}`)}}),o$2.registerTool("get_courses",{title:"Get Courses",description:"List courses with their steps and progress; filter by id, seed lesson or status. 'continue the course' = status active.",inputSchema:z.object({course_id:z.string().nullish().describe("One course"),lesson_id:z.string().nullish().describe("Courses seeded by this lesson"),status:m.nullish().describe("active | completed | abandoned")}),outputSchema:z.object({courses:z.array(g$2.extend({course_dir:z.string()}))}),annotations:p$2("Get Courses")},t=>{try{let n=E(r=>{if(t.course_id){let i=g$1(r,t.course_id);return i?[i]:[]}return h$1(r,{lesson_id:t.lesson_id,status:t.status})});return b({courses:n.map(r=>({...r,course_dir:c(r.id)}))})}catch{return b({courses:[]})}});let l=(t,n)=>({title:t,description:n,mimeType:"application/json"});return o$2.registerResource("profile","devcoach://profile",l("Knowledge Profile","Current knowledge map \u2014 topics, confidence scores, and groups."),t=>u(t,$e())),o$2.registerResource("notebook","devcoach://notebook",{title:"Coaching Notebook",description:"The coaching notebook (learning-state.md): observations, recurring patterns, recommended focus, and open hypotheses about the user. Read it to decide what to teach.",mimeType:"text/markdown"},t=>{let n="";try{existsSync(s)&&(n=readFileSync(s,"utf8"));}catch{}return {contents:[{uri:t.href,mimeType:"text/markdown",text:n}]}}),o$2.registerResource("settings","devcoach://settings",l("Settings","Current coaching settings (rate limits, UI theme)."),t=>{try{return u(t,E(n=>X(n)))}catch(n){return u(t,{error:String(n)})}}),o$2.registerResource("recent-lessons","devcoach://lessons/recent",l("Recent Lessons","Last 10 lessons from the current week."),t=>{try{return u(t,E(n=>H(n,{period:"week",page:1,per_page:10})))}catch(n){return u(t,[{error:String(n)}])}}),o$2.registerResource("stats","devcoach://stats",l("Stats","Aggregate coaching statistics: counts and weakest/strongest topics."),t=>{try{return u(t,E(n=>Fa(n)))}catch(n){return u(t,{error:String(n)})}}),o$2.registerResource("taught-topics","devcoach://taught-topics",l("Taught Topics","All topic_ids already taught \u2014 read before selecting a new lesson topic."),t=>{try{return u(t,E(n=>Ha(n)))}catch{return u(t,[])}}),o$2.registerResource("rate-limit","devcoach://rate-limit",l("Rate Limit","Current rate-limit status and running lesson total."),t=>{try{let{r:n,total:r}=E(c=>({r:Ba(c),total:G(c)})),i={allowed:n.allowed,total_lessons:r};return n.reason!=null&&(i.reason=n.reason),u(t,i)}catch(n){return u(t,{allowed:false,reason:`Rate limit check unavailable: ${n}`})}}),o$2.registerResource("briefing","devcoach://briefing",l("Lesson Briefing","Everything needed before delivering a lesson in ONE read: onboarding status, rate limit, taught topics, knowledge profile, and the coaching notebook."),t=>u(t,Ue())),o$2.registerResource("context","devcoach://context",l("Workspace Context","Auto-detected git context and most-used lesson metadata defaults."),t=>{try{let n=o(),r=E(i=>ca(i));return u(t,{git:n,usage_defaults:r})}catch(n){return u(t,{error:String(n)})}}),o$2.registerResource("onboarding","devcoach://onboarding",l("Onboarding","Onboarding status, the stack detected across the full Claude Code history (with per-project provenance), and project topic defaults."),t=>u(t,Ge())),o$2.registerResource("lesson",new ResourceTemplate("devcoach://lessons/{lesson_id}",{list:void 0}),l("Lesson","A single lesson by id."),(t,n)=>{try{let r=String(n.lesson_id),i=E(c=>O(c,r));return i?u(t,i):u(t,{error:`Lesson '${r}' not found`})}catch(r){return u(t,{error:String(r)})}}),o$2.registerPrompt("devcoach_instructions",{title:"devcoach coaching instructions",description:"Full coaching instructions for the devcoach skill (SKILL.md plus its reference files)."},()=>{let t=f$2().map(n=>`
4
4
 
5
5
  ---
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "devcoach",
3
- "version": "2.7.0-next.615.ge571c8f",
3
+ "version": "2.7.0-next.623.g2d36462",
4
4
  "mcpName": "io.github.UltimaPhoenix/devcoach",
5
- "description": "Stay sharp while your AI does the work — learn from every task with in-context coaching, all local, all free",
5
+ "description": "Stay sharp while your AI does the work — learn what you don't know yet from the tasks your agent completes, with in-context coaching, all local, all free",
6
6
  "license": "AGPL-3.0-only",
7
7
  "keywords": [
8
8
  "mcp",