@mod-computer/mod 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli.bundle.js +33 -7
- package/package.json +1 -1
package/cli.bundle.js
CHANGED
|
@@ -763,7 +763,7 @@ Run without --dry-run to delete.`)}import gst from"fs/promises";import BL from"p
|
|
|
763
763
|
`+r:""),l=Math.floor(o.length/s)-this.cursorPos.rows+(r?oAr(r):0);l>0&&(a+=Ist(l)),a+=_st(this.cursorPos.cols),this.write(Cst(this.extraLinesUnderPrompt)+kst(this.height)+a),this.extraLinesUnderPrompt=l,this.height=oAr(a)}checkCursorPos(){let t=this.rl.getCursorPos();t.cols!==this.cursorPos.cols&&(this.write(_st(t.cols)),this.cursorPos=t)}done({clearContent:t}){this.rl.setPrompt("");let r=Cst(this.extraLinesUnderPrompt);r+=t?kst(this.height):`
|
|
764
764
|
`,r+=nAr,this.write(r),this.rl.close()}};var dke=class extends Promise{static withResolver(){let t,r;return{promise:new Promise((o,i)=>{t=o,r=i}),resolve:t,reject:r}}};function Seo(){let e=Error.prepareStackTrace,t=[];try{Error.prepareStackTrace=(r,n)=>{let o=n.slice(1);return t=o,o},new Error().stack}catch{return t}return Error.prepareStackTrace=e,t}function UL(e){let t=Seo();return(n,o={})=>{let{input:i=process.stdin,signal:s}=o,a=new Set,c=new sAr.default;c.pipe(o.output??process.stdout);let l=iAr.createInterface({terminal:!0,input:i,output:c}),d=new pie(l),{promise:m,resolve:h,reject:A}=dke.withResolver(),b=()=>A(new lke);if(s){let _=()=>A(new cke({cause:s.reason}));if(s.aborted)return _(),Object.assign(m,{cancel:b});s.addEventListener("abort",_),a.add(()=>s.removeEventListener("abort",_))}a.add(K_e((_,D)=>{A(new uie(`User force closed the prompt with ${_} ${D}`))}));let w=()=>A(new uie("User force closed the prompt with SIGINT"));l.on("SIGINT",w),a.add(()=>l.removeListener("SIGINT",w));let v=()=>d.checkCursorPos();return l.input.on("keypress",v),a.add(()=>l.input.removeListener("keypress",v)),zgr(l,_=>{let D=weo.bind(()=>NL.clearAll());return l.on("close",D),a.add(()=>l.removeListener("close",D)),_(()=>{var R;try{let M=e(n,F=>{setImmediate(()=>h(F))});if(M===void 0){let F=(R=t[1])==null?void 0:R.getFileName();throw new Error(`Prompt functions must return a string.
|
|
765
765
|
at ${F}`)}let[P,U]=typeof M=="string"?[M]:M;d.render(P,U),NL.run()}catch(M){A(M)}}),Object.assign(m.then(R=>(NL.clearAll(),R),R=>{throw NL.clearAll(),R}).finally(()=>{a.forEach(R=>R()),d.done({clearContent:!!o.clearPromptOnDone}),c.end()}).then(()=>m),{cancel:b})})}}import{styleText as Ieo}from"node:util";var mT=class{separator=Ieo("dim",Array.from({length:15}).join(f5.line));type="separator";constructor(t){t&&(this.separator=t)}static isSeparator(t){return!!(t&&typeof t=="object"&&"type"in t&&t.type==="separator")}};function aAr(e,t){let r=t!==!1;return/^(y|yes)/i.test(e)?r=!0:/^(n|no)/i.test(e)&&(r=!1),r}function cAr(e){return e?"Yes":"No"}var Tst=UL((e,t)=>{let{transformer:r=cAr}=e,[n,o]=Up("idle"),[i,s]=Up(""),a=hT(e.theme),c=PL({status:n,theme:a});LL((h,A)=>{if(n==="idle")if(ML(h)){let b=aAr(i,e.default);s(r(b)),o("done"),t(b)}else if(lie(h)){let b=cAr(!aAr(i,e.default));A.clearLine(0),A.write(b),s(b)}else s(A.line)});let l=i,d="";n==="done"?l=a.style.answer(i):d=` ${a.style.defaultAnswer(e.default===!1?"y/N":"Y/n")}`;let m=a.style.message(e.message,n);return`${c} ${m}${d} ${l}`});var Ceo={validationFailureMode:"keep"},Dst=UL((e,t)=>{let{prefill:r="tab"}=e,n=hT(Ceo,e.theme),[o,i]=Up("idle"),[s="",a]=Up(e.default),[c,l]=Up(),[d,m]=Up(""),h=PL({status:o,theme:n});async function A(D){let{required:R,pattern:M,patternError:P="Invalid input"}=e;return R&&!D?"You must provide a value":M&&!M.test(D)?P:typeof e.validate=="function"?await e.validate(D)||"You must provide a valid value":!0}LL(async(D,R)=>{if(o==="idle")if(ML(D)){let M=d||s;i("loading");let P=await A(M);P===!0?(m(M),i("done"),t(M)):(n.validationFailureMode==="clear"?m(""):R.write(d),l(P),i("idle"))}else cie(D)&&!d?a(void 0):lie(D)&&!d?(a(void 0),R.clearLine(0),R.write(s),m(s)):(m(R.line),l(void 0))}),pT(D=>{r==="editable"&&s&&(D.write(s),m(s))},[]);let b=n.style.message(e.message,o),w=d;typeof e.transformer=="function"?w=e.transformer(d,{isFinal:o==="done"}):o==="done"&&(w=n.style.answer(d));let v;s&&o!=="done"&&!d&&(v=n.style.defaultAnswer(s));let _="";return c&&(_=n.style.error(c)),[[h,b,v,w].filter(D=>D!==void 0).join(" "),_]});import{styleText as hie}from"node:util";var _eo={icon:{cursor:f5.pointer},style:{disabled:e=>hie("dim",`- ${e}`),description:e=>hie("cyan",e),keysHelpTip:e=>e.map(([t,r])=>`${hie("bold",t)} ${hie("dim",r)}`).join(hie("dim"," \u2022 "))},indexMode:"hidden",keybindings:[]};function d5(e){return!mT.isSeparator(e)&&!e.disabled}function keo(e){return e.map(t=>{if(mT.isSeparator(t))return t;if(typeof t!="object"||t===null||!("value"in t)){let o=String(t);return{value:t,name:o,short:o,disabled:!1}}let r=t.name??String(t.value),n={value:t.value,name:r,short:t.short??r,disabled:t.disabled??!1};return t.description&&(n.description=t.description),n})}var Rst=UL((e,t)=>{let{loop:r=!0,pageSize:n=7}=e,o=hT(_eo,e.theme),{keybindings:i}=o,[s,a]=Up("idle"),c=PL({status:s,theme:o}),l=QL(),d=!i.includes("vim"),m=fie(()=>keo(e.choices),[e.choices]),h=fie(()=>{let F=m.findIndex(d5),L=m.findLastIndex(d5);if(F===-1)throw new l5("[select prompt] No selectable choices. All choices are disabled.");return{first:F,last:L}},[m]),A=fie(()=>"default"in e?m.findIndex(F=>d5(F)&&F.value===e.default):-1,[e.default,m]),[b,w]=Up(A===-1?h.first:A),v=m[b];LL((F,L)=>{if(clearTimeout(l.current),ML(F))a("done"),t(v.value);else if(aie(F,i)||ake(F,i)){if(L.clearLine(0),r||aie(F,i)&&b!==h.first||ake(F,i)&&b!==h.last){let j=aie(F,i)?-1:1,G=b;do G=(G+j+m.length)%m.length;while(!d5(m[G]));w(G)}}else if(bst(F)&&!Number.isNaN(Number(L.line))){let j=Number(L.line)-1,G=-1,ee=m.findIndex(re=>mT.isSeparator(re)?!1:(G++,G===j)),Z=m[ee];Z!=null&&d5(Z)&&w(ee),l.current=setTimeout(()=>{L.clearLine(0)},700)}else if(cie(F))L.clearLine(0);else if(d){let j=L.line.toLowerCase(),G=m.findIndex(ee=>mT.isSeparator(ee)||!d5(ee)?!1:ee.name.toLowerCase().startsWith(j));G!==-1&&w(G),l.current=setTimeout(()=>{L.clearLine(0)},700)}}),pT(()=>()=>{clearTimeout(l.current)},[]);let _=o.style.message(e.message,s),D=o.style.keysHelpTip([["\u2191\u2193","navigate"],["\u23CE","select"]]),R=0,M=wst({items:m,active:b,renderItem({item:F,isActive:L,index:j}){if(mT.isSeparator(F))return R++,` ${F.separator}`;let G=o.indexMode==="number"?`${j+1-R}. `:"";if(F.disabled){let re=typeof F.disabled=="string"?F.disabled:"(disabled)";return o.style.disabled(`${G}${F.name} ${re}`)}let ee=L?o.style.highlight:re=>re,Z=L?o.icon.cursor:" ";return ee(`${Z} ${G}${F.name}`)},pageSize:n,loop:r});if(s==="done")return[c,_,o.style.answer(v.short)].filter(Boolean).join(" ");let{description:P}=v;return`${[[c,_].filter(Boolean).join(" "),M," ",P?o.style.description(P):"",D].filter(Boolean).join(`
|
|
766
|
-
`).trimEnd()}${rAr}`});async function gT(e,t){return await Rst({message:e,choices:t.map(n=>({name:n.label,value:n.value,description:n.description}))})}async function pke(e,t){return await Dst({message:e,default:t==null?void 0:t.default,validate:t!=null&&t.validate?n=>t.validate(n)??!0:void 0})}async function Ost(e,t){return await Tst({message:e,default:(t==null?void 0:t.default)??!0})}function p5(e){return!e||e.trim().length===0?"Workspace name cannot be empty":e.length>100?"Workspace name must be 100 characters or less":/^[a-zA-Z0-9][a-zA-Z0-9-_ ]*[a-zA-Z0-9]$|^[a-zA-Z0-9]$/.test(e.trim())?null:"Workspace name must start and end with alphanumeric characters"}Xu();tie();Xu();async function Bst(e,t){let r=(t==null?void 0:t.timeout)??3e4,n=(t==null?void 0:t.verbose)??!1,o=n?console.log.bind(console):()=>{};o("[fetch] Creating temporary network repo...");let i=E7(),s=nle(),a=new Set;a.add(e);let c=new NM({storage:new R3(i),network:[new CD(s)],sharePolicy:async(l,d)=>d?a.has(d):!1});o("[fetch] Waiting for WebSocket connection..."),await new Promise(l=>setTimeout(l,500));try{o(`[fetch] Fetching workspace document: ${e}`);let l=await c.find(e);await Promise.race([l.whenReady(),new Promise((_,D)=>setTimeout(()=>D(new Error("Timeout waiting for workspace document")),r))]);let d=l.doc();if(!d)throw new Error("Workspace document is empty after fetch");let m=d.title||d.name||"Untitled";o(`[fetch] Workspace loaded: ${m}`);let h=d.fileRefs||[],A=d.folderRefs||[];o(`[fetch] Found ${h.length} files, ${A.length} folders`);let b=h.map(_=>_.id||_.documentId).filter(Boolean),w=A.map(_=>_.id||_.documentId).filter(Boolean);for(let _ of[...b,...w])a.add(_);o(`[fetch] Fetching ${b.length} file documents...`);let v=0;for(let _ of b)try{let D=await c.find(_);await Promise.race([D.whenReady(),new Promise((R,M)=>setTimeout(()=>M(new Error(`Timeout fetching file ${_}`)),1e4))]),v++,n&&v%10===0&&o(`[fetch] Fetched ${v}/${b.length} files...`)}catch(D){o(`[fetch] Warning: Could not fetch file ${_}: ${D}`)}o(`[fetch] Fetching ${w.length} folder documents...`);for(let _ of w)try{let D=await c.find(_);await Promise.race([D.whenReady(),new Promise((R,M)=>setTimeout(()=>M(new Error(`Timeout fetching folder ${_}`)),1e4))])}catch(D){o(`[fetch] Warning: Could not fetch folder ${_}: ${D}`)}return o("[fetch] Waiting for storage flush..."),await new Promise(_=>setTimeout(_,1e3)),TL(e),Jit([...b,...w]),o(`[fetch] Fetch complete: ${v} files, ${w.length} folders`),{workspaceId:e,workspaceName:m,fileCount:v,folderCount:w.length}}finally{o("[fetch] Shutting down temporary repo...");try{await new Promise(l=>setTimeout(l,500))}catch(l){o(`[fetch] Warning during shutdown: ${l}`)}}}async function uAr(e,t){let r=e.includes("--force"),n=process.cwd();if(!qW(qn.AUTH))return lAr(n,r);let o=e.indexOf("--workspace"),i=e.indexOf("--create"),s=o!==-1?e[o+1]:null,a=i!==-1?e[i+1]:null;try{oF();let c=is(n);if(c&&!r){console.log("Already initialized"),console.log(`Workspace: ${c.workspaceName}`),console.log("");let h=await gT("What would you like to do?",[{label:"Resume file import (if interrupted)",value:"resume"},{label:"Start syncing",value:"sync"},{label:"Reinitialize with different workspace",value:"force"}]);if(h==="resume"){console.log("Checking for files to import...");try{await mke(t,{id:c.workspaceId}),console.log("Resume complete")}catch(A){console.error("Resume failed:",A instanceof Error?A.message:A),process.exit(1)}}else h==="sync"?console.log("Run `mod sync start` to begin syncing"):h==="force"&&console.log("Reinitializing...");h!=="force"&&process.exit(0)}let l=sa();Cx.existsSync(y7())||QE(l);let d=!!l.auth;if(!d&&!a&&!s)return lAr(n,r);let m;d?m=await Teo(t,l.auth.email,{workspaceName:s,createName:a}):m=await Deo(t,{createName:a}),m.lastSyncedAt=new Date().toISOString(),w_(n,m),fAr(),console.log("Installed
|
|
766
|
+
`).trimEnd()}${rAr}`});async function gT(e,t){return await Rst({message:e,choices:t.map(n=>({name:n.label,value:n.value,description:n.description}))})}async function pke(e,t){return await Dst({message:e,default:t==null?void 0:t.default,validate:t!=null&&t.validate?n=>t.validate(n)??!0:void 0})}async function Ost(e,t){return await Tst({message:e,default:(t==null?void 0:t.default)??!0})}function p5(e){return!e||e.trim().length===0?"Workspace name cannot be empty":e.length>100?"Workspace name must be 100 characters or less":/^[a-zA-Z0-9][a-zA-Z0-9-_ ]*[a-zA-Z0-9]$|^[a-zA-Z0-9]$/.test(e.trim())?null:"Workspace name must start and end with alphanumeric characters"}Xu();tie();Xu();async function Bst(e,t){let r=(t==null?void 0:t.timeout)??3e4,n=(t==null?void 0:t.verbose)??!1,o=n?console.log.bind(console):()=>{};o("[fetch] Creating temporary network repo...");let i=E7(),s=nle(),a=new Set;a.add(e);let c=new NM({storage:new R3(i),network:[new CD(s)],sharePolicy:async(l,d)=>d?a.has(d):!1});o("[fetch] Waiting for WebSocket connection..."),await new Promise(l=>setTimeout(l,500));try{o(`[fetch] Fetching workspace document: ${e}`);let l=await c.find(e);await Promise.race([l.whenReady(),new Promise((_,D)=>setTimeout(()=>D(new Error("Timeout waiting for workspace document")),r))]);let d=l.doc();if(!d)throw new Error("Workspace document is empty after fetch");let m=d.title||d.name||"Untitled";o(`[fetch] Workspace loaded: ${m}`);let h=d.fileRefs||[],A=d.folderRefs||[];o(`[fetch] Found ${h.length} files, ${A.length} folders`);let b=h.map(_=>_.id||_.documentId).filter(Boolean),w=A.map(_=>_.id||_.documentId).filter(Boolean);for(let _ of[...b,...w])a.add(_);o(`[fetch] Fetching ${b.length} file documents...`);let v=0;for(let _ of b)try{let D=await c.find(_);await Promise.race([D.whenReady(),new Promise((R,M)=>setTimeout(()=>M(new Error(`Timeout fetching file ${_}`)),1e4))]),v++,n&&v%10===0&&o(`[fetch] Fetched ${v}/${b.length} files...`)}catch(D){o(`[fetch] Warning: Could not fetch file ${_}: ${D}`)}o(`[fetch] Fetching ${w.length} folder documents...`);for(let _ of w)try{let D=await c.find(_);await Promise.race([D.whenReady(),new Promise((R,M)=>setTimeout(()=>M(new Error(`Timeout fetching folder ${_}`)),1e4))])}catch(D){o(`[fetch] Warning: Could not fetch folder ${_}: ${D}`)}return o("[fetch] Waiting for storage flush..."),await new Promise(_=>setTimeout(_,1e3)),TL(e),Jit([...b,...w]),o(`[fetch] Fetch complete: ${v} files, ${w.length} folders`),{workspaceId:e,workspaceName:m,fileCount:v,folderCount:w.length}}finally{o("[fetch] Shutting down temporary repo...");try{await new Promise(l=>setTimeout(l,500))}catch(l){o(`[fetch] Warning during shutdown: ${l}`)}}}async function uAr(e,t){let r=e.includes("--force"),n=process.cwd();if(!qW(qn.AUTH))return lAr(n,r);let o=e.indexOf("--workspace"),i=e.indexOf("--create"),s=o!==-1?e[o+1]:null,a=i!==-1?e[i+1]:null;try{oF();let c=is(n);if(c&&!r){console.log("Already initialized"),console.log(`Workspace: ${c.workspaceName}`),console.log("");let h=await gT("What would you like to do?",[{label:"Resume file import (if interrupted)",value:"resume"},{label:"Start syncing",value:"sync"},{label:"Reinitialize with different workspace",value:"force"}]);if(h==="resume"){console.log("Checking for files to import...");try{await mke(t,{id:c.workspaceId}),console.log("Resume complete")}catch(A){console.error("Resume failed:",A instanceof Error?A.message:A),process.exit(1)}}else h==="sync"?console.log("Run `mod sync start` to begin syncing"):h==="force"&&console.log("Reinitializing...");h!=="force"&&process.exit(0)}let l=sa();Cx.existsSync(y7())||QE(l);let d=!!l.auth;if(!d&&!a&&!s)return lAr(n,r);let m;d?m=await Teo(t,l.auth.email,{workspaceName:s,createName:a}):m=await Deo(t,{createName:a}),m.lastSyncedAt=new Date().toISOString(),w_(n,m),fAr(),console.log("Installed mod skill to .claude/skills/mod/"),Beo(m,d),process.exit(0)}catch(c){console.error("Initialization failed:",c.message),process.exit(1)}}async function Teo(e,t,r={}){var l,d,m;console.log(`Signed in as ${t}`);let n=sa(),o=[];try{let h=(l=n.auth)==null?void 0:l.userDocId;if(!h)console.warn("User document not found. Creating local workspace.");else{let A=await e.find(h);await A.whenReady();let b=A.doc();if(b){let w=b.workspaceIds||[];for(let v of w)try{let _=await e.find(v);await _.whenReady();let D=_.doc();o.push({id:v,name:(D==null?void 0:D.title)||(D==null?void 0:D.name)||"Untitled"})}catch{o.push({id:v,name:`Workspace ${String(v).slice(0,8)}`})}}}}catch{console.warn("Could not load cloud workspaces. Creating local workspace.")}if(r.createName)return console.log(`Creating workspace: ${r.createName}`),await hke(e,r.createName);if(r.workspaceName){let h=o.find(_=>_.name.toLowerCase()===r.workspaceName.toLowerCase());if(!h)throw new Error(`Workspace "${r.workspaceName}" not found. Available: ${o.map(_=>_.name).join(", ")||"(none)"}`);console.log(`Selected workspace: ${h.name}`),console.log("Fetching workspace from server...");try{let _=await Bst(h.id,{verbose:!1});console.log(`Fetched ${_.fileCount} files, ${_.folderCount} folders`)}catch(_){console.warn(`Warning: Could not fetch workspace from server: ${_ instanceof Error?_.message:_}`),console.warn("Workspace will sync when daemon starts.")}let A={type:"existing",id:h.id,name:h.name};TL(A.id);let b=process.cwd(),w={path:b,workspaceId:A.id,workspaceName:A.name,connectedAt:new Date().toISOString(),lastSyncedAt:new Date().toISOString()};w_(b,w),console.log("Workspace connection saved");let v=sa();if((d=v.auth)!=null&&d.userDocId)try{await Sx(e).addWorkspace(v.auth.userDocId,A.id)}catch{}return await mke(e,{id:A.id}),w}let i=[...o.map(h=>({label:h.name,value:{type:"existing",id:h.id,name:h.name}})),{label:"+ Create new workspace",value:{type:"create",id:"",name:""}}],s=await gT("Select workspace:",i);if(s.type==="create")return await hke(e);console.log("Fetching workspace from server...");try{let h=await Bst(s.id,{verbose:!1});console.log(`Fetched ${h.fileCount} files, ${h.folderCount} folders`)}catch(h){console.warn(`Warning: Could not fetch workspace from server: ${h instanceof Error?h.message:h}`),console.warn("Workspace will sync when daemon starts.")}TL(s.id);let a=process.cwd(),c={path:a,workspaceId:s.id,workspaceName:s.name,connectedAt:new Date().toISOString(),lastSyncedAt:new Date().toISOString()};if(w_(a,c),console.log("Workspace connection saved"),(m=n.auth)!=null&&m.userDocId)try{await Sx(e).addWorkspace(n.auth.userDocId,s.id)}catch{}return await mke(e,{id:s.id}),c}async function Deo(e,t={}){return t.createName?(console.log(`Creating workspace: ${t.createName}`),await hke(e,t.createName)):(await gT("Select option:",[{label:"Create local workspace",value:"create",description:"Work offline, sync later"},{label:"Sign in to sync with team",value:"signin",description:"Access cloud workspaces"}])==="signin"&&(console.log(""),console.log("Please run `mod auth login` to sign in, then run `mod init` again."),process.exit(0)),await hke(e))}async function hke(e,t){var d;let r=$L.basename(process.cwd()),n=process.cwd(),o=r.charAt(0).toUpperCase()+r.slice(1),i;if(t){let m=p5(t);if(m)throw new Error(`Invalid workspace name: ${m}`);i=t}else i=await pke("Workspace name",{default:o,validate:p5});console.log("Creating workspace...");let a=await gs(e).createWorkspace({name:i});TL(a.id);let c={path:n,workspaceId:a.id,workspaceName:a.name,connectedAt:new Date().toISOString(),lastSyncedAt:new Date().toISOString()};w_(n,c),console.log("Workspace connection saved"),console.log("Syncing workspace..."),await new Promise(m=>setTimeout(m,2e3));let l=sa();if((d=l.auth)!=null&&d.userDocId)try{await Sx(e).addWorkspace(l.auth.userDocId,a.id),await new Promise(h=>setTimeout(h,1e3)),console.log("Workspace registered for sync")}catch{console.warn("Note: Could not register workspace for cross-device sync")}return await mke(e,a),console.log("Workspace synced"),c}async function mke(e,t){let n=await new s5(e).execute({paths:["."]},i=>{i.phase==="scanning"?process.stdout.write(`\rScanning... ${i.total} files found`):i.phase==="adding"&&process.stdout.write(`\rImporting... ${i.current}/${i.total} files`)});if(process.stdout.write("\r"+" ".repeat(60)+"\r"),n.summary.totalFiles===0){console.log("No files to import");return}let o=[];n.summary.created>0&&o.push(`${n.summary.created} created`),n.summary.unchanged>0&&o.push(`${n.summary.unchanged} unchanged`),n.summary.skipped>0&&o.push(`${n.summary.skipped} skipped`),n.summary.errors>0&&o.push(`${n.summary.errors} errors`),console.log(`Imported ${n.summary.totalFiles} files (${o.join(", ")})`)}function lAr(e,t){let r=$L.join(e,".mod","traces");!t&&Cx.existsSync(r)&&(console.log("Already initialized for local development."),console.log("Run `mod init --force` to reinitialize."),process.exit(0)),Cx.mkdirSync(r,{recursive:!0});let n=$L.join(e,"glassware.toml");Cx.existsSync(n)||(Cx.writeFileSync(n,`# Trace schema configuration
|
|
767
767
|
# See: https://docs.mod.computer/glassware
|
|
768
768
|
|
|
769
769
|
node_types = ["requirement", "specification", "implementation", "test"]
|
|
@@ -782,9 +782,9 @@ to = "specification"
|
|
|
782
782
|
attribute = "implementations"
|
|
783
783
|
from = "test"
|
|
784
784
|
to = "implementation"
|
|
785
|
-
`,"utf-8"),console.log("Created glassware.toml")),fAr(),console.log(""),console.log("Initialized for local spec-driven development:"),console.log(" .mod/traces/
|
|
785
|
+
`,"utf-8"),console.log("Created glassware.toml")),fAr(),console.log(""),console.log("Initialized for local spec-driven development:"),console.log(" .mod/traces/ Local trace storage (committable to git)"),console.log(" glassware.toml Trace schema configuration"),console.log(" .claude/skills/ Mod skill for AI agents"),console.log(""),console.log("Get started:"),console.log(" mod trace add <file>:<line> --type=implementation"),console.log(" mod trace list"),console.log(" mod trace report <file>"),process.exit(0)}function fAr(){let e=process.cwd(),t=$L.join(e,".claude","skills","mod"),r=$L.join(t,"references");Cx.existsSync(t)||Cx.mkdirSync(t,{recursive:!0}),Cx.existsSync(r)||Cx.mkdirSync(r,{recursive:!0});let n=$L.join(t,"SKILL.md"),o=$L.join(r,"commands.md");Cx.existsSync(n)||Cx.writeFileSync(n,Reo),Cx.existsSync(o)||Cx.writeFileSync(o,Oeo)}var Reo=`---
|
|
786
786
|
name: mod
|
|
787
|
-
description: Spec-driven development with traceability. Implements specs, adds traces, verifies coverage.
|
|
787
|
+
description: Spec-driven development with traceability. Implements specs, adds traces, verifies coverage.
|
|
788
788
|
---
|
|
789
789
|
|
|
790
790
|
# Mod: Spec-Driven Development
|
|
@@ -795,21 +795,27 @@ Use this skill when the user asks to implement from specs, add traceability, or
|
|
|
795
795
|
|
|
796
796
|
- "implement this spec"
|
|
797
797
|
- "implement specs/*.md"
|
|
798
|
-
- "
|
|
798
|
+
- "implement <file>"
|
|
799
799
|
- "add tests for this spec"
|
|
800
|
+
- "add traces"
|
|
800
801
|
- "check trace coverage"
|
|
802
|
+
- "check coverage"
|
|
803
|
+
- "trace report"
|
|
801
804
|
|
|
802
805
|
## Workflow
|
|
803
806
|
|
|
804
807
|
### 1. Setup (if needed)
|
|
805
808
|
|
|
809
|
+
Check if workspace exists:
|
|
810
|
+
\`\`\`bash
|
|
811
|
+
mod status
|
|
812
|
+
\`\`\`
|
|
813
|
+
|
|
806
814
|
If not initialized:
|
|
807
815
|
\`\`\`bash
|
|
808
816
|
mod init
|
|
809
817
|
\`\`\`
|
|
810
818
|
|
|
811
|
-
This creates \`.mod/traces/\` for local trace storage and \`glassware.toml\` for schema configuration. Traces are stored as JSON files committable to git.
|
|
812
|
-
|
|
813
819
|
### 2. Read the Spec
|
|
814
820
|
|
|
815
821
|
Parse the spec file for requirements. Requirements may be marked with glassware annotations or be plain markdown sections.
|
|
@@ -864,7 +870,7 @@ Only report completion when both commands exit 0 (all files traced, all requirem
|
|
|
864
870
|
|
|
865
871
|
| Command | Purpose |
|
|
866
872
|
|---------|---------|
|
|
867
|
-
| \`mod init\` | Initialize
|
|
873
|
+
| \`mod init\` | Initialize workspace |
|
|
868
874
|
| \`mod trace add <file>:<line> --type=<type> [--link=<id>]\` | Add trace |
|
|
869
875
|
| \`mod trace link <source> <target>\` | Link two traces |
|
|
870
876
|
| \`mod trace report <file>\` | Show trace coverage for spec |
|
|
@@ -880,6 +886,26 @@ Only report completion when both commands exit 0 (all files traced, all requirem
|
|
|
880
886
|
- \`test\` - Code that verifies
|
|
881
887
|
- \`utility\` - Intentionally untraced helpers
|
|
882
888
|
|
|
889
|
+
## Keeping Specs Up to Date
|
|
890
|
+
|
|
891
|
+
Specs are living documents. As you implement, update the spec to reflect reality:
|
|
892
|
+
|
|
893
|
+
- If behavior diverges from the spec during implementation, update the spec to match.
|
|
894
|
+
- If you add new capabilities not covered by the spec, add spec entries with traces.
|
|
895
|
+
- If requirements change or are removed, update the spec accordingly.
|
|
896
|
+
- Run \`mod trace report <spec-file>\` after spec edits to confirm trace integrity.
|
|
897
|
+
|
|
898
|
+
## Protecting Traced Content
|
|
899
|
+
|
|
900
|
+
Traces reference spec content by file path, line number, and quoted text. Rewriting or restructuring a spec file can orphan traces - the trace still exists in \`.mod/traces/\` but points to content that moved or disappeared.
|
|
901
|
+
|
|
902
|
+
**When editing spec files:**
|
|
903
|
+
- Avoid bulk-rewriting entire spec files. Prefer targeted edits that preserve line structure.
|
|
904
|
+
- If you must restructure a spec, run \`mod trace report <spec-file>\` before and after to confirm no traces were orphaned.
|
|
905
|
+
- When removing a spec item, delete its traces too: \`mod trace delete <trace-id> --force\`.
|
|
906
|
+
- When moving content to a different line, the trace's \`quotedText\` will still match on re-scan, but line numbers will drift. Re-run \`mod trace report\` to verify the chain is intact.
|
|
907
|
+
- If adding new sections to a spec, add corresponding traces with \`mod trace add\`.
|
|
908
|
+
|
|
883
909
|
## Important
|
|
884
910
|
|
|
885
911
|
- Always verify coverage before completing
|