prjct-cli 2.37.0 → 2.37.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.
@@ -648,14 +648,14 @@ ${o.join(`
648
648
  ### Conventions (${n.conventions.length})`);for(let o of n.conventions)i.push(`- [${o.category}] ${o.rule}`)}return{content:[{type:"text",text:i.join(`
649
649
  `)}]}})),t.tool("prjct_patterns","Project memory grouped by type (decision / pattern / anti-pattern / gotcha). Powered by projectMemory \u2014 same source the CLI memory verbs read.",{projectPath:ir.string().describe("Project directory path")},S("prjct_patterns",async e=>{let r=await x(e.projectPath),n=C.recall(r,{types:["decision","pattern","anti-pattern","gotcha"],limit:50});return n.length===0?{content:[{type:"text",text:"No decisions or patterns captured yet."}]}:{content:[{type:"text",text:mt(n)}]}}))}c(Zn,"registerProjectTools");import{z as y}from"zod";wt();gt();Qt();Vt();N();import{execFile as _a}from"node:child_process";import{promisify as ba}from"node:util";var va=ba(_a);async function Ra(s){try{let{stdout:t}=await va("git",["rev-parse","HEAD"],{cwd:s}),e=t.trim();return/^[0-9a-f]{7,40}$/.test(e)?e:null}catch{return null}}c(Ra,"readGitHead");var ur=class{static{c(this,"SpecService")}async create(t,e){let r=await this.requireProjectId(t),n=e.content.notes??"";if(e.autoContext!==!1&&!n.trim()){let{inferSpecContext:u,warnNoContextMatch:l}=await Promise.resolve().then(()=>(ri(),ei)),d=await u(e.title,r,t);d.empty?l(e.title):n=d.notesBlock}let o=Y.parse({goal:e.content.goal,eli10:e.content.eli10??"",stakes:e.content.stakes??"",acceptance_criteria:e.content.acceptance_criteria??[],scope:e.content.scope??[],out_of_scope:e.content.out_of_scope??[],risks:e.content.risks??[],test_plan:e.content.test_plan??[],reviews:e.content.reviews,linked_tasks:e.content.linked_tasks??[],notes:n}),a=P.create(r,{title:e.title,content:o,tags:e.tags});return await C.remember(t,{type:"spec",content:`${a.title}
650
650
 
651
- Goal: ${a.content.goal}`,tags:{...e.tags??{},spec_id:a.id,status:a.status},source:a.id,provenance:"declared"}),a}async get(t,e){let r=await this.requireProjectId(t);return P.get(r,e)}async list(t,e={}){let r=await this.requireProjectId(t);return P.list(r,e)}async setStatus(t,e,r){let n=await this.requireProjectId(t),i=P.setStatus(n,e,r);return i&&await C.remember(t,{type:"spec",content:`Spec status \u2192 ${r}: ${i.title}`,tags:{spec_id:e,status:r,event:"status_change"},source:e}),i}async update(t,e,r){let n=await this.requireProjectId(t);return P.updateContent(n,e,r)}async recordReview(t,e,r,n){let i=await this.requireProjectId(t),o=3,a=50,u=0,l=!1,d=null;for(;u<o;){let p=P.get(i,e);if(!p)return null;let g={...n,ts:f()},T={...p.content,reviews:{...p.content.reviews??{},[r]:g}};if(P.casUpdate(i,e,T,p.updatedAt)){l=!0,d=P.get(i,e);break}u++,u<o&&await new Promise(z=>setTimeout(z,a))}if(!l)throw new Error(`SPEC_RECORD_REVIEW_CONFLICT_RETRY_EXHAUSTED: ${o} retries failed for spec ${e}`);if(d&&this.allReviewsPass(d.content)&&d.status==="draft"){let p=P.setStatus(i,e,"reviewed");if(p){let{breakdownSpecToTasks:g}=await Promise.resolve().then(()=>(ni(),si));return await g(i,t,p),P.get(i,e)}return p}return d}async linkTask(t,e,r){let n=await this.requireProjectId(t);return P.linkTask(n,e,r)}async ship(t,e,r){let n=await this.requireProjectId(t);r!==void 0&&P.setShippedPr(n,e,r);let i=await Ra(t);return i&&P.setShippedSha(n,e,i),P.setStatus(n,e,"shipped")}unmetCriteria(t,e=new Set){return t.content.acceptance_criteria.filter(r=>!e.has(r))}allReviewsPass(t){let e=t.reviews;return e?e.strategic?.verdict==="pass"&&e.architecture?.verdict==="pass"&&e.design?.verdict==="pass":!1}async requireProjectId(t){let e=await B.readConfig(t);if(!e?.projectId)throw new Error("not a prjct project (run `prjct init` first)");return e.projectId}},J=new ur;Qt();Vt();function ii(s){let t=s;t.tool("prjct_spec_create",'CALL THIS when the user describes a feature, fix, or initiative WITH goals or stakes attached (e.g. "we need rate limiting on auth", "fix onboarding", "let\'s build SDD"). Drafts a spec \u2014 Goal/ELI10/Stakes/Acceptance criteria/Scope/Out-of-scope/Risks/Test plan. The structured fields default to empty; populate them by calling `prjct_spec_update` once you have the answers. Skip this tool only for routine work (single-file fix, doc tweak, GTD capture) \u2014 for that, use `prjct_capture` or the matching memory tool.',{projectPath:y.string().describe("Project directory path"),title:y.string().describe("One-line title (what you'd say to a coworker walking by)"),goal:y.string().describe("What success looks like, 1-3 sentences. Concrete, observable."),eli10:y.string().optional().describe("Plain English a 16-year-old follows, 2-4 sentences"),stakes:y.string().optional().describe("What breaks if we ship the wrong thing"),acceptance_criteria:y.array(y.string()).optional().describe("Testable, observable list. Each item ends in a verifiable claim."),scope:y.array(y.string()).optional().describe("What's IN \u2014 file paths, modules, surfaces"),out_of_scope:y.array(y.string()).optional().describe("What's OUT \u2014 anti-creep shield"),risks:y.array(y.object({risk:y.string(),mitigation:y.string()})).optional().describe("Each risk has a mitigation; a risk without one is just a complaint"),test_plan:y.array(y.string()).optional().describe("How you prove acceptance criteria"),tags:y.record(y.string(),y.string()).optional().describe('Key:value tags (e.g. {domain: "auth", priority: "high"})')},S("prjct_spec_create",async e=>{let r=await J.create(e.projectPath,{title:e.title,content:{goal:e.goal,eli10:e.eli10,stakes:e.stakes,acceptance_criteria:e.acceptance_criteria,scope:e.scope,out_of_scope:e.out_of_scope,risks:e.risks,test_plan:e.test_plan},tags:e.tags});return{content:[{type:"text",text:[`\u2713 spec drafted: ${r.title}`,"",`id: ${r.id}`,`status: ${r.status}`,`goal: ${r.content.goal}`,"","Next: `prjct_spec_audit` to dispatch the three review subagents (strategic / architecture / design) in parallel."].join(`
651
+ Goal: ${a.content.goal}`,tags:{...e.tags??{},spec_id:a.id,status:a.status},source:a.id,provenance:"declared"}),a}async get(t,e){let r=await this.requireProjectId(t);return P.get(r,e)}async list(t,e={}){let r=await this.requireProjectId(t);return P.list(r,e)}async setStatus(t,e,r){let n=await this.requireProjectId(t),i=P.setStatus(n,e,r);return i&&await C.remember(t,{type:"spec",content:`Spec status \u2192 ${r}: ${i.title}`,tags:{spec_id:e,status:r,event:"status_change"},source:e}),i}async update(t,e,r){let n=await this.requireProjectId(t);return P.updateContent(n,e,r)}async recordReview(t,e,r,n){let i=await this.requireProjectId(t),o=3,a=50,u=0,l=!1,d=null;for(;u<o;){let p=P.get(i,e);if(!p)return null;let g={...n,ts:f()},T={...p.content,reviews:{...p.content.reviews??{},[r]:g}};if(P.casUpdate(i,e,T,p.updatedAt)){l=!0,d=P.get(i,e);break}u++,u<o&&await new Promise(z=>setTimeout(z,a))}if(!l)throw new Error(`SPEC_RECORD_REVIEW_CONFLICT_RETRY_EXHAUSTED: ${o} retries failed for spec ${e}`);if(d&&this.allReviewsPass(d.content)&&d.status==="draft"){let p=P.setStatus(i,e,"reviewed");if(p){let{breakdownSpecToTasks:g}=await Promise.resolve().then(()=>(ni(),si));return await g(i,t,p),P.get(i,e)}return p}return d}async linkTask(t,e,r){let n=await this.requireProjectId(t);return P.linkTask(n,e,r)}async ship(t,e,r){let n=await this.requireProjectId(t);r!==void 0&&P.setShippedPr(n,e,r);let i=await Ra(t);return i&&P.setShippedSha(n,e,i),P.setStatus(n,e,"shipped")}unmetCriteria(t,e=new Set){return t.content.acceptance_criteria.filter(r=>!e.has(r))}allReviewsPass(t){let e=t.reviews;return e?e.strategic?.verdict==="pass"&&e.architecture?.verdict==="pass"&&e.design?.verdict==="pass":!1}async requireProjectId(t){let e=await B.readConfig(t);if(!e?.projectId)throw new Error("not a prjct project (run `prjct init` first)");return e.projectId}},J=new ur;Qt();Vt();function ii(s){let t=s;t.tool("prjct_spec_create",'Draft a spec when the user frames a feature/fix/initiative WITH goals or stakes (e.g. "rate limiting on auth", "fix onboarding"). Fields default empty \u2014 fill them via `prjct_spec_update`. Skip for routine work (single-file fix, doc tweak, capture); use `prjct_capture` instead.',{projectPath:y.string().describe("Project directory path"),title:y.string().describe("One-line title (what you'd say to a coworker walking by)"),goal:y.string().describe("What success looks like, 1-3 sentences. Concrete, observable."),eli10:y.string().optional().describe("Plain English a 16-year-old follows, 2-4 sentences"),stakes:y.string().optional().describe("What breaks if we ship the wrong thing"),acceptance_criteria:y.array(y.string()).optional().describe("Testable, observable list. Each item ends in a verifiable claim."),scope:y.array(y.string()).optional().describe("What's IN \u2014 file paths, modules, surfaces"),out_of_scope:y.array(y.string()).optional().describe("What's OUT \u2014 anti-creep shield"),risks:y.array(y.object({risk:y.string(),mitigation:y.string()})).optional().describe("Each risk has a mitigation; a risk without one is just a complaint"),test_plan:y.array(y.string()).optional().describe("How you prove acceptance criteria"),tags:y.record(y.string(),y.string()).optional().describe('Key:value tags (e.g. {domain: "auth", priority: "high"})')},S("prjct_spec_create",async e=>{let r=await J.create(e.projectPath,{title:e.title,content:{goal:e.goal,eli10:e.eli10,stakes:e.stakes,acceptance_criteria:e.acceptance_criteria,scope:e.scope,out_of_scope:e.out_of_scope,risks:e.risks,test_plan:e.test_plan},tags:e.tags});return{content:[{type:"text",text:[`\u2713 spec drafted: ${r.title}`,"",`id: ${r.id}`,`status: ${r.status}`,`goal: ${r.content.goal}`,"","Next: `prjct_spec_audit` to dispatch the three review subagents (strategic / architecture / design) in parallel."].join(`
652
652
  `)}]}})),t.tool("prjct_spec_list","List specs in this project. Use to check what specs exist before drafting a new one (avoid duplicates) or to find the right spec to link a task to.",{projectPath:y.string().describe("Project directory path"),status:y.enum(ft).optional().describe("Filter by status: draft|reviewed|in_progress|shipped|archived"),includeArchived:y.boolean().optional().describe("Include archived specs (default: false)")},S("prjct_spec_list",async e=>{let r=await x(e.projectPath),n=P.list(r,{status:e.status,includeArchived:e.includeArchived});if(n.length===0)return{content:[{type:"text",text:"_No specs match. Start one with `prjct_spec_create`._"}]};let i=["# Specs",""];for(let o of n){let a=o.content.acceptance_criteria.length,u=o.content.linked_tasks.length;i.push(`## ${o.title}
653
653
  - id: \`${o.id}\`
654
654
  - status: ${o.status}
655
655
  - acceptance criteria: ${a}
656
656
  - linked tasks: ${u}
657
657
  - created: ${o.createdAt}`),i.push("")}return{content:[{type:"text",text:i.join(`
658
- `)}]}})),t.tool("prjct_spec_get","Fetch one spec by id, including all structured fields (goal, acceptance criteria, scope, risks, reviews, linked tasks).",{projectPath:y.string().describe("Project directory path"),id:y.string().describe("Spec id")},S("prjct_spec_get",async e=>{let r=await J.get(e.projectPath,e.id);return r?{content:[{type:"text",text:xa(r)}]}:{content:[{type:"text",text:`_Spec not found: ${e.id}_`}]}})),t.tool("prjct_spec_update","Replace a spec's structured content. Pass the FULL content object (this is a replace, not a merge) \u2014 when filling in acceptance_criteria for the first time, fetch with `prjct_spec_get` first and merge in your changes.",{projectPath:y.string().describe("Project directory path"),id:y.string().describe("Spec id"),content:y.object({goal:y.string(),eli10:y.string().optional(),stakes:y.string().optional(),acceptance_criteria:y.array(y.string()).optional(),scope:y.array(y.string()).optional(),out_of_scope:y.array(y.string()).optional(),risks:y.array(y.object({risk:y.string(),mitigation:y.string()})).optional(),test_plan:y.array(y.string()).optional(),notes:y.string().optional(),linked_tasks:y.array(y.string()).optional()}).describe("Full SpecContent shape \u2014 Zod-validated server-side")},S("prjct_spec_update",async e=>{let r=Y.parse(e.content),n=await J.update(e.projectPath,e.id,r);return n?{content:[{type:"text",text:`\u2713 spec updated: ${n.title}`}]}:{content:[{type:"text",text:`_Spec not found: ${e.id}_`}]}})),t.tool("prjct_spec_set_status","Promote / demote a spec lifecycle state. Reviewers passing is auto-promoting (draft \u2192 reviewed); use this tool to mark a spec `in_progress` when work starts, `archived` when superseded, or `shipped` (use `prjct_spec_ship` instead when shipping for the first time, since it also records the PR).",{projectPath:y.string().describe("Project directory path"),id:y.string().describe("Spec id"),status:y.enum(ft).describe("Target status")},S("prjct_spec_set_status",async e=>await J.setStatus(e.projectPath,e.id,e.status)?{content:[{type:"text",text:`\u2713 spec ${e.id} \u2192 ${e.status}`}]}:{content:[{type:"text",text:`_Spec not found: ${e.id}_`}]})),t.tool("prjct_spec_audit","CALL THIS before any implementation work begins on a spec. Returns a dispatch prompt for THREE review subagents (strategic / architecture / design). RUN ALL THREE IN PARALLEL via your Agent / Task tool \u2014 one tool-use block per reviewer in the SAME message. Each returns a verdict (pass | fail) + notes. Persist each via `prjct_spec_record_review`. All three pass \u2192 the spec auto-promotes draft \u2192 reviewed and is safe to start a task against.",{projectPath:y.string().describe("Project directory path"),id:y.string().describe("Spec id to audit")},S("prjct_spec_audit",async e=>{let r=await J.get(e.projectPath,e.id);return r?{content:[{type:"text",text:Pa(r.id,r.title,r.content)}]}:{content:[{type:"text",text:`_Spec not found: ${e.id}_`}]}})),t.tool("prjct_spec_record_review","Persist one reviewer's verdict from `prjct_spec_audit` dispatch. Call once per reviewer (strategic, architecture, design). When all three are recorded with verdict=pass, the spec auto-promotes draft \u2192 reviewed.",{projectPath:y.string().describe("Project directory path"),id:y.string().describe("Spec id"),reviewer:y.enum(ar).describe("Which reviewer"),verdict:y.enum(["pass","fail"]).describe("Verdict"),notes:y.string().describe("2-4 sentence notes from the subagent")},S("prjct_spec_record_review",async e=>{let r=await J.recordReview(e.projectPath,e.id,e.reviewer,{verdict:e.verdict,notes:e.notes});if(!r)return{content:[{type:"text",text:`_Spec not found: ${e.id}_`}]};let n=r.status==="reviewed"?" (all reviewers passed \u2192 status: reviewed)":"";return{content:[{type:"text",text:`\u2713 ${e.reviewer} \u2192 ${e.verdict}${n}`}]}})),t.tool("prjct_spec_link_task","Link a task to its spec. Call this AFTER starting the task (when the user begins implementation) so `prjct_ship` later knows which spec to gate against. Idempotent \u2014 re-linking the same task is a no-op.",{projectPath:y.string().describe("Project directory path"),specId:y.string().describe("Spec id"),taskId:y.string().describe("Task id (from `prjct_session_start_task` or stateStorage)")},S("prjct_spec_link_task",async e=>await J.linkTask(e.projectPath,e.specId,e.taskId)?{content:[{type:"text",text:`\u2713 linked task ${e.taskId} to spec ${e.specId}`}]}:{content:[{type:"text",text:`_Spec not found: ${e.specId}_`}]})),t.tool("prjct_spec_ship","Mark a spec as shipped (after the linked PR merges). Records the PR number on the spec for provenance.",{projectPath:y.string().describe("Project directory path"),id:y.string().describe("Spec id"),pr:y.number().optional().describe("PR / MR number that delivered the spec")},S("prjct_spec_ship",async e=>{let r=await J.ship(e.projectPath,e.id,e.pr);return r?{content:[{type:"text",text:`\u2713 spec shipped: ${r.title}${e.pr?` (PR #${e.pr})`:""}`}]}:{content:[{type:"text",text:`_Spec not found: ${e.id}_`}]}}))}c(ii,"registerSpecTools");function xa(s){let t=s.content,e=[`# ${s.title}`,"",`**id:** \`${s.id}\` \xB7 **status:** ${s.status} \xB7 **created:** ${s.createdAt}`,"","## Goal",t.goal];if(t.eli10&&e.push("","## ELI10",t.eli10),t.stakes&&e.push("","## Stakes",t.stakes),t.acceptance_criteria.length>0){e.push("","## Acceptance criteria");for(let r of t.acceptance_criteria)e.push(`- [ ] ${r}`)}if(t.scope.length>0){e.push("","## Scope");for(let r of t.scope)e.push(`- ${r}`)}if(t.out_of_scope.length>0){e.push("","## Out of scope");for(let r of t.out_of_scope)e.push(`- ${r}`)}if(t.risks.length>0){e.push("","## Risks");for(let r of t.risks)e.push(`- **${r.risk}** \u2014 ${r.mitigation}`)}if(t.test_plan.length>0){e.push("","## Test plan");for(let r of t.test_plan)e.push(`- ${r}`)}if(t.reviews){e.push("","## Reviews");for(let r of ar){let n=t.reviews[r];n&&e.push(`- **${r}:** ${n.verdict} \u2014 ${n.notes} _(${n.ts})_`)}}return t.linked_tasks.length>0&&e.push("","## Linked tasks",...t.linked_tasks.map(r=>`- ${r}`)),t.notes&&e.push("","## Notes",t.notes),e.join(`
658
+ `)}]}})),t.tool("prjct_spec_get","Fetch one spec by id, including all structured fields (goal, acceptance criteria, scope, risks, reviews, linked tasks).",{projectPath:y.string().describe("Project directory path"),id:y.string().describe("Spec id")},S("prjct_spec_get",async e=>{let r=await J.get(e.projectPath,e.id);return r?{content:[{type:"text",text:xa(r)}]}:{content:[{type:"text",text:`_Spec not found: ${e.id}_`}]}})),t.tool("prjct_spec_update","Replace a spec's structured content. Pass the FULL content object (this is a replace, not a merge) \u2014 when filling in acceptance_criteria for the first time, fetch with `prjct_spec_get` first and merge in your changes.",{projectPath:y.string().describe("Project directory path"),id:y.string().describe("Spec id"),content:y.object({goal:y.string(),eli10:y.string().optional(),stakes:y.string().optional(),acceptance_criteria:y.array(y.string()).optional(),scope:y.array(y.string()).optional(),out_of_scope:y.array(y.string()).optional(),risks:y.array(y.object({risk:y.string(),mitigation:y.string()})).optional(),test_plan:y.array(y.string()).optional(),notes:y.string().optional(),linked_tasks:y.array(y.string()).optional()}).describe("Full SpecContent shape \u2014 Zod-validated server-side")},S("prjct_spec_update",async e=>{let r=Y.parse(e.content),n=await J.update(e.projectPath,e.id,r);return n?{content:[{type:"text",text:`\u2713 spec updated: ${n.title}`}]}:{content:[{type:"text",text:`_Spec not found: ${e.id}_`}]}})),t.tool("prjct_spec_set_status","Promote/demote a spec lifecycle state: `in_progress` when work starts, `archived` when superseded. (draft \u2192 reviewed auto-promotes when reviewers pass; for first ship use `prjct_spec_ship` so the PR is recorded.)",{projectPath:y.string().describe("Project directory path"),id:y.string().describe("Spec id"),status:y.enum(ft).describe("Target status")},S("prjct_spec_set_status",async e=>await J.setStatus(e.projectPath,e.id,e.status)?{content:[{type:"text",text:`\u2713 spec ${e.id} \u2192 ${e.status}`}]}:{content:[{type:"text",text:`_Spec not found: ${e.id}_`}]})),t.tool("prjct_spec_audit","Call before implementing a spec. Returns a dispatch prompt for THREE review subagents (strategic / architecture / design) \u2014 run ALL THREE IN PARALLEL (one Agent block per reviewer, same message). Persist each verdict via `prjct_spec_record_review`; all three pass \u2192 spec auto-promotes draft \u2192 reviewed.",{projectPath:y.string().describe("Project directory path"),id:y.string().describe("Spec id to audit")},S("prjct_spec_audit",async e=>{let r=await J.get(e.projectPath,e.id);return r?{content:[{type:"text",text:Pa(r.id,r.title,r.content)}]}:{content:[{type:"text",text:`_Spec not found: ${e.id}_`}]}})),t.tool("prjct_spec_record_review","Persist one reviewer's verdict from `prjct_spec_audit` dispatch. Call once per reviewer (strategic, architecture, design). When all three are recorded with verdict=pass, the spec auto-promotes draft \u2192 reviewed.",{projectPath:y.string().describe("Project directory path"),id:y.string().describe("Spec id"),reviewer:y.enum(ar).describe("Which reviewer"),verdict:y.enum(["pass","fail"]).describe("Verdict"),notes:y.string().describe("2-4 sentence notes from the subagent")},S("prjct_spec_record_review",async e=>{let r=await J.recordReview(e.projectPath,e.id,e.reviewer,{verdict:e.verdict,notes:e.notes});if(!r)return{content:[{type:"text",text:`_Spec not found: ${e.id}_`}]};let n=r.status==="reviewed"?" (all reviewers passed \u2192 status: reviewed)":"";return{content:[{type:"text",text:`\u2713 ${e.reviewer} \u2192 ${e.verdict}${n}`}]}})),t.tool("prjct_spec_link_task","Link a task to its spec (call after starting the task) so `prjct_ship` knows which spec to gate against. Idempotent.",{projectPath:y.string().describe("Project directory path"),specId:y.string().describe("Spec id"),taskId:y.string().describe("Task id (from `prjct_session_start_task` or stateStorage)")},S("prjct_spec_link_task",async e=>await J.linkTask(e.projectPath,e.specId,e.taskId)?{content:[{type:"text",text:`\u2713 linked task ${e.taskId} to spec ${e.specId}`}]}:{content:[{type:"text",text:`_Spec not found: ${e.specId}_`}]})),t.tool("prjct_spec_ship","Mark a spec as shipped (after the linked PR merges). Records the PR number on the spec for provenance.",{projectPath:y.string().describe("Project directory path"),id:y.string().describe("Spec id"),pr:y.number().optional().describe("PR / MR number that delivered the spec")},S("prjct_spec_ship",async e=>{let r=await J.ship(e.projectPath,e.id,e.pr);return r?{content:[{type:"text",text:`\u2713 spec shipped: ${r.title}${e.pr?` (PR #${e.pr})`:""}`}]}:{content:[{type:"text",text:`_Spec not found: ${e.id}_`}]}}))}c(ii,"registerSpecTools");function xa(s){let t=s.content,e=[`# ${s.title}`,"",`**id:** \`${s.id}\` \xB7 **status:** ${s.status} \xB7 **created:** ${s.createdAt}`,"","## Goal",t.goal];if(t.eli10&&e.push("","## ELI10",t.eli10),t.stakes&&e.push("","## Stakes",t.stakes),t.acceptance_criteria.length>0){e.push("","## Acceptance criteria");for(let r of t.acceptance_criteria)e.push(`- [ ] ${r}`)}if(t.scope.length>0){e.push("","## Scope");for(let r of t.scope)e.push(`- ${r}`)}if(t.out_of_scope.length>0){e.push("","## Out of scope");for(let r of t.out_of_scope)e.push(`- ${r}`)}if(t.risks.length>0){e.push("","## Risks");for(let r of t.risks)e.push(`- **${r.risk}** \u2014 ${r.mitigation}`)}if(t.test_plan.length>0){e.push("","## Test plan");for(let r of t.test_plan)e.push(`- ${r}`)}if(t.reviews){e.push("","## Reviews");for(let r of ar){let n=t.reviews[r];n&&e.push(`- **${r}:** ${n.verdict} \u2014 ${n.notes} _(${n.ts})_`)}}return t.linked_tasks.length>0&&e.push("","## Linked tasks",...t.linked_tasks.map(r=>`- ${r}`)),t.notes&&e.push("","## Notes",t.notes),e.join(`
659
659
  `)}c(xa,"renderSpecMarkdown");function Pa(s,t,e){let r=JSON.stringify(e);return[`# audit-spec dispatch \u2014 ${t}`,"",`Spec id: \`${s}\``,"","Run three review subagents IN PARALLEL via your Agent / Task tool \u2014 one tool-use block per reviewer, all in the SAME message so they run concurrently. Each subagent reads the spec body and applies its rubric, then returns a structured verdict (pass | fail + 2-4 sentence notes).","","## Reviewer A \u2014 strategic (scope sanity)",'Subagent prompt: "Review this spec for strategic soundness. Does it solve a real problem? Is the goal worth the cost? Is out_of_scope coherent with goal? Is the spec OVER- or UNDER-scoped? Return verdict (pass|fail) and 2-4 sentence notes."',"","## Reviewer B \u2014 architecture (eng feasibility)",'Subagent prompt: "Review this spec for engineering feasibility. Can this be built? Is the data flow / state machine implicit in the acceptance criteria coherent? What failure modes / dependencies / edge cases are missing? Include a short ASCII diagram of the proposed architecture in notes if applicable. Return verdict (pass|fail) and 2-4 sentence notes."',"","## Reviewer C \u2014 design (UX/DX)",'Subagent prompt: "Review this spec for design quality. Rate 0-10 across {clarity, ergonomics, consistency, accessibility} for the user-facing or developer-facing surface this spec defines. Note the lowest-scoring dimension and why. Return verdict (pass if all dimensions \u22656, fail otherwise) and notes including the four scores."',"","## Spec body (verbatim, pass to each reviewer)","```json",r,"```","","## After dispatch","For each reviewer that returns:",` Call \`prjct_spec_record_review\` with id="${s}", reviewer=<strategic|architecture|design>, verdict=<pass|fail>, notes="<their notes>"`,"","When all three are recorded with verdict=pass, the spec auto-promotes from `draft` \u2192 `reviewed`."].join(`
660
660
  `)}c(Pa,"renderAuditDispatch");import{z as te}from"zod";vt();j();var lr=class{static{c(this,"CustomWorkflowStorage")}createWorkflow(t,e){let r=new Date().toISOString();h.run(t,`INSERT INTO custom_workflows (name, description, created_at, updated_at, is_builtin, enabled, metadata)
661
661
  VALUES (?, ?, ?, ?, 0, 1, ?)`,e.name,e.description??null,r,r,e.metadata?JSON.stringify(e.metadata):null);let n=h.get(t,"SELECT id FROM custom_workflows WHERE name = ?",e.name);if(!n)throw new Error(`Failed to create workflow: ${e.name}`);return q({projectId:t,entityType:"custom_workflows",entityId:String(n.id),eventType:"upsert",data:{id:n.id,name:e.name,description:e.description??null,metadata:e.metadata??null,created_at:r,updated_at:r,is_builtin:0,enabled:1}}),n.id}getWorkflow(t,e){let r=h.get(t,"SELECT * FROM custom_workflows WHERE name = ?",e);return r?this.rowToWorkflow(r):null}getAllWorkflows(t,e=!1){let r=e?"SELECT * FROM custom_workflows ORDER BY is_builtin DESC, name ASC":"SELECT * FROM custom_workflows WHERE enabled = 1 ORDER BY is_builtin DESC, name ASC";return h.query(t,r).map(i=>this.rowToWorkflow(i))}updateWorkflow(t,e,r){if(!this.getWorkflow(t,e))return!1;let i=new Date().toISOString(),o=[],a=[];if(r.description!==void 0&&(o.push("description = ?"),a.push(r.description)),r.enabled!==void 0&&(o.push("enabled = ?"),a.push(r.enabled?1:0)),r.metadata!==void 0&&(o.push("metadata = ?"),a.push(JSON.stringify(r.metadata))),o.length===0)return!1;o.push("updated_at = ?"),a.push(i),a.push(e),h.run(t,`UPDATE custom_workflows SET ${o.join(", ")} WHERE name = ?`,...a);let u=this.getWorkflow(t,e);return u&&q({projectId:t,entityType:"custom_workflows",entityId:String(u.id),eventType:"upsert",data:{id:u.id,name:u.name,description:u.description??null,enabled:u.enabled?1:0,metadata:u.metadata??null,updated_at:i}}),!0}deleteWorkflow(t,e){let r=this.getWorkflow(t,e);if(!r)return!1;if(r.isBuiltin)throw new Error(`Cannot delete built-in workflow: ${e}`);return h.run(t,"UPDATE custom_workflows SET enabled = 0 WHERE name = ?",e),q({projectId:t,entityType:"custom_workflows",entityId:String(r.id),eventType:"delete",data:{id:r.id,name:e}}),!0}isBuiltin(t,e){return this.getWorkflow(t,e)?.isBuiltin??!1}isReservedName(t){let e=["task","done","ship","sync"],r=["add","rm","gate","list","create","delete","run","help","reset","init"];return e.includes(t)||r.includes(t)}isValidName(t){return/^[a-z0-9-]+$/.test(t)}rowToWorkflow(t){return{id:t.id,name:t.name,description:t.description,createdAt:t.created_at,updatedAt:t.updated_at,isBuiltin:t.is_builtin===1,enabled:t.enabled===1,metadata:t.metadata?JSON.parse(t.metadata):null}}},Zt=new lr;vt();j();function pr(s){let t=s.trust_source==="imported"?"imported":"local";return{id:s.id,type:s.type,command:s.command,position:s.position,action:s.action,description:s.description,enabled:s.enabled===1,timeoutMs:s.timeout_ms,createdAt:s.created_at,sortOrder:s.sort_order,whenExpr:s.when_expr??null,parallel:s.parallel===null?!0:s.parallel===1,trustSource:t}}c(pr,"rowToRule");var dr=class{static{c(this,"WorkflowRuleStorage")}addRule(t,e){let r=k.get(t,"SELECT MAX(sort_order) as m FROM workflow_rules WHERE command = ?",e.command),n=e.sortOrder||(r?.m??-1)+1;k.run(t,`INSERT INTO workflow_rules (type, command, position, action, description, enabled, timeout_ms, created_at, sort_order, when_expr, parallel, trust_source)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prjct-cli",
3
- "version": "2.37.0",
3
+ "version": "2.37.1",
4
4
  "description": "Context layer for AI agents. Project context for Claude Code, Gemini CLI, and more.",
5
5
  "main": "dist/bin/prjct.mjs",
6
6
  "bin": {