devcoach 0.8.0 → 0.8.1

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.
@@ -25,7 +25,11 @@ import {m as m$1,F,R as R$1,V,S as S$1,z,D,A,E,T,B as B$1,C as C$1,t,u,o,v,x,p,r
25
25
  </head>
26
26
  <body class="bg-gray-50 dark:bg-gray-950 text-gray-900 dark:text-gray-100 min-h-screen font-mono transition-colors duration-200">
27
27
  <nav class="bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-800 px-6 py-3 flex items-center gap-6">
28
- <span class="text-indigo-600 dark:text-indigo-400 font-bold text-lg">🎓 devcoach</span>
28
+ <!-- Brand wordmark, mirrors the docs site (website/src/css/custom.css): "dev" in the
29
+ theme foreground, "coach" in the teal accent (#0d9488 / #5eead4 = teal-600/300). -->
30
+ <span class="font-extrabold text-lg tracking-tight text-gray-900 dark:text-gray-100"
31
+ >🎓 dev<span class="text-teal-600 dark:text-teal-300">coach</span></span
32
+ >
29
33
  ${e("/","Profile",t.currentPath==="/")}
30
34
  ${e("/lessons","Lessons",t.currentPath.includes("/lessons"))}
31
35
  ${e("/settings","Settings",t.currentPath==="/settings")}
package/dist/bin.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- var r=process.argv[2]??"",t=new Set(["stop-hook","prompt-hook","onboard-hook","lesson-ready"]);try{if(t.has(r)){let{runHook:o}=await import('./hooks-YTQGB2OF.js');o(r);}else {let{runCli:o}=await import('./commands-Z4T2PK5O.js');await o();}}catch(o){console.error(o),process.exit(1);}
2
+ var r=process.argv[2]??"",t=new Set(["stop-hook","prompt-hook","onboard-hook","lesson-ready"]);try{if(t.has(r)){let{runHook:o}=await import('./hooks-7QPFYLPI.js');o(r);}else {let{runCli:o}=await import('./commands-NL54QLEU.js');await o();}}catch(o){console.error(o),process.exit(1);}
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ import {i as i$1,m,N as N$1,Y,h,l,M as M$1,X}from'./chunk-227BHE2B.js';import {existsSync,mkdirSync,statSync,writeFileSync,appendFileSync,fstatSync,readFileSync}from'fs';import {join}from'path';function T(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,last_assistant_message:typeof o?.last_assistant_message=="string"?o.last_assistant_message:null,transcript_path:typeof o?.transcript_path=="string"?o.transcript_path:null}}catch{return {stop_hook_active:false,permission_mode:null,session_id:null,last_assistant_message:null,transcript_path:null}}}function p(){let e={stop_hook_active:false,permission_mode:null,session_id:null,last_assistant_message:null,transcript_path:null};try{let o=fstatSync(0);return !o.isFIFO()&&!o.isFile()&&!o.isSocket()?e:T(readFileSync(0,"utf8"))}catch{return e}}function A(e){return e.stop_hook_active||e.permission_mode==="plan"}function i(e,o,t){if(process.env.DEVCOACH_HOOK_DEBUG==="1")try{mkdirSync(h,{recursive:!0});let n=join(h,"hook.log");existsSync(n)&&statSync(n).size>256*1024&&writeFileSync(n,""),appendFileSync(n,`${new Date().toISOString()} ${e} ${(o??"-").slice(0,8)} ${t}
3
+ `);}catch{}}function c(e,o){let t={decision:"block",reason:e};o&&(t.systemMessage=o),process.stdout.write(`${JSON.stringify(t)}
4
+ `),process.exit(0);}var f="devcoach: the user has no coaching profile yet. Ask them how they want to set it up \u2014 Automatic (detect this project's tech stack), Guided (a short conversation to map topics and confidence levels), or Import (restore from a backup) \u2014 and 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 N(e=p()){if(A(e)&&process.exit(0),existsSync(i$1)){let o;try{o=m(t=>N$1(t).knowledge_ready);}catch(t){i("onboard-hook",e.session_id,`error: ${t}`),process.exit(0);}o&&process.exit(0);}i("onboard-hook",e.session_id,"onboarding cue"),c(f);}var a=10;function R(e){let o=e%a===0,t=Math.ceil(e/a)*a;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
+
6
+ Invoke the \`devcoach\` skill (Skill tool) NOW and follow it to deliver ONE lesson. Three hard rules:
7
+ - The lesson card MUST be printed as your visible reply BEFORE calling log_lesson.
8
+ - After log_lesson returns, output NOTHING else \u2014 the card is the end of the reply.
9
+ - If the work just completed does not warrant a lesson (pure questions, chat, nothing technical), call the devcoach \`skip_lesson\` tool with a one-line reason and output nothing.
10
+ ${o?`This is lesson #${e} \u2014 a notebook checkpoint (every ${a} lessons): after log_lesson returns the feedback, also call update_notebook with the revised notebook markdown and record "(updated after ${e} lessons)".`:`Do NOT call update_notebook this time \u2014 the notebook is refreshed only every ${a} lessons (next checkpoint at lesson #${t}).`}
11
+
12
+ If the devcoach skill is not available, fall back to: read devcoach://profile, devcoach://taught-topics, and devcoach://notebook; pick ONE untaught profile topic at or above the user's confidence band; write the card as plain markdown (no '>' blockquote) between two band headings \`### \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u{1F393} devcoach \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\` and \`### \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 [topic] \xB7 [level] \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\`, with \`**[Title]** \xB7 [Category] \xB7 [Level]\`, 3\u20136 short paragraphs tied to the task, and a \`\u{1F4A1} *Senior tip:*\` line; THEN call log_lesson (body = clean markdown without bands or the title line).
13
+
14
+ The rate-limit check is already done by this hook \u2014 skip it. Output only the lesson card (or nothing). No preamble, no meta-commentary.`}var $="devcoach: the lesson was saved with log_lesson but its card is NOT visible in your reply. Print the lesson card now, verbatim, exactly as echoed by the log_lesson result (the block between the two `### \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u{1F393} devcoach \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500` band headings) \u2014 as your ENTIRE reply, no other text, no tool calls.",v="\u{1F393} devcoach";function I(e){if(e.last_assistant_message?.includes(v))return true;if(!e.transcript_path)return null;try{let o=readFileSync(e.transcript_path,"utf8").split(`
15
+ `);for(let t=o.length-1;t>=0;t--){let n=o[t];if(!n)continue;let s;try{s=JSON.parse(n);}catch{continue}if(s?.isSidechain===!0)continue;let d=s?.message?.content;if(s?.type==="assistant"&&Array.isArray(d)){for(let h of d)if(h?.type==="text"&&typeof h.text=="string"&&h.text.includes(v))return !0}if(s?.type==="user"&&!(Array.isArray(d)&&d.some(S=>S?.type==="tool_result")))return !1}return !1}catch{return null}}function B(e,o,t){let n=o.permission_mode==="plan";if(!n&&M$1(e)&&I(o)===false)return {kind:"recover-card",note:"card missing from reply \u2014 recovering"};if(o.stop_hook_active)return {kind:"silent",note:"hook-forced continuation"};if(!N$1(e).knowledge_ready)return !t.onboardCue||n?{kind:"silent",note:"onboarding not complete"}:{kind:"onboard",note:"onboarding cue (empty knowledge)"};let s=X(e,o.session_id,{planMode:n});return s.cue?{kind:"cue",nextLessonNumber:s.nextLessonNumber,note:s.reason}:{kind:"silent",note:s.reason}}function x(e,o,t){let n;try{n=m(s=>l(s,()=>B(s,o,t)));}catch(s){i(e,o.session_id,`error: ${s}`),process.exit(0);}switch(i(e,o.session_id,n.note),n.kind){case "onboard":c(f);break;case "recover-card":c($,"\u{1F393} devcoach: recovering the lesson card\u2026");break;case "cue":c(R(n.nextLessonNumber),"\u{1F393} devcoach: preparing a lesson\u2026");break;}process.exit(0);}function F(e=p()){existsSync(i$1)||process.exit(0),x("lesson-ready",e,{onboardCue:false});}function L(e=p()){existsSync(i$1)||((e.stop_hook_active||e.permission_mode==="plan")&&process.exit(0),i("stop-hook",e.session_id,"onboarding cue (no DB)"),c(f)),x("stop-hook",e,{onboardCue:true});}var M="devcoach: the lesson pacing threshold will be reached at the end of this turn. Resolve it before finishing:\n- If this task is technical: invoke the `devcoach` skill (Skill tool), then end your reply with ONE lesson card (the `### \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u{1F393} devcoach \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500` block) followed by a log_lesson call. The card must be the LAST visible text \u2014 after log_lesson returns, output nothing else.\n- If it is not technical: silently call the devcoach `skip_lesson` tool with a one-line reason and output nothing about it.";function U(e=p()){e.permission_mode==="plan"&&process.exit(0),existsSync(i$1)||process.exit(0);let o;try{o=m(t=>Y(t,e.session_id).wouldCue);}catch(t){i("prompt-hook",e.session_id,`error: ${t}`),process.exit(0);}i("prompt-hook",e.session_id,o?"priming":"no prime"),o||process.exit(0),process.stdout.write(`${JSON.stringify({hookSpecificOutput:{hookEventName:"UserPromptSubmit",additionalContext:M}})}
16
+ `),process.exit(0);}function G(e){switch(e){case "stop-hook":L();break;case "prompt-hook":U();break;case "onboard-hook":N();break;case "lesson-ready":F();break;default:process.exit(0);}}export{T as a,N as b,R as c,F as d,L as e,U as f,G as g};
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import {readFileSync,readdirSync,existsSync}from'fs';import {dirname,basename,join}from'path';import {execFileSync}from'child_process';import {fileURLToPath}from'url';var v=[["package.json","javascript",6],["tsconfig*.json","typescript",6],["requirements*.txt","python",6],["pyproject.toml","python",6],["setup.py","python",6],["go.mod","go",6],["Cargo.toml","rust",6],["pom.xml","java",6],["build.gradle","java",6],["build.gradle.kts","java",6],["*.csproj","csharp",6],["Gemfile","ruby",6],["composer.json","php",6],["Dockerfile","docker",7],["Dockerfile.*","docker",7],["docker-compose.yml","docker_compose",7],["docker-compose.yaml","docker_compose",7],["docker-compose.*.yml","docker_compose",7],[".github/workflows/*.yml","github_actions",6],[".github/workflows/*.yaml","github_actions",6],[".gitlab-ci.yml","gitlab_ci",6],["Jenkinsfile","ci_cd",6],["*.tf","terraform",6],["*.sql","sql",5],[".git","git",7],["kubernetes/*.yaml","kubernetes",6],["kubernetes/*.yml","kubernetes",6],["k8s/*.yaml","kubernetes",6],["*.bicep","azure",6],["serverless.yml","aws",6],["serverless.yaml","aws",6],["template.yaml","aws",5],["angular.json","angular",6],["svelte.config.*","svelte",6],["nuxt.config.*","vue",6],["vite.config.*","javascript",6]],x={react:"react","react-dom":"react",vue:"vue","@angular/core":"angular",svelte:"svelte",next:"nextjs",nuxt:"vue",fastify:"fastify",express:"express",koa:"express","@nestjs/core":"node_js"},j={django:"django",flask:"flask",fastapi:"fastapi",starlette:"fastapi",tornado:"python",aiohttp:"python",sqlalchemy:"postgresql",celery:"python"};function S(e){let t="";for(let n of e)n==="*"?t+="[^/]*":n==="?"?t+="[^/]":t+=n.replace(/[.+^${}()|[\]\\]/g,String.raw`\$&`);return new RegExp(`^${t}$`)}function w(e,t){let n=t.lastIndexOf("/"),r=n>=0?join(e,t.slice(0,n)):e,o=n>=0?t.slice(n+1):t,s;try{s=readdirSync(r);}catch{return false}let i=S(o);return s.some(h=>i.test(h))}function u(e){try{return existsSync(e)?readFileSync(e,"utf8"):null}catch{return null}}function R(e,t){let n=u(join(e,"package.json"));if(!n)return;let r;try{r=JSON.parse(n);}catch{return}let o=new Set([...Object.keys(r.dependencies??{}),...Object.keys(r.devDependencies??{})]);for(let[s,i]of Object.entries(x))o.has(s)&&t(i,6);}function A(e,t){let n=["requirements.txt","requirements-dev.txt","requirements/base.txt","pyproject.toml"];for(let r of n){let o=u(join(e,r))?.toLowerCase();if(o)for(let[s,i]of Object.entries(j))o.includes(s)&&t(i,6);}}function I(e){let t={},n=(r,o)=>{(t[r]??-1)<o&&(t[r]=o);};for(let[r,o,s]of v)w(e,r)&&n(o,s);return R(e,n),A(e,n),t}function a(...e){try{return execFileSync("git",e,{encoding:"utf8",timeout:3e3,stdio:["ignore","pipe","ignore"]}).trim()||null}catch{return null}}var p={"github.com":"github","gitlab.com":"gitlab","bitbucket.org":"bitbucket"};function E(e){if(!e)return [null,null];let t=/^git@([^:]+):(.+?)(?:\.git)?$/.exec(e);if(t){let r=(t[1]??"").toLowerCase();return [t[2]??e,p[r]??"local"]}let n=/^https?:\/\/([^/]+)\/(.+?)(?:\.git)?$/.exec(e);if(n){let r=(n[1]??"").toLowerCase();return [n[2]??e,p[r]??"local"]}return [e,"local"]}function $(){let e=process.cwd(),t=a("rev-parse","--abbrev-ref","HEAD"),n=a("rev-parse","HEAD"),r=a("remote","get-url","origin"),[o,s]=E(r),i;return o?i=o.replace(/\/+$/,"").split("/").pop()??null:i=basename(e)||null,t==="HEAD"&&(t=null),{project:i,repository:o,branch:t,commit_hash:n,folder:e,repository_platform:s}}var d=dirname(fileURLToPath(import.meta.url));function g(){for(let e of ["../assets","../../assets"])if(existsSync(join(d,e,"SKILL.md")))return join(d,e);return null}var J=()=>{let e=g();return e===null?"devcoach: coaching instructions unavailable (SKILL.md not found in package).":readFileSync(join(e,"SKILL.md"),"utf8")};function W(){let e=g();if(e===null)return [];try{return readdirSync(join(e,"references")).filter(t=>t.endsWith(".md")).sort().map(t=>({name:t,content:readFileSync(join(e,"references",t),"utf8")}))}catch{return []}}var f={version:"0.8.0"};var Q=f.version;export{I as a,$ as b,J as c,W as d,Q as e};
2
+ import {readFileSync,readdirSync,existsSync}from'fs';import {dirname,basename,join}from'path';import {execFileSync}from'child_process';import {fileURLToPath}from'url';var v=[["package.json","javascript",6],["tsconfig*.json","typescript",6],["requirements*.txt","python",6],["pyproject.toml","python",6],["setup.py","python",6],["go.mod","go",6],["Cargo.toml","rust",6],["pom.xml","java",6],["build.gradle","java",6],["build.gradle.kts","java",6],["*.csproj","csharp",6],["Gemfile","ruby",6],["composer.json","php",6],["Dockerfile","docker",7],["Dockerfile.*","docker",7],["docker-compose.yml","docker_compose",7],["docker-compose.yaml","docker_compose",7],["docker-compose.*.yml","docker_compose",7],[".github/workflows/*.yml","github_actions",6],[".github/workflows/*.yaml","github_actions",6],[".gitlab-ci.yml","gitlab_ci",6],["Jenkinsfile","ci_cd",6],["*.tf","terraform",6],["*.sql","sql",5],[".git","git",7],["kubernetes/*.yaml","kubernetes",6],["kubernetes/*.yml","kubernetes",6],["k8s/*.yaml","kubernetes",6],["*.bicep","azure",6],["serverless.yml","aws",6],["serverless.yaml","aws",6],["template.yaml","aws",5],["angular.json","angular",6],["svelte.config.*","svelte",6],["nuxt.config.*","vue",6],["vite.config.*","javascript",6]],x={react:"react","react-dom":"react",vue:"vue","@angular/core":"angular",svelte:"svelte",next:"nextjs",nuxt:"vue",fastify:"fastify",express:"express",koa:"express","@nestjs/core":"node_js"},j={django:"django",flask:"flask",fastapi:"fastapi",starlette:"fastapi",tornado:"python",aiohttp:"python",sqlalchemy:"postgresql",celery:"python"};function S(e){let t="";for(let n of e)n==="*"?t+="[^/]*":n==="?"?t+="[^/]":t+=n.replace(/[.+^${}()|[\]\\]/g,String.raw`\$&`);return new RegExp(`^${t}$`)}function w(e,t){let n=t.lastIndexOf("/"),r=n>=0?join(e,t.slice(0,n)):e,o=n>=0?t.slice(n+1):t,s;try{s=readdirSync(r);}catch{return false}let i=S(o);return s.some(h=>i.test(h))}function u(e){try{return existsSync(e)?readFileSync(e,"utf8"):null}catch{return null}}function R(e,t){let n=u(join(e,"package.json"));if(!n)return;let r;try{r=JSON.parse(n);}catch{return}let o=new Set([...Object.keys(r.dependencies??{}),...Object.keys(r.devDependencies??{})]);for(let[s,i]of Object.entries(x))o.has(s)&&t(i,6);}function A(e,t){let n=["requirements.txt","requirements-dev.txt","requirements/base.txt","pyproject.toml"];for(let r of n){let o=u(join(e,r))?.toLowerCase();if(o)for(let[s,i]of Object.entries(j))o.includes(s)&&t(i,6);}}function I(e){let t={},n=(r,o)=>{(t[r]??-1)<o&&(t[r]=o);};for(let[r,o,s]of v)w(e,r)&&n(o,s);return R(e,n),A(e,n),t}function a(...e){try{return execFileSync("git",e,{encoding:"utf8",timeout:3e3,stdio:["ignore","pipe","ignore"]}).trim()||null}catch{return null}}var p={"github.com":"github","gitlab.com":"gitlab","bitbucket.org":"bitbucket"};function E(e){if(!e)return [null,null];let t=/^git@([^:]+):(.+?)(?:\.git)?$/.exec(e);if(t){let r=(t[1]??"").toLowerCase();return [t[2]??e,p[r]??"local"]}let n=/^https?:\/\/([^/]+)\/(.+?)(?:\.git)?$/.exec(e);if(n){let r=(n[1]??"").toLowerCase();return [n[2]??e,p[r]??"local"]}return [e,"local"]}function $(){let e=process.cwd(),t=a("rev-parse","--abbrev-ref","HEAD"),n=a("rev-parse","HEAD"),r=a("remote","get-url","origin"),[o,s]=E(r),i;return o?i=o.replace(/\/+$/,"").split("/").pop()??null:i=basename(e)||null,t==="HEAD"&&(t=null),{project:i,repository:o,branch:t,commit_hash:n,folder:e,repository_platform:s}}var d=dirname(fileURLToPath(import.meta.url));function g(){for(let e of ["../assets","../../assets"])if(existsSync(join(d,e,"SKILL.md")))return join(d,e);return null}var J=()=>{let e=g();return e===null?"devcoach: coaching instructions unavailable (SKILL.md not found in package).":readFileSync(join(e,"SKILL.md"),"utf8")};function W(){let e=g();if(e===null)return [];try{return readdirSync(join(e,"references")).filter(t=>t.endsWith(".md")).sort().map(t=>({name:t,content:readFileSync(join(e,"references",t),"utf8")}))}catch{return []}}var f={version:"0.8.1"};var Q=f.version;export{I as a,$ as b,J as c,W as d,Q as e};
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import {e as e$1,f,b,d}from'./chunk-XSQUPJG7.js';export{c as buildLessonCue,d as cmdLessonReady,b as cmdOnboardHook,f as cmdPromptHook,e as cmdStopHook,a as parseHookPayload}from'./chunk-XSQUPJG7.js';import {e,b as b$1,a,c,d as d$1}from'./chunk-HQO42VUB.js';import {m,S,p,w as w$1,r,q,U as U$1,F,R as R$1,V,G,z,D,A,B,C,E,P,y,t as t$1,j,Q,i,N as N$1,H,J,Y}from'./chunk-227BHE2B.js';import {spawnSync}from'child_process';import {writeFileSync,existsSync,readFileSync,mkdirSync,accessSync,constants}from'fs';import {homedir,platform}from'os';import {join,dirname}from'path';import {createInterface}from'readline/promises';import {Command}from'commander';import {styleText}from'util';var Ee=!!process.stdout.isTTY&&!process.env.NO_COLOR,$=(e,o)=>Ee?styleText(e,o):o,t={green:e=>$("green",e),red:e=>$("red",e),yellow:e=>$("yellow",e),cyan:e=>$("cyan",e),magenta:e=>$("magenta",e),dim:e=>$("dim",e),bold:e=>$("bold",e)};function K(e,o){return e==="green"?t.green(o):e==="yellow"?t.yellow(o):t.red(o)}var Ae=/\[[0-9;]*m/g,He=e=>e.replace(Ae,""),R=e=>[...He(e)].length;function ye(e,o,n){let r=Math.max(0,o-R(e));if(n==="right")return " ".repeat(r)+e;if(n==="center"){let a=Math.floor(r/2);return " ".repeat(a)+e+" ".repeat(r-a)}return e+" ".repeat(r)}function L(e,o,n,r=true){let a=o.map((p,c)=>Math.max(r?R(p.header):0,...n.map(l=>R(l[c]??"")))),i=(p,c,l)=>p+a.map(g=>"\u2500".repeat(g+2)).join(c)+l,d=p=>`\u2502${p.map((c,l)=>` ${ye(c??"",a[l]??0,o[l]?.justify??"left")} `).join("\u2502")}\u2502`,m=a.reduce((p,c)=>p+c+3,1),u=[];e&&u.push(ye(t.bold(e),m,"center")),u.push(i("\u256D","\u252C","\u256E")),r&&u.push(d(o.map(p=>t.bold(p.header))),i("\u251C","\u253C","\u2524"));for(let p of n)u.push(d(p));return u.push(i("\u2570","\u2534","\u256F")),u.join(`
2
+ import {e as e$1,f,b,d}from'./chunk-2OWIYKQC.js';export{c as buildLessonCue,d as cmdLessonReady,b as cmdOnboardHook,f as cmdPromptHook,e as cmdStopHook,a as parseHookPayload}from'./chunk-2OWIYKQC.js';import {e,b as b$1,a,c,d as d$1}from'./chunk-CBUZNGSX.js';import {m,S,p,w as w$1,r,q,U as U$1,F,R as R$1,V,G,z,D,A,B,C,E,P,y,t as t$1,j,Q,i,N as N$1,H,J,Y}from'./chunk-227BHE2B.js';import {spawnSync}from'child_process';import {writeFileSync,existsSync,readFileSync,mkdirSync,accessSync,constants}from'fs';import {homedir,platform}from'os';import {join,dirname}from'path';import {createInterface}from'readline/promises';import {Command}from'commander';import {styleText}from'util';var Ee=!!process.stdout.isTTY&&!process.env.NO_COLOR,$=(e,o)=>Ee?styleText(e,o):o,t={green:e=>$("green",e),red:e=>$("red",e),yellow:e=>$("yellow",e),cyan:e=>$("cyan",e),magenta:e=>$("magenta",e),dim:e=>$("dim",e),bold:e=>$("bold",e)};function K(e,o){return e==="green"?t.green(o):e==="yellow"?t.yellow(o):t.red(o)}var Ae=/\[[0-9;]*m/g,He=e=>e.replace(Ae,""),R=e=>[...He(e)].length;function ye(e,o,n){let r=Math.max(0,o-R(e));if(n==="right")return " ".repeat(r)+e;if(n==="center"){let a=Math.floor(r/2);return " ".repeat(a)+e+" ".repeat(r-a)}return e+" ".repeat(r)}function L(e,o,n,r=true){let a=o.map((p,c)=>Math.max(r?R(p.header):0,...n.map(l=>R(l[c]??"")))),i=(p,c,l)=>p+a.map(g=>"\u2500".repeat(g+2)).join(c)+l,d=p=>`\u2502${p.map((c,l)=>` ${ye(c??"",a[l]??0,o[l]?.justify??"left")} `).join("\u2502")}\u2502`,m=a.reduce((p,c)=>p+c+3,1),u=[];e&&u.push(ye(t.bold(e),m,"center")),u.push(i("\u256D","\u252C","\u256E")),r&&u.push(d(o.map(p=>t.bold(p.header))),i("\u251C","\u253C","\u2524"));for(let p of n)u.push(d(p));return u.push(i("\u2570","\u2534","\u256F")),u.join(`
3
3
  `)}function N(e=""){if(!e)return "\u2500".repeat(60);let n=` ${e} `,r=Math.max(2,60-R(n)),a=Math.floor(r/2);return "\u2500".repeat(a)+n+"\u2500".repeat(r-a)}var we=e=>{let o=Math.max(0,Math.min(10,Math.round(e)));return "\u2588".repeat(o)+"\u2591".repeat(10-o)},$e=e=>e>=7?"green":e>=4?"yellow":"red";var s=(e="")=>{console.log(e);},xe=e=>e==="junior"?t.green(e):e==="mid"?t.yellow(e):e==="senior"?t.red(e):e,Ue=e=>e==="know"?` ${t.green("\u2713")}`:e==="dont_know"?` ${t.red("\u2717")}`:"",We=e=>e==="know"?t.green("\u2713 I know this"):e==="dont_know"?t.red("\u2717 I don't know this"):t.dim("no feedback");function Le(e,o){let n=new Map;for(let a of e.groups)for(let i of a.topics)n.set(i,a.name);let r=[...e.knowledge].sort((a,i)=>i.confidence-a.confidence).map(a=>{let i=$e(a.confidence);return [a.topic,n.get(a.topic)??"Other",K(i,`${a.confidence}/10`),K(i,we(a.confidence))]});return L(o,[{header:"Topic"},{header:"Group"},{header:"Confidence",justify:"right"},{header:"Bar"}],r)}function Je(){let e=m(o=>S(o));s(Le(e,"Knowledge Map"));}function Ye(e){let o=m(i=>p(i,{period:e.period==="all"?null:e.period,category:e.category,level:e.level,project:e.project,repository:e.repository,branch:e.branch,commit:e.commit,starred:e.starred?true:null,feedback:e.feedback,date_from:e.dateFrom,date_to:e.dateTo,sort:e.sort,order:e.order}));if(o.length===0){s(t.dim("No lessons found."));return}let n=o.some(i=>i.project||i.branch||i.commit_hash),r=[{header:""},{header:"Date"},{header:"Topic"},{header:"Title"},{header:"Level",justify:"center"},{header:"Categories"}];n&&r.push({header:"Project"},{header:"Branch"},{header:"Commit"});let a=o.map(i=>{let d=[i.starred?t.yellow("\u2605"):t.dim("\xB7"),i.timestamp.slice(0,10),t.cyan(i.topic_id),i.title+Ue(i.feedback),xe(i.level),i.categories.join(", ")];return n&&d.push(i.project??"",i.branch?t.magenta(i.branch):"",i.commit_hash?t.cyan(i.commit_hash.slice(0,7)):""),d});s(L("Lessons",r,a));}function Ve(e){let o=m(r=>w$1(r,e));o===null&&(s(t.red(`Lesson '${e}' not found.`)),process.exit(1)),s(N(t.bold(o.title))),s(`${t.dim("ID:")} ${o.id}`),s(`${t.dim("Date:")} ${o.timestamp.slice(0,19).replace("T"," ")}`),s(`${t.dim("Topic:")} ${o.topic_id}`),s(`${t.dim("Categories:")} ${o.categories.join(", ")}`),s(`${t.dim("Level:")} ${xe(o.level)}`);let n=o.starred?t.yellow("\u2605 starred"):t.dim("\u2606 not starred");if(s(`${t.dim("Star:")} ${n} ${t.dim("Feedback:")} ${We(o.feedback)}`),o.task_context&&s(`${t.dim("Context:")} ${o.task_context}`),o.project||o.repository||o.branch||o.commit_hash||o.folder){let r=[];o.project&&r.push(`project=${o.project}`),o.repository&&r.push(`repo=${o.repository}`),o.branch&&r.push(`branch=${t.magenta(o.branch)}`),o.commit_hash&&r.push(`commit=${t.cyan(o.commit_hash.slice(0,7))}`),o.folder&&r.push(`folder=${o.folder}`),s(`${t.dim("Git:")} ${r.join(" \xB7 ")}`);}s(N()),s(o.summary);}function Ce(e,o){m(r$1=>w$1(r$1,e)===null?false:(r(r$1,e,o),true))||(s(t.red(`Lesson '${e}' not found.`)),process.exit(1)),s(o?`Lesson ${t.cyan(e)} \u2192 ${t.yellow("\u2605 starred")}`:`Lesson ${t.cyan(e)} \u2192 ${t.dim("\u2606 unstarred")}`);}function Ze(e){m(n=>q(n,e))||(s(t.red(`Lesson '${e}' not found.`)),process.exit(1)),s(`Lesson ${t.cyan(e)} deleted.`);}function qe(e,o){["know","dont_know","clear"].includes(o)||(s(t.red(`Invalid feedback '${o}'. Use: know | dont_know | clear`)),process.exit(1));let n=o==="clear"?null:o,r=m(d=>{let m=U$1(d,e,n);if(m===null)return null;let u=d.prepare("SELECT confidence FROM knowledge WHERE topic = ?").get(m);return {topicId:m,newConf:u?u.confidence:5}});r===null&&(s(t.red(`Lesson '${e}' not found.`)),process.exit(1));let a;if(n==="know"||n==="dont_know"){let d=r.newConf+(n==="know"?-1:1);a=`${t.cyan(r.topicId)} confidence: ${d} \u2192 ${t.bold(String(r.newConf))}`;}else a="feedback cleared";let i=n==="know"?t.green("\u2713 I know this"):n==="dont_know"?t.red("\u2717 I don't know this"):t.dim("cleared");s(`Lesson ${t.cyan(e)} \u2192 ${i} (${a})`);}function Xe(){let e=m(a=>F(a)),o=Math.floor(e.min_gap_minutes/60),n=e.min_gap_minutes%60,r=o?`${o}h ${n}m`:`${n}m`;s(L("Settings",[{header:"Key"},{header:"Value",justify:"right"}],[["max_per_day",String(e.max_per_day)],["min_gap_minutes",`${e.min_gap_minutes} (${r})`],["nudge_every",e.nudge_every===0?"0 (off)":String(e.nudge_every)],["nudge_scope",e.nudge_scope]]));}function Qe(){let{stats:e,rateLimit:o,settings:n}=m(u=>({stats:V(u),rateLimit:R$1(u),settings:F(u)})),r=u=>Number(e[u]??0),a=o.allowed?t.green("Available now"):t.yellow(o.reason??"");s(L("Coaching Stats",[{header:"Metric"},{header:"Value",justify:"right"}],[["Total lessons",String(r("total_lessons"))],["Lessons today (24h)",`${r("lessons_today")} / ${n.max_per_day}`],["Lessons this week",String(r("lessons_this_week"))],["Next lesson",a]],false));let i=e.weakest_topics??[],d=e.strongest_topics??[];if(i.length||d.length){let u=[];for(let p=0;p<Math.max(i.length,d.length);p++){let c=i[p],l=d[p];u.push([c?`${t.red(c.topic)} ${t.dim(`(${c.confidence})`)}`:"",l?`${t.green(l.topic)} ${t.dim(`(${l.confidence})`)}`:""]);}s(L(void 0,[{header:"Weakest topics"},{header:"Strongest topics"}],u));}let m$1=Pe();m$1&&s(`
4
4
  ${m$1}`);}function eo(e,o){let n=["max_per_day","min_gap_minutes","nudge_every","nudge_scope"];if(n.includes(e)||(s(t.red(`Unknown key '${e}'. Valid keys: ${n.join(", ")}`)),process.exit(1)),e==="nudge_scope"&&o!=="session"&&o!=="global"&&(s(t.red(`Invalid nudge_scope '${o}'. Use: session | global`)),process.exit(1)),e==="nudge_every"){let r=Number.parseInt(o,10);(!Number.isInteger(r)||r<0)&&(s(t.red(`Invalid nudge_every '${o}'. Use a non-negative integer (0 = off).`)),process.exit(1));}m(r=>G(r,e,o)),s(t.green(`Set ${e} = ${o}`));}function oo(e,o,n){let r=e.trim();r||(s(t.red("Topic name must not be empty.")),process.exit(1)),m(i=>{z(i,r,o),n&&n!=="Other"&&D(i,r,n);});let a=n&&n!=="Other"?` \u2192 ${t.cyan(n)}`:"";s(`${t.green("Added")} ${t.bold(r)} (confidence ${t.cyan(String(o))})${a}`);}function to(e){let o=m(n=>A(n,e));s(o?`${t.green("Removed")} ${t.bold(e)} from knowledge map`:t.yellow(`Topic '${e}' not found.`));}function no(e){let o=e.trim();(!o||o==="Other")&&(s(t.red("Invalid group name.")),process.exit(1)),m(n=>B(n,o)),s(`${t.green(`Group '${t.cyan(o)}' ready.`)} Assign topics with: devcoach group-assign <topic> "${o}"`);}function ro(e){let o=m(n=>C(n,e));s(o?`${t.green(`Removed group '${t.cyan(e)}'`)}`:t.yellow(`Group '${e}' not found.`));}function so(e,o){m(r=>r.prepare("SELECT topic FROM knowledge WHERE topic = ?").get(e)===void 0?false:(o==="Other"?E(r,e):D(r,e,o),true))||(s(t.red(`Topic '${e}' not in knowledge map. Add it first.`)),process.exit(1)),s(o==="Other"?`${t.green("Moved")} ${t.bold(e)} \u2192 Other (ungrouped)`:`${t.green("Moved")} ${t.bold(e)} \u2192 ${t.cyan(o)}`);}function io(e){let{lessonsCount:o,knowledgeCount:n,data:r}=m(i=>({lessonsCount:t$1(i).length,knowledgeCount:y(i).length,data:P(i)}));writeFileSync(e,r);let a=existsSync(j)?" + notebook":"";s(`${t.green("Backup saved:")} ${e} (${t.cyan(String(o))} lessons, ${t.cyan(String(n))} topics${a})`);}function ao(e){existsSync(e)||(s(t.red(`File not found: ${e}`)),process.exit(1));let o=m(r=>Q(r,readFileSync(e)));o.settings&&s(`${t.green("\u2713")} Settings restored`),o.topics&&s(`${t.green("\u2713")} Knowledge map restored (${t.cyan(String(o.topics))} topics)`);let n=[`${t.cyan(String(o.lessons))} imported`];o.skipped&&n.push(`${t.yellow(String(o.skipped))} duplicates skipped`),o.invalid&&n.push(`${t.red(String(o.invalid))} rejected (invalid)`),s(`${t.green("\u2713")} Lessons: ${n.join(", ")}`),o.learning_state&&s(`${t.green("\u2713")} Notebook restored`);}function co(){let e=platform();if(e==="darwin")return join(homedir(),"Library","Application Support","Claude","claude_desktop_config.json");if(e==="win32"){let n=process.env.APPDATA??join(homedir(),"AppData","Roaming");return join(n,"Claude","claude_desktop_config.json")}let o=process.env.XDG_CONFIG_HOME??join(homedir(),".config");return join(o,"Claude","claude_desktop_config.json")}var M=join(homedir(),".claude","settings.json"),v=join(homedir(),".claude","skills","devcoach"),Te=join(v,".devcoach-version");function Me(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 Oe=e=>Me(e)!==null;function je(){return Oe("devcoach")?{command:"devcoach",args:["mcp"]}:{command:"npx",args:["-y","devcoach","mcp"]}}function lo(){let e=Me("devcoach");return e?e.includes("fnm_multishells")?"devcoach":e:"npx -y devcoach"}function uo(e,o){if(!Oe("claude"))return "";o&&spawnSync("claude",["mcp","remove","--scope",e,"devcoach"],{encoding:"utf8"});let n=je(),r=spawnSync("claude",["mcp","add","--scope",e,"devcoach",n.command,"--",...n.args],{encoding:"utf8"});return r.status===0?`${t.green("\u2713")} Registered via \`claude mcp add\` (scope: ${e})`:`${r.stderr??""}${r.stdout??""}`.toLowerCase().includes("already")?`${t.yellow("Already registered")} in Claude Code (use --force to overwrite)`:`${t.red("claude mcp add failed:")} ${(r.stderr||r.stdout||"").trim()}`}function O(e){if(!existsSync(e))return {ok:true,data:{}};try{return {ok:!0,data:JSON.parse(readFileSync(e,"utf8"))??{}}}catch{return {ok:false,error:`${t.red("\u2717")} ${e} is not valid JSON \u2014 fix it and re-run (left unchanged)`}}}function Se(e,o,n){let r=O(e);if(!r.ok)return r.error;let a=r.data;a.mcpServers??={};let i=a.mcpServers;return i.devcoach&&!n?`${t.yellow("Already registered")} in ${e} (use --force to overwrite)`:(i.devcoach=o,mkdirSync(dirname(e),{recursive:true}),writeFileSync(e,`${JSON.stringify(a,null,2)}
5
5
  `),`${t.green("\u2713")} Installed into ${e}`)}function po(){let e=lo();return {Stop:{hooks:[{type:"command",command:`${e} stop-hook`,timeout:60}]},UserPromptSubmit:{hooks:[{type:"command",command:`${e} prompt-hook`,timeout:30}]}}}function De(e){return Object.entries(e.enabledPlugins??{}).some(([o,n])=>n&&o.startsWith("devcoach@"))}function mo(){let e=M,o=O(e);if(!o.ok)return o.error;let n=o.data;if(De(n))return `${t.yellow("Skipped")} \u2014 the devcoach plugin already provides the hooks (installing both would double-count interactions)`;n.hooks??={};let r=n.hooks,a=false;for(let[i,d]of Object.entries(po())){r[i]??=[];let m=r[i],u=m.map((c,l)=>[c,l]).filter(([c])=>(c.hooks??[]).some(l=>(l.command??"").includes("devcoach"))).map(([,c])=>c),[p]=u;if(!(u.length===1&&p!==void 0&&JSON.stringify(m[p])===JSON.stringify(d))){for(let c of u.toReversed())m.splice(c,1);m.push(d),a=true;}}return a?(mkdirSync(dirname(e),{recursive:true}),writeFileSync(e,`${JSON.stringify(n,null,2)}
@@ -24,4 +24,4 @@ ${t.green("Setup complete!")} ${Object.keys(i).length} topics saved.`);}finally{
24
24
  ${t.bold(`devcoach ${t.dim(`v${e}`)}`)}`);for(let[n,r]of e$1)s(` ${t.cyan(`devcoach ${n}`.padEnd(32))} ${t.dim(r)}`);s(`
25
25
  ${t.dim("Run")} ${t.bold("devcoach <command> --help")} ${t.dim("for per-command options.")}
26
26
  `);let o=Pe();o&&s(`${o}
27
- `);}function w(e){return e??null}function yo(){let e$2=new Command;return e$2.exitOverride(),e$2.name("devcoach").description("devcoach \u2014 progressive technical coaching").version(`devcoach ${e}`,"-v, --version","output the version number").allowExcessArguments(false).showHelpAfterError("(add --help for usage)").action(()=>ko()),e$2.command("mcp").description("Start the MCP server (stdio transport)").action(async()=>{let{runStdio:o}=await import('./server-IQXZNPCO.js');await o();}),e$2.command("profile").description("Show the knowledge map").action(Je),e$2.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("--feedback <feedback>","know | 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=>Ye({period:o.period??"all",category:w(o.category),project:w(o.project),repository:w(o.repository),branch:w(o.branch),commit:w(o.commit),starred:!!o.starred,feedback:w(o.feedback),level:w(o.level),dateFrom:w(o.dateFrom),dateTo:w(o.dateTo),sort:o.sort??"timestamp",order:o.order??"desc"})),e$2.command("lesson").description("Show a single lesson in detail").argument("<id>","Lesson ID").action(o=>Ve(o)),e$2.command("star").description("Mark a lesson as starred").argument("<id>","Lesson ID").action(o=>Ce(o,true)),e$2.command("unstar").description("Remove the starred mark from a lesson").argument("<id>","Lesson ID").action(o=>Ce(o,false)),e$2.command("delete").description("Permanently delete a lesson").argument("<id>","Lesson ID").action(o=>Ze(o)),e$2.command("feedback").description("Record know/dont_know feedback for a lesson").argument("<id>","Lesson ID").argument("<value>","know | dont_know | clear").action((o,n)=>qe(o,n)),e$2.command("settings").description("Show current settings").action(Xe),e$2.command("stats").description("Coaching statistics and rate-limit status").action(Qe),e$2.command("set").description("Update a setting (max_per_day | min_gap_minutes)").argument("<key>","Setting key").argument("<value>","New value").action((o,n)=>eo(o,n)),e$2.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,n)=>oo(o,Math.max(0,Math.min(10,Number.parseInt(n.confidence??"5",10)||5)),w(n.group))),e$2.command("knowledge-remove").description("Remove a topic from the knowledge map").argument("<topic>","Topic ID").action(o=>to(o)),e$2.command("group-add").description("Register a new knowledge group").argument("<name>","Group name").action(o=>no(o)),e$2.command("group-remove").description("Delete a knowledge group (topics move to Other)").argument("<name>","Group name").action(o=>ro(o)),e$2.command("group-assign").description("Move a topic to a group").argument("<topic>","Topic ID").argument("<group>","Group name (use 'Other' to ungroup)").action((o,n)=>so(o,n)),e$2.command("backup").description("Export a full backup (settings + knowledge + lessons) as zip").argument("[file]","Output zip path","devcoach-backup.zip").action(o=>io(o)),e$2.command("restore").description("Restore from a backup zip file").argument("<file>","Path to backup zip").action(o=>ao(o)),e$2.command("install").description("Register the MCP server + Stop hooks + skill in Claude Code / Claude Desktop").option("--claude-code","Target Claude Code only").option("--claude-desktop","Target Claude Desktop only").option("--force","Overwrite existing devcoach entry").option("--skip-hook","Register MCP server only \u2014 skip the Stop hooks").action(o=>fo({claudeCode:!!o.claudeCode,claudeDesktop:!!o.claudeDesktop,force:!!o.force,skipHook:!!o.skipHook})),e$2.command("doctor").description("Diagnose the devcoach \u21C4 Claude Code wiring and the lesson pacing state").action(ho),e$2.command("ui").description("Launch the web dashboard").option("--port <port>","Port","7860").action(async o=>{let{startUi:n}=await import('./app-BL2DMYDO.js');n(Number.parseInt(o.port??"7860",10)||7860);}),e$2.command("setup").description("First-run wizard: import backup or build your knowledge profile").action(bo),e$2.command("stop-hook",{hidden:true}).description("Claude Code Stop hook: onboarding check + lesson cue in one spawn").action(()=>e$1()),e$2.command("prompt-hook",{hidden:true}).description("Claude Code UserPromptSubmit hook: prime the model when a lesson is due").action(()=>f()),e$2.command("onboard-hook",{hidden:true}).description("Claude Code Stop hook: cue onboarding (legacy two-entry layout)").action(()=>b()),e$2.command("lesson-ready",{hidden:true}).description("Claude Code Stop hook: signal a lesson is due (legacy two-entry layout)").action(()=>d()),e$2}async function No(){let e=yo();try{await e.parseAsync(process.argv);}catch(o){let n=o.code??"";if(!n.startsWith("commander."))throw o;if(n==="commander.helpDisplayed"||n==="commander.help"||n==="commander.version")return;process.exit(2);}}export{No as runCli};
27
+ `);}function w(e){return e??null}function yo(){let e$2=new Command;return e$2.exitOverride(),e$2.name("devcoach").description("devcoach \u2014 progressive technical coaching").version(`devcoach ${e}`,"-v, --version","output the version number").allowExcessArguments(false).showHelpAfterError("(add --help for usage)").action(()=>ko()),e$2.command("mcp").description("Start the MCP server (stdio transport)").action(async()=>{let{runStdio:o}=await import('./server-BHRDYYAU.js');await o();}),e$2.command("profile").description("Show the knowledge map").action(Je),e$2.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("--feedback <feedback>","know | 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=>Ye({period:o.period??"all",category:w(o.category),project:w(o.project),repository:w(o.repository),branch:w(o.branch),commit:w(o.commit),starred:!!o.starred,feedback:w(o.feedback),level:w(o.level),dateFrom:w(o.dateFrom),dateTo:w(o.dateTo),sort:o.sort??"timestamp",order:o.order??"desc"})),e$2.command("lesson").description("Show a single lesson in detail").argument("<id>","Lesson ID").action(o=>Ve(o)),e$2.command("star").description("Mark a lesson as starred").argument("<id>","Lesson ID").action(o=>Ce(o,true)),e$2.command("unstar").description("Remove the starred mark from a lesson").argument("<id>","Lesson ID").action(o=>Ce(o,false)),e$2.command("delete").description("Permanently delete a lesson").argument("<id>","Lesson ID").action(o=>Ze(o)),e$2.command("feedback").description("Record know/dont_know feedback for a lesson").argument("<id>","Lesson ID").argument("<value>","know | dont_know | clear").action((o,n)=>qe(o,n)),e$2.command("settings").description("Show current settings").action(Xe),e$2.command("stats").description("Coaching statistics and rate-limit status").action(Qe),e$2.command("set").description("Update a setting (max_per_day | min_gap_minutes)").argument("<key>","Setting key").argument("<value>","New value").action((o,n)=>eo(o,n)),e$2.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,n)=>oo(o,Math.max(0,Math.min(10,Number.parseInt(n.confidence??"5",10)||5)),w(n.group))),e$2.command("knowledge-remove").description("Remove a topic from the knowledge map").argument("<topic>","Topic ID").action(o=>to(o)),e$2.command("group-add").description("Register a new knowledge group").argument("<name>","Group name").action(o=>no(o)),e$2.command("group-remove").description("Delete a knowledge group (topics move to Other)").argument("<name>","Group name").action(o=>ro(o)),e$2.command("group-assign").description("Move a topic to a group").argument("<topic>","Topic ID").argument("<group>","Group name (use 'Other' to ungroup)").action((o,n)=>so(o,n)),e$2.command("backup").description("Export a full backup (settings + knowledge + lessons) as zip").argument("[file]","Output zip path","devcoach-backup.zip").action(o=>io(o)),e$2.command("restore").description("Restore from a backup zip file").argument("<file>","Path to backup zip").action(o=>ao(o)),e$2.command("install").description("Register the MCP server + Stop hooks + skill in Claude Code / Claude Desktop").option("--claude-code","Target Claude Code only").option("--claude-desktop","Target Claude Desktop only").option("--force","Overwrite existing devcoach entry").option("--skip-hook","Register MCP server only \u2014 skip the Stop hooks").action(o=>fo({claudeCode:!!o.claudeCode,claudeDesktop:!!o.claudeDesktop,force:!!o.force,skipHook:!!o.skipHook})),e$2.command("doctor").description("Diagnose the devcoach \u21C4 Claude Code wiring and the lesson pacing state").action(ho),e$2.command("ui").description("Launch the web dashboard").option("--port <port>","Port","7860").action(async o=>{let{startUi:n}=await import('./app-IXWAZDVH.js');n(Number.parseInt(o.port??"7860",10)||7860);}),e$2.command("setup").description("First-run wizard: import backup or build your knowledge profile").action(bo),e$2.command("stop-hook",{hidden:true}).description("Claude Code Stop hook: onboarding check + lesson cue in one spawn").action(()=>e$1()),e$2.command("prompt-hook",{hidden:true}).description("Claude Code UserPromptSubmit hook: prime the model when a lesson is due").action(()=>f()),e$2.command("onboard-hook",{hidden:true}).description("Claude Code Stop hook: cue onboarding (legacy two-entry layout)").action(()=>b()),e$2.command("lesson-ready",{hidden:true}).description("Claude Code Stop hook: signal a lesson is due (legacy two-entry layout)").action(()=>d()),e$2}async function No(){let e=yo();try{await e.parseAsync(process.argv);}catch(o){let n=o.code??"";if(!n.startsWith("commander."))throw o;if(n==="commander.helpDisplayed"||n==="commander.help"||n==="commander.version")return;process.exit(2);}}export{No as runCli};
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- export{c as buildLessonCue,d as cmdLessonReady,b as cmdOnboardHook,f as cmdPromptHook,e as cmdStopHook,a as parseHookPayload,g as runHook}from'./chunk-XSQUPJG7.js';import'./chunk-227BHE2B.js';
2
+ export{c as buildLessonCue,d as cmdLessonReady,b as cmdOnboardHook,f as cmdPromptHook,e as cmdStopHook,a as parseHookPayload,g as runHook}from'./chunk-2OWIYKQC.js';import'./chunk-227BHE2B.js';
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import {e,b as b$1,a as a$1,d as d$1,c as c$2}from'./chunk-HQO42VUB.js';import {c as c$1,b,a,m,O,g,n,I,L,s,T,p,r,q,K,d,z as z$1,D,A,B,C,f,e as e$1,S,j,F,V,W,o,R,N,k,w,G}from'./chunk-227BHE2B.js';import {spawn}from'child_process';import {mkdirSync,writeFileSync,existsSync,readFileSync,statSync}from'fs';import {dirname}from'path';import {McpServer,ResourceTemplate}from'@modelcontextprotocol/sdk/server/mcp.js';import {StdioServerTransport}from'@modelcontextprotocol/sdk/server/stdio.js';import {z}from'zod';function te(o){let d=[...o].length,e=Math.max(34-d-2,2),t=Math.floor(e/2);return `### ${"\u2500".repeat(t)} ${o} ${"\u2500".repeat(e-t)}`}function ne(o){let d=o.level.charAt(0).toUpperCase()+o.level.slice(1),e=o.categories.join(" \xB7 ");return [te("\u{1F393} devcoach"),`**${o.title}** \xB7 ${e} \xB7 ${d}`,"",o.body?.trim()||o.summary,"",te(`${o.topic_id} \xB7 ${o.level}`)].join(`
2
+ import {e,b as b$1,a as a$1,d as d$1,c as c$2}from'./chunk-CBUZNGSX.js';import {c as c$1,b,a,m,O,g,n,I,L,s,T,p,r,q,K,d,z as z$1,D,A,B,C,f,e as e$1,S,j,F,V,W,o,R,N,k,w,G}from'./chunk-227BHE2B.js';import {spawn}from'child_process';import {mkdirSync,writeFileSync,existsSync,readFileSync,statSync}from'fs';import {dirname}from'path';import {McpServer,ResourceTemplate}from'@modelcontextprotocol/sdk/server/mcp.js';import {StdioServerTransport}from'@modelcontextprotocol/sdk/server/stdio.js';import {z}from'zod';function te(o){let d=[...o].length,e=Math.max(34-d-2,2),t=Math.floor(e/2);return `### ${"\u2500".repeat(t)} ${o} ${"\u2500".repeat(e-t)}`}function ne(o){let d=o.level.charAt(0).toUpperCase()+o.level.slice(1),e=o.categories.join(" \xB7 ");return [te("\u{1F393} devcoach"),`**${o.title}** \xB7 ${e} \xB7 ${d}`,"",o.body?.trim()||o.summary,"",te(`${o.topic_id} \xB7 ${o.level}`)].join(`
3
3
  `)}var u=o=>({type:"text",text:o}),ie=o=>({content:[u(JSON.stringify(o))]}),l=o=>({isError:true,content:[u(o)]}),se=o=>({content:[u(JSON.stringify(o))],structuredContent:o}),me={id:z.string(),timestamp:z.string(),topic_id:z.string(),categories:z.array(z.string()),title:z.string(),level:a,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:b.nullable(),starred:z.boolean(),feedback:c$1.nullable()},he={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())}))},ge={max_per_day:z.number().int(),min_gap_minutes:z.number().int(),ui_theme:e$1,nudge_every:z.number().int(),nudge_scope:f};function c(o,d){return {contents:[{uri:o.href,mimeType:"application/json",text:JSON.stringify(d)}]}}function be(){let o$1=new McpServer({name:"devcoach",version:e},{instructions:"Progressive technical coaching server. Use the devcoach_instructions prompt for full coaching behaviour guidelines."});o$1.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. Returns the saved Lesson with all resolved fields.",inputSchema:{id:z.string().describe("Unique lesson id (uuid or random string)"),timestamp:z.string().describe("ISO 8601 timestamp; normalized to UTC and clamped to now"),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:a.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:b.nullish().describe("github | gitlab | bitbucket | local (auto-detected if omitted)")},outputSchema:me,annotations:{title:"Log Lesson",destructiveHint:false,idempotentHint:true,openWorldHint:false}},async e=>{try{let t=b$1(),r={};try{r=m(s=>O(s));}catch{r={};}let i=g({id:e.id,timestamp:e.timestamp,topic_id:e.topic_id,categories:e.categories,title:e.title,level:e.level,summary:e.summary,body:e.body,task_context:e.task_context??null,project:e.project??t.project??r.project??null,repository:e.repository??t.repository??r.repository??null,branch:e.branch??t.branch??r.branch??null,commit_hash:e.commit_hash??t.commit_hash??null,folder:e.folder??t.folder??null,repository_platform:e.repository_platform??t.repository_platform??r.repository_platform??null});m(s=>{n(s,i),I(s),L(s);});try{if(o$1.server.getClientCapabilities()?.elicitation){let h=await o$1.server.elicitInput({message:"Did that land?",requestedSchema:{type:"object",properties:{feedback:{type:"string",title:"Did that land?",description:"know = got it \xB7 dont_know = need to revisit \xB7 skip = no feedback",enum:["know","dont_know","skip"]}},required:[]}}),m$1=h.action==="accept"?h.content?.feedback:void 0;m$1&&m$1!=="skip"&&(m(A=>{s(A,e.id,m$1),T(A,e.topic_id,m$1==="know"?1:-1);}),i={...i,feedback:m$1});}}catch{}return {content:[u(`Lesson saved. REQUIRED: the lesson card must already be visible in your reply. If it is not, print the card below VERBATIM now, before any other output:
4
4
 
5
5
  ${ne(i)}`),u(JSON.stringify(i))],structuredContent:i}}catch(t){return l(`log_lesson failed: ${t}`)}}),o$1.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:{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 e=>{try{let t=m(r=>T(r,e.topic,e.delta));return {content:[u(String(t))]}}catch(t){return l(`update_knowledge failed for '${e.topic}': ${t}`)}}),o$1.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:{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"),feedback:z.enum(["know","dont_know","none"]).nullish().describe("Filter by feedback ('none' = no response)"),search:z.string().nullish().describe("Full-text search over title, topic_id, summary, body"),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}},e=>{try{let t=m(r=>p(r,{period:e.period,category:e.category,level:e.level,project:e.project,repository:e.repository,branch:e.branch,commit:e.commit,starred:e.starred,feedback:e.feedback,search:e.search,date_from:e.date_from,date_to:e.date_to,page:e.limit>0?1:null,per_page:e.limit}));return ie(t)}catch{return ie([])}}),o$1.registerTool("star_lesson",{title:"Star Lesson",description:"Set the starred (favourite) flag on a lesson. Returns true if found and updated. Idempotent.",inputSchema:{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 e=>{try{let t=m(r$1=>r(r$1,e.lesson_id,e.starred));return {content:[u(String(t))]}}catch(t){return l(`star_lesson failed for '${e.lesson_id}': ${t}`)}}),o$1.registerTool("delete_lesson",{title:"Delete Lesson",description:"Permanently delete a lesson by id. Returns true if found and deleted.",inputSchema:{lesson_id:z.string().describe("Lesson id to delete")},annotations:{title:"Delete Lesson",destructiveHint:true,idempotentHint:true,openWorldHint:false}},async e=>{try{let t=m(r=>q(r,e.lesson_id));return {content:[u(String(t))]}}catch(t){return l(`delete_lesson failed for '${e.lesson_id}': ${t}`)}}),o$1.registerTool("submit_feedback",{title:"Submit Feedback",description:"Record comprehension feedback for a lesson and adjust knowledge confidence. know = +1, dont_know = -1, clear = remove feedback (no confidence change). Idempotent \u2014 the same feedback twice adjusts confidence only once.",inputSchema:{lesson_id:z.string().describe("Lesson id"),feedback:z.enum(["know","dont_know","clear"]).describe("know | dont_know | clear")},annotations:{title:"Submit Feedback",destructiveHint:false,idempotentHint:true,openWorldHint:false}},async e=>{try{let t=e.feedback==="clear"?null:e.feedback,r=m(i=>{let s$1=i.prepare("SELECT feedback, topic_id FROM lessons WHERE id = ?").get(e.lesson_id);return s$1?(s$1.feedback===t||(s(i,e.lesson_id,t),(t==="know"||t==="dont_know")&&s$1.topic_id&&T(i,s$1.topic_id,t==="know"?1:-1)),!0):!1});return {content:[u(String(r))]}}catch(t){return l(`submit_feedback failed for '${e.lesson_id}': ${t}`)}}),o$1.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:{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 e=>{try{return m(t=>K(t,e.reason)),{content:[u("Cue skipped \u2014 pacing re-armed. Output nothing to the user.")]}}catch(t){return l(`skip_lesson failed: ${t}`)}}),o$1.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:{topic:z.string().describe("Topic id \u2014 prefer a single word, max 3 words"),confidence:d.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 e=>{try{return m(t=>{z$1(t,e.topic,e.confidence),e.group&&e.group!=="Other"&&D(t,e.topic,e.group);}),{content:[u("true")]}}catch(t){return l(`add_topic failed for '${e.topic}': ${t}`)}}),o$1.registerTool("remove_topic",{title:"Remove Topic",description:"Remove a topic from the knowledge map entirely. Returns true if it existed.",inputSchema:{topic:z.string().describe("Topic id to remove")},annotations:{title:"Remove Topic",destructiveHint:true,idempotentHint:true,openWorldHint:false}},async e=>{try{let t=m(r=>A(r,e.topic));return {content:[u(String(t))]}}catch(t){return l(`remove_topic failed for '${e.topic}': ${t}`)}}),o$1.registerTool("add_group",{title:"Add Group",description:"Create a new (initially empty) knowledge group. Idempotent \u2014 returns true either way.",inputSchema:{name:z.string().describe("Group name, e.g. 'Machine Learning'")},annotations:{title:"Add Group",destructiveHint:false,idempotentHint:true,openWorldHint:false}},async e=>{try{return m(t=>B(t,e.name.trim())),{content:[u("true")]}}catch(t){return l(`add_group failed for '${e.name}': ${t}`)}}),o$1.registerTool("remove_group",{title:"Remove Group",description:"Delete a knowledge group. Its topics move to Other. Returns true if it existed.",inputSchema:{name:z.string().describe("Group name to delete")},annotations:{title:"Remove Group",destructiveHint:true,idempotentHint:true,openWorldHint:false}},async e=>{try{let t=m(r=>C(r,e.name));return {content:[u(String(t))]}}catch(t){return l(`remove_group failed for '${e.name}': ${t}`)}}),o$1.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'. Returns the full updated Settings.",inputSchema:{key:z.enum(["max_per_day","min_gap_minutes","nudge_every","nudge_scope"]).describe("Setting key"),value:z.string().describe("New value (integer string; or 'session'|'global' for nudge_scope)")},outputSchema:ge,annotations:{title:"Update Settings",destructiveHint:false,idempotentHint:true,openWorldHint:false}},e=>{let t=i=>se(m(s=>(G(s,e.key,i),F(s))));if(e.key==="nudge_scope")return e.value!=="session"&&e.value!=="global"?l("nudge_scope must be 'session' or 'global'"):t(e.value);let r=Number.parseInt(e.value,10);return Number.isNaN(r)?l(`Value must be an integer, got '${e.value}'`):e.key==="max_per_day"&&!(r>=1&&r<=20)?l("max_per_day must be between 1 and 20"):e.key==="min_gap_minutes"&&!(r>=0&&r<=1440)?l("min_gap_minutes must be between 0 and 1440"):e.key==="nudge_every"&&!(r>=0&&r<=1e3)?l("nudge_every must be between 0 and 1000"):t(String(r))}),o$1.registerTool("open_ui",{title:"Open UI",description:"Launch the devcoach web dashboard in the background. port must be 1024-65535.",inputSchema:{port:z.number().int().default(7860).describe("Port (default 7860)")},annotations:{title:"Open UI",destructiveHint:false,idempotentHint:false,openWorldHint:true}},e=>{let t=e.port;return t>=1024&&t<=65535?(spawn(process.execPath,[process.argv[1]??"","ui","--port",String(t)],{detached:true,stdio:"ignore"}).unref(),{content:[u(`devcoach UI starting at http://localhost:${t}`)]}):{content:[u(`error: port ${t} is out of valid range (1024-65535)`)]}}),o$1.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'). Pass the personalized coaching notebook as `notebook`; it is saved to learning-state.md atomically with the profile. Returns the updated Profile.",inputSchema:{topics:z.record(z.string(),d).describe("{topic_id: confidence 0-10}"),groups:z.record(z.string(),z.array(z.string())).optional().describe("{group_name: [topic_id, ...]}"),notebook:z.string().optional().describe("Full personalized coaching notebook (markdown) saved verbatim to learning-state.md. Write real observations about THIS user \u2014 background, strengths, gaps, and how they work across ALL their projects (not just this one), per the template in the devcoach skill. If omitted, a minimal placeholder is written so the file is never empty.")},outputSchema:he,annotations:{title:"Complete Onboarding",destructiveHint:true,idempotentHint:false,openWorldHint:false}},async e=>{try{let t=m(i=>{i.exec("DELETE FROM knowledge; DELETE FROM knowledge_groups; DELETE FROM knowledge_group_names;");for(let[s,h]of Object.entries(e.topics))z$1(i,s,Math.max(0,Math.min(10,h)));if(e.groups)for(let[s,h]of Object.entries(e.groups))for(let m of h)m in e.topics&&D(i,m,s);return S(i)}),r=e.notebook?.trim();return mkdirSync(dirname(j),{recursive:!0}),r?writeFileSync(j,r.endsWith(`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devcoach",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "mcpName": "io.github.UltimaPhoenix/devcoach",
5
5
  "description": "Stay sharp while your AI does the work — learn from every task with in-context coaching, all local, all free",
6
6
  "license": "AGPL-3.0-only",
@@ -1,15 +0,0 @@
1
- #!/usr/bin/env node
2
- import {i as i$1,m,N as N$1,Y,h as h$1,l,M,X}from'./chunk-227BHE2B.js';import {existsSync,mkdirSync,statSync,writeFileSync,appendFileSync,fstatSync,readFileSync}from'fs';import {join}from'path';function H(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,last_assistant_message:typeof o?.last_assistant_message=="string"?o.last_assistant_message:null}}catch{return {stop_hook_active:false,permission_mode:null,session_id:null,last_assistant_message:null}}}function h(){let e={stop_hook_active:false,permission_mode:null,session_id:null,last_assistant_message:null};try{let o=fstatSync(0);return !o.isFIFO()&&!o.isFile()&&!o.isSocket()?e:H(readFileSync(0,"utf8"))}catch{return e}}function D(e){return e.stop_hook_active||e.permission_mode==="plan"}function i(e,o,t){if(process.env.DEVCOACH_HOOK_DEBUG==="1")try{mkdirSync(h$1,{recursive:!0});let n=join(h$1,"hook.log");existsSync(n)&&statSync(n).size>256*1024&&writeFileSync(n,""),appendFileSync(n,`${new Date().toISOString()} ${e} ${(o??"-").slice(0,8)} ${t}
3
- `);}catch{}}function c(e,o){let t={decision:"block",reason:e};o&&(t.systemMessage=o),process.stdout.write(`${JSON.stringify(t)}
4
- `),process.exit(0);}var k="devcoach: the user has no coaching profile yet. Ask them how they want to set it up \u2014 Automatic (detect this project's tech stack), Guided (a short conversation to map topics and confidence levels), or Import (restore from a backup) \u2014 and 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 P(e=h()){if(D(e)&&process.exit(0),existsSync(i$1)){let o;try{o=m(t=>N$1(t).knowledge_ready);}catch(t){i("onboard-hook",e.session_id,`error: ${t}`),process.exit(0);}o&&process.exit(0);}i("onboard-hook",e.session_id,"onboarding cue"),c(k);}var a=10;function E(e){let o=e%a===0,t=Math.ceil(e/a)*a;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
-
6
- Invoke the \`devcoach\` skill (Skill tool) NOW and follow it to deliver ONE lesson. Three hard rules:
7
- - The lesson card MUST be printed as your visible reply BEFORE calling log_lesson.
8
- - After log_lesson returns, output NOTHING else \u2014 the card is the end of the reply.
9
- - If the work just completed does not warrant a lesson (pure questions, chat, nothing technical), call the devcoach \`skip_lesson\` tool with a one-line reason and output nothing.
10
- ${o?`This is lesson #${e} \u2014 a notebook checkpoint (every ${a} lessons): after log_lesson returns the feedback, also call update_notebook with the revised notebook markdown and record "(updated after ${e} lessons)".`:`Do NOT call update_notebook this time \u2014 the notebook is refreshed only every ${a} lessons (next checkpoint at lesson #${t}).`}
11
-
12
- If the devcoach skill is not available, fall back to: read devcoach://profile, devcoach://taught-topics, and devcoach://notebook; pick ONE untaught profile topic at or above the user's confidence band; write the card as plain markdown (no '>' blockquote) between two band headings \`### \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u{1F393} devcoach \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\` and \`### \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 [topic] \xB7 [level] \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\`, with \`**[Title]** \xB7 [Category] \xB7 [Level]\`, 3\u20136 short paragraphs tied to the task, and a \`\u{1F4A1} *Senior tip:*\` line; THEN call log_lesson (body = clean markdown without bands or the title line).
13
-
14
- The rate-limit check is already done by this hook \u2014 skip it. Output only the lesson card (or nothing). No preamble, no meta-commentary.`}var T="devcoach: the lesson was saved with log_lesson but its card is NOT visible in your reply. Print the lesson card now, verbatim, exactly as echoed by the log_lesson result (the block between the two `### \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u{1F393} devcoach \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500` band headings) \u2014 as your ENTIRE reply, no other text, no tool calls.";function N(e,o,t){let n=o.permission_mode==="plan";if(!n&&M(e)&&o.last_assistant_message!==null&&!o.last_assistant_message.includes("\u{1F393} devcoach"))return {kind:"recover-card",note:"card missing from reply \u2014 recovering"};if(o.stop_hook_active)return {kind:"silent",note:"hook-forced continuation"};if(!N$1(e).knowledge_ready)return !t.onboardCue||n?{kind:"silent",note:"onboarding not complete"}:{kind:"onboard",note:"onboarding cue (empty knowledge)"};let s=X(e,o.session_id,{planMode:n});return s.cue?{kind:"cue",nextLessonNumber:s.nextLessonNumber,note:s.reason}:{kind:"silent",note:s.reason}}function g(e,o,t){let n;try{n=m(s=>l(s,()=>N(s,o,t)));}catch(s){i(e,o.session_id,`error: ${s}`),process.exit(0);}switch(i(e,o.session_id,n.note),n.kind){case "onboard":c(k);break;case "recover-card":c(T,"\u{1F393} devcoach: recovering the lesson card\u2026");break;case "cue":c(E(n.nextLessonNumber),"\u{1F393} devcoach: preparing a lesson\u2026");break;}process.exit(0);}function A(e=h()){existsSync(i$1)||process.exit(0),g("lesson-ready",e,{onboardCue:false});}function $(e=h()){existsSync(i$1)||((e.stop_hook_active||e.permission_mode==="plan")&&process.exit(0),i("stop-hook",e.session_id,"onboarding cue (no DB)"),c(k)),g("stop-hook",e,{onboardCue:true});}var R="devcoach: the lesson pacing threshold will be reached at the end of this turn. Resolve it before finishing:\n- If this task is technical: invoke the `devcoach` skill (Skill tool), then end your reply with ONE lesson card (the `### \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u{1F393} devcoach \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500` block) followed by a log_lesson call. The card must be the LAST visible text \u2014 after log_lesson returns, output nothing else.\n- If it is not technical: silently call the devcoach `skip_lesson` tool with a one-line reason and output nothing about it.";function I(e=h()){e.permission_mode==="plan"&&process.exit(0),existsSync(i$1)||process.exit(0);let o;try{o=m(t=>Y(t,e.session_id).wouldCue);}catch(t){i("prompt-hook",e.session_id,`error: ${t}`),process.exit(0);}i("prompt-hook",e.session_id,o?"priming":"no prime"),o||process.exit(0),process.stdout.write(`${JSON.stringify({hookSpecificOutput:{hookEventName:"UserPromptSubmit",additionalContext:R}})}
15
- `),process.exit(0);}function U(e){switch(e){case "stop-hook":$();break;case "prompt-hook":I();break;case "onboard-hook":P();break;case "lesson-ready":A();break;default:process.exit(0);}}export{H as a,P as b,E as c,A as d,$ as e,I as f,U as g};