diff --git a/dist/.spec-format-checksum b/dist/.spec-format-checksum index 0eac7c4..bfc676e 100644 --- a/dist/.spec-format-checksum +++ b/dist/.spec-format-checksum @@ -1 +1 @@ -b5237e0e0259daa79485936c06d04b5dd2f8d1bb1d54a4494d1782cfd6f59c06 spec-validator.js +4dd76412450cbf4368a107906a4950326019c149184279a24303d121bff0cabb spec-validator.js diff --git a/dist/.spec-format-version b/dist/.spec-format-version index a918a2a..faef31a 100644 --- a/dist/.spec-format-version +++ b/dist/.spec-format-version @@ -1 +1 @@ -0.6.0 +0.7.0 diff --git a/dist/spec-validator.js b/dist/spec-validator.js index 7d53e7d..4af1364 100755 --- a/dist/spec-validator.js +++ b/dist/spec-validator.js @@ -1,11 +1,11 @@ #!/usr/bin/env node -// @ido4/spec-format v0.6.0 | bundled 2026-04-06 +// @ido4/spec-format v0.7.0 | bundled 2026-04-06 // Source: https://github.com/ido4-dev/ido4/tree/main/packages/spec-format | DO NOT EDIT "use strict";var $=require("node:fs"),E=require("node:path");var P=["must-have","should-have","nice-to-have"],j=["low","medium","high"];function M(l){let s={};for(let r of l.split("|")){let t=r.trim().match(/^(\w+):\s*(.+)$/);t&&t[1]&&t[2]&&(s[t[1]]=t[2].trim())}return s}function D(l){let s=l.split(/\s+/).filter(Boolean);return s.length===1?s[0].substring(0,3).toUpperCase():s.map(r=>r[0]).join("").toUpperCase()}var J=/^# (.+)$/,H=/^>\s*format:\s*(.+?)\s*\|\s*version:\s*(.+)$/,K=/^## Group:\s*(.+)$/,Q=/^## Cross-Cutting Concerns\s*$/i,F=/^### (.+)$/,V=/^### ([A-Z]{2,5}-\d{2,3}):\s*(.+)$/,q=/^>\s?(.*)$/,W=/^(?:[-*+]|\d+\.)\s+(.+)$/,L=/^\*\*(.+?):\*\*\s*$/,z=/^---\s*$/,Z=new Set(["priority"]),X=new Set(["priority","risk","depends_on"]);function B(l){let s=l.split(` `),r=[],t="INIT",i={name:"",format:"",version:"",description:"",stakeholders:[],constraints:[],nonGoals:[],openQuestions:[]},g=[],n=[],d=[],m=new Set,a=null,e=null,u=null,S=!1,T="project",G=!1,C=null,h=[];function I(){e&&(e.body=h.join(` `).trim(),h=[],C=null,e=null)}function b(){a&&(a.description=h.join(` `).trim(),h=[],C=null)}function O(){u&&(u.content=h.join(` -`).trim(),h=[],u=null)}for(let c=0;c0){for(let y of Object.keys(p))Z.has(y)||r.push({line:x,message:`Unknown group metadata key: ${y}`,severity:"warning"});p.priority&&(a.priority=p.priority);continue}}if(S&&T==="capability"&&e){let p=M(o);if(Object.keys(p).length>0){for(let y of Object.keys(p))X.has(y)||r.push({line:x,message:`Unknown capability metadata key: ${y}`,severity:"warning"});if(p.priority&&(e.priority=p.priority),p.risk&&(e.risk=p.risk),p.depends_on){let y=p.depends_on.trim();y!=="-"&&(e.dependsOn=y.split(",").map(Y=>Y.trim()).filter(Boolean))}continue}}S=!1}t==="PROJECT"&&T==="project"&&!w&&f.trim()&&(L.test(f)?(G=!0,S=!1):G||(i.description&&(i.description+=" "),i.description+=f.trim()));let N=L.exec(f);if(N&&N[1]){let o=N[1].toLowerCase();if(t==="PROJECT"){o==="stakeholders"?C="stakeholders":o==="constraints"?C="constraints":o==="non-goals"?C="nonGoals":o==="open questions"&&(C="openQuestions");continue}if(t==="CAPABILITY"&&e){S=!1,o==="success conditions"?C="successConditions":(C="body",h.push(f));continue}}let A=W.exec(f);if(A&&A[1]){if(t==="PROJECT"&&C){let o=A[1].trim();if(C==="stakeholders"){let p=ee(o);p&&i.stakeholders.push(p)}else C==="constraints"?i.constraints.push(o):C==="nonGoals"?i.nonGoals.push(o):C==="openQuestions"&&i.openQuestions.push(o);continue}if(t==="CAPABILITY"&&e&&C==="successConditions"){e.successConditions.push(A[1].trim());continue}}if(t==="CAPABILITY"){S=!1,h.push(f);continue}if(t==="GROUP"&&a&&a.capabilities.length===0){S=!1,h.push(f);continue}if(t==="CROSS_CUTTING"&&u){h.push(f);continue}}t==="CAPABILITY"&&I(),t==="GROUP"&&a&&a.capabilities.length===0&&b(),t==="CROSS_CUTTING"&&O(),i.format!=="strategic-spec"&&r.push({line:0,message:'Missing or invalid format marker: expected "format: strategic-spec | version: 1.0"',severity:"error"});for(let c of n)c.capabilities.length===0&&r.push({line:0,message:`Group "${c.name}" has no capabilities`,severity:"warning"});let _=[...n.flatMap(c=>c.capabilities),...d];_.length===0&&r.push({line:0,message:"Strategic spec contains no capabilities",severity:"warning"});for(let c of n)c.priority&&!P.includes(c.priority)&&r.push({line:0,message:`Group "${c.name}" has invalid priority: "${c.priority}" (expected: ${P.join(", ")})`,severity:"warning"});for(let c of _)c.priority&&!P.includes(c.priority)&&r.push({line:0,message:`Capability ${c.ref} has invalid priority: "${c.priority}" (expected: ${P.join(", ")})`,severity:"warning"}),c.risk&&!j.includes(c.risk)&&r.push({line:0,message:`Capability ${c.ref} has invalid risk: "${c.risk}" (expected: ${j.join(", ")})`,severity:"warning"});for(let c of _)for(let f of c.dependsOn)m.has(f)||r.push({line:0,message:`Capability ${c.ref} depends on "${f}" which does not exist`,severity:"error"});let U=te(_);return U&&r.push({line:0,message:`Circular dependency detected: ${U.join(" \u2192 ")}`,severity:"error"}),{project:i,crossCuttingConcerns:g,groups:n,orphanCapabilities:d,errors:r}}function ee(l){let s=l.match(/^(.+?):\s+(.+)$/);return s&&s[1]&&s[2]?{name:s[1].trim(),perspective:s[2].trim()}:null}function te(l){let s=new Set(l.map(n=>n.ref)),r=new Map,t=new Map;for(let n of s)r.set(n,[]),t.set(n,0);for(let n of l)for(let d of n.dependsOn)s.has(d)&&(r.get(d).push(n.ref),t.set(n.ref,(t.get(n.ref)??0)+1));let i=[];for(let[n,d]of t)d===0&&i.push(n);let g=0;for(;i.length>0;){let n=i.shift();g++;for(let d of r.get(n)??[]){let m=(t.get(d)??1)-1;t.set(d,m),m===0&&i.push(d)}}return g===s.size?null:[...t.entries()].filter(([,n])=>n>0).map(([n])=>n)}function ie(){return"0.6.0";try{return JSON.parse((0,$.readFileSync)((0,E.join)(__dirname,"../package.json"),"utf-8")).version}catch{return"unknown"}}var ne=ie();function re(){let l=process.argv[2];if(!l||l==="--help"||l==="-h"){let e=["Usage: ido4-spec-format ","","Parses a strategic spec and outputs structured JSON to stdout.","Exit codes: 0 = valid, 1 = structural errors, 2 = usage/IO error."];process.stderr.write(e.join(` +`).trim(),h=[],u=null)}for(let c=0;c0){for(let y of Object.keys(p))Z.has(y)||r.push({line:x,message:`Unknown group metadata key: ${y}`,severity:"warning"});p.priority&&(a.priority=p.priority);continue}}if(S&&T==="capability"&&e){let p=M(o);if(Object.keys(p).length>0){for(let y of Object.keys(p))X.has(y)||r.push({line:x,message:`Unknown capability metadata key: ${y}`,severity:"warning"});if(p.priority&&(e.priority=p.priority),p.risk&&(e.risk=p.risk),p.depends_on){let y=p.depends_on.trim();y!=="-"&&(e.dependsOn=y.split(",").map(Y=>Y.trim()).filter(Boolean))}continue}}S=!1}t==="PROJECT"&&T==="project"&&!w&&f.trim()&&(L.test(f)?(G=!0,S=!1):G||(i.description&&(i.description+=" "),i.description+=f.trim()));let N=L.exec(f);if(N&&N[1]){let o=N[1].toLowerCase();if(t==="PROJECT"){o==="stakeholders"?C="stakeholders":o==="constraints"?C="constraints":o==="non-goals"?C="nonGoals":o==="open questions"&&(C="openQuestions");continue}if(t==="CAPABILITY"&&e){S=!1,o==="success conditions"?C="successConditions":(C="body",h.push(f));continue}}let A=W.exec(f);if(A&&A[1]){if(t==="PROJECT"&&C){let o=A[1].trim();if(C==="stakeholders"){let p=ee(o);p&&i.stakeholders.push(p)}else C==="constraints"?i.constraints.push(o):C==="nonGoals"?i.nonGoals.push(o):C==="openQuestions"&&i.openQuestions.push(o);continue}if(t==="CAPABILITY"&&e&&C==="successConditions"){e.successConditions.push(A[1].trim());continue}}if(t==="CAPABILITY"){S=!1,h.push(f);continue}if(t==="GROUP"&&a&&a.capabilities.length===0){S=!1,h.push(f);continue}if(t==="CROSS_CUTTING"&&u){h.push(f);continue}}t==="CAPABILITY"&&I(),t==="GROUP"&&a&&a.capabilities.length===0&&b(),t==="CROSS_CUTTING"&&O(),i.format!=="strategic-spec"&&r.push({line:0,message:'Missing or invalid format marker: expected "format: strategic-spec | version: 1.0"',severity:"error"});for(let c of n)c.capabilities.length===0&&r.push({line:0,message:`Group "${c.name}" has no capabilities`,severity:"warning"});let _=[...n.flatMap(c=>c.capabilities),...d];_.length===0&&r.push({line:0,message:"Strategic spec contains no capabilities",severity:"warning"});for(let c of n)c.priority&&!P.includes(c.priority)&&r.push({line:0,message:`Group "${c.name}" has invalid priority: "${c.priority}" (expected: ${P.join(", ")})`,severity:"warning"});for(let c of _)c.priority&&!P.includes(c.priority)&&r.push({line:0,message:`Capability ${c.ref} has invalid priority: "${c.priority}" (expected: ${P.join(", ")})`,severity:"warning"}),c.risk&&!j.includes(c.risk)&&r.push({line:0,message:`Capability ${c.ref} has invalid risk: "${c.risk}" (expected: ${j.join(", ")})`,severity:"warning"});for(let c of _)for(let f of c.dependsOn)m.has(f)||r.push({line:0,message:`Capability ${c.ref} depends on "${f}" which does not exist`,severity:"error"});let U=te(_);return U&&r.push({line:0,message:`Circular dependency detected: ${U.join(" \u2192 ")}`,severity:"error"}),{project:i,crossCuttingConcerns:g,groups:n,orphanCapabilities:d,errors:r}}function ee(l){let s=l.match(/^(.+?):\s+(.+)$/);return s&&s[1]&&s[2]?{name:s[1].trim(),perspective:s[2].trim()}:null}function te(l){let s=new Set(l.map(n=>n.ref)),r=new Map,t=new Map;for(let n of s)r.set(n,[]),t.set(n,0);for(let n of l)for(let d of n.dependsOn)s.has(d)&&(r.get(d).push(n.ref),t.set(n.ref,(t.get(n.ref)??0)+1));let i=[];for(let[n,d]of t)d===0&&i.push(n);let g=0;for(;i.length>0;){let n=i.shift();g++;for(let d of r.get(n)??[]){let m=(t.get(d)??1)-1;t.set(d,m),m===0&&i.push(d)}}return g===s.size?null:[...t.entries()].filter(([,n])=>n>0).map(([n])=>n)}function ie(){return"0.7.0";try{return JSON.parse((0,$.readFileSync)((0,E.join)(__dirname,"../package.json"),"utf-8")).version}catch{return"unknown"}}var ne=ie();function re(){let l=process.argv[2];if(!l||l==="--help"||l==="-h"){let e=["Usage: ido4-spec-format ","","Parses a strategic spec and outputs structured JSON to stdout.","Exit codes: 0 = valid, 1 = structural errors, 2 = usage/IO error."];process.stderr.write(e.join(` `)+` `),process.exit(l?0:2)}let s=(0,E.resolve)(l),r;try{r=(0,$.readFileSync)(s,"utf-8")}catch(e){let u=e instanceof Error?e.message:String(e);process.stderr.write(`Error reading file: ${u} `),process.exit(2)}let t=Date.now(),i=B(r),g=Date.now()-t,n=i.errors.filter(e=>e.severity==="error"),d=i.errors.filter(e=>e.severity==="warning"),m=[...i.groups.flatMap(e=>e.capabilities),...i.orphanCapabilities],a={valid:n.length===0,meta:{file:s,parseDurationMs:g,parserVersion:ne},metrics:{groupCount:i.groups.length,capabilityCount:m.length,orphanCapabilityCount:i.orphanCapabilities.length,crossCuttingConcernCount:i.crossCuttingConcerns.length,dependencyEdgeCount:m.reduce((e,u)=>e+u.dependsOn.length,0),maxDependencyDepth:oe(m),errorCount:n.length,warningCount:d.length},project:i.project,crossCuttingConcerns:i.crossCuttingConcerns,groups:i.groups.map(e=>({name:e.name,prefix:e.prefix,priority:e.priority,description:e.description,capabilityCount:e.capabilities.length,capabilities:e.capabilities.map(u=>({ref:u.ref,title:u.title,priority:u.priority,risk:u.risk,dependsOn:u.dependsOn,successConditions:u.successConditions,groupName:u.groupName}))})),orphanCapabilities:i.orphanCapabilities.map(e=>({ref:e.ref,title:e.title,priority:e.priority,risk:e.risk,dependsOn:e.dependsOn,successConditions:e.successConditions})),dependencyGraph:se(m),errors:n.map(e=>({line:e.line,message:e.message})),warnings:d.map(e=>({line:e.line,message:e.message}))};process.stdout.write(JSON.stringify(a,null,2)+`